|
|
#include "priv.h"
#include "privpath.h"
#define CH_WHACK TEXT(FILENAME_SEPARATOR)
#define SZ_WHACK TEXT(FILENAME_SEPARATOR_STR)
#include <platform.h>
// Warning this define is in NTDEF.H but not winnt.h...
#ifdef UNICODE
typedef WCHAR TUCHAR; #else
typedef unsigned char TUCHAR; #endif
#ifdef UNICODE // {
//*** FAST_CharNext -- fast CharNext for path operations
// DESCRIPTION
// when we're just stepping thru chars in a path, a simple '++' is fine.
#define FAST_CharNext(p) (DBNotNULL(p) + 1)
#ifdef DEBUG
LPWSTR WINAPI DBNotNULL(LPCWSTR lpszCurrent) { ASSERT(*lpszCurrent); return (LPWSTR) lpszCurrent; } #else
#define DBNotNULL(p) (p)
#endif
#else // }{
#define FAST_CharNext(p) CharNext(p)
#endif // }
static const TCHAR c_szPATH[] = TEXT("PATH"); static const TCHAR c_szEllipses[] = TEXT("...");
//
// Inline function to check for a double-backslash at the
// beginning of a string
//
static __inline BOOL DBL_BSLASH(LPCTSTR psz) { return (psz[0] == TEXT('\\') && psz[1] == TEXT('\\')); }
#ifdef DBCS
// NOTE:
// LCMAP_IGNOREDBCS is a private bit has been redefined to
// 0x80000000 in NT5 source tree becuase it conflicts with
// another public bit.
// To make this code work with the OLD platforms, namely
// Win95 and OSRs. We have to define this flag.
#define LCMAP_IGNOREDBCS_WIN95 0x01000000
//
// This is supposed to work only with Path string.
//
int CaseConvertPathExceptDBCS(LPTSTR pszPath, int cch, BOOL fUpper) { TCHAR szTemp[MAX_PATH]; int cchUse; DWORD fdwMap = (fUpper? LCMAP_UPPERCASE:LCMAP_LOWERCASE);
// APPCOMPAT !!! (ccteng)
// Do we need to check for Memphis? Is Memphis shipping a
// kernel compiled with new headers?
// LCMAP_IGNOREDBCS is ignored on NT.
// And also this flag has been redefined in NT5 headers to
// resolve a conflict which broke the backward compatibility.
// So we only set the old flag when it's NOT running on NT.
cchUse = (cch == 0)? lstrlen(pszPath): cch;
// LCMapString cannot deal with src/dst in the same address.
//
if (pszPath) { if (SUCCEEDED(StringCchCopy(szTemp, ARRAYSIZE(szTemp), pszPath))) { return LCMapString(LOCALE_SYSTEM_DEFAULT,fdwMap, szTemp, cchUse, pszPath, cchUse); } } return 0; }
STDAPI_(LPTSTR) CharLowerNoDBCS(LPTSTR psz) { if(CaseConvertPathExceptDBCS(psz, 0, FALSE)) { return psz; } return NULL; }
STDAPI_(LPTSTR) CharUpperNoDBCS(LPTSTR psz) { if(CaseConvertPathExceptDBCS(psz, 0, TRUE)) { return psz; } return NULL; }
UINT CharLowerBuffNoDBCS(LPTSTR lpsz, UINT cb) { return (UINT)CaseConvertPathExceptDBCS(lpsz, cb, FALSE); }
UINT CharUpperBuffNoDBCS(LPTSTR lpsz, UINT cb) { return (UINT)CaseConvertPathExceptDBCS(lpsz, cb, TRUE); } #endif // DBCS
// FEATURE, we should validate the sizes of all path buffers by filling them
// with MAX_PATH fill bytes.
/*----------------------------------------------------------
Purpose: converts a file path to make it look a bit better if it is all upper case characters.
Returns: */ STDAPI_(BOOL) PathMakePretty(LPTSTR lpPath) { LPTSTR lp;
RIPMSG(lpPath && IS_VALID_STRING_PTR(lpPath, -1), "PathMakePretty: caller passed bad lpPath");
if (!lpPath) { return FALSE; }
// REVIEW: INTL need to deal with lower case chars in (>127) range?
// check for all uppercase
for (lp = lpPath; *lp; lp = FAST_CharNext(lp)) { if ((*lp >= TEXT('a')) && (*lp <= TEXT('z')) || IsDBCSLeadByte(*lp)) { // this is a LFN or DBCS, dont mess with it
return FALSE; } }
#ifdef DBCS
// In order to be compatible with the file system, we cannot
// case convert DBCS Roman characters.
//
CharLowerNoDBCS(lpPath); CharUpperBuffNoDBCS(lpPath, 1); #else
CharLower(lpPath); CharUpperBuff(lpPath, 1); #endif
return TRUE; // did the conversion
}
// returns a pointer to the arguments in a cmd type path or pointer to
// NULL if no args exist
//
// "foo.exe bar.txt" -> "bar.txt"
// "foo.exe" -> ""
//
// Spaces in filenames must be quoted.
// " "A long name.txt" bar.txt " -> "bar.txt"
STDAPI_(LPTSTR) PathGetArgs(LPCTSTR pszPath) { RIPMSG(!pszPath || IS_VALID_STRING_PTR(pszPath, -1), "PathGetArgs: caller passed bad pszPath");
if (pszPath) { BOOL fInQuotes = FALSE;
while (*pszPath) { if (*pszPath == TEXT('"')) { fInQuotes = !fInQuotes; } else if (!fInQuotes && *pszPath == TEXT(' ')) { return (LPTSTR)pszPath+1; }
pszPath = FAST_CharNext(pszPath); } }
return (LPTSTR)pszPath; }
/*----------------------------------------------------------
Purpose: Remove arguments from pszPath.
Returns: -- Cond: -- */ STDAPI_(void) PathRemoveArgs(LPTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathRemoveArgs: caller passed bad pszPath");
if (pszPath) { LPTSTR pArgs = PathGetArgs(pszPath); if (*pArgs) { // clobber the ' '
*(pArgs - 1) = TEXT('\0'); } else { // Handle trailing space
pArgs = CharPrev(pszPath, pArgs);
if (*pArgs == TEXT(' ')) { *pArgs = TEXT('\0'); } } } }
/*----------------------------------------------------------
Purpose: Determines if a file exists. This is fast.
Returns: TRUE if it exists
*********************************************************************************************** !!NOTE!! If you want to see if a UNC server, or UNC server\share exists (eg "\\pyrex" or "\\banyan\iptd"), then you have to call PathFileExistsAndAttributes, as this function will fail on the UNC server and server\share case! ***********************************************************************************************
*/ STDAPI_(BOOL) PathFileExists(LPCTSTR pszPath) { BOOL fResult = FALSE;
RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFileExists: caller passed bad pszPath");
#ifdef DEBUG
if (PathIsUNCServer(pszPath) || PathIsUNCServerShare(pszPath)) { TraceMsg(TF_WARNING, "PathFileExists: called with a UNC server or server-share, use PathFileExistsAndAttributes for correct results in this case!!"); } #endif
if (pszPath) { DWORD dwErrMode;
dwErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
fResult = (BOOL)(GetFileAttributes(pszPath) != (DWORD)-1);
SetErrorMode(dwErrMode); }
return fResult; }
/*----------------------------------------------------------
Purpose: Determines if a file exists, and returns the attributes of the file.
Returns: TRUE if it exists. If the function is able to get the file attributes and the caller passed a pdwAttributes, it will fill them in, else it will fill in -1.
******************************************************************************************************* !!NOTE!! If you want to fail on UNC servers (eg "\\pyrex") or UNC server\shares (eg "\\banyan\iptd") then you should call PathFileExists and not this api! *******************************************************************************************************
*/ STDAPI_(BOOL) PathFileExistsAndAttributes(LPCTSTR pszPath, OPTIONAL DWORD* pdwAttributes) { DWORD dwAttribs; BOOL fResult = FALSE;
RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFileExistsAndAttributes: caller passed bad pszPath");
if (pdwAttributes) { *pdwAttributes = (DWORD)-1; } if (pszPath) { DWORD dwErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
dwAttribs = GetFileAttributes(pszPath);
if (pdwAttributes) { *pdwAttributes = dwAttribs; }
if (dwAttribs == (DWORD)-1) { if (PathIsUNCServer(pszPath) || PathIsUNCServerShare(pszPath)) { NETRESOURCE nr = {0}; LPTSTR lpSystem = NULL; DWORD dwRet; DWORD dwSize; TCHAR Buffer[256];
nr.dwScope = RESOURCE_GLOBALNET; nr.dwType = RESOURCETYPE_ANY; nr.lpRemoteName = (LPTSTR)pszPath; dwSize = sizeof(Buffer); // the net api's might at least tell us if this exists or not in the \\server or \\server\share cases
// even if GetFileAttributes() failed
dwRet = WNetGetResourceInformation(&nr, Buffer, &dwSize, &lpSystem);
fResult = (dwRet == WN_SUCCESS || dwRet == WN_MORE_DATA); } } else { // GetFileAttributes succeeded!
fResult = TRUE; }
SetErrorMode(dwErrMode); }
return fResult; }
static const TCHAR c_szDotPif[] = TEXT(".pif"); static const TCHAR c_szDotCom[] = TEXT(".com"); static const TCHAR c_szDotBat[] = TEXT(".bat"); static const TCHAR c_szDotCmd[] = TEXT(".cmd"); static const TCHAR c_szDotLnk[] = TEXT(".lnk"); static const TCHAR c_szDotExe[] = TEXT(".exe"); static const TCHAR c_szNone[] = TEXT(""); // NB Look for .pif's first so that bound OS/2 apps (exe's)
// can have their dos stubs run via a pif.
//
// The COMMAND.COM search order is COM then EXE then BAT. Windows 3.x
// matched this search order. We need to search in the same order.
// *** WARNING *** The order of the PFOPEX_ flags must be identical to the order
// of the c_aDefExtList array. PathFileExistsDefExt relies on it.
static const LPCTSTR c_aDefExtList[] = { c_szDotPif, c_szDotCom, c_szDotExe, c_szDotBat, c_szDotLnk, c_szDotCmd, c_szNone }; #define IEXT_NONE (ARRAYSIZE(c_aDefExtList) - 1)
// *** END OF WARNING ***
static UINT _FindInDefExts(LPCTSTR pszExt, UINT fExt) { UINT iExt = 0; for (; iExt < ARRAYSIZE(c_aDefExtList); iExt++, fExt >>= 1) { // let NONE match up even tho there is
// no bit for it. that way find folders
// without a trailing dot correctly
if (fExt & 1 || (iExt == IEXT_NONE)) { if (0 == StrCmpI(pszExt, c_aDefExtList[iExt])) break; } } return iExt; }
// pszPath assumed to be MAX_PATH or larger...
static BOOL _ApplyDefaultExts(LPTSTR pszPath, UINT fExt, DWORD *pdwAttribs) { UINT cchPath = lstrlen(pszPath); // Bail if not enough space for 4 more chars
if (cchPath + ARRAYSIZE(c_szDotPif) < MAX_PATH) { LPTSTR pszPathEnd = pszPath + cchPath; UINT cchFileSpecEnd = (UINT)(pszPathEnd - PathFindFileName(pszPath)); DWORD dwAttribs = (DWORD) -1; // init to outside bounds
UINT iExtBest = ARRAYSIZE(c_aDefExtList); WIN32_FIND_DATA wfd = {0}; // set it up for the find
if (SUCCEEDED(StringCchCat(pszPath, MAX_PATH, TEXT(".*")))) { HANDLE h = FindFirstFile(pszPath, &wfd); if (h != INVALID_HANDLE_VALUE) { do { // use cchFileSpecEnd, instead of PathFindExtension(),
// so that if there is foo.bat and foo.bar.exe
// we dont incorrectly return foo.exe.
// this way we always compare apples to apples.
UINT iExt = _FindInDefExts((wfd.cFileName + cchFileSpecEnd), fExt); if (iExt < iExtBest) { iExtBest = iExt; dwAttribs = wfd.dwFileAttributes; }
} while (FindNextFile(h, &wfd));
FindClose(h); } if ((iExtBest < ARRAYSIZE(c_aDefExtList)) && SUCCEEDED(StringCchCopyEx(pszPathEnd, MAX_PATH - cchPath, c_aDefExtList[iExtBest], NULL, NULL, STRSAFE_NO_TRUNCATION))) { if (pdwAttribs) { *pdwAttribs = dwAttribs; } return TRUE; } else { // Get rid of any extension
*pszPathEnd = TEXT('\0'); } } }
return FALSE; }
//------------------------------------------------------------------
// Return TRUE if a file exists (by attribute check) after
// applying a default extensions (if req).
STDAPI_(BOOL) PathFileExistsDefExtAndAttributes(LPTSTR pszPath, UINT fExt, DWORD *pdwAttribs) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFileExistsDefExt: caller passed bad pszPath");
if (fExt) { RIPMSG(!pszPath || !IS_VALID_STRING_PTR(pszPath, -1) || // avoid RIP when above RIP would have caught it
IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathFileExistsDefExt: caller passed bad pszPath"); DEBUGWhackPathString(pszPath, MAX_PATH); }
if (pdwAttribs) *pdwAttribs = (DWORD) -1;
if (pszPath) { // Try default extensions?
if (fExt && (!*PathFindExtension(pszPath) || !(PFOPEX_OPTIONAL & fExt))) { return _ApplyDefaultExts(pszPath, fExt, pdwAttribs); } else { return PathFileExistsAndAttributes(pszPath, pdwAttribs); } } return FALSE; }
//------------------------------------------------------------------
// Return TRUE if a file exists (by attribute check) after
// applying a default extensions (if req).
STDAPI_(BOOL) PathFileExistsDefExt(LPTSTR pszPath, UINT fExt) { // No sense sticking an extension on a server or share...
if (PathIsUNCServer(pszPath) || PathIsUNCServerShare(pszPath)) { return FALSE; } else return PathFileExistsDefExtAndAttributes(pszPath, fExt, NULL); }
// walk through a path type string (semicolon seperated list of names)
// this deals with spaces and other bad things in the path
//
// call with initial pointer, then continue to call with the
// result pointer until it returns NULL
//
// input: "C:\FOO;C:\BAR;"
//
// in:
// lpPath starting point of path string "C:\foo;c:\dos;c:\bar"
// cchPath size of szPath
//
// out:
// szPath buffer with path piece
//
// returns:
// pointer to next piece to be used, NULL if done
//
//
// FEATURE, we should write some test cases specifically for this code
//
STDAPI_(LPCTSTR) NextPath(LPCTSTR lpPath, LPTSTR szPath, int cchPath) { LPCTSTR lpEnd;
if (!lpPath) return NULL;
// skip any leading ; in the path...
while (*lpPath == TEXT(';')) { lpPath++; }
// See if we got to the end
if (*lpPath == 0) { // Yep
return NULL; }
lpEnd = StrChr(lpPath, TEXT(';'));
if (!lpEnd) { lpEnd = lpPath + lstrlen(lpPath); }
StrCpyN(szPath, lpPath, min((DWORD)cchPath, (DWORD)(lpEnd - lpPath + 1)));
szPath[lpEnd-lpPath] = TEXT('\0');
PathRemoveBlanks(szPath);
if (szPath[0]) { if (*lpEnd == TEXT(';')) { // next path string (maybe NULL)
return lpEnd + 1; } else { // pointer to NULL
return lpEnd; } } else { return NULL; } }
// check to see if a dir is on the other dir list
// use this to avoid looking in the same directory twice (don't make the same dos call)
BOOL IsOtherDir(LPCTSTR pszPath, LPCTSTR *ppszOtherDirs) { for (;*ppszOtherDirs; ppszOtherDirs++) { if (lstrcmpi(pszPath, *ppszOtherDirs) == 0) { return TRUE; } } return FALSE; }
//----------------------------------------------------------------------------
// fully qualify a path by walking the path and optionally other dirs
//
// in:
// ppszOtherDirs a list of LPCTSTRs to other paths to look
// at first, NULL terminated.
//
// fExt
// PFOPEX_ flags specifying what to look for (exe, com, bat, lnk, pif)
//
// in/out
// pszFile non qualified path, returned fully qualified
// if found (return was TRUE), otherwise unaltered
// (return FALSE);
//
// returns:
// TRUE the file was found on and qualified
// FALSE the file was not found
//
STDAPI_(BOOL) PathFindOnPathEx(LPTSTR pszFile, LPCTSTR* ppszOtherDirs, UINT fExt) { TCHAR szPath[MAX_PATH]; TCHAR szFullPath[256]; // Default size for buffer
LPTSTR pszEnv = NULL; // Use if greater than default
LPCTSTR lpPath; int i;
RIPMSG(pszFile && IS_VALID_STRING_PTR(pszFile, -1) && IS_VALID_WRITE_BUFFER(pszFile, TCHAR, MAX_PATH), "PathFindOnPathEx: caller passed bad pszFile"); DEBUGWhackPathString(pszFile, MAX_PATH);
if (!pszFile) // REVIEW: do we need to check !*pszFile too?
return FALSE;
// REVIEW, we may want to just return TRUE here but for
// now assume only file specs are allowed
if (!PathIsFileSpec(pszFile)) return FALSE;
// first check list of other dirs
for (i = 0; ppszOtherDirs && ppszOtherDirs[i] && *ppszOtherDirs[i]; i++) { PathCombine(szPath, ppszOtherDirs[i], pszFile); if (PathFileExistsDefExt(szPath, fExt)) { StringCchCopy(pszFile, MAX_PATH, szPath); return TRUE; } }
// Look in system dir (system for Win95, system32 for NT)
// - this should probably be optional.
GetSystemDirectory(szPath, ARRAYSIZE(szPath)); if (!PathAppend(szPath, pszFile)) return FALSE;
if (PathFileExistsDefExt(szPath, fExt)) { StringCchCopy(pszFile, MAX_PATH, szPath); return TRUE; }
{ #ifdef WX86
// Look in WX86 system directory (WindDir\Sys32x86)
NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = TRUE; GetSystemDirectory(szPath, ARRAYSIZE(szPath)); NtCurrentTeb()->Wx86Thread.UseKnownWx86Dll = FALSE;
if (!PathAppend(szPath, pszFile)) return FALSE;
if (PathFileExistsDefExt(szPath, fExt)) { StringCchCopy(pszFile, MAX_PATH, szPath); return TRUE; } #endif
// Look in WOW directory (\nt\system instead of \nt\system32)
GetWindowsDirectory(szPath, ARRAYSIZE(szPath));
if (!PathAppend(szPath,TEXT("System"))) return FALSE; if (!PathAppend(szPath, pszFile)) return FALSE;
if (PathFileExistsDefExt(szPath, fExt)) { StringCchCopy(pszFile, MAX_PATH, szPath); return TRUE; } }
// Look in windows dir - this should probably be optional.
GetWindowsDirectory(szPath, ARRAYSIZE(szPath)); if (!PathAppend(szPath, pszFile)) return FALSE;
if (PathFileExistsDefExt(szPath, fExt)) { StringCchCopy(pszFile, MAX_PATH, szPath); return TRUE; }
// Look along the path.
i = GetEnvironmentVariable(c_szPATH, szFullPath, ARRAYSIZE(szFullPath)); if (i >= ARRAYSIZE(szFullPath)) { pszEnv = (LPTSTR)LocalAlloc(LPTR, i*sizeof(TCHAR)); // no need for +1, i includes it
if (pszEnv == NULL) return FALSE;
GetEnvironmentVariable(c_szPATH, pszEnv, i);
lpPath = pszEnv; } else { if (i == 0) return FALSE;
lpPath = szFullPath; }
while (NULL != (lpPath = NextPath(lpPath, szPath, ARRAYSIZE(szPath)))) { if (!ppszOtherDirs || !IsOtherDir(szPath, ppszOtherDirs)) { PathAppend(szPath, pszFile); if (PathFileExistsDefExt(szPath, fExt)) { StringCchCopy(pszFile, MAX_PATH, szPath); if (pszEnv) LocalFree((HLOCAL)pszEnv); return TRUE; } } }
if (pszEnv) LocalFree((HLOCAL)pszEnv);
return FALSE; }
/*----------------------------------------------------------
Purpose: Find the given file on the path.
Returns: Cond: -- */ STDAPI_(BOOL) PathFindOnPath(LPTSTR pszFile, LPCTSTR* ppszOtherDirs) { return PathFindOnPathEx(pszFile, ppszOtherDirs, PFOPEX_NONE); }
// returns a pointer to the extension of a file.
//
// in:
// qualified or unqualfied file name
//
// returns:
// pointer to the extension of this file. if there is no extension
// as in "foo" we return a pointer to the NULL at the end
// of the file
//
// foo.txt ==> ".txt"
// foo ==> ""
// foo. ==> "."
//
STDAPI_(LPTSTR) PathFindExtension(LPCTSTR pszPath) { LPCTSTR pszDot = NULL;
RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFindExtension: caller passed bad pszPath");
if (pszPath) { for (; *pszPath; pszPath = FAST_CharNext(pszPath)) { switch (*pszPath) { case TEXT('.'): pszDot = pszPath; // remember the last dot
break;
case CH_WHACK: case TEXT(' '): // extensions can't have spaces
pszDot = NULL; // forget last dot, it was in a directory
break; } } }
// if we found the extension, return ptr to the dot, else
// ptr to end of the string (NULL extension) (cast->non const)
return pszDot ? (LPTSTR)pszDot : (LPTSTR)pszPath; }
//
// Find if a given pathname contains any one of the suffixes in a given array of suffixes
//
// in:
// pszPath A filename with or without a path.
//
// apszSuffix An array of suffixes that we are looking for.
//
// returns:
// pointer to the suffix in pszPath, if it exists.
// NULL is returned if the given path does not end with the given suffix.
//
// NOTE: This does a CASE SENSITIVE comparison!!! So, the suffix will have to match exactly.
//
STDAPI_(LPCTSTR) PathFindSuffixArray(LPCTSTR pszPath, const LPCTSTR* apszSuffix, int iArraySize) { RIPMSG((iArraySize>=0 && (pszPath && IS_VALID_STRING_PTR(pszPath, -1) && apszSuffix)), "PathFindSuffixArray: caller passed bad parameters");
if (pszPath && apszSuffix) { int iLenSuffix; int iLenPath = lstrlen(pszPath); LPCTSTR pszTail; int i;
for(i = 0; i< iArraySize; i++) { iLenSuffix = lstrlen(apszSuffix[i]); if(iLenPath < iLenSuffix) continue;
// Let's get to a pointer to the tail piece which is the same length as the suffix
// we are looking for.
pszTail = (LPCTSTR)(pszPath+iLenPath-iLenSuffix);
#ifndef UNICODE
{ LPCSTR pszTemp = pszTail; // In the ANSI world, pszTemp could be in the middle of a DBCS character.
// So, move pszTemp such that it points to the begining of a valid character Lead char.
while(pszTemp > pszPath) { pszTemp--; if(!IsDBCSLeadByte(*pszTemp)) { // Since pszTemp is pointing to the FIRST trail Byte, the next byte must be a
// valid character. Move pszTemp to point to a valid character.
pszTemp++; break; } }
// Everything between pszTemp and pszTail is nothing but lead characters. So, see if they
// are Odd or Even number of them.
if(((int)(pszTail - pszTemp)&1) && (pszTail > pszPath)) { // There are odd number of lead bytes. That means that pszTail is definitely in the
// middle of a DBCS character. Move it to such that it points to a valid char.
pszTail--; } } #endif
if(!lstrcmp(pszTail, apszSuffix[i])) return pszTail; } }
//Given suffix is not found in the array!
return NULL; }
// add .exe to a file name (if no extension was already there)
//
// in:
// pszExtension extension to tag on, if NULL .exe is assumed
// (".bat", ".txt", etc)
//
// in/out:
// pszPath path string to modify
//
//
// returns:
// TRUE added .exe (there was no extension to begin with)
// FALSE didn't change the name (it already had an extension)
STDAPI_(BOOL) PathAddExtension(LPTSTR pszPath, LPCTSTR pszExtension) { BOOL bRet = FALSE;
RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1) && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathAddExtension: caller passed bad pszPath"); RIPMSG(!pszExtension || IS_VALID_STRING_PTR(pszExtension, -1), "PathAddExtension: caller passed bad pszExtension"); DEBUGWhackPathString(pszPath, MAX_PATH);
if (pszPath) { if (*PathFindExtension(pszPath) == 0 && ((lstrlen(pszPath) + lstrlen(pszExtension ? pszExtension : c_szDotExe)) < MAX_PATH)) { if (pszExtension == NULL) { pszExtension = c_szDotExe; }
bRet = SUCCEEDED(StringCchCatEx(pszPath, MAX_PATH, pszExtension, NULL, NULL, STRSAFE_NO_TRUNCATION)); } }
return bRet; }
/*----------------------------------------------------------
Purpose: Remove the extension from pszPath, if one exists.
Returns: -- Cond: -- */ STDAPI_(void) PathRemoveExtension(LPTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathRemoveExtension: caller passed bad pszPath");
if (pszPath) { LPTSTR pExt = PathFindExtension(pszPath); if (*pExt) { ASSERT(*pExt == TEXT('.')); *pExt = 0; // null out the "."
} } }
/*----------------------------------------------------------
Purpose: Renames the extension
Returns: FALSE if not enough room Cond: -- */ STDAPI_(BOOL) PathRenameExtension(LPTSTR pszPath, LPCTSTR pszExt) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1) && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathRenameExtension: caller passed bad pszPath"); RIPMSG(pszExt && IS_VALID_STRING_PTR(pszExt, -1), "PathRenameExtension: caller passed bad pszExt"); DEBUGWhackPathString(pszPath, MAX_PATH);
if (pszPath && pszExt) { LPTSTR pCurExt = PathFindExtension(pszPath); // Rets ptr to end of str if none
int cchNewExt = lstrlen(pszExt); size_t cchCurOther = pCurExt - pszPath;
if (cchNewExt + cchCurOther + 1 > MAX_PATH) // +1 for the null terminator
{ return FALSE; }
StringCchCopy(pCurExt, MAX_PATH - cchCurOther, pszExt); return TRUE; } return FALSE; }
// find the next slash or null terminator
LPCTSTR StrSlash(LPCTSTR psz) { for (; *psz && *psz != CH_WHACK; psz = FAST_CharNext(psz));
return psz; }
//
// in:
// pszFile1 -- fully qualified path name to file #1.
// pszFile2 -- fully qualified path name to file #2.
//
// out:
// pszPath -- pointer to a string buffer (may be NULL)
//
// returns:
// length of output buffer not including the NULL
//
// examples:
// c:\win\desktop\foo.txt
// c:\win\tray\bar.txt
// -> c:\win
//
// c:\ ;
// c:\ ;
// -> c:\ NOTE, includes slash
//
// Returns:
// Length of the common prefix string usually does NOT include
// trailing slash, BUT for roots it does.
//
STDAPI_(int) PathCommonPrefix(LPCTSTR pszFile1, LPCTSTR pszFile2, LPTSTR pszPath) { RIPMSG(pszFile1 && IS_VALID_STRING_PTR(pszFile1, -1), "PathCommonPrefix: caller passed bad pszFile1"); RIPMSG(pszFile2 && IS_VALID_STRING_PTR(pszFile2, -1), "PathCommonPrefix: caller passed bad pszFile2"); RIPMSG(!pszPath || IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathCommonPrefix: caller passed bad pszPath");
if (pszFile1 && pszFile2) { LPCTSTR psz1, psz2, pszNext1, pszNext2, pszCommon; int cch;
pszCommon = NULL; if (pszPath) *pszPath = TEXT('\0');
psz1 = pszFile1; psz2 = pszFile2;
// special cases for UNC, don't allow "\\" to be a common prefix
if (DBL_BSLASH(pszFile1)) { if (!DBL_BSLASH(pszFile2)) return 0;
psz1 = pszFile1 + 2; } if (DBL_BSLASH(pszFile2)) { if (!DBL_BSLASH(pszFile1)) return 0;
psz2 = pszFile2 + 2; }
while (1) { if (!(*psz1 != CH_WHACK && *psz2 != CH_WHACK)) TraceMsg(TF_WARNING, "PathCommonPrefix: caller passed in ill-formed or non-qualified path");
pszNext1 = StrSlash(psz1); pszNext2 = StrSlash(psz2);
cch = (int) (pszNext1 - psz1);
if (cch != (pszNext2 - psz2)) break; // lengths of segments not equal
if (StrIntlEqNI(psz1, psz2, cch)) pszCommon = pszNext1; else break;
ASSERT(*pszNext1 == TEXT('\0') || *pszNext1 == CH_WHACK); ASSERT(*pszNext2 == TEXT('\0') || *pszNext2 == CH_WHACK);
if (*pszNext1 == TEXT('\0')) break;
psz1 = pszNext1 + 1;
if (*pszNext2 == TEXT('\0')) break;
psz2 = pszNext2 + 1; }
if (pszCommon) { cch = (int) (pszCommon - pszFile1);
// special case the root to include the slash
if (cch == 2) { ASSERT(pszFile1[1] == TEXT(':')); cch++; } } else cch = 0;
if (pszPath && (cch < MAX_PATH)) { CopyMemory(pszPath, pszFile1, cch * sizeof(TCHAR)); pszPath[cch] = TEXT('\0'); }
return cch; }
return 0; }
/*----------------------------------------------------------
Purpose: Returns TRUE if pszPrefix is the full prefix of pszPath.
Returns: Cond: -- */ STDAPI_(BOOL) PathIsPrefix(IN LPCTSTR pszPrefix, IN LPCTSTR pszPath) { RIPMSG(pszPrefix && IS_VALID_STRING_PTR(pszPrefix, -1), "PathIsPrefix: caller passed bad pszPrefix"); RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsPrefix: caller passed bad pszPath");
if (pszPrefix && pszPath) { int cch = PathCommonPrefix(pszPath, pszPrefix, NULL);
return (lstrlen(pszPrefix) == cch); } return FALSE; }
static const TCHAR c_szDot[] = TEXT("."); static const TCHAR c_szDotDot[] = TEXT("..");
static const TCHAR c_szDotDotSlash[] = TEXT("..\\");
// in:
// pszFrom base path, including filespec!
// pszTo path to be relative to pszFrom
// out:
// relative path to construct pszTo from the base path of pszFrom
//
// c:\a\b\FileA
// c:\a\x\y\FileB
// -> ..\x\y\FileB
//
STDAPI_(BOOL) PathRelativePathTo(LPTSTR pszPath, LPCTSTR pszFrom, DWORD dwAttrFrom, LPCTSTR pszTo, DWORD dwAttrTo) { #ifdef DEBUG
TCHAR szFromCopy[MAX_PATH]; TCHAR szToCopy[MAX_PATH];
RIPMSG(pszPath && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathRelativePathTo: caller passed bad pszPath"); RIPMSG(pszFrom && IS_VALID_STRING_PTR(pszFrom, -1), "PathRelativePathTo: caller passed bad pszFrom"); RIPMSG(pszTo && IS_VALID_STRING_PTR(pszTo, -1), "PathRelativePathTo: caller passed bad pszTo");
// we make copies of the pszFrom and pszTo buffers in case one of the strings they are passing is a pointer
// inside pszPath buffer. If this were the case, it would be trampled when we call DEBUGWhackPathBuffer().
if (pszFrom) { StrCpyN(szFromCopy, pszFrom, ARRAYSIZE(szFromCopy)); pszFrom = szFromCopy; } if (pszTo) { StrCpyN(szToCopy, pszTo, ARRAYSIZE(szToCopy)); pszTo = szToCopy; } #endif DEBUG
if (pszPath && pszFrom && pszTo) { TCHAR szFrom[MAX_PATH], szTo[MAX_PATH]; LPTSTR psz; UINT cchCommon;
DEBUGWhackPathBuffer(pszPath, MAX_PATH);
*pszPath = 0; // assume none
StrCpyN(szFrom, pszFrom, ARRAYSIZE(szFrom)); StrCpyN(szTo, pszTo, ARRAYSIZE(szTo));
if (!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY)) PathRemoveFileSpec(szFrom);
if (!(dwAttrTo & FILE_ATTRIBUTE_DIRECTORY)) PathRemoveFileSpec(szTo);
cchCommon = PathCommonPrefix(szFrom, szTo, NULL); if (cchCommon == 0) return FALSE;
psz = szFrom + cchCommon;
if (*psz) { // build ..\.. part of the path
if (*psz == CH_WHACK) { // skip slash
psz++; }
while (*psz) { psz = PathFindNextComponent(psz); StringCchCat(pszPath, MAX_PATH, *psz ? c_szDotDotSlash : c_szDotDot); } } else { StringCchCopy(pszPath, MAX_PATH, c_szDot); }
if (pszTo[cchCommon]) { // deal with root case
if (pszTo[cchCommon] != CH_WHACK) cchCommon--;
if ((lstrlen(pszPath) + lstrlen(pszTo + cchCommon)) >= MAX_PATH) { TraceMsg(TF_ERROR, "PathRelativePathTo: path won't fit in buffer"); *pszPath = 0; return FALSE; }
ASSERT(pszTo[cchCommon] == CH_WHACK); StringCchCat(pszPath, MAX_PATH, pszTo + cchCommon); }
ASSERT(PathIsRelative(pszPath)); ASSERT(lstrlen(pszPath) < MAX_PATH);
return TRUE; }
return FALSE; }
/*----------------------------------------------------------
Purpose: Build a root path name given a drive number.
Returns: pszRoot */ STDAPI_(LPTSTR) PathBuildRoot(LPTSTR pszRoot, int iDrive) { RIPMSG(pszRoot && IS_VALID_WRITE_BUFFER(pszRoot, TCHAR, 4), "PathBuildRoot: caller passed bad pszRoot"); RIPMSG(iDrive >= 0 && iDrive < 26, "PathBuildRoot: caller passed bad iDrive");
if (pszRoot && iDrive >= 0 && iDrive < 26) { pszRoot[0] = (TCHAR)iDrive + (TCHAR)TEXT('A'); pszRoot[1] = TEXT(':'); pszRoot[2] = TEXT('\\'); pszRoot[3] = 0; }
return pszRoot; }
// Strips leading and trailing blanks from a string.
// Alters the memory where the string sits.
//
// in:
// lpszString string to strip
//
// out:
// lpszString string sans leading/trailing blanks
//
STDAPI_(void) PathRemoveBlanks(LPTSTR lpszString) { RIPMSG(lpszString && IS_VALID_STRING_PTR(lpszString, -1), "PathRemoveBlanks: caller passed bad lpszString");
if (lpszString) { LPTSTR lpszPosn = lpszString;
/* strip leading blanks */ while (*lpszPosn == TEXT(' ')) { lpszPosn++; }
if (lpszPosn != lpszString) { StringCchCopy(lpszString, MAX_PATH, lpszPosn); }
/* strip trailing blanks */
// Find the last non-space
// Note that AnsiPrev is cheap is non-DBCS, but very expensive otherwise
for (lpszPosn=lpszString; *lpszString; lpszString=FAST_CharNext(lpszString)) { if (*lpszString != TEXT(' ')) { lpszPosn = lpszString; } }
// Note AnsiNext is a macro for non-DBCS, so it will not stop at NULL
if (*lpszPosn) { *FAST_CharNext(lpszPosn) = TEXT('\0'); } } }
// Removes a trailing backslash from a path
//
// in:
// lpszPath (A:\, C:\foo\, etc)
//
// out:
// lpszPath (A:\, C:\foo, etc)
//
// returns:
// ponter to NULL that replaced the backslash
// or the pointer to the last character if it isn't a backslash.
//
STDAPI_(LPTSTR) PathRemoveBackslash(LPTSTR lpszPath) { RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathRemoveBackslash: caller passed bad lpszPath");
if (lpszPath) { int len = lstrlen(lpszPath)-1; if (IsDBCSLeadByte(*CharPrev(lpszPath,lpszPath+len+1))) len--;
if (!PathIsRoot(lpszPath) && lpszPath[len] == CH_WHACK) lpszPath[len] = TEXT('\0');
return lpszPath + len; } return NULL; }
//
// Return a pointer to the end of the next path componenent in the string.
// ie return a pointer to the next backslash or terminating NULL.
//
LPCTSTR GetPCEnd(LPCTSTR lpszStart) { LPCTSTR lpszEnd;
lpszEnd = StrChr(lpszStart, CH_WHACK); if (!lpszEnd) { lpszEnd = lpszStart + lstrlen(lpszStart); }
return lpszEnd; }
//
// Given a pointer to the end of a path component, return a pointer to
// its begining.
// ie return a pointer to the previous backslash (or start of the string).
//
LPCTSTR PCStart(LPCTSTR lpszStart, LPCTSTR lpszEnd) { LPCTSTR lpszBegin = StrRChr(lpszStart, lpszEnd, CH_WHACK); if (!lpszBegin) { lpszBegin = lpszStart; } return lpszBegin; }
//
// Fix up a few special cases so that things roughly make sense.
//
void NearRootFixups(LPTSTR lpszPath, BOOL fUNC) { // Check for empty path.
if (lpszPath[0] == TEXT('\0')) { // Fix up.
lpszPath[0] = CH_WHACK; lpszPath[1] = TEXT('\0'); } // Check for missing slash.
if (!IsDBCSLeadByte(lpszPath[0]) && lpszPath[1] == TEXT(':') && lpszPath[2] == TEXT('\0')) { // Fix up.
lpszPath[2] = TEXT('\\'); lpszPath[3] = TEXT('\0'); } // Check for UNC root.
if (fUNC && lpszPath[0] == TEXT('\\') && lpszPath[1] == TEXT('\0')) { // Fix up.
//lpszPath[0] = TEXT('\\'); // already checked in if guard
lpszPath[1] = TEXT('\\'); lpszPath[2] = TEXT('\0'); } }
/*----------------------------------------------------------
Purpose: Canonicalize a path.
Returns: Cond: -- */ STDAPI_(BOOL) PathCanonicalize(LPTSTR lpszDst, LPCTSTR lpszSrc) { LPCTSTR lpchSrc; LPCTSTR lpchPCEnd; // Pointer to end of path component.
LPTSTR lpchDst; BOOL fUNC; int cchPC; HRESULT hr;
RIPMSG(lpszDst && IS_VALID_WRITE_BUFFER(lpszDst, TCHAR, MAX_PATH), "PathCanonicalize: caller passed bad lpszDst"); RIPMSG(lpszSrc && IS_VALID_STRING_PTR(lpszSrc, -1), "PathCanonicalize: caller passed bad lpszSrc"); RIPMSG(lpszDst != lpszSrc, "PathCanonicalize: caller passed the same buffer for lpszDst and lpszSrc");
if (!lpszDst || !lpszSrc) { SetLastError(ERROR_INVALID_PARAMETER); return FALSE; }
DEBUGWhackPathBuffer(lpszDst, MAX_PATH); *lpszDst = TEXT('\0'); fUNC = PathIsUNC(lpszSrc); // Check for UNCness.
// Init.
lpchSrc = lpszSrc; lpchDst = lpszDst;
while (*lpchSrc) { // REVIEW: this should just return the count
lpchPCEnd = GetPCEnd(lpchSrc); cchPC = (int) (lpchPCEnd - lpchSrc)+1;
if (cchPC == 1 && *lpchSrc == CH_WHACK) // Check for slashes.
{ // Just copy them.
hr = StringCchCopy(lpchDst, lpszDst + MAX_PATH - lpchDst, SZ_WHACK); if (FAILED(hr)) { return FALSE; // dest exceeded MAX_PATH!
}
lpchDst++; lpchSrc++; } else if (cchPC == 2 && *lpchSrc == TEXT('.')) // Check for dots.
{ // Skip it...
// Are we at the end?
if (*(lpchSrc+1) == TEXT('\0')) { lpchSrc++;
// remove the last slash we copied (if we've copied one), but don't make a mal-formed root
if ((lpchDst > lpszDst) && !PathIsRoot(lpszDst)) { lpchDst--; } } else // ".\"
{ lpchSrc += 2; // ignore this path segment
} } else if (cchPC == 3 && *lpchSrc == TEXT('.') && *(lpchSrc + 1) == TEXT('.')) // Check for dot dot.
{ // make sure we aren't already at the root
if (!PathIsRoot(lpszDst)) { // Go up... Remove the previous path component.
lpchDst = (LPTSTR)PCStart(lpszDst, lpchDst - 1); } else { // When we can't back up, skip the trailing backslash
// so we don't copy one again. (C:\..\FOO would otherwise
// turn into C:\\FOO).
if (*(lpchSrc + 2) == CH_WHACK) { lpchSrc++; } }
// skip ".."
lpchSrc += 2; } else // Everything else
{ // Just copy it.
hr = StringCchCopyN(lpchDst, lpszDst + MAX_PATH - lpchDst, lpchSrc, cchPC - 1); if (FAILED(hr)) { return FALSE; // dest exceeded MAX_PATH!
} lpchDst += cchPC - 1; lpchSrc += cchPC - 1; }
// Keep everything nice and tidy.
*lpchDst = TEXT('\0'); }
// Check for weirdo root directory stuff.
NearRootFixups(lpszDst, fUNC);
return TRUE; }
// Modifies:
// pszRoot
//
// Returns:
// TRUE if a drive root was found
// FALSE otherwise
//
STDAPI_(BOOL) PathStripToRoot(LPTSTR pszRoot) { RIPMSG(pszRoot && IS_VALID_STRING_PTR(pszRoot, -1), "PathStripToRoot: caller passed bad pszRoot");
if (pszRoot) { while (!PathIsRoot(pszRoot)) { if (!PathRemoveFileSpec(pszRoot)) { // If we didn't strip anything off,
// must be current drive
return FALSE; } } return TRUE; } return FALSE; }
/*----------------------------------------------------------
Purpose: Concatenate lpszDir and lpszFile into a properly formed path and canonicalize any relative path pieces.
lpszDest and lpszFile can be the same buffer lpszDest and lpszDir can be the same buffer
Returns: pointer to lpszDest */ STDAPI_(LPTSTR) PathCombine(LPTSTR lpszDest, LPCTSTR lpszDir, LPCTSTR lpszFile) { #ifdef DEBUG
TCHAR szDirCopy[MAX_PATH]; TCHAR szFileCopy[MAX_PATH];
RIPMSG(lpszDest && IS_VALID_WRITE_BUFFER(lpszDest, TCHAR, MAX_PATH), "PathCombine: caller passed bad lpszDest"); RIPMSG(!lpszDir || IS_VALID_STRING_PTR(lpszDir, -1), "PathCombine: caller passed bad lpszDir"); RIPMSG(!lpszFile || IS_VALID_STRING_PTR(lpszFile, -1), "PathCombine: caller passed bad lpszFile"); RIPMSG(lpszDir || lpszFile, "PathCombine: caller neglected to pass lpszDir or lpszFile");
// we make copies of all the lpszDir and lpszFile buffers in case one of the strings they are passing is a pointer
// inside lpszDest buffer. If this were the case, it would be trampled when we call DEBUGWhackPathBuffer().
if (lpszDir) { StrCpyN(szDirCopy, lpszDir, ARRAYSIZE(szDirCopy)); lpszDir = szDirCopy; } if (lpszFile) { StrCpyN(szFileCopy, lpszFile, ARRAYSIZE(szFileCopy)); lpszFile = szFileCopy; }
// lpszDest could be lpszDir, so be careful which one we call
if (lpszDest != lpszDir && lpszDest != lpszFile) DEBUGWhackPathBuffer(lpszDest, MAX_PATH); else if (lpszDest) DEBUGWhackPathString(lpszDest, MAX_PATH); #endif DEBUG
if (lpszDest) { TCHAR szTemp[MAX_PATH]; LPTSTR pszT;
*szTemp = TEXT('\0');
if (lpszDir && *lpszDir) { if (!lpszFile || *lpszFile==TEXT('\0')) { StrCpyN(szTemp, lpszDir, ARRAYSIZE(szTemp)); // lpszFile is empty
} else if (PathIsRelative(lpszFile)) { StrCpyN(szTemp, lpszDir, ARRAYSIZE(szTemp)); pszT = PathAddBackslash(szTemp); if (pszT) { int iRemaining = (int)(ARRAYSIZE(szTemp) - (pszT - szTemp));
if (lstrlen(lpszFile) < iRemaining) { StrCpyN(pszT, lpszFile, iRemaining); } else { *szTemp = TEXT('\0'); } } else { *szTemp = TEXT('\0'); } } else if (*lpszFile == CH_WHACK && !PathIsUNC(lpszFile)) { StrCpyN(szTemp, lpszDir, ARRAYSIZE(szTemp)); // FEATURE: Note that we do not check that an actual root is returned;
// it is assumed that we are given valid parameters
PathStripToRoot(szTemp);
pszT = PathAddBackslash(szTemp); if (pszT) { // Skip the backslash when copying
// Note: We don't support strings longer than 4GB, but that's
// okay because we already barf at MAX_PATH
StrCpyN(pszT, lpszFile+1, (int)(ARRAYSIZE(szTemp) - (pszT - szTemp))); } else { *szTemp = TEXT('\0'); } } else { StrCpyN(szTemp, lpszFile, ARRAYSIZE(szTemp)); // already fully qualified file part
} } else if (lpszFile && *lpszFile) { StrCpyN(szTemp, lpszFile, ARRAYSIZE(szTemp)); // no dir just use file.
}
//
// if szTemp has something in it we succeeded. Also if szTemp is empty and
// the input strings are empty we succeed and PathCanonicalize() will
// return "\"
//
if (*szTemp || ((lpszDir || lpszFile) && !((lpszDir && *lpszDir) || (lpszFile && *lpszFile)))) { // this deals with .. and . stuff
// returns "\" on empty szTemp
if (!PathCanonicalize(lpszDest, szTemp)) { *lpszDest = TEXT('\0'); lpszDest = NULL; } } else { *lpszDest = TEXT('\0'); // set output buffer to empty string.
lpszDest = NULL; // return failure.
} }
return lpszDest; }
/*----------------------------------------------------------
Purpose: Appends a filename to a path. Checks the \ problem first (which is why one can't just use StrCatBuff()) Also don't append a \ to : so we can have drive-relative paths... this last bit is no longer appropriate since we qualify first!
Returns: */ STDAPI_(BOOL) PathAppend(LPTSTR pszPath, LPCTSTR pszMore) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1) && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathAppend: caller passed bad pszPath"); RIPMSG(pszMore && IS_VALID_STRING_PTR(pszMore, -1), "PathAppend: caller passed bad pszMore"); // PathCombine will do this for us: DEBUGWhackPathString(pszPath, MAX_PATH);
if (pszPath && pszMore) { // Skip any initial terminators on input, unless it is a UNC path in wich case we will
// treat it as a full path
if (!PathIsUNC(pszMore)) { while (*pszMore == CH_WHACK) { #ifndef UNICODE
pszMore = FAST_CharNext(pszMore); #else
pszMore++; #endif
} }
return PathCombine(pszPath, pszPath, pszMore) ? TRUE : FALSE; } return FALSE; }
// rips the last part of the path off including the backslash
// C:\foo -> C:\ // C:\foo\bar -> C:\foo
// C:\foo\ -> C:\foo
// \\x\y\x -> \\x\y
// \\x\y -> \\x
// \\x -> \\ (Just the double slash!)
// \foo -> \ (Just the slash!)
//
// in/out:
// pFile fully qualified path name
// returns:
// TRUE we stripped something
// FALSE didn't strip anything (root directory case)
//
STDAPI_(BOOL) PathRemoveFileSpec(LPTSTR pFile) { RIPMSG(pFile && IS_VALID_STRING_PTR(pFile, -1), "PathRemoveFileSpec: caller passed bad pFile");
if (pFile) { LPTSTR pT; LPTSTR pT2 = pFile;
for (pT = pT2; *pT2; pT2 = FAST_CharNext(pT2)) { if (*pT2 == CH_WHACK) { pT = pT2; // last "\" found, (we will strip here)
} else if (*pT2 == TEXT(':')) // skip ":\" so we don't
{ if (pT2[1] ==TEXT('\\')) // strip the "\" from "C:\"
{ pT2++; } pT = pT2 + 1; } }
if (*pT == 0) { // didn't strip anything
return FALSE; } else if (((pT == pFile) && (*pT == CH_WHACK)) || // is it the "\foo" case?
((pT == pFile+1) && (*pT == CH_WHACK && *pFile == CH_WHACK))) // or the "\\bar" case?
{ // Is it just a '\'?
if (*(pT+1) != TEXT('\0')) { // Nope.
*(pT+1) = TEXT('\0'); return TRUE; // stripped something
} else { // Yep.
return FALSE; } } else { *pT = 0; return TRUE; // stripped something
} } return FALSE; }
// add a backslash to a qualified path
//
// in:
// lpszPath path (A:, C:\foo, etc)
//
// out:
// lpszPath A:\, C:\foo\ ;
//
// returns:
// pointer to the NULL that terminates the path
//
STDAPI_(LPTSTR) PathAddBackslash(LPTSTR lpszPath) { LPTSTR lpszRet = NULL;
RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathAddBackslash: caller passed bad lpszPath");
if (lpszPath) { int ichPath = lstrlen(lpszPath); LPTSTR lpszEnd = lpszPath + ichPath;
if (ichPath) {
// Get the end of the source directory
switch(*CharPrev(lpszPath, lpszEnd)) { case CH_WHACK: break;
default: // try to keep us from tromping over MAX_PATH in size.
// if we find these cases, return NULL. Note: We need to
// check those places that call us to handle their GP fault
// if they try to use the NULL!
if (ichPath >= (MAX_PATH - 2)) // -2 because ichPath doesn't include NULL, and we're adding a CH_WHACK.
{ TraceMsg(TF_WARNING, "PathAddBackslash: caller passed in lpszPath > MAX_PATH, can't append whack"); return(NULL); }
*lpszEnd++ = CH_WHACK; *lpszEnd = TEXT('\0'); } }
lpszRet = lpszEnd; }
return lpszRet; }
// Returns a pointer to the last component of a path string.
//
// in:
// path name, either fully qualified or not
//
// returns:
// pointer into the path where the path is. if none is found
// returns a poiter to the start of the path
//
// c:\foo\bar -> bar
// c:\foo -> foo
// c:\foo\ -> c:\foo\ (REVIEW: is this case busted?)
// c:\ -> c:\ (REVIEW: this case is strange)
// c: -> c:
// foo -> foo
//
STDAPI_(LPTSTR) PathFindFileName(LPCTSTR pPath) { LPCTSTR pT = pPath; RIPMSG(pPath && IS_VALID_STRING_PTR(pPath, -1), "PathFindFileName: caller passed bad pPath");
if (pPath) { for ( ; *pPath; pPath = FAST_CharNext(pPath)) { if ((pPath[0] == TEXT('\\') || pPath[0] == TEXT(':') || pPath[0] == TEXT('/')) && pPath[1] && pPath[1] != TEXT('\\') && pPath[1] != TEXT('/')) pT = pPath + 1; } }
return (LPTSTR)pT; // const -> non const
}
// determine if a path is just a filespec (contains no path parts)
//
// REVIEW: we may want to count the # of elements, and make sure
// there are no illegal chars, but that is probably another routing
// PathIsValid()
//
// in:
// lpszPath path to look at
// returns:
// TRUE no ":" or "\" chars in this path
// FALSE there are path chars in there
//
//
STDAPI_(BOOL) PathIsFileSpec(LPCTSTR lpszPath) { RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathIsFileSpec: caller passed bad lpszPath");
if (lpszPath) { for (; *lpszPath; lpszPath = FAST_CharNext(lpszPath)) { if (*lpszPath == CH_WHACK || *lpszPath == TEXT(':')) return FALSE; } return TRUE; } return FALSE; }
//---------------------------------------------------------------------------
// Returns TRUE if the given string is a UNC path.
//
// TRUE
// "\\foo\bar"
// "\\foo" <- careful
// "\\"
// FALSE
// "\foo"
// "foo"
// "c:\foo"
//
//
STDAPI_(BOOL) PathIsUNC(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNC: caller passed bad pszPath");
if (pszPath) { return DBL_BSLASH(pszPath); } return FALSE; }
//---------------------------------------------------------------------------
// Returns TRUE if the given string is a path that is on a mounted network drive
//
// Cond: Calls SHELL32's IsNetDrive function
//
STDAPI_(BOOL) PathIsNetworkPath(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsNetworkPath: caller passed bad pszPath");
if (pszPath) { return DBL_BSLASH(pszPath) || IsNetDrive(PathGetDriveNumber(pszPath)); }
return FALSE; }
//---------------------------------------------------------------------------
// Returns TRUE if the given string is a UNC path to a server only (no share name).
//
// TRUE
// "\\foo" <- careful
// "\\"
// FALSE
// "\\foo\bar"
// "\foo"
// "foo"
// "c:\foo"
//
STDAPI_(BOOL) PathIsUNCServer(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNCServer: caller passed bad pszPath");
if (pszPath) { if (DBL_BSLASH(pszPath)) { int i = 0; LPTSTR szTmp;
for (szTmp = (LPTSTR)pszPath; szTmp && *szTmp; szTmp = FAST_CharNext(szTmp) ) { if (*szTmp==TEXT('\\')) { i++; } }
return (i == 2); } }
return FALSE; }
//---------------------------------------------------------------------------
// Returns TRUE if the given string is a UNC path to a server\share only.
//
// TRUE
// "\\foo\bar" <- careful
// FALSE
// "\\foo\bar\bar"
// "\foo"
// "foo"
// "c:\foo"
//
STDAPI_(BOOL) PathIsUNCServerShare(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNCServerShare: caller passed bad pszPath");
if (pszPath) { if (DBL_BSLASH(pszPath)) { int i = 0; LPTSTR szTmp;
for (szTmp = (LPTSTR)pszPath; szTmp && *szTmp; szTmp = FAST_CharNext(szTmp) ) { if (*szTmp==TEXT('\\')) { i++; } }
return (i == 3); } } return FALSE; }
//---------------------------------------------------------------------------
// Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has
// a drive letter, otherwise returns -1.
//
//
STDAPI_(int) PathGetDriveNumber(LPCTSTR lpsz) { RIPMSG(lpsz && IS_VALID_STRING_PTR(lpsz, -1), "PathGetDriveNumber: caller passed bad lpsz");
if (lpsz) { if (!IsDBCSLeadByte(lpsz[0]) && lpsz[1] == TEXT(':')) { if (lpsz[0] >= TEXT('a') && lpsz[0] <= TEXT('z')) { return (lpsz[0] - TEXT('a')); } else if (lpsz[0] >= TEXT('A') && lpsz[0] <= TEXT('Z')) { return (lpsz[0] - TEXT('A')); } } }
return -1; }
//---------------------------------------------------------------------------
// Return TRUE if the path isn't absoulte.
//
// TRUE
// "foo.exe"
// ".\foo.exe"
// "..\boo\foo.exe"
//
// FALSE
// "\foo"
// "c:bar" <- be careful
// "c:\bar"
// "\\foo\bar"
//
STDAPI_(BOOL) PathIsRelative(LPCTSTR lpszPath) { RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathIsRelative: caller passed bad lpszPath");
if (!lpszPath || *lpszPath == 0) { // The NULL path is assumed relative
return TRUE; }
if (lpszPath[0] == CH_WHACK) { // Does it begin with a slash ?
return FALSE; } else if (!IsDBCSLeadByte(lpszPath[0]) && lpszPath[1] == TEXT(':')) { // Does it begin with a drive and a colon ?
return FALSE; } else { // Probably relative.
return TRUE; } }
// remove the path part from a fully qualified spec
//
// c:\foo\bar -> bar
// c:\foo -> foo
// c:\ -> c:\ and the like
//
STDAPI_(void) PathStripPath(LPTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathStripPath: caller passed bad pszPath");
if (pszPath) { LPTSTR pszName = PathFindFileName(pszPath);
if (pszName != pszPath) { StringCchCopy(pszPath, MAX_PATH, pszName); } } }
// replaces forward slashes with backslashes
// NOTE: the "AndColon" part is not implemented
STDAPI_(void) FixSlashesAndColon(LPTSTR pszPath) { // walk the entire path string, keep track of last
// char in the path
for (; *pszPath; pszPath = FAST_CharNext(pszPath)) { if (*pszPath == TEXT('/')) { *pszPath = CH_WHACK; } } }
#ifdef DEBUG
BOOL IsFullPath(LPCTSTR pcszPath) { BOOL bResult = FALSE; TCHAR rgchFullPath[MAX_PATH];
if (IS_VALID_STRING_PTR(pcszPath, -1) && EVAL(lstrlen(pcszPath) < MAX_PATH)) { DWORD dwPathLen; LPTSTR pszFileName;
dwPathLen = GetFullPathName(pcszPath, SIZECHARS(rgchFullPath), rgchFullPath, &pszFileName);
if (EVAL(dwPathLen > 0) && EVAL(dwPathLen < SIZECHARS(rgchFullPath))) bResult = EVAL(! lstrcmpi(pcszPath, rgchFullPath)); }
return(bResult); } #endif // DEBUG
/*----------------------------------------------------------
Purpose: Fully qualify a path and search for it.
Returns: TRUE if the path is qualified FALSE if not
Cond: -- */ STDAPI_(BOOL) PathSearchAndQualify(LPCTSTR pcszPath, LPTSTR pszFullyQualifiedPath, UINT cchFullyQualifiedPath) { BOOL bRet = FALSE;
RIPMSG(pcszPath && IS_VALID_STRING_PTR(pcszPath, -1), "PathSearchAndQualify: caller passed bad pcszPath"); RIPMSG(IS_VALID_WRITE_BUFFER(pszFullyQualifiedPath, TCHAR, cchFullyQualifiedPath), "PathSearchAndQualify: caller passed bad pszFullyQualifiedPath"); DEBUGWhackPathBuffer(pszFullyQualifiedPath, cchFullyQualifiedPath);
if (pcszPath && ((cchFullyQualifiedPath == 0) || pszFullyQualifiedPath)) { LPTSTR pszFileName; /* Any path separators? */ if (!StrPBrk(pcszPath, TEXT(":/\\"))) { /* No. Search for file. */ bRet = (SearchPath(NULL, pcszPath, NULL, cchFullyQualifiedPath, pszFullyQualifiedPath, &pszFileName) > 0); }
if (!bRet && (GetFullPathName(pcszPath, cchFullyQualifiedPath, pszFullyQualifiedPath, &pszFileName) > 0)) { bRet = TRUE; }
if ( !bRet ) { if (cchFullyQualifiedPath > 0) { *pszFullyQualifiedPath = '\0'; } } ASSERT((bRet && IsFullPath(pszFullyQualifiedPath)) || (!bRet && (!cchFullyQualifiedPath || !*pszFullyQualifiedPath))); }
return bRet; }
// check if a path is a root
//
// returns:
// TRUE
// "\" "X:\" "\\" "\\foo" "\\foo\bar"
//
// FALSE for others including "\\foo\bar\" (!)
//
STDAPI_(BOOL) PathIsRoot(LPCTSTR pPath) { RIPMSG(pPath && IS_VALID_STRING_PTR(pPath, -1), "PathIsRoot: caller passed bad pPath"); if (!pPath || !*pPath) { return FALSE; } if (!IsDBCSLeadByte(*pPath)) { if (!lstrcmpi(pPath + 1, TEXT(":\\"))) { return TRUE; // "X:\" case
} } if ((*pPath == CH_WHACK) && (*(pPath + 1) == 0)) { return TRUE; // "/" or "\" case
} if (DBL_BSLASH(pPath)) // smells like UNC name
{ LPCTSTR p; int cBackslashes = 0; for (p = pPath + 2; *p; p = FAST_CharNext(p)) { if (*p == TEXT('\\')) { //
// return FALSE for "\\server\share\dir"
// so just check if there is more than one slash
//
// "\\server\" without a share name causes
// problems for WNet APIs. we should return
// FALSE for this as well
//
if ((++cBackslashes > 1) || !*(p+1)) return FALSE; } } // end of string with only 1 more backslash
// must be a bare UNC, which looks like a root dir
return TRUE; } return FALSE; }
/*----------------------------------------------------------
Purpose: Determines if pszPath is a directory. "C:\" is considered a directory too.
Returns: TRUE if it is */ STDAPI_(BOOL) PathIsDirectory(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsDirectory: caller passed bad pszPath");
if (pszPath) { if (PathIsUNCServer(pszPath)) { return FALSE; } else if (PathIsUNCServerShare(pszPath)) { union { NETRESOURCE nr; TCHAR buf[512]; } nrb = {0};
LPTSTR lpSystem = NULL; DWORD dwSize = sizeof(nrb); DWORD dwRet;
nrb.nr.dwScope = RESOURCE_GLOBALNET; nrb.nr.dwType = RESOURCETYPE_ANY; nrb.nr.lpRemoteName = (LPTSTR)pszPath;
dwRet = WNetGetResourceInformation(&nrb.nr, &nrb, &dwSize, &lpSystem);
if (dwRet != WN_SUCCESS) goto TryGetFileAttrib;
if (nrb.nr.dwDisplayType == RESOURCEDISPLAYTYPE_GENERIC) goto TryGetFileAttrib;
if ((nrb.nr.dwDisplayType == RESOURCEDISPLAYTYPE_SHARE) && ((nrb.nr.dwType == RESOURCETYPE_ANY) || (nrb.nr.dwType == RESOURCETYPE_DISK))) { return TRUE; } } else { DWORD dwAttribs; TryGetFileAttrib:
dwAttribs = GetFileAttributes(pszPath); if (dwAttribs != (DWORD)-1) return (BOOL)(dwAttribs & FILE_ATTRIBUTE_DIRECTORY); } } return FALSE; }
/*----------------------------------------------------------
Purpose: Determines if pszPath is a directory. "C:\" is considered a directory too.
Returns: TRUE if it is, FALSE if it is not a directory or there is at least one file other than "." or ".." */ STDAPI_(BOOL) PathIsDirectoryEmpty(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsDirectoryEmpty: caller passed bad pszPath");
if (pszPath) { TCHAR szDirStarDotStar[MAX_PATH]; HANDLE hDir; WIN32_FIND_DATA wfd;
if (!PathIsDirectory(pszPath)) { // its not even an directory, so it dosent fall into the
// category of "empty" directory
return FALSE; }
StringCchCopy(szDirStarDotStar, ARRAYSIZE(szDirStarDotStar), pszPath); if (!PathAddBackslash(szDirStarDotStar)) { return FALSE; }
StrCatBuff(szDirStarDotStar, TEXT("*.*"), ARRAYSIZE(szDirStarDotStar));
hDir = FindFirstFile(szDirStarDotStar, &wfd);
if (INVALID_HANDLE_VALUE == hDir) { // we cant see into it, so assume some stuff is there
return FALSE; }
while (PathIsDotOrDotDot(wfd.cFileName)) { if (!FindNextFile(hDir, &wfd)) { // failed and all we found was "." and "..", so I guess
// the directory is empty
FindClose(hDir); return TRUE; }
}
// If we made it out of the loop, it means we found a file that
// wasen't "." or ".." Therefore, directory is NOT empty
FindClose(hDir); } return FALSE; }
#ifndef UNICODE
// light weight logic for charprev that is not painful for sbcs
BOOL IsTrailByte(LPCTSTR pszSt, LPCTSTR pszCur) { LPCTSTR psz = pszCur;
// if the given pointer is at the top of string, at least it's not a trail byte.
if (psz <= pszSt) return FALSE;
while (psz > pszSt) { psz--; if (!IsDBCSLeadByte(*psz)) { // This is either a trail byte of double byte char
// or a single byte character we've first seen.
// Thus, the next pointer must be at either of a leadbyte
// or pszCur itself.
psz++; break; } }
// Now psz can point to:
// 1) a leadbyte of double byte character.
// 2) pszSt
// 3) pszCur
//
// if psz == pszSt, psz should point to a valid double byte char.
// because we didn't hit the above if statement.
//
// if psz == pszCur, the *(pszCur-1) was non lead byte so pszCur can't
// be a trail byte.
//
// Thus, we can see pszCur as trail byte pointer if the distance from
// psz is not DBCS boundary that is 2.
//
return (BOOL) ((pszCur-psz) & 1); } #endif
// modify lpszPath in place so it fits within dx space (using the
// current font). the base (file name) of the path is the minimal
// thing that will be left prepended with ellipses
//
// examples:
// c:\foo\bar\bletch.txt -> c:\foo...\bletch.txt -> TRUE
// c:\foo\bar\bletch.txt -> c:...\bletch.txt -> TRUE
// c:\foo\bar\bletch.txt -> ...\bletch.txt -> FALSE
// relative-path -> relative-... -> TRUE
//
// in:
// hDC used to get font metrics
// lpszPath path to modify (in place)
// dx width in pixels
//
// returns:
// TRUE path was compacted to fit in dx
// FALSE base part didn't fit, the base part of the path was
// bigger than dx
//
STDAPI_(BOOL) PathCompactPath(HDC hDC, LPTSTR lpszPath, UINT dx) { BOOL bRet = TRUE;
RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1) && IS_VALID_WRITE_BUFFER(lpszPath, TCHAR, MAX_PATH), "PathCompactPath: caller passed bad lpszPath"); DEBUGWhackPathString(lpszPath, MAX_PATH);
if (lpszPath) { int len; UINT dxFixed, dxEllipses; LPTSTR lpEnd; /* end of the unfixed string */ LPTSTR lpFixed; /* start of text that we always display */ BOOL bEllipsesIn; SIZE sz; TCHAR szTemp[MAX_PATH]; HDC hdcGet = NULL;
if (!hDC) hDC = hdcGet = GetDC(NULL);
/* Does it already fit? */
GetTextExtentPoint(hDC, lpszPath, lstrlen(lpszPath), &sz); if ((UINT)sz.cx <= dx) { goto Exit; }
lpFixed = PathFindFileName(lpszPath); if (lpFixed != lpszPath) { lpFixed = CharPrev(lpszPath, lpFixed); // point at the slash
}
/* Save this guy to prevent overlap. */ StrCpyN(szTemp, lpFixed, ARRAYSIZE(szTemp));
lpEnd = lpFixed; bEllipsesIn = FALSE;
GetTextExtentPoint(hDC, lpFixed, lstrlen(lpFixed), &sz); dxFixed = sz.cx;
GetTextExtentPoint(hDC, c_szEllipses, 3, &sz); dxEllipses = sz.cx;
// PERF: GetTextExtentEx() or something should let us do this without looping
if (lpFixed == lpszPath) { // if we're just doing a file name, just tack on the ellipses at the end
lpszPath = lpszPath + lstrlen(lpszPath);
if ((3 + lpszPath - lpFixed) >= MAX_PATH) { lpszPath = lpFixed + MAX_PATH - 4; }
while (TRUE) { #ifndef UNICODE
if (IsTrailByte(lpFixed, lpszPath)) lpszPath--; #endif
StringCchCopy(lpszPath, MAX_PATH, c_szEllipses); // Note: We don't support strings longer than 4GB, but that's
// okay because we already barf at MAX_PATH
GetTextExtentPoint(hDC, lpFixed, (int)(3 + lpszPath - lpFixed), &sz);
if (sz.cx <= (int)dx) break; lpszPath--; }
} else { // Note that we need to avoid calling GetTextExtentPoint with a
// length of zero (because Win95 allegedly crashes under conditions
// yet to be determined precisely), but lpEnd is guaranteed
// to be greater than lpszPath to start.
//
// raymondc - I'm guessing that some crappy display driver has
// patched GetTextExtent and messed up their "optimized" version.
do { // Note: We don't support strings longer than 4GB, but that's
// okay because we already barf at MAX_PATH
GetTextExtentPoint(hDC, lpszPath, (int)(lpEnd - lpszPath), &sz);
len = dxFixed + sz.cx;
if (bEllipsesIn) len += dxEllipses;
if (len <= (int)dx) break;
// Step back a character.
lpEnd = CharPrev(lpszPath, lpEnd); if (!bEllipsesIn) { // if this is the first
// truncation, go ahead and truncate by 3 (lstrlen of c_szEllipses);
// so that we don't just go back one, then write 3 and overwrite the buffer
lpEnd = CharPrev(lpszPath, lpEnd); lpEnd = CharPrev(lpszPath, lpEnd); }
bEllipsesIn = TRUE;
} while (lpEnd > lpszPath);
// Things didn't fit. Note that we'll still overflow here because the
// filename is larger than the available space. We should probably trim
// the file name, but I'm just trying to prevent a crash, not actually
// make this work.
if (lpEnd <= lpszPath) { StringCchCopy(lpszPath, MAX_PATH, c_szEllipses); StrCatBuff(lpszPath, szTemp, MAX_PATH); bRet = FALSE; goto Exit; }
if (bEllipsesIn) { StrCpyN(lpEnd, c_szEllipses, MAX_PATH - (int)(lpEnd - lpszPath)); StrCatBuff(lpEnd, szTemp, MAX_PATH - (int)(lpEnd - lpszPath)); } } Exit: if (hdcGet) ReleaseDC(NULL, hdcGet); } return bRet; }
#define LEN_MID_ELLIPSES 4
#define LEN_END_ELLIPSES 3
#define MIN_CCHMAX LEN_MID_ELLIPSES + LEN_END_ELLIPSES
// PathCompactPathEx
// Output:
// "."
// ".."
// "..."
// "...\"
// "...\."
// "...\.."
// "...\..."
// "...\Truncated filename..."
// "...\whole filename"
// "Truncated path\...\whole filename"
// "Whole path\whole filename"
// The '/' might be used instead of a '\' if the original string used it
// If there is no path, but only a file name that does not fit, the output is:
// "truncated filename..."
//
STDAPI_(BOOL) PathCompactPathEx(LPTSTR pszOut, LPCTSTR pszSrc, UINT cchMax, DWORD dwFlags) { RIPMSG(pszSrc && IS_VALID_STRING_PTR(pszSrc, -1), "PathCompactPathEx: caller passed bad pszSrc"); RIPMSG(pszOut && IS_VALID_WRITE_BUFFER(pszOut, TCHAR, cchMax), "PathCompactPathEx: caller passed bad pszOut"); RIPMSG(!dwFlags, "PathCompactPathEx: caller passed non-ZERO dwFlags"); DEBUGWhackPathBuffer(pszOut, cchMax);
if (pszSrc) { TCHAR * pszFileName, *pszWalk; UINT uiFNLen = 0; int cchToCopy = 0, n; TCHAR chSlash = TEXT('0');
ZeroMemory(pszOut, cchMax * sizeof(TCHAR));
if ((UINT)lstrlen(pszSrc)+1 < cchMax) { StringCchCopy(pszOut, cchMax, pszSrc); ASSERT(pszOut[cchMax-1] == TEXT('\0')); return TRUE; }
// Determine what we use as a slash - a / or a \ (default \)
pszWalk = (TCHAR*)pszSrc; chSlash = TEXT('\\');
// Scan the entire string as we want the path separator closest to the end
// eg. "file://\\Themesrv\desktop\desktop.htm"
while(*pszWalk) { if ((*pszWalk == TEXT('/')) || (*pszWalk == TEXT('\\'))) chSlash = *pszWalk;
pszWalk = FAST_CharNext(pszWalk); }
pszFileName = PathFindFileName(pszSrc); uiFNLen = lstrlen(pszFileName);
// if the whole string is a file name
if(pszFileName == pszSrc && cchMax > LEN_END_ELLIPSES) { StrCpyN(pszOut, pszSrc, cchMax - LEN_END_ELLIPSES); #ifndef UNICODE
if (IsTrailByte(pszSrc, pszSrc+cchMax-LEN_END_ELLIPSES-1)) { *(pszOut+cchMax-LEN_END_ELLIPSES-2) = TEXT('\0'); } #endif
StringCchCat(pszOut, cchMax, TEXT("...")); ASSERT(pszOut[cchMax-1] == TEXT('\0')); return TRUE; }
// Handle all the cases where we just use ellipses ie '.' to '.../...'
if ((cchMax < MIN_CCHMAX)) { for (n = 0; n < (int)cchMax-1; n++) { if ((n+1) == LEN_MID_ELLIPSES) { pszOut[n] = chSlash; } else { pszOut[n] = TEXT('.'); } }
ASSERT(0==cchMax || pszOut[cchMax-1] == TEXT('\0')); return TRUE; }
// Ok, how much of the path can we copy ? Buffer - (Lenght of MID_ELLIPSES + Len_Filename)
cchToCopy = cchMax - (LEN_MID_ELLIPSES + uiFNLen); if (cchToCopy < 0) cchToCopy = 0;
#ifndef UNICODE
if (cchToCopy > 0 && IsTrailByte(pszSrc, pszSrc+cchToCopy)) cchToCopy--; #endif
StrCpyN(pszOut, pszSrc, cchToCopy);
// Now throw in the ".../" or "...\"
StringCchCat(pszOut, cchMax, TEXT(".../")); pszOut[lstrlen(pszOut) - 1] = chSlash;
//Finally the filename and ellipses if necessary
if (cchMax > (LEN_MID_ELLIPSES + uiFNLen)) { StringCchCat(pszOut, cchMax, pszFileName); } else { cchToCopy = cchMax - LEN_MID_ELLIPSES - LEN_END_ELLIPSES; #ifndef UNICODE
if (cchToCopy >0 && IsTrailByte(pszFileName, pszFileName+cchToCopy)) { cchToCopy--; } #endif
StrCpyN(pszOut + LEN_MID_ELLIPSES, pszFileName, cchToCopy); StringCchCat(pszOut, cchMax, TEXT("...")); }
ASSERT(pszOut[cchMax-1] == TEXT('\0')); return TRUE; } return FALSE; }
// fill a control with a path, using PathCompactPath() to crunch the
// path to fit.
//
// in:
// hDlg dialog box or parent window
// id child id to put the path in
// pszPath path to put in
//
STDAPI_(void) PathSetDlgItemPath(HWND hDlg, int id, LPCTSTR pszPath) { RECT rc; HDC hdc; HFONT hFont; TCHAR szPath[MAX_PATH + 1]; // can have one extra char
HWND hwnd;
hwnd = GetDlgItem(hDlg, id); if (!hwnd) return;
szPath[0] = 0;
if (pszPath) StrCpyN(szPath, pszPath, ARRAYSIZE(szPath));
GetClientRect(hwnd, &rc);
hdc = GetDC(hDlg); hFont = (HANDLE)SendMessage(hwnd, WM_GETFONT, 0, 0L); if (NULL != (hFont = SelectObject(hdc, hFont))) { PathCompactPath(hdc, szPath, (UINT)rc.right); SelectObject(hdc, hFont); } ReleaseDC(hDlg, hdc); SetWindowText(hwnd, szPath); }
/*----------------------------------------------------------
Purpose: If a path is contained in quotes then remove them.
Returns: -- Cond: -- */ STDAPI_(void) PathUnquoteSpaces(LPTSTR lpsz) { RIPMSG(lpsz && IS_VALID_STRING_PTR(lpsz, -1), "PathUnquoteSpaces: caller passed bad lpsz");
if (lpsz) { int cch;
cch = lstrlen(lpsz);
// Are the first and last chars quotes?
// (It is safe to go straight to the last character because
// the quotation mark is not a valid DBCS trail byte.)
if (lpsz[0] == TEXT('"') && lpsz[cch-1] == TEXT('"')) { // Yep, remove them.
lpsz[cch-1] = TEXT('\0'); hmemcpy(lpsz, lpsz+1, (cch-1) * sizeof(TCHAR)); } } }
//----------------------------------------------------------------------------
// If a path contains spaces then put quotes around the whole thing.
//
STDAPI_(void)PathQuoteSpaces(LPTSTR lpsz) { RIPMSG(lpsz && IS_VALID_STRING_PTR(lpsz, -1) && IS_VALID_WRITE_BUFFER(lpsz, TCHAR, MAX_PATH), "PathQuoteSpaces: caller passed bad lpsz"); DEBUGWhackPathString(lpsz, MAX_PATH);
if (lpsz) { int cch;
if (StrChr(lpsz, TEXT(' '))) { // NB - Use hmemcpy coz it supports overlapps.
cch = lstrlen(lpsz)+1;
if (cch+1 < MAX_PATH) { hmemcpy(lpsz+1, lpsz, cch * sizeof(TCHAR)); lpsz[0] = TEXT('"'); lpsz[cch] = TEXT('"'); lpsz[cch+1] = TEXT('\0'); } } } }
//---------------------------------------------------------------------------
// Given a pointer to a point in a path - return a ptr the start of the
// next path component. Path components are delimted by slashes or the
// null at the end.
// There's special handling for UNC names.
// This returns NULL if you pass in a pointer to a NULL ie if you're about
// to go off the end of the path.
//
STDAPI_(LPTSTR) PathFindNextComponent(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFindNextComponent: caller passed bad pszPath");
if (pszPath) { LPTSTR pszLastSlash;
// Are we at the end of a path.
if (!*pszPath) { // Yep, quit.
return NULL; } // Find the next slash.
// REVIEW UNDONE - can slashes be quoted?
pszLastSlash = StrChr(pszPath, TEXT('\\')); // Is there a slash?
if (!pszLastSlash) { // No - Return a ptr to the NULL.
return (LPTSTR)pszPath + lstrlen(pszPath); } else { // Is it a UNC style name?
if (*(pszLastSlash + 1) == TEXT('\\')) { // Yep, skip over the second slash.
return pszLastSlash + 2; } else { // Nope. just skip over one slash.
return pszLastSlash + 1; } } }
return NULL; }
// helper for PathMatchSpec.
// originally PathMatchSpec had this logic embedded in it and it recursively called itself.
// only problem is the recursion picked up all the extra specs, so for example
// PathMatchSpec("foo....txt", "*.txt;*.a;*.b;*.c;*.d;*.e;*.f;*.g") called itself too much
// and wound up being O(N^3).
// in fact this logic doesnt match strings efficiently, but we shipped it so leave it be.
// just test one spec at a time and its all good.
// pszSpec is a pointer within the pszSpec passed to PathMatchSpec so we terminate at ';' in addition to '\0'.
BOOL PathMatchSingleSpec(LPCTSTR pszFileParam, LPCTSTR pszSpec) { LPCTSTR pszFile = pszFileParam;
// Strip leading spaces from each spec. This is mainly for commdlg
// support; the standard format that apps pass in for multiple specs
// is something like "*.bmp; *.dib; *.pcx" for nicer presentation to
// the user.
while (*pszSpec == TEXT(' ')) pszSpec++;
while (*pszFile && *pszSpec && *pszSpec != TEXT(';')) { switch (*pszSpec) { case TEXT('?'): pszFile = FAST_CharNext(pszFile); pszSpec++; // NLS: We know that this is a SBCS
break;
case TEXT('*'):
// We found a * so see if this is the end of our file spec
// or we have *.* as the end of spec, in which case we
// can return true.
//
if (*(pszSpec + 1) == 0 || *(pszSpec + 1) == TEXT(';')) // "*" matches everything
return TRUE;
// Increment to the next character in the list
pszSpec = FAST_CharNext(pszSpec);
// If the next character is a . then short circuit the
// recursion for performance reasons
if (*pszSpec == TEXT('.')) { pszSpec++; // Get beyond the .
// Now see if this is the *.* case
if ((*pszSpec == TEXT('*')) && ((*(pszSpec+1) == TEXT('\0')) || (*(pszSpec+1) == TEXT(';')))) return TRUE;
// find the extension (or end in the file name)
while (*pszFile) { // If the next char is a dot we try to match the
// part on down else we just increment to next item
if (*pszFile == TEXT('.')) { pszFile++;
if (PathMatchSingleSpec(pszFile, pszSpec)) return TRUE;
} else pszFile = FAST_CharNext(pszFile); }
return FALSE; // No item found so go to next pattern
} else { // Not simply looking for extension, so recurse through
// each of the characters until we find a match or the
// end of the file name
while (*pszFile) { // recurse on our self to see if we have a match
if (PathMatchSingleSpec(pszFile, pszSpec)) return TRUE; pszFile = FAST_CharNext(pszFile); }
return FALSE; // No item found so go to next pattern
}
default: if (CharUpper((LPTSTR)(ULONG_PTR)(TUCHAR)*pszSpec) == CharUpper((LPTSTR)(ULONG_PTR)(TUCHAR)*pszFile)) { if (IsDBCSLeadByte(*pszSpec)) { #ifdef DBCS
// Because AnsiUpper(CharUpper) just return 0
// for broken DBCS char passing case, above if state
// always true with DBCS char so that we should check
// first byte of DBCS char here again.
if (*pszFile != *pszSpec) return FALSE; #endif
pszFile++; pszSpec++; if (*pszFile != *pszSpec) return FALSE; } pszFile++; pszSpec++; } else { return FALSE; } } }
// If we made it to the end of both strings, we have a match...
//
if (!*pszFile) { if ((!*pszSpec || *pszSpec == TEXT(';'))) return TRUE;
// Also special case if things like foo should match foo*
// as well as foo*; for foo*.* or foo*.*;
if ( (*pszSpec == TEXT('*')) && ( (*(pszSpec+1) == TEXT('\0')) || (*(pszSpec+1) == TEXT(';')) || ((*(pszSpec+1) == TEXT('.')) && (*(pszSpec+2) == TEXT('*')) && ((*(pszSpec+3) == TEXT('\0')) || (*(pszSpec+3) == TEXT(';'))))))
return TRUE; } return FALSE; }
//
// Match a DOS wild card spec against a dos file name
// Both strings must be ANSI.
//
STDAPI_(BOOL) PathMatchSpec(LPCTSTR pszFileParam, LPCTSTR pszSpec) { RIPMSG(pszSpec && IS_VALID_STRING_PTR(pszSpec, -1), "PathMathSpec: caller passed bad pszSpec"); RIPMSG(pszFileParam && IS_VALID_STRING_PTR(pszFileParam, -1), "PathMathSpec: caller passed bad pszFileParam");
if (pszSpec && pszFileParam) { // Special case empty string, "*", and "*.*"...
//
if (*pszSpec == 0) { return TRUE; }
// loop over the spec, break off at ';', and call our helper for each.
do { if (PathMatchSingleSpec(pszFileParam, pszSpec)) return TRUE;
// Skip to the end of the path spec...
while (*pszSpec && *pszSpec != TEXT(';')) pszSpec = FAST_CharNext(pszSpec);
// If we have more specs, keep looping...
} while (*pszSpec++ == TEXT(';')); }
return FALSE; }
/*----------------------------------------------------------
Purpose: Returns a pointer to the beginning of the subpath that follows the root (drive letter or UNC server/share).
Returns: Cond: --
Notes: dsheldon - won't properly handle \\?\ */ STDAPI_(LPTSTR) PathSkipRoot(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathSkipRoot: caller passed bad pszPath");
if (pszPath) { if (DBL_BSLASH(pszPath)) { pszPath = StrChr(pszPath+2, TEXT('\\')); if (pszPath) { pszPath = StrChr(pszPath+1, TEXT('\\')); if (pszPath) { ++pszPath; } } } else if (!IsDBCSLeadByte(pszPath[0]) && pszPath[1]==TEXT(':') && pszPath[2]==TEXT('\\')) { pszPath += 3; } else { pszPath = NULL; } }
return (LPTSTR)pszPath; }
// see if two paths have the same root component
//
STDAPI_(BOOL) PathIsSameRoot(LPCTSTR pszPath1, LPCTSTR pszPath2) { RIPMSG(pszPath1 && IS_VALID_STRING_PTR(pszPath1, -1), "PathIsSameRoot: caller passed bad pszPath1"); RIPMSG(pszPath2 && IS_VALID_STRING_PTR(pszPath2, -1), "PathIsSameRoot: caller passed bad pszPath2");
if (pszPath1 && pszPath2) { LPTSTR pszAfterRoot = PathSkipRoot(pszPath1); int nLen = PathCommonPrefix(pszPath1, pszPath2, NULL);
// Add 1 to account for the '\\'
return pszAfterRoot && (pszAfterRoot - pszPath1) <= (nLen + 1); } return FALSE; }
#define IsDigit(c) ((c) >= TEXT('0') && c <= TEXT('9'))
/*----------------------------------------------------------
Purpose: Takes a location string ("shell32.dll,3") and parses it into a file-component and an icon index.
Returns: icon index Cond: -- */ STDAPI_(int) PathParseIconLocation(IN OUT LPTSTR pszIconFile) { int iIndex = 0;
RIPMSG(pszIconFile && IS_VALID_STRING_PTR(pszIconFile, -1), "PathParseIconLocation: caller passed bad pszIconFile"); if (pszIconFile) { LPTSTR pszComma, pszEnd;
// look for the last comma in the string
pszEnd = pszIconFile + lstrlen(pszIconFile); pszComma = StrRChr(pszIconFile, pszEnd, TEXT(',')); if (pszComma && *pszComma) { LPTSTR pszComma2 = pszComma + 1; BOOL fIsDigit = FALSE;
// Sometimes we get something like: "C:\path, comma\path\file.ico"
// where the ',' is in the path and does not indicates that an icon index follows
while (*pszComma2) { if ((TEXT(' ') == *pszComma2) || (TEXT('-') == *pszComma2)) { ++pszComma2; } else { if (IsDigit(*pszComma2)) { fIsDigit = TRUE; } break; } }
if (fIsDigit) { *pszComma++ = 0; // terminate the icon file name.
iIndex = StrToInt(pszComma); } }
PathUnquoteSpaces(pszIconFile); PathRemoveBlanks(pszIconFile); } return iIndex; }
/*----------------------------------------------------------
Purpose: Returns TRUE if the given path is of a URL format. See http://www.w3.org for a complete description of
the URL format.
A complete URL looks like: <URL:http://www.microsoft.com/software/index.html>
But generally URLs don't have the leading "URL:" and the wrapping angle brackets. So this function only tests for the following format:
http://www.microsoft.com/software
It does not check if the path points to an existing site, only if is in a legal URL format.
Returns: TRUE if URL format FALSE if not Cond: -- */ STDAPI_(BOOL) PathIsURL(IN LPCTSTR pszPath) { PARSEDURL pu;
if (!pszPath) return FALSE;
RIPMSG(IS_VALID_STRING_PTR(pszPath, -1), "PathIsURL: caller passed bad pszPath");
pu.cbSize = sizeof(pu); return SUCCEEDED(ParseURL(pszPath, &pu)); }
/****************************************************\
FUNCTION: PathIsContentType
PARAMETERS: pszPath - File Name to check. pszContentType - Content Type to look for.
DESCRIPTION: Is the file (pszPath) of the content type specified (pszContentType)?. \****************************************************/ #define SZ_VALUE_CONTENTTYPE TEXT("Content Type")
BOOL PathIsContentType(LPCTSTR pszPath, LPCTSTR pszContentType) { BOOL fDoesMatch = FALSE;
RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsContentType: caller passed bad pszPath"); RIPMSG(pszContentType && IS_VALID_STRING_PTR(pszContentType, -1), "PathIsContentType: caller passed bad pszContentType");
if (pszPath) { LPTSTR pszExtension = PathFindExtension(pszPath);
if (pszExtension && pszExtension[0]) { TCHAR szRegData[MAX_PATH]; DWORD dwDataSize = ARRAYSIZE(szRegData); if (SUCCEEDED(AssocQueryString(0, ASSOCSTR_CONTENTTYPE, pszExtension, NULL, szRegData, &dwDataSize))) { fDoesMatch = (0 == lstrcmpi(szRegData, pszContentType)); } } }
return fDoesMatch; }
/*----------------------------------------------------------
Purpose: Returns the character type (GCT_)
FEATURE (reinerf) - this API is not very good, use PathIsValidChar() instead, its more customizable */ UINT PathGetCharType(TUCHAR ch) { switch (ch) { case TEXT('|'): case TEXT('>'): case TEXT('<'): case TEXT('"'): case TEXT('/'): return GCT_INVALID;
case TEXT('?'): case TEXT('*'): return GCT_WILD;
case TEXT('\\'): // path separator
case TEXT(':'): // drive colon
return GCT_SEPARATOR;
case TEXT(';'): case TEXT(','): case TEXT(' '): return GCT_LFNCHAR; // actually valid in short names
// but we want to avoid this
default: if (ch > TEXT(' ')) { return GCT_SHORTCHAR | GCT_LFNCHAR; } else { // control character
return GCT_INVALID; } } }
/*----------------------------------------------------------
Purpose: returns if a character is a valid path character given the flags that you pass in (PIVC_XXX). Some basic flags are given below:
PIVC_ALLOW_QUESTIONMARK treat '?' as valid PIVC_ALLOW_STAR treat '*' as valid PIVC_ALLOW_DOT treat '.' as valid PIVC_ALLOW_SLASH treat '\\' as valid PIVC_ALLOW_COLON treat ':' as valid PIVC_ALLOW_SEMICOLON treat ';' as valid PIVC_ALLOW_COMMA treat ',' as valid PIVC_ALLOW_SPACE treat ' ' as valid PIVC_ALLOW_NONALPAHABETIC treat non-alphabetic extenede chars as valid PIVC_ALLOW_QUOTE treat '"' as valid
if you pass 0, then only alphabetic characters are valid. there are also basic conglomerations of the above flags:
PIVC_ALLOW_FULLPATH, PIVC_ALLOW_WILDCARD, PIVC_ALLOW_LFN, ...
Returns: TRUE if the character is a valid path character given the dwFlags constraints FALSE if this does not qualify as a valid path character given the dwFlags constraints Cond: -- */ STDAPI_(BOOL) PathIsValidChar(TUCHAR ch, DWORD dwFlags) { switch (ch) { case TEXT('|'): case TEXT('>'): case TEXT('<'): case TEXT('/'): return FALSE; // these are allways illegal in a path
break;
case TEXT('?'): return dwFlags & PIVC_ALLOW_QUESTIONMARK; break;
case TEXT('*'): return dwFlags & PIVC_ALLOW_STAR; break;
case TEXT('.'): return dwFlags & PIVC_ALLOW_DOT; break;
case TEXT('\\'): return dwFlags & PIVC_ALLOW_SLASH; break;
case TEXT(':'): return dwFlags & PIVC_ALLOW_COLON; break;
case TEXT(';'): return dwFlags & PIVC_ALLOW_SEMICOLON; break;
case TEXT(','): return dwFlags & PIVC_ALLOW_COMMA; break;
case TEXT(' '): return dwFlags & PIVC_ALLOW_SPACE; break;
case TEXT('"'): return dwFlags & PIVC_ALLOW_QUOTE; break;
default: if (InRange(ch, TEXT('a'), TEXT('z')) || InRange(ch, TEXT('A'), TEXT('Z'))) { // we have an alphabetic character,
// this is always valid
return TRUE; } else if (ch < TEXT(' ')) { // we have a control sequence,
// this is allways illegal
return FALSE; } else { // we have an non-alphabetic extenede character
return dwFlags & PIVC_ALLOW_NONALPAHABETIC; } break; } }
BOOL IsSystemSpecialCase(LPCTSTR pszPath) { static TCHAR *g_pszWin = NULL, *g_pszSys = NULL;
if (g_pszWin == NULL) { TCHAR szTemp[MAX_PATH]; UINT cch = GetWindowsDirectory(szTemp, ARRAYSIZE(szTemp));
if (cch && cch < ARRAYSIZE(szTemp)) g_pszWin = StrDup(szTemp); }
if (g_pszSys == NULL) { TCHAR szTemp[MAX_PATH]; UINT cch = GetSystemDirectory(szTemp, ARRAYSIZE(szTemp));
if (cch && cch < ARRAYSIZE(szTemp)) g_pszSys = StrDup(szTemp); }
return (g_pszWin && (lstrcmpi(g_pszWin, pszPath) == 0)) || (g_pszSys && (lstrcmpi(g_pszSys, pszPath) == 0)); }
/*----------------------------------------------------------
Purpose: Mark a folder to be a shell folder by stamping either FILE_ATTRIBUTES_READONLY or FILE_ATTRIBUTE_SYSTEM into it's attributes. Which flag is used is based on the presence/absense of a registry switch
NOTE: we also mark the contained desktop.ini +s +h if it exists */ BOOL PathMakeSystemFolder(LPCTSTR pszPath) { BOOL fRet = FALSE; RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathMakeSystemFolder: caller passed bad pszPath");
if (pszPath && *pszPath) { TCHAR szTemp[MAX_PATH];
if (IsSystemSpecialCase(pszPath)) { fRet = TRUE; } else { DWORD dwAttrb, dwAttrbSet = FILE_ATTRIBUTE_READONLY;
if (SHGetValue(HKEY_LOCAL_MACHINE, REGSTR_PATH_EXPLORER, TEXT("UseSystemForSystemFolders"), NULL, NULL, NULL) == ERROR_SUCCESS) { dwAttrbSet = FILE_ATTRIBUTE_SYSTEM; }
dwAttrb = GetFileAttributes(pszPath); if ((dwAttrb != (DWORD)-1) && (dwAttrb & FILE_ATTRIBUTE_DIRECTORY)) { dwAttrb &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM); dwAttrb |= dwAttrbSet;
fRet = SetFileAttributes(pszPath, dwAttrb); }
if (fRet) { FILETIME ftCurrent; HANDLE h;
// People typically call this API after writing a desktop.ini in the
// folder. Doing this often changes the thumbnail of the folder.
// But on FAT systems, this doesn't update the Modified time of the
// folder like it does for NTFS. So manually do that now:
//
GetSystemTimeAsFileTime(&ftCurrent); // woohoo yay for private flags
// 0x100 lets us open a directory in write access
h = CreateFile(pszPath, GENERIC_READ | 0x100, FILE_SHARE_READ | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); if (h != INVALID_HANDLE_VALUE) { SetFileTime(h, NULL, NULL, &ftCurrent); CloseHandle(h); }
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, pszPath, NULL); } }
// we also set the contained desktop.ini file to be (+h +s), if it exists
if (PathCombine(szTemp, pszPath, TEXT("desktop.ini"))) { // we explicitly do not OR in the attribs, because we want to reset the
// readonly bit since writeprivateprofilestring fails on reaonly files.
SetFileAttributes(szTemp, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM); } }
return fRet; }
/*----------------------------------------------------------
Purpose: Unmark a folder so it is no longer a system folder. (remove both FILE_ATTRIBUTES_READONLY and FILE_ATTRIBUTE_SYSTEM attribute). */ BOOL PathUnmakeSystemFolder(LPCTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathUnmakeSystemFolder: caller passed bad pszPath");
if (pszPath && *pszPath) { DWORD dwAttrb = GetFileAttributes( pszPath );
if ((dwAttrb != (DWORD)-1) && (dwAttrb & FILE_ATTRIBUTE_DIRECTORY)) { dwAttrb &= ~(FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM);
return SetFileAttributes(pszPath, dwAttrb); } } return FALSE; }
/*----------------------------------------------------------
Purpose: checks whether given path is a system (shell) folder. if path is NULL, then use the attributes passed in instead of reading them off the disk.
*/ BOOL PathIsSystemFolder(LPCTSTR pszPath, DWORD dwAttrb) { if (pszPath && *pszPath) dwAttrb = GetFileAttributes(pszPath);
if ((dwAttrb != (DWORD)-1) && (dwAttrb & FILE_ATTRIBUTE_DIRECTORY)) { if (dwAttrb & (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_SYSTEM)) { return TRUE; } } return FALSE; }
LPCTSTR PathSkipLeadingSlashes(LPCTSTR pszURL) { LPCTSTR pszURLStart = pszURL;
RIPMSG(pszURL && IS_VALID_STRING_PTR(pszURL, -1), "PathSkipLeadingSlashes: caller passed bad pszURL"); if (pszURL) { // Skip two leading slashes.
if (pszURL[0] == TEXT('/') && pszURL[1] == TEXT('/')) pszURLStart += 2;
ASSERT(IS_VALID_STRING_PTR(pszURL, -1) && IsStringContained(pszURL, pszURLStart)); } return pszURLStart; }
//
// returns:
// TRUE given filespec is long (> 8.3 form)
// FALSE filespec is short
//
STDAPI_(BOOL) PathIsLFNFileSpec(LPCTSTR pszName) { RIPMSG(pszName && IS_VALID_STRING_PTR(pszName, -1), "PathIsLFNFileSpec: caller passed bad pszName");
if (pszName) { BOOL bSeenDot = FALSE; int iCount = 1; while (*pszName) { if (bSeenDot) { if (iCount > 3) { // found a long name
return TRUE; } }
if (*pszName == TEXT(' ')) { // Short names dont have blanks in them.
return TRUE; }
if (*pszName == TEXT('.')) { if (bSeenDot) { // short names can only have one '.'
return TRUE; }
bSeenDot = TRUE; iCount = 0; // don't include the '.'
} else if (iCount > 8) { // long name
return TRUE; }
if (IsDBCSLeadByte(*pszName)) // should this be CharNext?
{ pszName += 2; iCount += 2; } else { pszName++; iCount++; } } }
return FALSE; // short name
}
/*----------------------------------------------------------
Purpose: Removes regexp \[[0-9]*\] from base name of file that is typically added by the wininet cache. */
#define DECORATION_OPENING_CHAR TEXT('[')
#define DECORATION_CLOSING_CHAR TEXT(']')
STDAPI_(void) PathUndecorate(LPTSTR pszPath) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathUndecorate: caller passed bad pszPath");
if (pszPath) { LPTSTR pszExt, pszScan; DWORD cchMove; // First, skip over the extension, if any.
pszExt = PathFindExtension(pszPath); ASSERT(pszExt >= pszPath); // points to null at end if no ext
// Whoa, a completely empty path
if (pszExt <= pszPath) return;
// Scan backwards from just before the "."
pszScan = pszExt - 1;
// Check for closing bracket.
if (*pszScan-- != DECORATION_CLOSING_CHAR) return; if (pszScan <= pszPath) // it was a 1-char filename ")"
return; #ifndef UNICODE
if (IsTrailByte(pszPath, pszScan+1)) // Oops, that ")" was the 2nd byte of a DBCS char
return; #endif
// Skip over digits.
while (pszScan > pszPath && IsDigit(*pszScan)) pszScan--; #ifndef UNICODE
if (IsTrailByte(pszPath, pszScan+1)) // Oops, that last number was the 2nd byte of a DBCS char
return; #endif
// Check for opening bracket
if (*pszScan != DECORATION_OPENING_CHAR) return; if (pszScan <= pszPath) // it was all decoration (we don't want to go to an empty filename)
return; #ifndef UNICODE
if (IsTrailByte(pszPath, pszScan)) // Oops, that "(" was the 2nd byte of a DBCS char
return; #endif
// Make sure we're not looking at the end of the path (we don't want to go to an empty filename)
if (*(pszScan-1) == FILENAME_SEPARATOR #ifndef UNICODE
// make sure that slash isn't the 2nd byte of a DBCS char
&& ((pszScan-1) == pszPath || !IsTrailByte(pszPath, pszScan-1)) #endif
) { return; } // Got a decoration. Cut it out of the string.
cchMove = lstrlen(pszExt) + 1; memmove(pszScan, pszExt, cchMove * sizeof(TCHAR)); } }
// If the given environment variable exists as the first part of the path,
// then the environment variable is inserted into the output buffer.
//
// Returns TRUE if pszResult is filled in.
//
// Example: Input -- C:\WINNT\SYSTEM32\FOO.TXT -and- lpEnvVar = %SYSTEMROOT%
// Output -- %SYSTEMROOT%\SYSTEM32\FOO.TXT
//
#ifdef UNICODE
#define UnExpandEnvironmentStringForUser UnExpandEnvironmentStringForUserW
#else
#define UnExpandEnvironmentStringForUser UnExpandEnvironmentStringForUserA
#endif
BOOL UnExpandEnvironmentStringForUser(HANDLE hToken, LPCTSTR pszPath, LPCTSTR pszEnvVar, LPTSTR pszResult, UINT cchResult) { TCHAR szEnvVar[MAX_PATH]; DWORD dwEnvVar = SHExpandEnvironmentStringsForUser(hToken, pszEnvVar, szEnvVar, ARRAYSIZE(szEnvVar)); if (dwEnvVar) { dwEnvVar--; // don't count the NULL
if (CompareString(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, szEnvVar, dwEnvVar, pszPath, dwEnvVar) == 2) { if (lstrlen(pszPath) - (int)dwEnvVar + lstrlen(pszEnvVar) < (int)cchResult) { StringCchCopy(pszResult, cchResult, pszEnvVar); StringCchCat(pszResult, cchResult, pszPath + dwEnvVar); return TRUE; } } } return FALSE; }
STDAPI_(BOOL) PathUnExpandEnvStringsForUser(HANDLE hToken, LPCTSTR pszPath, LPTSTR pszBuf, UINT cchBuf) { RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathUnExpandEnvStrings: caller passed bad pszPath"); RIPMSG(pszBuf && IS_VALID_WRITE_BUFFER(pszBuf, TCHAR, cchBuf), "PathUnExpandEnvStrings: caller passed bad pszBuf"); DEBUGWhackPathBuffer(pszBuf, cchBuf);
// Bail out if we're not in NT (nothing to do if those environment variables
// aren't defined).
//
if (pszPath && pszBuf) {
// 99/05/28 #346950 vtan: WARNING!!! Be careful about the order of comparison
// here. The longer paths (supersets of other possible paths) MUST be compared
// first. For example (default case):
// %APPDATA% = x:\Documents And Settings\user\Application Data
// %USERPROFILE% = x:\Documents And Settings\user
// If %USERPROFILE% is matched first then %APPDATA% will never be matched.
// Added %APPDATA% to support Darwin installation into that folder and the
// setting of the link icon location.
// Also note that %APPDATA% and %USERPROFILE% are user relative and depend on
// the context in which this function is invoked. Normally it is within the
// currently logged on user's context but Darwin installs from msiexec.exe which
// is launched from SYSTEM. Unless the process' environment block is correctly
// modified the current user information is incorrect. In this case it is up
// to the process to impersonate a user on a thread. We get the impersonated
// user information from the hToken passed to us.
return (UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%APPDATA%"), pszBuf, cchBuf) || UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%USERPROFILE%"), pszBuf, cchBuf) || UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%ALLUSERSPROFILE%"), pszBuf, cchBuf) || UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%ProgramFiles%"), pszBuf, cchBuf) || UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%SystemRoot%"), pszBuf, cchBuf) || UnExpandEnvironmentStringForUser(hToken, pszPath, TEXT("%SystemDrive%"), pszBuf, cchBuf)); } else { // Zero out the string if there's room.
if (pszBuf && (cchBuf > 0)) *pszBuf = TEXT('\0'); return FALSE; } }
STDAPI_(BOOL) PathUnExpandEnvStrings(LPCTSTR pszPath, LPTSTR pszBuf, UINT cchBuf)
{ return(PathUnExpandEnvStringsForUser(NULL, pszPath, pszBuf, cchBuf)); }
|