Windows NT 4.0 source code leak
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

4498 lines
128 KiB

#include "shellprv.h"
#pragma hdrstop
extern const TCHAR c_szControlPanel[];
extern const TCHAR c_szPrinters[];
const TCHAR c_szSlash[] = TEXT("\\");
//
// Inline function to check for a double-backslash at the
// beginning of a string
//
__inline BOOL DBL_BSLASH(LPNCTSTR psz)
{
return (psz[0] == TEXT('\\') && psz[1] == TEXT('\\'));
}
// From Bitbuck.c
#define NONETHOMEDIR ((LPBYTE)-1)
LPCTSTR GetNetHomeDir(void);
int GetWindowsDrive(void);
#define IsPathSep(ch) ((ch) == TEXT('\\') || (ch) == TEXT('/'))
//----------------------------------------------------------------------------
// in:
// pszPath fully qualified path (unc or x:\) to test
// NULL for windows directory
//
// returns:
// TRUE volume supports name longer than 12 chars
// BUGBUG: and UNICODE on disk to avoid netware
// character space problems, remove this!
//
// note: if this is too slow we should cache this info
//
BOOL WINAPI IsLFNDrive(LPCTSTR pszPath)
{
TCHAR szRoot[MAX_PATH];
DWORD dwMaxLength = 13; // assume yes
if (pszPath == NULL)
return DriveIsLFN(GetWindowsDrive());
Assert(!PathIsRelative(pszPath));
//
// UNC name? gota check each time
//
if (PathIsUNC(pszPath))
{
lstrcpyn(szRoot, pszPath, ARRAYSIZE(szRoot));
PathStripToRoot(szRoot);
// Deal with busted kernel UNC stuff
// Is it a \\foo or a \\foo\bar thing?
if (StrChr(szRoot+2, TEXT('\\')))
{
// "\\foo\bar - Append a slash to be NT compatible.
lstrcat(szRoot, c_szSlash);
}
else
{
// "\\foo" - assume it's always a LFN volume
return TRUE;
}
}
//
// removable media? gota check each time
//
else if (IsRemovableDrive(DRIVEID(pszPath)))
{
PathBuildRoot(szRoot, DRIVEID(pszPath));
}
//
// fixed media use cached value.
//
else
{
return DriveIsLFN(DRIVEID(pszPath));
}
//
// Right now we will say that it is an LFN Drive if the maximum
// component is > 12
GetVolumeInformation(szRoot, NULL, 0, NULL, &dwMaxLength, NULL, NULL, 0);
return dwMaxLength > 12;
}
//---------------------------------------------------------------------------
// Returns one of the following to describe the given path.
// NORMAL_PATH == 0
// UNC_PATH == 1 // path had '\\' as first two characters
// UNC_SERVER_ONLY == 2 // UNC server path only (2 '\'s only)
// UNC_SERVER_SHARE == 3 // UNC server/share path (3 '\'s only)
//
DWORD WINAPI PathIsUNCServerShare(LPCTSTR pszPath)
{
int i = 0;
LPCTSTR pszTmp;
DWORD dwRes = NORMAL_PATH;
if (!pszPath || !(*pszPath))
{
return dwRes;
}
// Did the path start with "\\"
if (DBL_BSLASH(pszPath))
{
// walk the string...
for (pszTmp = pszPath; pszTmp && *pszTmp; pszTmp++ )
{
if (*pszTmp==TEXT('\\'))
{
i++;
}
}
// Only two slashes means \\server
if (i == 2)
{
dwRes = UNC_SERVER_ONLY;
}
// Three slashes means \\server\share
else if (i == 3)
{
dwRes = UNC_SERVER_SHARE;
}
// It's just UNC path...
else
{
dwRes = UNC_PATH;
}
}
return dwRes;
}
//---------------------------------------------------------------------------
// Returns whether the given net path exists. This fails for NON net paths.
//
BOOL WINAPI NetPathExists(LPCTSTR lpszPath, LPDWORD lpdwType)
{
BOOL fResult = FALSE;
NETRESOURCE nr;
LPTSTR lpSystem;
DWORD dwRes, dwSize = 1024;
LPVOID lpv;
if (!lpszPath || !(*lpszPath))
{
return FALSE;
}
lpv = (LPVOID)LocalAlloc( LPTR, dwSize );
if (!lpv)
{
return FALSE;
}
TryWNetAgain:
nr.dwScope = RESOURCE_GLOBALNET;
nr.dwType = RESOURCETYPE_ANY;
nr.dwDisplayType = 0;
nr.lpLocalName = NULL;
nr.lpRemoteName = (LPTSTR)lpszPath;
nr.lpProvider = NULL;
nr.lpComment = NULL;
dwRes = WNetGetResourceInformation( &nr,
lpv,
&dwSize,
&lpSystem
);
// If our buffer wasn't big enough, try a bigger buffer...
if (dwRes == WN_MORE_DATA)
{
LPVOID tmp;
tmp = LocalReAlloc( lpv, dwSize, LMEM_MOVEABLE );
if (!tmp)
{
LocalFree( lpv );
SetLastError( ERROR_OUTOFMEMORY );
return FALSE;
}
lpv = tmp;
goto TryWNetAgain;
}
fResult = (dwRes == WN_SUCCESS);
if (fResult && lpdwType)
{
*lpdwType = ((LPNETRESOURCE)lpv)->dwType;
}
// Free our buffer
LocalFree( lpv );
return fResult;
}
// BUGBUG, we should validate the sizes of all path buffers by filing them
// with MAX_PATH fill bytes.
// convert a file spec to make it look a bit better
// if it is all upper case chars
BOOL PathMakePretty(LPTSTR lpPath)
{
LPTSTR lp;
static BOOL fQueryState = TRUE;
static BOOL fDontPrettyNames = FALSE;
CABINETSTATE cs;
/* Only read the cabinet state once (as its the registry for gawd sake, and
/ stash away the pretty names bit somewhere. */
if ( fQueryState )
{
ReadCabinetState( &cs, SIZEOF(cs) ); // flag stored in the cabinet state structure
fDontPrettyNames = cs.fDontPrettyNames;
fQueryState = FALSE;
}
/* Are we allowed to do pretty names? If not then bail */
if ( fDontPrettyNames )
return FALSE;
// REVIEW: INTL need to deal with lower case chars in (>127) range?
// check for all uppercase
for (lp = lpPath; *lp; lp = CharNext(lp)) {
if ((*lp >= TEXT('a')) && (*lp <= TEXT('z')) || IsDBCSLeadByte(*lp))
return FALSE; // this is a LFN or DBCS, dont mess with it
}
CharLower(lpPath);
CharUpperBuff(lpPath, 1);
return TRUE; // did the conversion
}
BOOL PathIsRemovable(LPNCTSTR pszPath)
{
int iDrive = PathGetDriveNumber(pszPath);
if (iDrive != -1)
{
return (DriveType(iDrive) == DRIVE_REMOVABLE);
}
return FALSE;
}
// 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"
LPTSTR WINAPI PathGetArgs(LPCTSTR pszPath)
{
BOOL fInQuotes = FALSE;
if (!pszPath)
return NULL;
while (*pszPath)
{
if (*pszPath == TEXT('"'))
fInQuotes = !fInQuotes;
else if (!fInQuotes && *pszPath == TEXT(' '))
return (LPTSTR)pszPath+1;
pszPath = CharNext(pszPath);
}
return (LPTSTR)pszPath;
}
void PathRemoveArgs(LPTSTR pszPath)
{
LPTSTR pArgs = PathGetArgs(pszPath);
if (*pArgs)
*(pArgs - 1) = TEXT('\0'); // clobber the ' '
// Handle trailing space.
else
{
pArgs = CharPrev(pszPath, pArgs);
if (*pArgs == TEXT(' '))
*pArgs = TEXT('\0');
}
}
// Return TRUE if a file exists (by attribute check)
BOOL WINAPI PathFileExists(LPCTSTR lpszPath)
{
DWORD dwErrMode;
BOOL fResult = FALSE;
if (!lpszPath || !(*lpszPath))
{
return fResult;
}
dwErrMode = SetErrorMode(SEM_FAILCRITICALERRORS);
fResult = ((UINT)GetFileAttributes(lpszPath) != (UINT)-1);
SetErrorMode(dwErrMode);
return fResult;
}
TCHAR const c_szDotPif[] = TEXT(".pif");
TCHAR const c_szDotCom[] = TEXT(".com");
TCHAR const c_szDotBat[] = TEXT(".bat");
#ifdef WINNT
TCHAR const c_szDotCmd[] = TEXT(".cmd");
#endif
// 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 these flags must be identical to to order
// of the c_aDefExtList array. PathFileExistsDefExt relies on it.
#define EXT_NONE 0x0000
#define EXT_PIF 0x0001
#define EXT_COM 0x0002
#define EXT_EXE 0x0004
#define EXT_BAT 0x0008
#define EXT_LNK 0x0010
#define EXT_CMD 0x0020
#define EXT_DEFAULT (EXT_CMD | EXT_COM | EXT_BAT | EXT_PIF | EXT_EXE | EXT_LNK)
LPCTSTR const c_aDefExtList[] = {
c_szDotPif,
c_szDotCom,
c_szDotExe,
c_szDotBat,
c_szDotLnk,
#ifdef WINNT
c_szDotCmd
#endif
};
// *** END OF WARNING ***
//------------------------------------------------------------------
// Return TRUE if a file exists (by attribute check) after
// applying a default extensions (if req).
BOOL PathFileExistsDefExt(LPTSTR lpszPath, UINT fExt)
{
// Try default extensions?
if (fExt)
{
DWORD dwPathType;
UINT i;
UINT iPathLen;
LPTSTR lpszPathEnd;
// No sense sticking an extension on a server or share...
dwPathType = PathIsUNCServerShare( lpszPath );
if ((dwPathType == UNC_SERVER_ONLY) || (dwPathType == UNC_SERVER_SHARE))
{
return FALSE;
}
iPathLen = lstrlen(lpszPath);
lpszPathEnd = lpszPath + iPathLen;
Assert(*PathFindExtension(lpszPath) == 0);
//
// Bail if not enough space for 4 more chars
//
if (MAX_PATH-iPathLen < ARRAYSIZE(c_szDotPif)) {
return FALSE;
}
for (i = 0; i < ARRAYSIZE(c_aDefExtList); i++, fExt = fExt >> 1) {
if (fExt & 1) {
lstrcpy(lpszPathEnd, c_aDefExtList[i]);
if (PathFileExists(lpszPath))
return TRUE;
}
}
*lpszPathEnd = 0; // Get rid of any extension
}
else
{
return PathFileExists(lpszPath);
}
return FALSE;
}
// 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"
// cbPath size of szPath
//
// out:
// szPath buffer with path piece
//
// returns:
// pointer to next piece to be used, NULL if done
//
//
// BUGBUG, we should write some test cases specifically for this code
LPCTSTR NextPath(LPCTSTR lpPath, LPTSTR szPath, int cbPath)
{
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)
return NULL; // Yep
lpEnd = StrChr(lpPath, TEXT(';'));
if (!lpEnd)
lpEnd = lpPath + lstrlen(lpPath);
lstrcpyn(szPath, lpPath, min(cbPath, lpEnd - lpPath + 1));
// BUGBUG: Neither strncpy nor StrCpyN is compatible with lstrcpyn!
szPath[lpEnd-lpPath] = TEXT('\0');
PathRemoveBlanks(szPath);
if (szPath[0]) {
//REVIEW FE: Deleted as a bug. - kenichin
//#ifdef DBCS
// if ((*lpEnd == ';') && (AnsiPrev(lpPath, lpEnd) != lpEnd-2))
//#else
if (*lpEnd == TEXT(';'))
//#endif
return lpEnd + 1; // next path string (maybe NULL)
else
return lpEnd; // pointer to NULL
} 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 LPCSTRs to other paths to look
// at first, NULL terminated.
//
// fExt
// EXT_ 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
//
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;
// REVIEW, we may want to just return TRUE here but for
// now assume only file specs are allowed
Assert(PathIsFileSpec(pszFile));
// first check list of other dirs
for (i = 0; ppszOtherDirs && ppszOtherDirs[i] && *ppszOtherDirs[i]; i++)
{
PathCombine(szPath, ppszOtherDirs[i], pszFile);
if (PathFileExistsDefExt(szPath, fExt))
{
lstrcpy(pszFile, szPath);
return TRUE;
}
}
// Look in system dir - this should probably be optional.
GetSystemDirectory(szPath, ARRAYSIZE(szPath));
if (!PathAppend(szPath, pszFile))
return FALSE;
if (PathFileExistsDefExt(szPath, fExt))
{
lstrcpy(pszFile, szPath);
return TRUE;
}
#ifdef WINNT
// 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))
{
lstrcpy(pszFile, szPath);
return TRUE;
}
#endif
// Look in windows dir - this should probably be optional.
GetWindowsDirectory(szPath, ARRAYSIZE(szPath));
if (!PathAppend(szPath, pszFile))
return FALSE;
if (PathFileExistsDefExt(szPath, fExt))
{
lstrcpy(pszFile, 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))
{
lstrcpy(pszFile, szPath);
if (pszEnv)
LocalFree((HLOCAL)pszEnv);
return TRUE;
}
}
}
if (pszEnv)
LocalFree((HLOCAL)pszEnv);
return FALSE;
}
//---------------------------------------------------------------------------
BOOL WINAPI PathFindOnPath(LPTSTR pszFile, LPCTSTR *ppszOtherDirs)
{
return PathFindOnPathEx(pszFile, ppszOtherDirs, EXT_NONE);
}
//---------------------------------------------------------------------------
// Get the path for the CSIDL_ folders and optionally create it if it
// doesn't exist.
//
// Returns FALSE if the special folder given isn't one of those above or the
// directory couldn't be created.
// By default all the special folders are in the windows directory.
// This can be overidden by a [.Shell Folders] section in win.ini with
// entries like Desktop = c:\stuff\desktop
// This in turn can be overidden by a "per user" section in win.ini eg
// [Shell Folder Ianel] - the user name for this section is the current
// network user name, if this fails the default network user name is used
// and if this fails the name given at setup time is used.
//
// c_szShellFolders is the key that records all the absolute paths to the
// shell folders. The values there are always supposed to be present.
//
// c_szUserShellFolders is the key where the user's modifications from
// the defaults are stored. If a folder is in the default location, the
// corresponding value is not present under this key; it's only found
// under c_szShellFolders.
//
// When we need to find the location of a path, we look in c_szUserShellFolders
// first, and if that's not there, generate the default path. In either
// case we then write the absolute path under c_szShellFolders for other
// apps to look at. This is so that HKEY_CURRENT_USER can be propagated
// to a machine with Windows installed in a different directory, and as
// long as the user hasn't changed the setting, they won't have the other
// Windows directory hard-coded in the registry.
// -- gregj, 11/10/94
extern ITEMIDLIST c_idlDesktop;
const TCHAR c_szShellFolders[] = TEXT("Shell Folders");
const TCHAR c_szUserShellFolders[] = TEXT("User Shell Folders");
const TCHAR c_szUserShellFoldersNew[] = TEXT("User Shell Folders\\New");
#pragma data_seg(".text", "CODE")
// BUGBUG: we need to free all these pidls
const struct {
int id;
int idsLong; // String id of name to use on LFN drives
int idsShort; // Srting id of name to use on non LFN drives.
LPCTSTR pszRegKey; // reg key (not localized)
HKEY hKey; // Current User or Local Machine
} c_SpecialDirInfo[] = {
{ CSIDL_DESKTOP, -1, -1, NULL, NULL },
{ CSIDL_NETWORK, -1, -1, NULL, NULL },
{ CSIDL_DRIVES, -1, -1, NULL, NULL },
#define CSIDL_LASTCONSTANTIDLIST 2
{ CSIDL_CONTROLS, -1, -1, NULL, NULL } ,
{ CSIDL_PRINTERS, -1, -1, NULL, NULL } ,
{ CSIDL_BITBUCKET, -1, -1, NULL, NULL },
#define CSIDL_LASTFIXEDFOLDER 5
{ CSIDL_FONTS, IDS_CSIDL_FONTS_L, IDS_CSIDL_FONTS_S, TEXT("Fonts"), HKEY_CURRENT_USER },
{ CSIDL_DESKTOPDIRECTORY, IDS_CSIDL_DESKTOPDIRECTORY_L, IDS_CSIDL_DESKTOPDIRECTORY_S, TEXT("Desktop"), HKEY_CURRENT_USER } ,
{ CSIDL_PROGRAMS, IDS_CSIDL_PROGRAMS_L, IDS_CSIDL_PROGRAMS_S, TEXT("Programs"), HKEY_CURRENT_USER } ,
{ CSIDL_RECENT, IDS_CSIDL_RECENT_L, IDS_CSIDL_RECENT_S, TEXT("Recent"), HKEY_CURRENT_USER } ,
{ CSIDL_SENDTO, IDS_CSIDL_SENDTO_L, IDS_CSIDL_SENDTO_S, TEXT("SendTo"), HKEY_CURRENT_USER } ,
{ CSIDL_PERSONAL, IDS_CSIDL_PERSONAL_L, IDS_CSIDL_PERSONAL_S, TEXT("Personal"), HKEY_CURRENT_USER } ,
{ CSIDL_STARTUP, IDS_CSIDL_STARTUP_L, IDS_CSIDL_STARTUP_S, TEXT("Startup"), HKEY_CURRENT_USER } ,
{ CSIDL_FAVORITES, IDS_CSIDL_FAVORITES_L, IDS_CSIDL_FAVORITES_S, TEXT("Favorites"), HKEY_CURRENT_USER },
{ CSIDL_STARTMENU, IDS_CSIDL_STARTMENU_L, IDS_CSIDL_STARTMENU_S, TEXT("Start Menu"), HKEY_CURRENT_USER },
{ CSIDL_NETHOOD, IDS_CSIDL_NETHOOD_L, IDS_CSIDL_NETHOOD_S, TEXT("NetHood"), HKEY_CURRENT_USER },
{ CSIDL_PRINTHOOD, IDS_CSIDL_PRINTHOOD_L, IDS_CSIDL_PRINTHOOD_S, TEXT("PrintHood"), HKEY_CURRENT_USER },
{ CSIDL_TEMPLATES, IDS_CSIDL_TEMPLATES_L, IDS_CSIDL_TEMPLATES_S, TEXT("Templates"), HKEY_CURRENT_USER },
// Common special folders
{ CSIDL_COMMON_STARTMENU, IDS_CSIDL_CSTARTMENU_L, IDS_CSIDL_CSTARTMENU_S, TEXT("Common Start Menu"), HKEY_LOCAL_MACHINE },
{ CSIDL_COMMON_PROGRAMS, IDS_CSIDL_CPROGRAMS_L, IDS_CSIDL_CPROGRAMS_S, TEXT("Common Programs"), HKEY_LOCAL_MACHINE },
{ CSIDL_COMMON_STARTUP, IDS_CSIDL_CSTARTUP_L, IDS_CSIDL_CSTARTUP_S, TEXT("Common Startup"), HKEY_LOCAL_MACHINE },
{ CSIDL_COMMON_DESKTOPDIRECTORY, IDS_CSIDL_CDESKTOPDIRECTORY_L, IDS_CSIDL_CDESKTOPDIRECTORY_S, TEXT("Common Desktop"), HKEY_LOCAL_MACHINE },
// Application Data special folder
{ CSIDL_APPDATA, IDS_CSIDL_APPDATA_L, IDS_CSIDL_APPDATA_S, TEXT("AppData"), HKEY_CURRENT_USER },
};
#pragma data_seg()
#pragma data_seg(DATASEG_PERINSTANCE)
//
// must be per process since it has pointers to code variables that can
// be loaded at different base addresses
//
LPITEMIDLIST g_apidlSpecialFolders[ARRAYSIZE(c_SpecialDirInfo)] = {
(LPITEMIDLIST)&c_idlDesktop,
(LPITEMIDLIST)&c_idlNet,
(LPITEMIDLIST)&c_idlDrives,
NULL, NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL,
NULL, NULL, NULL};
#pragma data_seg()
int _GetSpecialFolderIDIndex(int nFolder)
{
int i;
for (i = 0; i < ARRAYSIZE(c_SpecialDirInfo); i++) {
if (c_SpecialDirInfo[i].id == nFolder)
return i;
}
return -1;
}
void _GetSpecialFolderName(int idFolder, LPTSTR psz, int cch)
{
// Initialize to know if our windows directory is an LFN drive or
// not. We only need to do this once.
// See if the file system support long file names or not
static int s_bWindowsDriveLFN = -1;
VDATEINPUTBUF(psz, TCHAR, cch);
if (s_bWindowsDriveLFN == -1)
{
GetWindowsDirectory(psz, cch);
s_bWindowsDriveLFN = IsLFNDrive(psz) ? 1 : 0;
}
Assert(idFolder > CSIDL_LASTFIXEDFOLDER);
LoadString(HINST_THISDLL, s_bWindowsDriveLFN ?
c_SpecialDirInfo[idFolder].idsLong : c_SpecialDirInfo[idFolder].idsShort,
psz, cch);
Assert(*psz);
}
#define FILE_ATTRIBUTE_MISSING ((DWORD)-1)
void SetSpecialPath(int i, LPCTSTR pszPath);
// SetAbsoluteSpecialPath() saves the given absolute path under the absolute
// shell folders key for other apps to look at.
#define SetAbsoluteSpecialPath(i, pszPath) RegSetSpecialPath(i, pszPath, c_szShellFolders)
#define SetMoveTargetSpecialPath(i, pszPath) RegSetSpecialPath(i, pszPath, c_szUserFolderNew)
void RegSetSpecialPath(int i, LPCTSTR pszPath, LPCTSTR lpszSubKey);
LPCITEMIDLIST _CacheSpecialFolderIDList(HWND hwndOwner, int idFolder, BOOL fCreate)
{
LPITEMIDLIST pidl = NULL;
LPITEMIDLIST pidlGlobal = NULL;
if (!g_apidlSpecialFolders[idFolder])
{
TCHAR szPath[MAX_PATH];
TCHAR szDst[MAX_PATH];
HKEY hk;
BOOL fUseDefault;
int nFolder = c_SpecialDirInfo[idFolder].id;
DWORD dwAttribs;
int iExt;
// special case printers and controls
switch (nFolder)
{
case CSIDL_PRINTERS:
pidl = CDrives_CreateRegID(CDRIVES_REGITEM_PRINTERS);
break;
case CSIDL_CONTROLS:
pidl = CDrives_CreateRegID(CDRIVES_REGITEM_CONTROLS);
break;
case CSIDL_BITBUCKET:
pidl = CDesktop_CreateRegIDFromCLSID(&CLSID_ShellBitBucket);
break;
default:
fUseDefault = TRUE; // assume error
if (NULL != (hk = SHGetExplorerSubHkey(c_SpecialDirInfo[idFolder].hKey, c_szUserShellFolders, TRUE)))
{
DWORD dwType;
LONG cbPath = SIZEOF(szPath);
if ((RegQueryValueEx(hk, (LPTSTR)c_SpecialDirInfo[idFolder].pszRegKey, NULL, &dwType, (LPBYTE)szPath, &cbPath) == ERROR_SUCCESS) &&
(dwType == REG_SZ))
{
DebugMsg(DM_TRACE, TEXT("sh TR - GetSpecialFolderIDList: found %s at %s%s"),
szPath, c_szUserShellFolders, (c_SpecialDirInfo->pszRegKey ? c_SpecialDirInfo->pszRegKey : TEXT("<NULL>")) );
fUseDefault = FALSE;
}
RegCloseKey(hk);
}
TryDefault:
if (fUseDefault)
{
TCHAR szEntry[MAX_PATH];
// the shell never creates these by default, so if there's
// nothing in the registry, fail it always
//
if (nFolder == CSIDL_FAVORITES ||
nFolder == CSIDL_PERSONAL) {
return NULL;
}
// Great, even the default section is missing.
// Put everything in the windows directory.
GetWindowsDirectory(szPath, ARRAYSIZE(szPath));
#ifdef MYDIR_LIVES
if (c_SpecialDirInfo[idFolder].id == CSIDL_PERSONAL)
{
// Now the fun begins. In order to make the network
// PM people happy we need to jump through loops
// We should first look to see if there is a
// MYDOCSONNET policy is set, we should call the
// WNetGetHomeDirectory to find out where to place
// the sucker. If this fails or the policy is not
// set, we Call the function to find if there is a
// server based setup home directory and use it.
// if all of this fais we select the
// root of the windows drive...
BOOL fFoundDir = FALSE;
switch (SHRestricted(REST_MYDOCSONNET))
{
case 1:
{
// oh .... the WNET api does not look up the
// default for me...
HKEY hkeyNet;
if (RegOpenKey(HKEY_LOCAL_MACHINE, TEXT("Network\\Logon"),
&hkeyNet) == ERROR_SUCCESS)
{
TCHAR szProvider[MAX_PATH];
DWORD dwType;
DWORD cb = SIZEOF(szProvider);
if (RegQueryValueEx(hkeyNet, TEXT("PrimaryProvider"),
0, &dwType, (LPBYTE)szProvider, &cb) == ERROR_SUCCESS)
{
UINT cch = ARRAYSIZE(szPath);
fFoundDir = (WNetGetHomeDirectory(szProvider, szPath, &cch) == WN_SUCCESS);
}
RegCloseKey(hkeyNet);
}
}
break;
case 2:
{
// Case 2: the admin has setup a base path name
// to use and we are supposed to then tack on
// the user name to this to generate the name
//
HKEY hkeyPolicies;
if (RegOpenKey(HKEY_CURRENT_USER, REGSTR_PATH_POLICIES TEXT("\\explorer"), &hkeyPolicies) == ERROR_SUCCESS)
{
DWORD dwType;
DWORD cb = SIZEOF(szPath);
if (RegQueryValueEx(hkeyPolicies, TEXT("NetBasePath"),
0, &dwType, (LPBYTE)szPath, &cb) == ERROR_SUCCESS)
{
TCHAR szUserName[MAX_PATH];
cch = ARRAYSIZE(szUserName) - (lstrlen(szPath) + 2);
fFoundDir = (WNetGetUser(szPath, szUserName, &cch) == WN_SUCCESS);
if (fFoundDir)
{
PathCleanupSpec(szPath, szUserName);
PathAppend(szPath, szUserName);
}
}
RegCloseKey(hkeyPolicies);
}
}
}
// See if we have found the place to save it yet
if (!fFoundDir)
{
LPCTSTR pszT = GetNetHomeDir();
if (pszT != NONETHOMEDIR)
lstrcpy(szPath, pszT);
else
{
GetWindowsDirectory(szPath, ARRAYSIZE(szPath));
PathStripToRoot(szPath);
}
}
}
#endif // MYDIR_LIVES
_GetSpecialFolderName(idFolder, szEntry, ARRAYSIZE(szEntry));
PathAppend(szPath, szEntry);
}
// Handle LFN/SFN issues.
PathQualifyDef(szPath, NULL, 0);
dwAttribs = GetFileAttributes(szPath);
if ((dwAttribs != FILE_ATTRIBUTE_MISSING) && !(dwAttribs & FILE_ATTRIBUTE_DIRECTORY))
{
// Eeek. A regular file already exists.
DebugMsg(DM_TRACE, TEXT("csfidl: Regular file exists with the same name as required special folder."));
// Move the file out of the way.
iExt = 0;
do
{
TCHAR szExt[32];
wsprintf(szExt, TEXT(".%d"), iExt);
lstrcpy(szDst, szPath);
lstrcat(szDst, szExt);
DebugMsg(DM_TRACE, TEXT("csfidl: Moving %s to %s"), szPath, szDst);
if (MoveFile(szPath, szDst))
iExt = 0;
else
iExt++;
} while (iExt);
// now set the attribute up for the next if block
dwAttribs = GetFileAttributes(szPath);
}
if (fCreate && hwndOwner && (dwAttribs == FILE_ATTRIBUTE_MISSING))
{
//If it is a UNC path try to validate it first
if (PathIsUNC(szPath))
{
if (SHValidateUNC(hwndOwner, szPath, 0))
dwAttribs = GetFileAttributes(szPath);
}
else if (IsNetDrive(DRIVEID(szPath))==2)
{
TCHAR szDrive[4];
szDrive[0] = szPath[0];
szDrive[1] = TEXT(':');
szDrive[2] = TEXT('\0');
if (WNetRestoreConnection(hwndOwner, szDrive) == WN_SUCCESS)
dwAttribs = GetFileAttributes(szPath);
}
}
// Do we need to create the directory?
if (fCreate && (dwAttribs == FILE_ATTRIBUTE_MISSING))
{
// Make it!
DebugMsg(DM_TRACE, TEXT("Creating Shell folder %s"), (LPTSTR)szPath);
// to create each piece of the path
// REVIEW: this might already work in CreateDirectory()
SHCreateDirectory(NULL, szPath);
dwAttribs = GetFileAttributes(szPath);
if (dwAttribs == FILE_ATTRIBUTE_MISSING)
{
Assert(pidl==NULL);
if (!fUseDefault)
{
// our registry had something we couldn't create..
// try it again with the default stuff.
fUseDefault = TRUE;
goto TryDefault;
}
break; // break from switch(nFolder) one level above
}
switch (nFolder)
{
case CSIDL_RECENT:
case CSIDL_NETHOOD:
case CSIDL_PRINTHOOD:
case CSIDL_TEMPLATES:
// mark some folders as hidden
SetFileAttributes(szPath, FILE_ATTRIBUTE_HIDDEN);
break; // from switch(nFolder) right above
}
}
if (dwAttribs == FILE_ATTRIBUTE_MISSING)
return NULL;
// it wasn't in the registry before... add it
DebugMsg(DM_TRACE, TEXT("sh TR - Setting special path to %d %s"), nFolder, szPath);
SetAbsoluteSpecialPath(idFolder, szPath);
pidl = ILCreateFromPath(szPath);
break;
}
Assert(pidl);
pidlGlobal = ILGlobalClone(pidl);
ILFree(pidl);
}
Assert(pidlGlobal);
// NB We do most of the work outside a critical section because
// ILCreate can take a long time if it hits the net
// and we end up locking up the shell (RNA cases in particular).
ENTERCRITICAL
if (!g_apidlSpecialFolders[idFolder])
{
g_apidlSpecialFolders[idFolder] = pidlGlobal;
}
else
{
ILGlobalFree(pidlGlobal);
}
LEAVECRITICAL
return g_apidlSpecialFolders[idFolder];
}
// Per instance count of mods to Special Folder cache.
#pragma data_seg(DATASEG_PERINSTANCE)
int gi_nSFUpdate = 0;
#pragma data_seg()
// Global count of mods to Special Folder cache.
int gs_nSFUpdate = 0;
//----------------------------------------------------------------------------
// Make sure the special folder cache is up to date.
void CheckUpdateSFCache(void)
{
int i;
// DebugMsg(DM_TRACE, "s.cusfc: Inst %d Glob %d", gi_nSFUpdate, gs_nSFUpdate);
// Is the cache up to date?
if (gs_nSFUpdate != gi_nSFUpdate)
{
ENTERCRITICAL
// Nope, invalidate them.
for (i = CSIDL_LASTFIXEDFOLDER + 1; i < ARRAYSIZE(g_apidlSpecialFolders); i++)
{
if (g_apidlSpecialFolders[i])
{
ILGlobalFree(g_apidlSpecialFolders[i]);
g_apidlSpecialFolders[i] = NULL;
}
}
gi_nSFUpdate = gs_nSFUpdate;
LEAVECRITICAL
}
}
// this simply returns the global one. for shelldll internal use only
//
// BUGBUG: This is not thread safe at all!
//
LPCITEMIDLIST GetSpecialFolderIDList(HWND hwndOwner, int nFolder, BOOL fCreate)
{
int i = _GetSpecialFolderIDIndex(nFolder);
if (i == -1)
return NULL;
CheckUpdateSFCache();
// Check it before taking the critical section to avoid extra enter.
if (g_apidlSpecialFolders[i])
return g_apidlSpecialFolders[i];
else
return _CacheSpecialFolderIDList(hwndOwner, i, fCreate);
}
LPITEMIDLIST WINAPI SHCloneSpecialIDList(HWND hwndOwner, int nFolder, BOOL fCreate)
{
LPITEMIDLIST pidlReturn;
LPCITEMIDLIST pidlGlobal;
ENTERCRITICAL;
pidlGlobal = GetSpecialFolderIDList(hwndOwner, nFolder, fCreate);
pidlReturn = pidlGlobal ? ILClone(pidlGlobal) : NULL;
LEAVECRITICAL;
return pidlReturn;
}
HRESULT WINAPI SHGetSpecialFolderLocation(HWND hwndOwner, int nFolder, LPITEMIDLIST * ppidl)
{
int i = _GetSpecialFolderIDIndex(nFolder);
if (i == -1)
{
*ppidl = NULL; // we must fill NULL in case of error
return E_INVALIDARG;
}
*ppidl = SHCloneSpecialIDList(hwndOwner, nFolder, FALSE);
return *ppidl ? NOERROR : E_OUTOFMEMORY;
}
BOOL WINAPI SHGetSpecialFolderPath(HWND hwndOwner, LPTSTR lpszPath, int nFolder, BOOL fCreate)
{
LPCITEMIDLIST pidl;
BOOL fRet;
ENTERCRITICAL;
pidl = GetSpecialFolderIDList(hwndOwner, nFolder, fCreate);
if (pidl) {
SHGetPathFromIDList(pidl, lpszPath);
fRet = TRUE;
} else
fRet = FALSE;
LEAVECRITICAL;
return fRet;
}
void SpecialFolderIDTerminate()
{
int i;
for (i = CSIDL_LASTCONSTANTIDLIST + 1; i < ARRAYSIZE(g_apidlSpecialFolders) ; i++) {
if (g_apidlSpecialFolders[i]) {
ILGlobalFree(g_apidlSpecialFolders[i]);
g_apidlSpecialFolders[i] = NULL;
}
}
}
void RegSetSpecialPath(int i, LPCTSTR pszPath, LPCTSTR lpszSubKey)
{
HKEY hk;
ENTERCRITICAL;
if (NULL != (hk = SHGetExplorerSubHkey(c_SpecialDirInfo[i].hKey, lpszSubKey, TRUE)))
{
if (pszPath) {
RegSetValueEx(hk, (LPTSTR)c_SpecialDirInfo[i].pszRegKey, 0, REG_SZ, (LPBYTE)pszPath, (1 + lstrlen(pszPath)) * SIZEOF(TCHAR));
} else {
RegDeleteValue(hk, (LPTSTR)c_SpecialDirInfo[i].pszRegKey);
}
RegCloseKey(hk);
}
LEAVECRITICAL;
}
BOOL RegGetSpecialPath(int i, LPTSTR pszPath, LPCTSTR lpszSubKey)
{
BOOL fRet = FALSE;
HKEY hk;
ENTERCRITICAL;
if (pszPath) {
if (NULL != (hk = SHGetExplorerSubHkey(c_SpecialDirInfo[i].hKey, lpszSubKey, TRUE)))
{
DWORD dwType;
LONG cbPath = MAX_PATH;
fRet = (RegQueryValueEx(hk, (LPTSTR)c_SpecialDirInfo[i].pszRegKey, NULL, &dwType, (LPBYTE)pszPath, &cbPath) == ERROR_SUCCESS) &&
dwType == REG_SZ;
RegCloseKey(hk);
}
}
LEAVECRITICAL;
return fRet;
}
//
// UnExpandEnvironmentString
//
// If the given environment variable exists as the first part of the path,
// then the environment variable is inserted into the output buffer.
//
// Assumes that lpResult is MAX_PATH characters in length.
//
// Returns TRUE if lpResult is filled in.
//
// Example: Input -- C:\WINNT\SYSTEM32\FOO.TXT -and- lpEnvVar = %SystemRoot%
// Output -- %SystemRoot%\SYSTEM32\FOO.TXT
//
BOOL UnExpandEnvironmentString(LPCTSTR lpPath, LPTSTR lpResult, LPTSTR lpEnvVar)
{
TCHAR szEnvVar[MAX_PATH];
LPTSTR lpFileName;
DWORD dwEnvVar;
if (!lpPath || !*lpPath) {
return FALSE;
}
//
// If the first part of lpPath is the expanded value of lpEnvVar
// then we want to un-expand the environment variable.
//
ExpandEnvironmentStrings (lpEnvVar, szEnvVar, MAX_PATH);
dwEnvVar = lstrlen(szEnvVar);
//
// Make sure the source is long enough
//
if ((DWORD)lstrlen(lpPath) < dwEnvVar) {
return FALSE;
}
if (CompareString (LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
szEnvVar, dwEnvVar,
lpPath, dwEnvVar) == 2) {
//
// The szReturn buffer starts with lpEnvVar.
// Actually insert lpEnvVar in the result buffer.
//
lstrcpy (lpResult, lpEnvVar);
lstrcat (lpResult, (lpPath + dwEnvVar));
return TRUE;
}
return FALSE;
}
void SetSpecialPath(int i, LPCTSTR pszPath)
{
LONG err;
ENTERCRITICAL;
ILGlobalFree(g_apidlSpecialFolders[i]);
g_apidlSpecialFolders[i] = NULL;
if (pszPath)
{
HKEY hk = SHGetExplorerSubHkey(c_SpecialDirInfo[i].hKey, c_szUserShellFolders, TRUE);
if (hk)
{
// If the path being set is the default, delete the custom
// setting. Otherwise, set the new path as the custom
// setting.
TCHAR szDefaultPath[MAX_PATH];
TCHAR szEntry[MAX_PATH];
GetWindowsDirectory(szDefaultPath, ARRAYSIZE(szDefaultPath));
_GetSpecialFolderName(i, szEntry, ARRAYSIZE(szEntry));
PathAppend(szDefaultPath, szEntry);
if (!lstrcmpi(szDefaultPath, pszPath))
err = RegDeleteValue(hk, (LPTSTR)c_SpecialDirInfo[i].pszRegKey);
else {
DWORD dwType, dwSize;
//
// Check for an existing path, and if the unexpanded version
// of the existing path does not match the new path, then
// write the new path to the registry.
//
// Remember, RegQueryValueEx is defined to be
// SHRegQueryValueEx, so it will automaticly
// expand the environment variables for us, and on NT
// these shell folder entries will contain environment variables,
// so we can't just blindly set the new value to the registry.
//
dwSize = ARRAYSIZE(szDefaultPath) * sizeof(TCHAR);
RegQueryValueEx (hk,c_SpecialDirInfo[i].pszRegKey, NULL, &dwType,
(LPBYTE) szDefaultPath, &dwSize);
if (lstrcmpi(szDefaultPath, pszPath) != 0) {
//
// The paths are different. Check if an
// environment variable can be used.
//
if (!UnExpandEnvironmentString(pszPath, szDefaultPath, TEXT("%USERPROFILE%"))) {
if (!UnExpandEnvironmentString(pszPath, szDefaultPath, TEXT("%SystemRoot%"))) {
lstrcpy (szDefaultPath, pszPath);
}
}
err = RegSetValueEx(hk, (LPTSTR)c_SpecialDirInfo[i].pszRegKey, 0, REG_EXPAND_SZ, (LPBYTE)szDefaultPath, (1 + lstrlen(szDefaultPath)) * SIZEOF(TCHAR));
} else
err = ERROR_SUCCESS;
}
// clear out any temp paths
RegSetSpecialPath(i, NULL, c_szUserShellFoldersNew);
if (err==ERROR_SUCCESS)
{
// this will force a new creation (see TRUE as fCreate).
// This will also copy the path from c_szUserShellFolders
// to c_szShellFolders.
if (!_CacheSpecialFolderIDList(NULL, i, TRUE))
{
// failed! null out the entry. this will go back to our default
RegDeleteValue(hk, (LPTSTR)c_SpecialDirInfo[i].pszRegKey);
Assert(g_apidlSpecialFolders[i] == NULL);
_CacheSpecialFolderIDList(NULL, i, TRUE);
Assert(0);
}
}
RegCloseKey(hk);
}
}
LEAVECRITICAL;
}
void RenameSpecialDir(int i, LPITEMIDLIST pidlSrc, LPITEMIDLIST pidlDest)
{
LPITEMIDLIST pidl;
LPITEMIDLIST pidlNew;
ENTERCRITICAL;
pidl = (LPITEMIDLIST)ILFindChild(pidlSrc, g_apidlSpecialFolders[i]);
pidlNew = ILCombine(pidlDest, pidl);
if (pidlNew)
{
TCHAR szPath[MAX_PATH];
// get the name and set it instead of using the pidl because
// it might be a simple pidl.
SHGetPathFromIDList(pidlNew, szPath);
SetSpecialPath(i, szPath);
ILFree(pidlNew);
}
LEAVECRITICAL;
}
#if 0
int CALLBACK PickNewSpecialDirCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
if (uMsg == BFFM_SELCHANGED) {
LPITEMIDLIST pidl = (LPITEMIDLIST)lParam;
TCHAR szPath[MAX_PATH];
BOOL fEnable = FALSE;
if (SHGetPathFromIDList(pidl, szPath)) {
if (!PathIsRemovable(szPath)) {
fEnable = TRUE;
}
}
SendMessage(hwnd, BFFM_ENABLEOK, 0, fEnable);
}
return 0;
}
// warn user then do the file open dialog box thing
int PickNewSpecialDir(HWND hwnd, int i)
{
TCHAR szPath[MAX_PATH];
LPITEMIDLIST pidl;
TCHAR szEntry[MAX_PATH];
TCHAR szTemplate[80];
BROWSEINFO bi = {
hwnd,
NULL,
NULL,
NULL,
BIF_RETURNONLYFSDIRS,
PickNewSpecialDirCallback,
};
bi.pidlRoot = GetSpecialFolderIDList(NULL, CSIDL_DRIVES, FALSE);
_GetSpecialFolderName(i, szEntry, ARRAYSIZE(szEntry));
LoadString(HINST_THISDLL, IDS_SPECIALSEARCHTITLE, szTemplate, ARRAYSIZE(szTemplate));
wsprintf(szPath, szTemplate, szEntry);
bi.lpszTitle = szPath;
TryAgain:
pidl = SHBrowseForFolder(&bi);
if (pidl) {
int j;
SHGetPathFromIDList(pidl, szPath);
for (j = CSIDL_LASTCONSTANTIDLIST + 1; j < ARRAYSIZE(g_apidlSpecialFolders) ; j++) {
if ((j != i) && g_apidlSpecialFolders[j] &&
ILIsEqual(pidl, g_apidlSpecialFolders[j])) {
// trying to set it to a current special pidl
ShellMessageBox(HINST_THISDLL, NULL, MAKEINTRESOURCE(IDS_ALREADYSPECIALDIR),
szEntry, MB_ICONEXCLAMATION, szPath);
goto TryAgain;
}
}
SetSpecialPath(i, szPath);
gi_nSFUpdate = InterlockedIncrement(&gs_nSFUpdate);
return IDYES;
} else {
// no. abort!
return IDNO;
}
}
#endif
void SFP_FSEvent(LONG lEvent, LPITEMIDLIST pidl, LPITEMIDLIST pidlExtra)
{
int i;
BOOL fCacheChanged = FALSE;
if (!(lEvent & (SHCNE_RENAMEFOLDER | SHCNE_RMDIR)) || ILIsEmpty(pidl))
return;
CheckUpdateSFCache();
for (i = CSIDL_LASTFIXEDFOLDER + 1; i < ARRAYSIZE(g_apidlSpecialFolders); i++) {
if (!g_apidlSpecialFolders[i]) {
_CacheSpecialFolderIDList(NULL, i, FALSE);
}
if (g_apidlSpecialFolders[i]) {
// move the pointer
if (ILIsParent(pidl, g_apidlSpecialFolders[i], FALSE)) {
if (lEvent & SHCNE_RMDIR) {
TCHAR szPath[MAX_PATH];
Assert(!pidlExtra);
if (RegGetSpecialPath(i, szPath, c_szUserShellFoldersNew))
pidlExtra = SHSimpleIDListFromPath(szPath);
}
if (pidlExtra) {
RenameSpecialDir(i, pidl, pidlExtra);
if (lEvent & SHCNE_RMDIR){
// then we allocated it above.. free it
ILFree(pidlExtra);
}
fCacheChanged = TRUE;
}
}
}
}
if (fCacheChanged)
gi_nSFUpdate = InterlockedIncrement(&gs_nSFUpdate);
}
int PathCopyHookCallback(HWND hwnd, UINT wFunc, LPCTSTR pszSrc, LPCTSTR pszDest)
{
int ret = IDYES;
if ((wFunc == FO_DELETE) || (wFunc == FO_MOVE) || (wFunc == FO_RENAME)) {
LPITEMIDLIST pidl = SHSimpleIDListFromPath(pszSrc);
if (pidl) {
int i;
CheckUpdateSFCache();
// is one of our system directories being affected?
for (i = CSIDL_LASTFIXEDFOLDER + 1; i < ARRAYSIZE(g_apidlSpecialFolders); i++) {
if (!g_apidlSpecialFolders[i]) {
_CacheSpecialFolderIDList(NULL, i, FALSE);
}
if (g_apidlSpecialFolders[i] &&
ILIsParent(pidl, g_apidlSpecialFolders[i], FALSE)) {
if (wFunc == FO_DELETE) {
ShellMessageBox(HINST_THISDLL, hwnd, MAKEINTRESOURCE(IDS_CANTDELETESPECIALDIR),
MAKEINTRESOURCE(IDS_DELETE), MB_OK | MB_ICONINFORMATION, PathFindFileName(pszSrc));
ret = IDNO;
} else {
int idSrc, idDest;
// wFunc == FO_MOVE
idSrc = PathGetDriveNumber(pszSrc);
idDest = PathGetDriveNumber(pszDest);
if (((idSrc != -1) && (idDest == -1)) ||
((idSrc != idDest) && PathIsRemovable(pszDest))) {
ShellMessageBox(HINST_THISDLL, NULL, MAKEINTRESOURCE(IDS_CANTMOVESPECIALDIRHERE),
PathFindFileName(pszSrc), MB_ICONERROR, pszDest);
ret = IDNO;
}
else
{
if (idSrc != idDest)
{
// this is going to be a move across volumes
// which means a delete then a create notificationa
RegSetSpecialPath(i, pszDest, c_szUserShellFoldersNew);
}
}
}
break;
}
}
ILFree(pidl);
}
}
return ret;
}
// returns:
// TRUE given filespec is long (> 8.3 form)
// FALSE filespec is short
BOOL PathIsLFNFileSpec(LPCTSTR lpName)
{
BOOL bSeenDot = FALSE;
int iCount;
for (iCount = 1; *lpName; ) {
if (bSeenDot) {
if (iCount > 3)
return TRUE; // long name
}
if (*lpName == TEXT(' ')) {
return TRUE; // Short names dont have blanks in them.
}
if (*lpName == TEXT('.')) {
if (bSeenDot)
return TRUE; // short names can only have one .
bSeenDot = TRUE;
iCount = 0; // don't include the '.'
} else if (iCount > 8) {
return TRUE; // long name
}
if (IsDBCSLeadByte(*lpName)) {
lpName += 2;
iCount += 2;
}
else {
lpName++;
iCount++;
}
}
return FALSE; // short name
}
// 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. ==> "."
//
LPTSTR WINAPI PathFindExtension(LPCTSTR pszPath)
{
LPCTSTR pszDot;
for (pszDot = NULL; *pszPath; pszPath = CharNext(pszPath))
{
switch (*pszPath) {
case TEXT('.'):
pszDot = pszPath; // remember the last dot
break;
case TEXT('\\'):
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;
}
#ifdef UNICODE
LPSTR WINAPI PathFindExtensionA(LPCSTR lpszPath)
{
LPCSTR lpDot, lp;
for (lpDot = NULL, lp = lpszPath; *lp; lp = CharNextA(lp)) {
switch (*lp) {
case '.':
lpDot = lp; // remember the last dot
break;
case '\\':
lpDot = NULL; // forget last dot, it was in a directory
break;
}
}
if (!lpDot)
return (LPSTR)lp; // NULL extension (cast->non const)
else
return (LPSTR)lpDot; // here is the extension (cast->non const)
}
#endif
//BUGBUG this is exported so we need to support it, RIP IT OUT
LPTSTR WINAPI PathGetExtension(LPCTSTR lpszPath, LPTSTR lpszExtension, int cchExt)
{
LPTSTR pszExt = PathFindExtension(lpszPath);
Assert(lpszExtension==NULL); // we dont handle this case.
return *pszExt ? pszExt + 1 : pszExt;
}
// 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)
BOOL PathAddExtension(LPTSTR pszPath, LPCTSTR pszExtension)
{
if (*PathFindExtension(pszPath) == 0 && ((lstrlen(pszPath) + lstrlen(pszExtension ? pszExtension : c_szDotExe)) < MAX_PATH))
{
if (pszExtension == NULL)
pszExtension = c_szDotExe;
lstrcat(pszPath, pszExtension);
return TRUE;
}
return FALSE;
}
void PathRemoveExtension(LPTSTR pszPath)
{
LPTSTR pExt = PathFindExtension(pszPath);
if (*pExt)
{
Assert(*pExt == TEXT('.'));
*pExt = 0; // null out the "."
}
}
BOOL PathRenameExtension(LPTSTR pszPath, LPCTSTR pszExt)
{
LPTSTR pExt = PathFindExtension(pszPath); // Rets ptr to end of str if none
if (pExt - pszPath + lstrlen(pszExt) > MAX_PATH - 1) {
return(FALSE);
}
lstrcpy(pExt, pszExt);
return(TRUE);
}
// find the next slash or null terminator
LPCTSTR StrSlash(LPCTSTR psz)
{
for (; *psz && *psz != TEXT('\\'); psz = 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.
//
int WINAPI PathCommonPrefix(LPCTSTR pszFile1, LPCTSTR pszFile2, LPTSTR pszPath)
{
LPCTSTR psz1, psz2, pszNext1, pszNext2, pszCommon;
int cch;
#ifdef DEBUG
{
#pragma data_seg(DATASEG_PERINSTANCE)
static BOOL s_fTested = FALSE;
#pragma data_seg()
if (!s_fTested)
{
TCHAR szTest[MAX_PATH];
s_fTested = TRUE;
Assert(!PathCommonPrefix(TEXT("C:\\windows\\system"), TEXT("D:\\windows\\system"), szTest));
Assert(PathCommonPrefix(TEXT("c:\\windows\\system"), TEXT("c:\\windows"), szTest)
&& lstrcmpi(szTest, TEXT("c:\\windows"))==0);
Assert(PathCommonPrefix(TEXT("c:\\windows\\system"), TEXT("c:\\windows\\desktop"), szTest)
&& lstrcmpi(szTest, TEXT("c:\\windows"))==0);
Assert(PathCommonPrefix(TEXT("c:\\foo"), TEXT("c:\\bar"), szTest)
&& lstrcmpi(szTest, TEXT("c:\\"))==0);
Assert(PathCommonPrefix(TEXT("c:\\foo"), TEXT("c:\\"), szTest)
&& lstrcmpi(szTest, TEXT("c:\\"))==0);
}
}
#endif // DEBUG
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)
{
Assert(*psz1 != TEXT('\\') && *psz2 != TEXT('\\'));
pszNext1 = StrSlash(psz1);
pszNext2 = StrSlash(psz2);
cch = pszNext1 - psz1;
if (cch != (pszNext2 - psz2))
break; // lengths of segments not equal
if (IntlStrEqNI(psz1, psz2, cch))
pszCommon = pszNext1;
else
break;
Assert(*pszNext1 == TEXT('\0') || *pszNext1 == TEXT('\\'));
Assert(*pszNext2 == TEXT('\0') || *pszNext2 == TEXT('\\'));
if (*pszNext1 == TEXT('\0'))
break;
psz1 = pszNext1 + 1;
if (*pszNext2 == TEXT('\0'))
break;
psz2 = pszNext2 + 1;
}
if (pszCommon)
{
cch = pszCommon - pszFile1;
// special case the root to include the slash
if (cch == 2)
{
Assert(pszFile1[1] == TEXT(':'));
cch++;
}
}
else
cch = 0;
if (pszPath)
{
CopyMemory(pszPath, pszFile1, cch * SIZEOF(TCHAR));
pszPath[cch] = TEXT('\0');
}
return cch;
}
// 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
extern const TCHAR c_szDot[];
const TCHAR c_szDotDot[] = TEXT("..");
const TCHAR c_szDotDotSlash[] = TEXT("..\\");
BOOL PathRelativePathTo(LPTSTR pszPath, LPCTSTR pszFrom, DWORD dwAttrFrom, LPCTSTR pszTo, DWORD dwAttrTo)
{
TCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
LPTSTR psz;
UINT cchCommon;
*pszPath = 0; // assume none
lstrcpyn(szFrom, pszFrom, ARRAYSIZE(szFrom));
lstrcpyn(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 == TEXT('\\'))
psz++; // skip slash
while (*psz)
{
psz = PathFindNextComponent(psz);
// BUGBUG: in a degenerate case where each path component
// is 1 character (less than "..\") we can overflow pszPath
lstrcat(pszPath, *psz ? c_szDotDotSlash : c_szDotDot);
}
}
else
{
lstrcpy(pszPath, c_szDot);
}
if (pszTo[cchCommon])
{
// deal with root case
if (pszTo[cchCommon] != TEXT('\\'))
cchCommon--;
if ((lstrlen(pszPath) + lstrlen(pszTo + cchCommon)) >= MAX_PATH)
{
DebugMsg(DM_ERROR, TEXT("path won't fit in buffer"));
*pszPath = 0;
return FALSE;
}
Assert(pszTo[cchCommon] == TEXT('\\'));
lstrcat(pszPath, pszTo + cchCommon);
}
Assert(PathIsRelative(pszPath));
Assert(lstrlen(pszPath) < MAX_PATH);
return TRUE;
}
UINT PathGetCharType(TUCHAR ch)
{
switch (ch) {
case TEXT('|'):
case TEXT('>'):
case TEXT('<'):
case TEXT('"'):
return GCT_INVALID;
case TEXT('?'):
case TEXT('*'):
return GCT_WILD;
case TEXT('\\'): // path separator
case TEXT('/'): // path sep
case TEXT(':'): // drive colon
return GCT_SEPERATOR;
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
return GCT_INVALID; // control character
}
}
int WINAPI PathCleanupSpec(LPCTSTR pszDir, LPTSTR pszSpec)
{
LPTSTR pszNext, pszCur;
UINT uMatch = IsLFNDrive(pszDir) ? GCT_LFNCHAR : GCT_SHORTCHAR;
int iRet = 0;
LPTSTR pszPrevDot = NULL;
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE))) // BUGBUG: This is only for FE-M7, we'll remove "ifdef" after FE-M7.
TCHAR pszTmp[MAX_PATH];
for (pszCur = pszTmp, pszNext = pszSpec; *pszNext; pszNext = CharNext(pszNext)) {
#else
for (pszCur = pszNext = pszSpec; *pszNext; pszNext = CharNext(pszNext)) {
#endif
if (PathGetCharType(*pszNext) & uMatch) {
*pszCur = *pszNext;
if (uMatch == GCT_SHORTCHAR && *pszCur == TEXT('.')) {
if (pszPrevDot) { // Only one '.' allowed for short names
*pszPrevDot = TEXT('-');
iRet |= PCS_REPLACEDCHAR;
}
pszPrevDot = pszCur;
}
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE))) // BUGBUG: This is only for FE-M7, we'll remove "ifdef" after FE-M7.
if (IsDBCSLeadByte(*pszNext))
*(pszCur + 1) = *(pszNext + 1);
#endif
pszCur = CharNext(pszCur);
} else {
switch (*pszNext) {
case TEXT('/'): // used often for things like add/remove
case TEXT(' '): // blank (only replaced for short name drives)
*pszCur = TEXT('-');
pszCur = CharNext(pszCur);
iRet |= PCS_REPLACEDCHAR;
break;
default:
iRet |= PCS_REMOVEDCHAR;
}
}
}
*pszCur = 0; // null terminate
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE))) // BUGBUG: This is only for FE-M7, we'll remove "ifdef" after FE-M7.
lstrcpy(pszSpec, pszTmp);
#endif
//
// For short names, limit to 8.3
//
if (uMatch == GCT_SHORTCHAR) {
int i = 8;
for (pszCur = pszNext = pszSpec; *pszNext; pszNext = CharNext(pszNext)) {
if (*pszNext == TEXT('.')) {
i = 4; // Copy "." + 3 more characters
}
if (i > 0) {
*pszCur = *pszNext;
pszCur = CharNext(pszCur);
i--;
} else {
iRet |= PCS_TRUNCATED;
}
}
*pszCur = 0;
CharUpper(pszSpec);
} else { // Path too long only possible on LFN drives
if (pszDir && (lstrlen(pszDir) + lstrlen(pszSpec) > MAX_PATH - 1)) {
iRet |= PCS_PATHTOOLONG | PCS_FATAL;
}
}
return(iRet);
}
BOOL IsWild(LPCTSTR lpszPath)
{
while (*lpszPath) {
if (*lpszPath == TEXT('?') || *lpszPath == TEXT('*'))
return TRUE;
lpszPath = CharNext(lpszPath);
}
return FALSE;
}
LPNTSTR WINAPI PathBuildRoot(LPNTSTR szRoot, int iDrive)
{
Assert(iDrive >= 0 && iDrive < 26);
szRoot[0] = (TCHAR)iDrive + (TCHAR)TEXT('A');
szRoot[1] = TEXT(':');
szRoot[2] = TEXT('\\');
szRoot[3] = 0;
return szRoot;
}
// 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
void WINAPI PathRemoveBlanks(LPTSTR lpszString)
{
LPTSTR lpszPosn = lpszString;
/* strip leading blanks */
while(*lpszPosn == TEXT(' ')) {
lpszPosn++;
}
if (lpszPosn != lpszString)
lstrcpy(lpszString, 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=CharNext(lpszString))
{
if (*lpszString != TEXT(' '))
{
lpszPosn = lpszString;
}
}
// Note AnsiNext is a macro for non-DBCS, so it will not stop at NULL
if (*lpszPosn)
{
*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.
LPTSTR WINAPI PathRemoveBackslash(LPTSTR lpszPath)
{
int len = lstrlen(lpszPath)-1;
if (IsDBCSLeadByte(*CharPrev(lpszPath,lpszPath+len+1)))
len--;
if (!PathIsRoot(lpszPath) && lpszPath[len] == TEXT('\\'))
lpszPath[len] = TEXT('\0');
return lpszPath + len;
}
//--------------------------------------------------------------------------
// 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, TEXT('\\'));
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, TEXT('\\'));
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] = TEXT('\\');
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('\\');
lpszPath[1] = TEXT('\\');
lpszPath[2] = TEXT('\0');
}
}
//--------------------------------------------------------------------------
// Canonicalizes a path.
BOOL PathCanonicalize(LPTSTR lpszDst, LPCTSTR lpszSrc)
{
LPCTSTR lpchSrc;
LPCTSTR lpchPCEnd; // Pointer to end of path component.
LPTSTR lpchDst;
BOOL fUNC;
int cbPC;
fUNC = PathIsUNC(lpszSrc); // Check for UNCness.
// Init.
lpchSrc = lpszSrc;
lpchDst = lpszDst;
while (*lpchSrc)
{
// REVIEW: this should just return the count
lpchPCEnd = GetPCEnd(lpchSrc);
cbPC = (lpchPCEnd - lpchSrc)+1;
// Check for slashes.
if (cbPC == 1 && *lpchSrc == TEXT('\\'))
{
// Just copy them.
*lpchDst = TEXT('\\');
lpchDst++;
lpchSrc++;
}
// Check for dots.
else if (cbPC == 2 && *lpchSrc == TEXT('.'))
{
// Skip it...
// Are we at the end?
if (*(lpchSrc+1) == TEXT('\0'))
{
lpchDst--;
lpchSrc++;
}
else
lpchSrc += 2;
}
// Check for dot dot.
else if (cbPC == 3 && *lpchSrc == TEXT('.') && *(lpchSrc + 1) == TEXT('.'))
{
// 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, remove the trailing backslash
// so we don't copy one again. (C:\..\FOO would otherwise
// turn into C:\\FOO).
if (*(lpchSrc + 2) == TEXT('\\'))
{
lpchSrc++;
}
}
lpchSrc += 2; // skip ".."
}
// Everything else
else
{
// Just copy it.
lstrcpyn(lpchDst, lpchSrc, cbPC);
lpchDst += cbPC - 1;
lpchSrc += cbPC - 1;
}
// Keep everything nice and tidy.
*lpchDst = TEXT('\0');
}
// Check for weirdo root directory stuff.
NearRootFixups(lpszDst, fUNC);
return TRUE;
}
#if 0
void TestPC(LPCTSTR pszPath, LPCTSTR pszFile)
{
TCHAR szDst[MAX_PATH];
PathCombine(szDst, pszPath, pszFile);
if (pszPath == NULL)
pszPath = TEXT("(NULL)");
if (pszFile == NULL)
pszFile = TEXT("(NULL)");
DebugMsg(DM_TRACE, TEXT("PathCombine(%s, %s) -> %s"), pszPath, pszFile, szDst);
}
void TestPathCombine()
{
TestPC(TEXT("C:\\foo"), TEXT("bar"));
TestPC(TEXT("C:\\foo"), TEXT(".."));
TestPC(TEXT("C:\\foo"), TEXT("..\\.."));
TestPC(TEXT("C:\\foo"), TEXT(""));
TestPC(TEXT("C:\\foo"), NULL);
TestPC(TEXT("C:\\foo\\bar"), TEXT(".."));
TestPC(TEXT("C:\\foo\\bar"), TEXT("..\\.."));
TestPC(TEXT("C:\\foo\\bar"), TEXT("..\\..\\.."));
TestPC(TEXT("C:\\foo\\bar"), TEXT(".\foo"));
TestPC(TEXT("C:\\foo\\bar"), TEXT(".\\"));
TestPC(TEXT("C:\\foo\\bar"), TEXT("."));
TestPC(TEXT("C:\\foo\\bar"), TEXT("..."));
TestPC(TEXT("C:\\foo\\bar"), TEXT("...."));
TestPC(TEXT(""), TEXT("C:\\foo"));
TestPC(TEXT("d:\\bar"), TEXT("C:\\foo"));
TestPC(TEXT("d:\\bar"), TEXT("\\foo"));
TestPC(TEXT("d:\\bar"), TEXT("d:foo"));
}
#endif
// Modifies:
// szRoot
//
// Returns:
// TRUE if a drive root was found
// FALSE otherwise
//
BOOL PathStripToRoot(LPTSTR szRoot)
{
while(!PathIsRoot(szRoot))
{
if (!PathRemoveFileSpec(szRoot))
{
// If we didn't strip anything off,
// must be current drive
return(FALSE);
}
}
return(TRUE);
}
// concatinate lpszDir and lpszFile into a properly formed path
// and canonicalizes any relative path pieces
//
// returns:
// pointer to destination buffer
//
// lpszDest and lpszFile can be the same buffer
// lpszDest and lpszDir can be the same buffer
//
// assumes:
// lpszDest is MAX_PATH bytes
//
// History:
// 01-25-93 SatoNa Made a temporary fix for the usability test.
// ??-??-?? ChrisG hacked upon
//
LPTSTR WINAPI PathCombine(LPTSTR lpszDest, LPCTSTR lpszDir, LPNCTSTR lpszFile)
{
TCHAR szTemp[MAX_PATH];
LPTSTR pszT;
if (!lpszFile || *lpszFile==TEXT('\0')) {
ualstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp)); // lpszFile is empty
} else if (lpszDir && *lpszDir && PathIsRelative(lpszFile)) {
ualstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp));
pszT = PathAddBackslash(szTemp);
if (pszT) {
int iLen = lstrlen(szTemp);
if ((iLen + ualstrlen(lpszFile)) < ARRAYSIZE(szTemp)) {
ualstrcpy(pszT, lpszFile);
} else
return NULL;
} else
return NULL;
} else if (lpszDir && *lpszDir &&
*lpszFile == TEXT('\\') && !PathIsUNC(lpszFile)) {
ualstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp));
// BUGBUG: 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
ualstrcpyn(pszT, lpszFile+1, ARRAYSIZE(szTemp) - 1 - (pszT-szTemp));
} else
return NULL;
} else {
ualstrcpyn(szTemp, lpszFile, ARRAYSIZE(szTemp)); // already fully qualified file part
}
PathCanonicalize(lpszDest, szTemp); // this deals with .. and . stuff
return lpszDest;
}
/* Appends a filename to a path. Checks the \ problem first
* (which is why one can't just use lstrcat())
* Also don't append a \ to : so we can have drive-relative paths...
* this last bit is no longer appropriate since we qualify first!
*
* REVIEW, is this relative junk needed anymore? if not this can be
* replaced with PathAddBackslash(); lstrcat() */
BOOL WINAPI PathAppend(LPTSTR pPath, LPNCTSTR pMore)
{
/* Skip any initial terminators on input. */
#ifndef UNICODE
while (*pMore == TEXT('\\'))
pMore = CharNext(pMore);
#else
while (*pMore == TEXT('\\'))
pMore++;
#endif
return (BOOL)PathCombine(pPath, pPath, pMore);
}
#if 0
// we do not need these right now
// check to see if a given path is on the "PATH" env variable
BOOL PathOnPath(LPCTSTR pszPath)
{
TCHAR szPath[MAX_PATH];
TCHAR szFullPath[MAX_PATH];
LPCTSTR lpPath;
GetEnvironmentVariable(c_szPATH, szFullPath, ARRAYSIZE(szFullPath));
lpPath = szFullPath;
while (lpPath = NextPath(lpPath, szPath, ARRAYSIZE(szPath)))
{
if (!lstrcmpi(szPath, pszPath))
return TRUE;
}
return FALSE;
}
#endif
// given a path that potentially points to an un-extensioned program
// file, check to see if a program file exists with that name.
//
// returns: TRUE if a program with that name is found.
// (extension is added to name).
// FALSE no program file found or the path did not have an extension
BOOL LookForExtensions(LPTSTR lpszPath, LPCTSTR dirs[], BOOL bPathSearch, UINT fExt)
{
Assert(fExt); // should have some bits set
if (*PathFindExtension(lpszPath) == 0)
{
if (bPathSearch)
{
// NB Try every extension on each path component in turn to
// mimic command.com's search order.
return PathFindOnPathEx(lpszPath, dirs, fExt);
}
else
{
return PathFileExistsDefExt(lpszPath, fExt);
}
}
return FALSE;
}
//
// converts the relative or unqualified path name to the fully
// qualified path name.
//
// in:
// lpszPath path to convert
// lpszCurrentDir current directory to use
//
// PRF_TRYPROGRAMEXTENSIONS (implies PRF_VERIFYEXISTS)
// PRF_VERIFYEXISTS
//
// returns:
// TRUE the file was verifyed to exist
// FALSE the file was not verified to exist (but it may)
//
int WINAPI PathResolve(LPTSTR lpszPath, LPCTSTR dirs[], UINT fFlags)
{
UINT fExt = (fFlags & PRF_DONTFINDLNK) ? (EXT_COM | EXT_BAT | EXT_PIF | EXT_EXE) : EXT_DEFAULT;
if (PathIsRoot(lpszPath)) {
DWORD dwPathType = PathIsUNCServerShare(lpszPath);
// No sense qualifying just a server or share name...
if ((dwPathType != UNC_SERVER_ONLY) && (dwPathType != UNC_SERVER_SHARE))
{
// Be able to resolve "\" from different drives.
if (lpszPath[0] == TEXT('\\') && lpszPath[1] == TEXT('\0') )
{
PathQualifyDef(lpszPath, fFlags & PRF_FIRSTDIRDEF ? dirs[0] : NULL, 0);
}
}
if (fFlags & PRF_VERIFYEXISTS)
{
if (PathFileExists(lpszPath))
return(TRUE);
// BUGBUG: there is a better way to do this...
// If it is a UNC root, then we will see if the root exists
//
if (PathIsUNC(lpszPath))
{
// See if the network knows about this one.
// It appears like some network provider croak if not everything
// if filled in, so we might as well bloat ourself to make them happy...
NETRESOURCE nr = {RESOURCE_GLOBALNET,RESOURCETYPE_ANY,
RESOURCEDISPLAYTYPE_GENERIC, RESOURCEUSAGE_CONTAINER,
NULL, lpszPath, NULL, NULL};
HANDLE hEnum;
// This uses WNetGetResourceInformation which does a more
// complete job of verifying if a network resource exists
// than WNetOpenEnum below. But for compatability, if
// NetPathExists fails we still try the old way...
if (NetPathExists(lpszPath,NULL))
return(TRUE);
// BUGBUG - Maybe we can remove this later (talk with net guys)
// see comment above about WNetGetResourceInformation call
if (WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY,
RESOURCEUSAGE_ALL, &nr, &hEnum) == WN_SUCCESS)
{
// If it succeeded then assume it worked...
WNetCloseEnum(hEnum);
return(TRUE);
}
}
return(FALSE);
}
return TRUE;
} else if (PathIsFileSpec(lpszPath)) {
// REVIEW: look for programs before looking for paths
if ((fFlags & PRF_TRYPROGRAMEXTENSIONS) && (LookForExtensions(lpszPath, dirs, TRUE, fExt)))
return TRUE;
if (PathFindOnPath(lpszPath, dirs))
{
// PathFindOnPath() returns TRUE iff PathFileExists(lpszPath),
// so we always returns true here:
//return (!(fFlags & PRF_VERIFYEXISTS)) || PathFileExists(lpszPath);
return TRUE;
}
} else {
// If there is a trailing '.', we should not try extensions
PathQualifyDef(lpszPath, fFlags & PRF_FIRSTDIRDEF ? dirs[0] : NULL,
PQD_NOSTRIPDOTS);
if (fFlags & PRF_VERIFYEXISTS) {
if ((fFlags & PRF_TRYPROGRAMEXTENSIONS) && (LookForExtensions(lpszPath, dirs, FALSE, fExt)))
return TRUE;
if (PathFileExists(lpszPath))
return TRUE;
}
else
return TRUE;
}
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 -> ?? (test this)
// \foo -> \ (Just the slash!)
//
// in/out:
// pFile fully qualified path name
// returns:
// TRUE we stripped something
// FALSE didn't strip anything (root directory case)
//
BOOL WINAPI PathRemoveFileSpec(LPTSTR pFile)
{
LPTSTR pT;
LPTSTR pT2 = pFile;
for (pT = pT2; *pT2; pT2 = CharNext(pT2)) {
if (*pT2 == TEXT('\\'))
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)
return FALSE; // didn't strip anything
//
// handle the \foo case
//
else if ((pT == pFile) && (*pT == TEXT('\\'))) {
// 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
}
}
// 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
LPTSTR WINAPI PathAddBackslash(LPTSTR lpszPath)
{
LPTSTR lpszEnd;
// 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!
int ichPath = lstrlen(lpszPath);
if (ichPath >= (MAX_PATH - 1))
{
Assert(FALSE); // Let the caller know!
return(NULL);
}
lpszEnd = lpszPath + ichPath;
// this is really an error, caller shouldn't pass
// an empty string
if (!*lpszPath)
return lpszEnd;
/* Get the end of the source directory
*/
switch(*CharPrev(lpszPath, lpszEnd)) {
case TEXT('\\'):
break;
default:
*lpszEnd++ = TEXT('\\');
*lpszEnd = TEXT('\0');
}
return lpszEnd;
}
// 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
LPTSTR WINAPI PathFindFileName(LPCTSTR pPath)
{
LPCTSTR pT;
for (pT = pPath; *pPath; pPath = CharNext(pPath)) {
if ((pPath[0] == TEXT('\\') || pPath[0] == TEXT(':')) && pPath[1] && (pPath[1] != TEXT('\\')))
pT = pPath + 1;
}
return (LPTSTR)pT; // const -> non const
}
#ifdef UNICODE
LPSTR WINAPI PathFindFileNameA(LPCSTR pPath)
{
LPCSTR pT;
for (pT = pPath; *pPath; pPath = CharNextA(pPath)) {
if ((pPath[0] == '\\' || pPath[0] == ':') && pPath[1] && (pPath[1] != '\\'))
pT = pPath + 1;
}
return (LPSTR)pT; // const -> non const
}
#endif
// 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
//
//
BOOL PathIsFileSpec(LPCTSTR lpszPath)
{
for (; *lpszPath; lpszPath = CharNext(lpszPath)) {
if (*lpszPath == TEXT('\\') || *lpszPath == TEXT(':'))
return FALSE;
}
return TRUE;
}
// returns:
// TRUE if path starts with "\\" or "X:\\"
// the X: crap is for a bug in kernel16 where things run
// from UNC shares get a drive letter prepended
//
//BOOL IsUNC(LPCSTR lpszPath)
//{
// return DBL_BSLASH(lpszPath) || ((lpszPath[1] == ':') && DBL_BSLASH(lpszPath + 2));
//}
//---------------------------------------------------------------------------
// Returns TRUE if the given string is a UNC path.
//
// TRUE
// "\\foo\bar"
// "\\foo" <- careful
// "\\"
// FALSE
// "\foo"
// "foo"
// "c:\foo"
BOOL WINAPI PathIsUNC(LPNCTSTR pszPath)
{
return DBL_BSLASH(pszPath);
}
//---------------------------------------------------------------------------
// Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has
// a drive letter, otherwise returns -1.
//
int WINAPI PathGetDriveNumber(LPNCTSTR 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"
BOOL WINAPI PathIsRelative(LPNCTSTR lpszPath)
{
// The NULL path is assumed relative
if (*lpszPath == 0)
return TRUE;
// Does it begin with a slash ?
if (lpszPath[0] == TEXT('\\'))
return FALSE;
// Does it begin with a drive and a colon ?
else if (!IsDBCSLeadByte(lpszPath[0]) && lpszPath[1] == TEXT(':'))
return FALSE;
// Probably relative.
else
return TRUE;
}
// remove the path part from a fully qualified spec
//
// c:\foo\bar -> bar
// c:\foo -> foo
// c:\ -> c:\ and the like
//
void PathStripPath(LPTSTR pszPath)
{
LPTSTR pszName = PathFindFileName(pszPath);
if (pszName != pszPath)
lstrcpy(pszPath, pszName);
}
// replaces forward slashes with backslashes
// and removes trailing colons from device names
// excluding drive
// removes trailing colon if not a drive letter.
// this is to support DOS character devices (CON:, COM1: LPT1:). DOS
// can't deal with these things having a colon on the end (so we strip it).
void FixSlashesAndColon(LPTSTR lpPath)
{
LPTSTR lpLast;
int cbPath;
// walk the entire path string, keep track of last
// char in the path
for (cbPath = 0; *lpPath; lpPath = CharNext(lpPath)) {
lpLast = lpPath;
if (*lpPath == TEXT('/'))
*lpPath = TEXT('\\');
if (IsDBCSLeadByte(*lpPath)) {
lpPath += 2;
cbPath += 2;
}
else {
lpPath++;
cbPath++;
}
}
// if not a drive letter "C:" nuke the colon
if (cbPath > 2 && *lpLast == TEXT(':'))
*lpLast = 0;
}
BOOL IsValidChar(TUCHAR ch, BOOL fPath)
{
switch (ch) {
case TEXT(';'): // terminator
case TEXT(','): // terminator
case TEXT('|'): // pipe
case TEXT('>'): // redir
case TEXT('<'): // redir
case TEXT('"'): // quote
return FALSE;
case TEXT('?'): // wc we only do wilds here because they're
case TEXT('*'): // wc legal for pathqualify
case TEXT('\\'): // path separator
case TEXT(':'): // drive colon
case TEXT('/'): // path sep
return fPath;
}
// cannot be a control character or space
return ch > TEXT(' ');
}
// Return the index to the drive that contains the windows directory.
int GetWindowsDrive(void)
{
TCHAR szWin[MAX_PATH];
GetWindowsDirectory(szWin, ARRAYSIZE(szWin));
Assert(!PathIsRelative(szWin));
Assert(!IsRemovableDrive(DRIVEID(szWin)));
return PathGetDriveNumber(szWin);
}
// qualify a DOS (or LFN) file name based on the currently active window.
// this code is not careful to not write more than MAX_PATH characters
// into psz
//
// in:
// psz path to be qualified of at least MAX_PATH characters
// ANSI string
//
// out:
// psz fully qualified version of input string based
// on the current active window (current directory)
//
void PathQualifyDef(LPTSTR psz, LPCTSTR szDefDir, DWORD dwFlags)
{
int cb, nSpaceLeft;
TCHAR szTemp[MAX_PATH];
int iDrive;
LPTSTR pOrig, pFileName;
BOOL flfn = FALSE;
BOOL fDriveSpecified = FALSE;
LPTSTR pszSlash;
LPTSTR pExt;
int nOldSpaceLeft;
// DebugMsg(DM_TRACE, "pqd: In: %s", psz);
/* Save it away. */
lstrcpyn(szTemp, psz, ARRAYSIZE(szTemp));
FixSlashesAndColon(szTemp);
nSpaceLeft = ARRAYSIZE(szTemp);
pOrig = szTemp;
pFileName = PathFindFileName(szTemp);
if (pOrig[0] == TEXT('\\') && pOrig[1] == TEXT('\\'))
{
// leave the \\ in thebuffer so that the various parts
// of the UNC path will be qualified and appended. Note
// we must assume that UNCs are LFN's, since computernames
// and sharenames can be longer than 11 characters.
flfn = IsLFNDrive(pOrig);
if (flfn)
{
psz[2] = 0;
nSpaceLeft -= 3;
pOrig+=2;
goto GetComps;
}
else
{
// NB UNC doesn't support LFN's but we don't want to truncate
// \\foo or \\foo\bar so skip them here.
// Is it a \\foo\bar\fred thing?
pszSlash = StrChr(psz+2, TEXT('\\'));
if (pszSlash && (NULL != (pszSlash = StrChr(pszSlash+1, TEXT('\\')))))
{
// Yep - skip the first bits but mush the rest.
*(pszSlash+1) = TEXT('\0');
nSpaceLeft -= (pszSlash-psz)+1;
pOrig += pszSlash-psz;
goto GetComps;
}
else
{
// Nope - just pretend it's an LFN and leave it alone.
flfn = TRUE;
psz[2] = 0;
nSpaceLeft -= 2;
pOrig+=2;
goto GetComps;
}
}
}
if (pOrig[0] && pOrig[1] == TEXT(':') && !IsDBCSLeadByte(pOrig[0]))
{
iDrive = DRIVEID(pOrig);
flfn = IsLFNDrive(pOrig);
fDriveSpecified = TRUE;
/* Skip over the drive letter. */
pOrig += 2;
}
else
{
iDrive = szDefDir ? PathGetDriveNumber(szDefDir) : GetWindowsDrive(); // GetDefaultDrive();
flfn = IsLFNDrive(szDefDir); // can be NULL
}
// REVIEW, do we really need to do different stuff on LFN names here?
// on FAT devices, replace any illegal chars with underscores
if (!flfn)
{
LPTSTR pT;
for (pT = pOrig; *pT; pT = CharNext(pT))
{
if (!IsValidChar(*pT, TRUE))
*pT = TEXT('_');
}
}
if (pOrig[0] == TEXT('\\'))
{
PathBuildRoot(psz, iDrive);
nSpaceLeft -= 4;
pOrig++;
}
else
{
// NB We don't do that default drive stuff anymore. If the
// user passes in a drive relative path we assume they just
// meant to use the root.
if (szDefDir && !fDriveSpecified)
{
lstrcpy(psz, szDefDir);
}
else
{
PathBuildRoot(psz, iDrive);
}
nSpaceLeft -= (lstrlen(psz) + 1);
}
GetComps:
while (*pOrig && nSpaceLeft > 0)
{
/* If the component 0is parent dir, go up one dir.
* If its the current dir, skip it, else add it normally */
if (pOrig[0] == TEXT('.'))
{
if (pOrig[1] == TEXT('.') && (!pOrig[2] || pOrig[2] == TEXT('\\')))
PathRemoveFileSpec(psz);
else if (pOrig[1] && pOrig[1] != TEXT('\\'))
goto addcomponent;
while (*pOrig && *pOrig != TEXT('\\'))
pOrig = CharNext(pOrig);
if (*pOrig)
pOrig++;
}
else
{
LPTSTR pT, pTT = NULL;
addcomponent:
PathAddBackslash(psz);
nSpaceLeft--;
pT = psz + lstrlen(psz);
if (flfn)
{
// copy the component
while (*pOrig && *pOrig != TEXT('\\') && nSpaceLeft>0)
{
nSpaceLeft--;
if (IsDBCSLeadByte(*pOrig))
{
if (nSpaceLeft <= 0)
{
// Copy nothing more
continue;
}
nSpaceLeft--;
*pT++ = *pOrig++;
}
*pT++ = *pOrig++;
}
}
else
{
// copy the filename (up to 8 chars)
for (cb = 8; *pOrig && !IsPathSep(*pOrig) && *pOrig != TEXT('.') && nSpaceLeft > 0;)
{
if (cb > 0)
{
cb--;
nSpaceLeft--;
if (IsDBCSLeadByte(*pOrig))
{
if (nSpaceLeft<=0 || cb<=0)
{
// Copy nothing more
cb = 0;
continue;
}
cb--;
nSpaceLeft--;
*pT++ = *pOrig++;
}
*pT++ = *pOrig++;
}
else
{
pOrig = CharNext(pOrig);
}
}
// if there's an extension, copy it, up to 3 chars
if (*pOrig == TEXT('.') && nSpaceLeft > 0)
{
*pT++ = TEXT('.');
nSpaceLeft--;
pOrig++;
pExt = pT;
nOldSpaceLeft = nSpaceLeft;
for (cb = 3; *pOrig && *pOrig != TEXT('\\') && nSpaceLeft > 0;)
{
if (*pOrig == TEXT('.'))
{
// Another extension, start again.
cb = 3;
pT = pExt;
nSpaceLeft = nOldSpaceLeft;
pOrig++;
}
if (cb > 0)
{
cb--;
nSpaceLeft--;
if (IsDBCSLeadByte(*pOrig))
{
if (nSpaceLeft<=0 || cb<=0)
{
// Copy nothing more
cb = 0;
continue;
}
cb--;
nSpaceLeft--;
*pT++ = *pOrig++;
}
*pT++ = *pOrig++;
}
else
{
pOrig = CharNext(pOrig);
}
}
}
}
// skip the backslash
if (*pOrig)
pOrig++;
// null terminate for next pass...
*pT = 0;
}
}
PathRemoveBackslash(psz);
if (!(dwFlags & PQD_NOSTRIPDOTS))
{
// remove any trailing dots
LPTSTR pszPrev = CharPrev(psz, psz + lstrlen(psz));
if (*pszPrev == TEXT('.'))
{
*pszPrev = TEXT('\0');
}
}
// DebugMsg(DM_TRACE, "pqd: Out: %s", psz);
}
void WINAPI PathQualify(LPTSTR psz)
{
PathQualifyDef(psz, NULL, 0);
}
const TCHAR c_szColonSlash[] = TEXT(":\\");
// check if a path is a root
//
// returns:
// TRUE for "\" "X:\" "\\foo\asdf" "\\foo\"
// FALSE for others
BOOL WINAPI PathIsRoot(LPCTSTR pPath)
{
if (!IsDBCSLeadByte(*pPath))
{
if (!lstrcmpi(pPath + 1, c_szColonSlash)) // "X:\" case
return TRUE;
}
if ((*pPath == TEXT('\\')) && (*(pPath + 1) == 0)) // "\" case
return TRUE;
if (DBL_BSLASH(pPath)) // smells like UNC name
{
LPCTSTR p;
int cBackslashes = 0;
for (p = pPath + 2; *p; p = CharNext(p)) {
if (*p == TEXT('\\') && (++cBackslashes > 1))
return FALSE; /* not a bare UNC name, therefore not a root dir */
}
return TRUE; /* end of string with only 1 more backslash */
/* must be a bare UNC, which looks like a root dir */
}
return FALSE;
}
// returns:
// TRUE if pPath is a directory, including the root
// FALSE not a dir
BOOL WINAPI PathIsDirectory(LPCTSTR pszPath)
{
DWORD dwAttribs;
DWORD dwPathType = PathIsUNCServerShare(pszPath);
if (dwPathType == UNC_SERVER_ONLY)
{
// Be Win95 compatible in our error code
SetLastError( ERROR_BAD_PATHNAME );
}
else
{
dwAttribs = GetFileAttributes(pszPath);
if (dwAttribs != (DWORD)-1)
return (BOOL)(dwAttribs & FILE_ATTRIBUTE_DIRECTORY);
}
return FALSE;
}
BOOL OnExtList(LPNCTSTR pszExtList, LPNCTSTR pszExt)
{
for (; *pszExtList; pszExtList += ualstrlen(pszExtList) + 1)
{
if (!ualstrcmpi(pszExt, pszExtList))
{
return TRUE; // yes
}
}
return FALSE;
}
#ifdef WINNT
// Character offset where binary exe extensions begin in above
#define BINARY_EXE_OFFSET 15
#define EXT_TABLE_SIZE 26 // Understand line above before changing
static const TCHAR achExes[EXT_TABLE_SIZE] = TEXT(".cmd\0.bat\0.pif\0.exe\0.com\0");
#else
// Character offset where binary exe extensions begin in above
#define BINARY_EXE_OFFSET 10
#define EXT_TABLE_SIZE 21 // Understand line above before changing
static const TCHAR achExes[EXT_TABLE_SIZE] = TEXT(".bat\0.pif\0.exe\0.com\0");
#endif
BOOL WINAPI PathIsBinaryExe(LPCTSTR szFile)
{
Assert(BINARY_EXE_OFFSET < ARRAYSIZE(achExes) &&
EXT_TABLE_SIZE == ARRAYSIZE(achExes) &&
achExes[BINARY_EXE_OFFSET]==TEXT('.'));
return OnExtList(achExes+BINARY_EXE_OFFSET, PathFindExtension(szFile));
}
// determine if a path is a program by looking at the extension
//
BOOL WINAPI PathIsExe(LPCTSTR szFile)
{
LPCTSTR temp = PathFindExtension(szFile);
return OnExtList((LPCTSTR) achExes, temp);
}
BOOL PathIsLink(LPCTSTR szFile)
{
return lstrcmpi(c_szDotLnk, PathFindExtension(szFile)) == 0;
}
// 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
BOOL WINAPI PathCompactPath(HDC hDC, LPTSTR lpszPath, UINT dx)
{
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];
BOOL bRet = TRUE;
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. */
lstrcpyn(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;
// BUGBUG: 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) {
lstrcpy(lpszPath, c_szEllipses);
GetTextExtentPoint(hDC, lpFixed, 3 + lpszPath - lpFixed, &sz);
if (sz.cx <= (int)dx)
break;
lpszPath--;
}
} else {
while (TRUE) {
GetTextExtentPoint(hDC, lpszPath, lpEnd - lpszPath, &sz);
len = dxFixed + sz.cx;
if (bEllipsesIn)
len += dxEllipses;
if (len <= (int)dx)
break;
bEllipsesIn = TRUE;
if (lpEnd <= lpszPath) {
/* Things didn't fit. */
lstrcpy(lpszPath, c_szEllipses);
lstrcat(lpszPath, szTemp);
bRet = FALSE;
goto Exit;
}
/* Step back a character. */
lpEnd = CharPrev(lpszPath, lpEnd);
}
if (bEllipsesIn) {
lstrcpy(lpEnd, c_szEllipses);
lstrcat(lpEnd, szTemp);
}
}
Exit:
if (hdcGet)
ReleaseDC(NULL, hdcGet);
return bRet;
}
// 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
//
void WINAPI 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;
lstrcpy(szPath, pszPath);
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);
}
/*** FIX30: This "could use some cleaning up." ***/
//
// REVIEW, note that the above comment comes from the win3.0 code... haha
//
/* Used to generate destination filenames given a pattern and an original
* source name. ? is replaced by the corresponding character in the source,
* and * is replaced by the remainder of the source name.
*
* pPath path with wildcards to be expanded
* pName mask used to expand pName
*
* DBCS by 07/21/90 - Yukinin
*
*/
// REVIEW This will need re-working for Win32 eg how does "foo.bar.fred" merge with "*.*"?
// It's been hacked up to allow LFN's of the form x.y to work.
// Added a test to make sure we don't blow MAX_PATH...
BOOL PathMergePathName(LPTSTR pPath, LPCTSTR pName)
{
int i, j;
int cch;
LPTSTR pWild, p2, pEnd;
BOOL bNoDir = FALSE;
TCHAR szWildPart[MAX_PATH];
// if there are no wild cards the destination path does not need merging.
if (!IsWild(pPath))
return TRUE;
// copy only x.y... this part may not be fully qualified for rename
pWild = PathFindFileName(pPath);
// Special cases, "*.*" is the same as "*" which mean just copy everything.
if ((lstrcmp(pWild, c_szStarDotStar)==0) || (lstrcmp(pWild, c_szStar)==0))
{
if (((int)(pWild-pPath) + lstrlen(pName)) >= (MAX_PATH-1))
return FALSE; // Does not fit!
lstrcpy(pWild, pName);
return TRUE;
}
//
// BUGBUG:: This part does not check for overflowing the buffer.
// Fix this later. Note: For the most part we probably never get to
// here.
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE)))
for (p2=szWildPart,i=0; *pWild && *pWild != TEXT('.') && i<MAX_PATH; i++, pWild++, p2++) {
*p2 = *pWild;
if (IsDBCSLeadByte(*pWild)) {
if (i == 7)
break;
*(++p2) = *(++pWild);
i++;
}
}
while (*pWild && *pWild != TEXT('.'))
pWild = CharNext(pWild);
if (*pWild == TEXT('.')) {
*p2++ = TEXT('.');
pWild++;
for (j=0; *pWild && j < MAX_PATH-i; j++, pWild++, p2++) {
*p2 = *pWild;
if (IsDBCSLeadByte( *pWild )) {
if (j == 2)
break;
*(++p2) = *(++pWild);
j++;
}
}
}
*p2 = 0;
#else
// limit the mask to x.y form
for (p2 = szWildPart, i = 0; *pWild && *pWild != TEXT('.') && i < MAX_PATH; i++, pWild++, p2++)
*p2 = *pWild;
while (*pWild && *pWild != TEXT('.')) // skip extra after 8
pWild++;
if (*pWild == TEXT('.')) { // extension?
// do dot ext part
*p2++ = TEXT('.');
pWild++;
for (j = 0; *pWild && j < MAX_PATH-i; j++, pWild++, p2++)
*p2 = *pWild;
}
*p2 = 0;
#endif
// szWildPart now has the x.y form of the wildcard mask
PathRemoveFileSpec(pPath);
PathAddBackslash(pPath);
for (pEnd = pPath; *pEnd; pEnd++); // point to end of string
pWild = szWildPart;
cch = MAX_PATH;
merge:
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE))) // 07/21/90 - Yukinin
for (i=0; i < cch; i+=(IsDBCSLeadByte(*pWild)?2:1), pWild=CharNext(pWild)) {
#else
for (i=0; i < cch; i++, pWild++) {
#endif
switch (*pWild) {
case TEXT('\0'):
case TEXT(' '):
case TEXT('.'):
break;
case TEXT('*'):
pWild--;
/*** FALL THRU ***/
case TEXT('?'):
if (*pName && *pName!=TEXT('.'))
*pEnd++ = *pName++;
continue;
default:
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE)))
*pEnd++ = *pWild;
if (IsDBCSLeadByte(*pWild)) {
*pEnd++ = pWild[1];
if (*pName && *pName != TEXT('.'))
pName++;
}
#else
*pEnd++ = *pWild;
if (*pName && *pName != TEXT('.'))
pName++;
#endif
continue;
}
break;
}
while (*pName && *pName != TEXT('.'))
pName = CharNext(pName);
if (*pName)
pName++;
while (*pWild && *pWild != TEXT('.'))
pWild = CharNext(pWild);
if (*pWild)
pWild++;
if (*pWild) {
*pEnd++ = TEXT('.');
cch = MAX_PATH;
goto merge; // do it for the extension part now
} else {
if (pEnd[-1]==TEXT('.'))
pEnd[-1]=0;
else
pEnd[0] = TEXT('\0');
}
// Assume this part merged in without overflowing our buffer.
return(TRUE);
}
/* Checks to see if a file spec is an evil character device or if it is
* too long... */
BOOL IsInvalidPath(LPCTSTR pPath)
{
TCHAR sz[9];
int n = 0;
// REVIEW, this is the list of dos devices that will cause problems if
// we try to move/copy/delete/rename. are there more? (ask aaronr/jeffpar)
static const LPTSTR aDevices[] = {
TEXT("CON"),
TEXT("MS$MOUSE"),
TEXT("EMMXXXX0"),
TEXT("CLOCK$")
};
// BUGBUG, this should check for invalid chars in the path
if (lstrlen(pPath) >= MAX_PATH-1)
return TRUE;
pPath = PathFindFileName(pPath);
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE)))
while (*pPath && *pPath != TEXT('.') && *pPath != TEXT(':') && n < 8) {
if (IsDBCSLeadByte( *pPath )) {
if (n == 7)
break;
sz[n++] = *pPath;
}
sz[n++] = *pPath++;
}
#else
while (*pPath && *pPath != TEXT('.') && *pPath != TEXT(':') && n < 8)
sz[n++] = *pPath++;
#endif
sz[n] = TEXT('\0');
for (n = 0; n < ARRAYSIZE(aDevices); n++) {
if (!lstrcmpi(sz, aDevices[n])) {
return TRUE;
}
}
return FALSE;
}
//
// Funciton: PathMakeUniqueName
//
// Parameters:
// pszUniqueName -- Specify the buffer where the unique name should be copied
// cchMax -- Specify the size of the buffer
// pszTemplate -- Specify the base name
// pszLongPlate -- Specify the base name for a LFN drive. format below
// pszDir -- Specify the directory
//
// History:
// 03-11-93 SatoNa Created
//
// REVIEW:
// For long names, we should be able to generate more user friendly name
// such as "Copy of MyDocument" of "Link #2 to MyDocument". In this case,
// we need additional flags which indicates if it is copy, or link.
//
// Format:
// pszLongPlate will search for the first ( and then finds the matching )
// to look for a number:
// given: Copy () of my doc gives: Copy (_number_) of my doc
// given: Copy (1023) of my doc gives: Copy (_number_) of my doc
// BUGBUG: if making n unique names, the time grows n^2 because it always
// starts from 0 and checks for existing file.
BOOL WINAPI PathMakeUniqueNameEx(LPTSTR pszUniqueName,
UINT cchMax,
LPCTSTR pszTemplate,
LPCTSTR pszLongPlate,
LPCTSTR pszDir,
int iMinLong)
{
BOOL fSuccess=FALSE;
LPTSTR lpszFormat = pszUniqueName; // use their buffer instead of creating our own
LPTSTR pszName, pszDigit;
LPCTSTR pszRest;
LPCTSTR pszEndUniq; // End of Uniq sequence part...
LPCTSTR pszStem;
int cchRest, cchStem, cchDir, cchMaxName;
int iMax, iMin, i;
TCHAR achFullPath[MAX_PATH];
if (pszLongPlate == NULL)
pszLongPlate = pszTemplate;
// this if/else set up lpszFormat and all the other pointers for the
// sprintf/file_exists loop below
iMin = iMinLong;
if (pszLongPlate && IsLFNDrive(pszDir)) {
cchMaxName = 0;
// for long name drives
pszStem = pszLongPlate;
pszRest = StrChr(pszLongPlate, TEXT('('));
while (pszRest)
{
// First validate that this is the right one
pszEndUniq = CharNext(pszRest);
while (*pszEndUniq && *pszEndUniq >= TEXT('0') && *pszEndUniq <= TEXT('9')) {
pszEndUniq++;
}
if (*pszEndUniq == TEXT(')'))
break; // We have the right one!
pszRest = StrChr(CharNext(pszRest), TEXT('('));
}
// if no (, punt to short name
if (!pszRest) {
// if no (, then tack it on at the end. (but before the extension)
// eg. New Link yields New Link (1)
pszRest = PathFindExtension(pszLongPlate);
cchStem = pszRest - pszLongPlate;
wsprintf(lpszFormat, TEXT(" (%%d)%s"), pszRest ? pszRest : c_szNULL);
iMax = 100;
} else {
pszRest++; // stop over the #
cchStem = pszRest - pszLongPlate;
while (*pszRest && *pszRest >= TEXT('0') && *pszRest <= TEXT('9')) {
pszRest++;
}
// how much room do we have to play?
switch(cchMax - cchStem - lstrlen(pszRest)) {
case 0:
// no room, bail to short name
return PathMakeUniqueName(pszUniqueName, cchMax, pszTemplate, NULL, pszDir);
case 1:
iMax = 10;
break;
case 2:
iMax = 100;
break;
default:
iMax = 1000;
break;
}
// we are guaranteed enough room because we don't include
// the stuff before the # in this format
wsprintf(lpszFormat, TEXT("%%d%s"), pszRest);
}
} else {
// for short name drives
pszStem = pszTemplate;
pszRest = PathFindExtension(pszTemplate);
cchRest=lstrlen(pszRest)+1; // 5 for ".foo";
if (cchRest<5)
cchRest=5;
cchStem=pszRest-pszTemplate; // 8 for "fooobarr.foo"
cchDir=lstrlen(pszDir);
cchMaxName = 8+cchRest-1;
//
// Remove all the digit characters from the stem
//
for (;cchStem>1; cchStem--)
{
TCHAR ch;
LPCTSTR pszPrev = CharPrev(pszTemplate, pszTemplate + cchStem);
// Don't remove if it is a DBCS character
if (pszPrev != pszTemplate+cchStem-1)
break;
// Don't remove it it is not a digit
ch=pszPrev[0];
if (ch<TEXT('0') || ch>TEXT('9'))
break;
}
//
// Truncate characters from the stem, if it does not fit.
// In the case were LFNs are supported we use the cchMax that was passed in
// but for Non LFN drives we use the 8.3 rule.
//
if ((UINT)cchStem > (8-1)) {
cchStem=8-1; // Needs to fit into the 8 part of the name
}
//
// We should have at least one character in the stem.
//
if (cchStem < 1 || (cchDir+cchStem+1+cchRest+1) > MAX_PATH)
{
goto Error;
}
wsprintf(lpszFormat, TEXT("%%d%s"), pszRest);
iMax = 1000; iMin = 1;
}
if (pszDir)
{
lstrcpy(achFullPath, pszDir);
PathAddBackslash(achFullPath);
}
else
{
achFullPath[0] = 0;
}
pszName=achFullPath+lstrlen(achFullPath);
lstrcpyn(pszName, pszStem, cchStem+1);
pszDigit = pszName + cchStem;
for (i = iMin; i < iMax ; i++) {
wsprintf(pszDigit, lpszFormat, i);
//
// if we have a limit on the length of the name (ie on a non-LFN drive)
// backup the pszDigit pointer when i wraps from 9to10 and 99to100 etc
//
if (cchMaxName && lstrlen(pszName) > cchMaxName)
{
pszDigit = CharPrev(pszName, pszDigit);
wsprintf(pszDigit, lpszFormat, i);
}
#ifdef SN_TRACE
DebugMsg(DM_TRACE, TEXT("path.c MakeUniquePath: trying %s"), (LPCTSTR)achFullPath);
#endif
//
// Check if this name is unique or not.
//
if (!PathFileExists(achFullPath))
{
lstrcpyn(pszUniqueName, pszName, cchMax);
fSuccess=TRUE;
break;
}
}
Error:
return fSuccess;
}
BOOL WINAPI PathMakeUniqueName(LPTSTR pszUniqueName,
UINT cchMax,
LPCTSTR pszTemplate,
LPCTSTR pszLongPlate,
LPCTSTR pszDir)
{
return PathMakeUniqueNameEx(pszUniqueName, cchMax, pszTemplate, pszLongPlate, pszDir, 1);
}
// in:
// pszPath directory to do this into or full dest path
// if pszShort is NULL
// pszShort file name (short version) if NULL assumes
// pszPath is both path and spec
// pszFileSpec file name (long version)
//
// out:
// pszUniqueName
//
// returns:
// TRUE success, name can be used
BOOL WINAPI PathYetAnotherMakeUniqueName(LPTSTR pszUniqueName,
LPCTSTR pszPath,
LPCTSTR pszShort,
LPCTSTR pszFileSpec)
{
BOOL fRet = FALSE;
TCHAR szTemp[MAX_PATH];
TCHAR szPath[MAX_PATH];
if (pszShort == NULL) {
pszShort = PathFindFileName(pszPath);
lstrcpy(szPath, pszPath);
PathRemoveFileSpec(szPath);
pszPath = szPath;
}
if (pszFileSpec == NULL) {
pszFileSpec = pszShort;
}
if (IsLFNDrive(pszPath)) {
LPTSTR lpsz;
LPTSTR lpszNew;
if ((lstrlen(pszPath) + lstrlen(pszFileSpec) + 5 ) > MAX_PATH)
return FALSE;
// try it without the ( if there's a space after it
lpsz = StrChr(pszFileSpec, TEXT('('));
while (lpsz)
{
if (*(CharNext(lpsz)) == TEXT(')'))
break;
lpsz = StrChr(CharNext(lpsz), TEXT('('));
}
if (lpsz) {
// We have the (). See if we have either x () y or x ().y in which case
// we probably want to get rid of one of the blanks...
int ichSkip = 2;
LPTSTR lpszT = CharPrev(pszFileSpec, lpsz);
if (*lpszT == TEXT(' '))
{
ichSkip = 3;
lpsz = lpszT;
}
lstrcpy(szTemp, pszPath);
lpszNew = PathAddBackslash(szTemp);
lstrcpy(lpszNew, pszFileSpec);
lpszNew += (lpsz - pszFileSpec);
lstrcpy(lpszNew, lpsz + ichSkip);
fRet = !PathFileExists(szTemp);
} else {
PathCombine(szTemp, pszPath, pszFileSpec);
fRet = !PathFileExists(szTemp);
}
}
else {
Assert(lstrlen(PathFindExtension(pszShort)) <= 4);
lstrcpy(szTemp,pszShort);
PathRemoveExtension(szTemp);
if (lstrlen(szTemp) <= 8) {
PathCombine(szTemp, pszPath, pszShort);
fRet = !PathFileExists(szTemp);
}
}
if (!fRet) {
fRet = PathMakeUniqueNameEx(szTemp, ARRAYSIZE(szTemp), pszShort, pszFileSpec, pszPath, 2);
PathCombine(szTemp, pszPath, szTemp);
}
if (fRet)
lstrcpy(pszUniqueName, szTemp);
return fRet;
}
void WINAPI PathGetShortPath(LPTSTR pszLongPath)
{
TCHAR szShortPath[MAX_PATH];
if (GetShortPathName(pszLongPath, szShortPath, ARRAYSIZE(szShortPath)))
lstrcpy(pszLongPath, szShortPath);
}
#if 0
BOOL WINAPI PathGetLongName(LPCTSTR lpszShortName, LPTSTR lpszLongName, UINT cbLongName)
{
WIN32_FIND_DATA fd;
HANDLE hff;
LPTSTR lpszFileName;
UINT cbLeft;
// This if block fixes #14503, a self-host stopper for build 76
if (PathIsRoot(lpszShortName))
return FALSE;
hff = FindFirstFile(lpszShortName, &fd);
if (hff != INVALID_HANDLE_VALUE)
{
FindClose(hff);
lstrcpyn(lpszLongName, lpszShortName, cbLongName);
lpszFileName = PathFindFileName(lpszLongName);
cbLeft = cbLongName - (lpszFileName-lpszLongName);
lstrcpyn(lpszFileName, fd.cFileName, cbLeft);
return TRUE;
}
return FALSE;
}
#endif // 0
//----------------------------------------------------------------------------
// If a path is contained in quotes then remove them.
void WINAPI PathUnquoteSpaces(LPTSTR lpsz)
{
int cch;
cch = lstrlen(lpsz);
// Are the first and last chars quotes?
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.
void WINAPI PathQuoteSpaces(LPTSTR lpsz)
{
int cch;
if (StrChr(lpsz, TEXT(' ')))
{
// NB - Use hmemcpy coz it supports overlapps.
cch = lstrlen(lpsz)+1;
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.
LPTSTR PathFindNextComponent(LPCTSTR 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;
}
}
}
//
// Match a DOS wild card spec against a dos file name
// Both strings must be ANSI.
//
// History:
// 01-25-94 SatoNa Moved from search.c
//
BOOL WINAPI PathMatchSpec(LPCTSTR pszFileParam, LPCTSTR pszSpec)
{
// Special case empty string, "*", and "*.*"...
//
if (*pszSpec == 0)
return TRUE;
do
{
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=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 = 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 (PathMatchSpec(pszFile, pszSpec))
return(TRUE);
}
else
pszFile = CharNext(pszFile);
}
goto NoMatch; // 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 (PathMatchSpec(pszFile, pszSpec))
return(TRUE);
pszFile = CharNext(pszFile);
}
goto NoMatch; // No item found so go to next pattern
}
default:
if (CharUpper((LPTSTR)(DWORD)(TUCHAR)*pszSpec) ==
CharUpper((LPTSTR)(DWORD)(TUCHAR)*pszFile))
{
if (IsDBCSLeadByte(*pszSpec))
{
#if (defined(DBCS) || (defined(FE_SB) && !defined(UNICODE)))
// 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)
goto NoMatch;
#endif
pszFile++;
pszSpec++;
if (*pszFile != *pszSpec)
goto NoMatch;
}
pszFile++;
pszSpec++;
}
else
{
goto NoMatch;
}
}
}
// 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;
}
// Skip to the end of the path spec...
//
NoMatch:
while (*pszSpec && *pszSpec != TEXT(';'))
pszSpec = CharNext(pszSpec);
// If we have more specs, keep looping...
//
} while (*pszSpec++ == TEXT(';'));
return FALSE;
}
//
// REVIEW: I haven't actually exported this
//
LPTSTR WINAPI PathSkipRoot(LPCTSTR 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
//
BOOL WINAPI PathIsSameRoot(LPCTSTR pszPath1, LPCTSTR pszPath2)
{
LPTSTR pszAfterRoot = PathSkipRoot(pszPath1);
int nLen = PathCommonPrefix(pszPath1, pszPath2, NULL);
// Add 1 to account for the '\\'
return pszAfterRoot && (pszAfterRoot - pszPath1) <= (nLen + 1);
}
//
// PathIsSlow() - is a path slow or not
//
BOOL PathIsSlow(LPCTSTR szFile)
{
if (PathIsUNC(szFile))
{
DWORD speed = GetPathSpeed(szFile);
return speed != 0 && speed <= SPEED_SLOW;
}
else
{
return DriveIsSlow(PathGetDriveNumber(szFile));
}
}
BOOL PathIsDotOrDotDot(LPTSTR pszDir)
{
return ((pszDir[0] == TEXT('.')) &&
((pszDir[1] == TEXT('\0')) ||
((pszDir[1] == TEXT('.')) &&
(pszDir[2] == TEXT('\0')))));
}
// PathParseIconLocation
// in/out:
// pszIconFile location string
// "progman.exe,3" -> "progman.exe"
//
// returns:
// icon index (zero based) ready for ExtractIcon
//
int WINAPI PathParseIconLocation(LPTSTR pszIconFile)
{
int iIndex = 0;
LPTSTR pszComma = StrChr(pszIconFile, TEXT(','));
if (SELECTOROF(pszComma)) {
*pszComma++ = 0; // terminate the icon file name.
while (*pszComma == TEXT(' ')) *pszComma++; // skip spaces
iIndex = StrToInt(pszComma);
}
PathRemoveBlanks(pszIconFile);
return iIndex;
}
/*----------------------------------------------------------------------------
/ PathProcessCommand implementation
/ ------------------
/ Purpose:
/ Process the specified command line and generate a suitably quoted
/ name, with arguments attached if required.
/
/ Notes:
/ - The destination buffer size can be determined if NULL is passed as a
/ destination pointer.
/ - If the source string is quoted then we assume that it exists on the
/ filing system.
/
/ In:
/ lpSrc -> null terminate source path
/ lpDest -> destination buffer / = NULL to return buffer size
/ iMax = maximum number of characters to return into destination
/ dwFlags =
/ PPCF_ADDQUOTES = 1 => if path requires quotes then add tehm
/ PPCF_ADDARGUMENTS = 1 => append trailing arguments to resulting string (forces ADDQUOTES)
/ PPCF_NODIRECTORIES = 1 => don't match against directories, only file objects
/ PPCF_NORELATIVEQUALIFY = 1 => locate relative objects and return the full qualified path
/ Out:
/ > 0 if the call works
/ < 0 if the call fails (object not found, buffer too small for resulting string)
/----------------------------------------------------------------------------*/
LONG WINAPI PathProcessCommand( LPCTSTR lpSrc, LPTSTR lpDest, int iDestMax, DWORD dwFlags )
{
TCHAR szName[MAX_PATH];
LPTSTR lpBuffer, lpBuffer2;
LPCTSTR lpArgs = NULL;
DWORD dwAttrib;
LONG i, iTotal;
LONG iResult = -1;
BOOL bAddQuotes = FALSE;
BOOL bQualify = FALSE;
BOOL bFound = FALSE;
BOOL bHitSpace = FALSE;
// Process the given source string, attempting to find what is that path, and what is its
// arguments.
if ( lpSrc )
{
// Extract the sub string, if its is realative then resolve (if required).
if ( *lpSrc == TEXT('\"') )
{
for ( lpSrc++, i=0 ; i<MAX_PATH && *lpSrc && *lpSrc!=TEXT('\"') ; i++, lpSrc++ )
szName[i] = *lpSrc;
szName[i] = TEXT('\0');
if ( *lpSrc )
lpArgs = lpSrc+1;
if ( ((dwFlags & PPCF_FORCEQUALIFY) || PathIsRelative( szName ))
&& !( dwFlags & PPCF_NORELATIVEOBJECTQUALIFY ) )
{
if ( !PathResolve( szName, NULL, PRF_TRYPROGRAMEXTENSIONS ) )
goto exit_gracefully;
}
bFound = TRUE;
}
else
{
// Is this a relative object, and then take each element upto a seperator
// and see if we hit an file system object. If not then we can
bQualify = PathIsRelative( lpSrc ) || ((dwFlags & PPCF_FORCEQUALIFY) != 0);
for ( i=0; i < MAX_PATH ; i++ )
{
szName[i] = lpSrc[i];
// If we hit a space then the string either contains a LFN or we have
// some arguments. Therefore attempt to get the attributes for the string
// we have so far, if we are unable to then we can continue
// checking, if we hit then we know that the object exists and the
// trailing string are its arguments.
if ( !szName[i] || szName[i] == TEXT(' ') )
{
szName[i] = TEXT('\0');
while ( TRUE )
{
if ( bQualify && !PathResolve( szName, NULL, PRF_TRYPROGRAMEXTENSIONS ) )
break;
dwAttrib = GetFileAttributes( szName );
if ( dwAttrib == -1 || ( ( dwAttrib & FILE_ATTRIBUTE_DIRECTORY ) && dwFlags & PPCF_NODIRECTORIES ) )
break;
if ( bQualify && ( dwFlags & PPCF_NORELATIVEOBJECTQUALIFY ) )
*lstrcpyn( szName, lpSrc, i ) = TEXT(' ');
bFound = TRUE; // success
lpArgs = &lpSrc[i];
goto exit_gracefully;
}
if ( bQualify )
hmemcpy( szName, lpSrc, (i+1)*SIZEOF(TCHAR) );
else
szName[i]=lpSrc[i];
bHitSpace = TRUE;
}
if ( !szName[i] )
break;
}
}
}
exit_gracefully:
// Work out how big the temporary buffer should be, allocate it and
// build the returning string into it. Then compose the string
// to be returned.
if ( bFound )
{
if ( StrChr( szName, TEXT(' ') ) )
bAddQuotes = dwFlags & PPCF_ADDQUOTES;
iTotal = lstrlen(szName) + 1; // for terminator
iTotal += bAddQuotes ? 2 : 0;
iTotal += (dwFlags & PPCF_ADDARGUMENTS) && lpArgs ? lstrlen(lpArgs) : 0;
if ( lpDest )
{
if ( iTotal <= iDestMax )
{
lpBuffer = lpBuffer2 = (LPTSTR)LocalAlloc( LMEM_FIXED, SIZEOF(TCHAR)*iTotal );
if ( lpBuffer )
{
// First quote if required
if ( bAddQuotes )
*lpBuffer2++ = TEXT('\"');
// Matching name
lstrcpy( lpBuffer2, szName );
// Closing quote if required
if ( bAddQuotes )
lstrcat( lpBuffer2, TEXT("\"") );
// Arguments (if requested)
if ( (dwFlags & PPCF_ADDARGUMENTS) && lpArgs )
lstrcat( lpBuffer2, lpArgs );
// Then copy into callers buffer, and free out temporary buffer
lstrcpy( lpDest, lpBuffer );
LocalFree( (HGLOBAL)lpBuffer );
// Return the length of the resulting string
iResult = iTotal;
}
}
}
else
{
// Resulting string is this big, although nothing returned (allows them to allocate a buffer)
iResult = iTotal;
}
}
return iResult;
}