682 lines
18 KiB
C
682 lines
18 KiB
C
//---------------------------------------------------------------------------
|
|
//
|
|
// Copyright (c) Microsoft Corporation 1993-1994
|
|
//
|
|
// File: path.c
|
|
//
|
|
// This files contains the path whacking code.
|
|
//
|
|
// History:
|
|
// 01-31-94 ScottH Moved from shellext.c
|
|
//
|
|
//---------------------------------------------------------------------------
|
|
|
|
///////////////////////////////////////////////////// INCLUDES
|
|
|
|
#include "brfprv.h" // common headers
|
|
#include "res.h"
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Removes the trailing backslash from a path.
|
|
|
|
A:\ --> A:\
|
|
C:\foo\ --> C:\foo
|
|
\\Pyrex\User\ --> \\Pyrex\User
|
|
|
|
Returns: pointer to NULL that replaced the backslash or
|
|
the pointer to the last character if it isn't
|
|
a backslash
|
|
|
|
Cond: pimped this code from the shell
|
|
*/
|
|
LPTSTR PUBLIC MyPathRemoveBackslash(
|
|
LPTSTR lpszPath)
|
|
{
|
|
int len = lstrlen(lpszPath)-1;
|
|
if (IsDBCSLeadByte((BYTE)*CharPrev(lpszPath,lpszPath+len+1)))
|
|
len--;
|
|
|
|
if (!PathIsRoot(lpszPath) && lpszPath[len] == TEXT('\\'))
|
|
lpszPath[len] = TEXT('\0');
|
|
|
|
return lpszPath + len;
|
|
}
|
|
|
|
|
|
#ifdef NOTUSED
|
|
/*----------------------------------------------------------
|
|
Purpose: copies the path without the extension into the buffer
|
|
|
|
Returns: new path
|
|
Cond: --
|
|
*/
|
|
LPTSTR PUBLIC PathRemoveExt(
|
|
LPCTSTR pszPath,
|
|
LPTSTR pszBuf)
|
|
{
|
|
LPTSTR psz;
|
|
LPTSTR pszMark = NULL;
|
|
|
|
ASSERT(pszPath);
|
|
ASSERT(pszBuf);
|
|
|
|
psz = pszBuf;
|
|
while (*pszPath)
|
|
{
|
|
*psz = *pszPath;
|
|
pszPath = CharNext(pszPath);
|
|
if (TEXT('.') == *psz)
|
|
pszMark = psz;
|
|
else if (TEXT('\\') == *psz)
|
|
pszMark = NULL;
|
|
psz = CharNext(psz);
|
|
}
|
|
*psz = TEXT('\0');
|
|
|
|
if (pszMark)
|
|
*pszMark = TEXT('\0');
|
|
|
|
return pszBuf;
|
|
}
|
|
#endif
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Convert a file spec to make it look a bit better
|
|
if it is all upper case chars.
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
BOOL PRIVATE PathMakeComponentPretty(LPTSTR lpPath)
|
|
{
|
|
LPTSTR lp;
|
|
|
|
// 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')))
|
|
return FALSE; // this is a LFN, dont mess with it
|
|
}
|
|
|
|
CharLower(lpPath);
|
|
CharUpperBuff(lpPath, 1);
|
|
return TRUE; // did the conversion
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// 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 PUBLIC PathFindNextComponentI(LPCTSTR lpszPath)
|
|
{
|
|
LPTSTR lpszLastSlash;
|
|
|
|
// Are we at the end of a path.
|
|
if (!*lpszPath)
|
|
{
|
|
// Yep, quit.
|
|
return NULL;
|
|
}
|
|
// Find the next slash.
|
|
// REVIEW UNDONE - can slashes be quoted?
|
|
lpszLastSlash = StrChr(lpszPath, TEXT('\\'));
|
|
// Is there a slash?
|
|
if (!lpszLastSlash)
|
|
{
|
|
// No - Return a ptr to the NULL.
|
|
return (LPTSTR) (lpszPath+lstrlen(lpszPath));
|
|
}
|
|
else
|
|
{
|
|
// Is it a UNC style name?
|
|
if (TEXT('\\') == *(lpszLastSlash+1))
|
|
{
|
|
// Yep, skip over the second slash.
|
|
return lpszLastSlash+2;
|
|
}
|
|
else
|
|
{
|
|
// Nope. just skip over one slash.
|
|
return lpszLastSlash+1;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Takes the path and makes it presentable.
|
|
|
|
The rules are:
|
|
If the LFN name is simply the short name (all caps),
|
|
then convert to lowercase with first letter capitalized
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC PathMakePresentable(
|
|
LPTSTR pszPath)
|
|
{
|
|
LPTSTR pszComp; // pointers to begining and
|
|
LPTSTR pszEnd; // end of path component
|
|
LPTSTR pch;
|
|
int cComponent = 0;
|
|
BOOL bUNCPath;
|
|
TCHAR ch;
|
|
|
|
bUNCPath = PathIsUNC(pszPath);
|
|
|
|
pszComp = pszPath;
|
|
while (pszEnd = PathFindNextComponentI(pszComp))
|
|
{
|
|
// pszEnd may be pointing to the right of the backslash
|
|
// beyond the path component, so back up one
|
|
//
|
|
ch = *pszEnd;
|
|
*pszEnd = 0; // temporary null
|
|
|
|
// pszComp points to the path component
|
|
//
|
|
pch = CharNext(pszComp);
|
|
if (TEXT(':') == *pch)
|
|
{
|
|
// Simply capitalize the drive-portion of the path
|
|
//
|
|
CharUpper(pszComp);
|
|
}
|
|
else if (bUNCPath && cComponent++ < 3)
|
|
{
|
|
// Network server or share name
|
|
// FEATURE: handle LFN network names
|
|
//
|
|
CharUpper(pszComp);
|
|
PathMakeComponentPretty(pszComp);
|
|
}
|
|
else
|
|
{
|
|
// Normal path component
|
|
//
|
|
PathMakeComponentPretty(pszComp);
|
|
}
|
|
|
|
*pszEnd = ch;
|
|
pszComp = pszEnd;
|
|
}
|
|
}
|
|
|
|
|
|
#ifdef NOTUSED
|
|
/*----------------------------------------------------------
|
|
Purpose: Takes the path and pretties up each component of
|
|
the path.
|
|
|
|
The rules are:
|
|
Use the LFN name of the component
|
|
If the LFN name is simply the short name (all caps),
|
|
then convert to lowercase with first letter capitalized
|
|
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PRIVATE PathGetCompleteLFN(
|
|
LPCTSTR pszPath,
|
|
LPTSTR pszLong,
|
|
int cbLong)
|
|
{
|
|
TCHAR sz[MAX_PATH];
|
|
TCHAR szPath[MAX_PATH+1];
|
|
LPTSTR pszComp; // pointers to begining and end of path component
|
|
LPTSTR pszEnd;
|
|
int cbPath;
|
|
int cb;
|
|
BOOL bAtEnd = FALSE;
|
|
int cComponent = 0;
|
|
BOOL bUNCPath;
|
|
TCHAR ch;
|
|
|
|
// REARCHITECT: this is broken for double-byte characters for sure
|
|
|
|
// For each component in string, get the LFN and add it to
|
|
// the pszLong buffer.
|
|
//
|
|
|
|
cbPath = lstrlen(pszPath) * sizeof(TCHAR);
|
|
ASSERT(cbPath+1 <= sizeof(szPath));
|
|
lstrcpy(szPath, pszPath);
|
|
|
|
bUNCPath = PathIsUNC(szPath);
|
|
|
|
*pszLong = NULL_CHAR;
|
|
cb = 0;
|
|
|
|
pszComp = szPath;
|
|
while (pszEnd = PathFindNextComponentI(pszComp))
|
|
{
|
|
// pszEnd may be pointing to the right of the backslash beyond the
|
|
// path component, so back up one
|
|
//
|
|
if (0 == *pszEnd)
|
|
bAtEnd = TRUE;
|
|
else
|
|
{
|
|
if (!bUNCPath || cComponent > 0)
|
|
pszEnd--; // not the server or share portions of a UNC path
|
|
ch = *pszEnd;
|
|
*pszEnd = 0; // temporary null
|
|
}
|
|
|
|
// pszComp points to the path component now
|
|
//
|
|
if (TEXT(':') == *(pszEnd-1) || TEXT(':') == *(pszEnd-2))
|
|
{
|
|
// Simply capitalize the drive-portion of the path
|
|
//
|
|
CharUpper(szPath);
|
|
}
|
|
else if (bUNCPath && cComponent++ < 3)
|
|
{
|
|
// Network server or share name
|
|
// FEATURE: handle LFN network names
|
|
//
|
|
CharUpper(pszComp);
|
|
PathMakeComponentPretty(pszComp);
|
|
}
|
|
else
|
|
{
|
|
int ib;
|
|
|
|
// Try to get the LFN
|
|
//
|
|
*sz = NULL_CHAR;
|
|
PathGetLongName(szPath, sz, ARRAYSIZE(sz));
|
|
|
|
// If an LFN does not exist, keep the path component
|
|
// as it is. (Sometimes the path component can be
|
|
// something like "Link to Foo.txt")
|
|
//
|
|
if (*sz)
|
|
{
|
|
// Make pszComp point to the same offset in sz now
|
|
// (the components in each are the same offsets)
|
|
//
|
|
ib = pszComp - (LPTSTR)szPath;
|
|
pszComp = &sz[ib];
|
|
}
|
|
PathMakeComponentPretty(pszComp);
|
|
}
|
|
|
|
// Save new LFN-ized component to buffer
|
|
//
|
|
cb += lstrlen(pszComp) * sizeof(TCHAR);
|
|
if (cbLong <= cb)
|
|
break; // reached end of pszLong buffer
|
|
lstrcat(pszLong, pszComp);
|
|
if (!bAtEnd)
|
|
{
|
|
PathAddBackslash(pszLong);
|
|
*pszEnd = ch;
|
|
if (bUNCPath && 1 == cComponent)
|
|
pszComp = pszEnd; // pointing to share portion of path
|
|
else
|
|
pszComp = pszEnd+1; // Move component pointer to next part
|
|
}
|
|
else
|
|
pszComp = pszEnd;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Returns TRUE if the combined path of pszFolder and
|
|
pszName is greater than MAX_PATH.
|
|
|
|
Returns: see above
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC PathsTooLong(
|
|
LPCTSTR pszFolder,
|
|
LPCTSTR pszName)
|
|
{
|
|
// +1 for possible '\' between the two path components
|
|
return lstrlen(pszFolder) + lstrlen(pszName) + 1 >= MAX_PATH;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Fully qualifies a path
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC BrfPathCanonicalize(
|
|
LPCTSTR pszPath,
|
|
LPTSTR pszBuf) // Must be sizeof(MAX_PATH)
|
|
{
|
|
DWORD dwcPathLen;
|
|
|
|
dwcPathLen = GetFullPathName(pszPath, MAX_PATH, pszBuf, NULL);
|
|
|
|
if (! dwcPathLen || dwcPathLen >= MAX_PATH)
|
|
lstrcpy(pszBuf, pszPath);
|
|
|
|
// If pszBuf won't cover losslessly to ANSI, use the short name instead
|
|
|
|
#if defined(UNICODE)
|
|
{
|
|
CHAR szAnsi[MAX_PATH];
|
|
WCHAR szUnicode[MAX_PATH];
|
|
szUnicode[0] = L'\0';
|
|
|
|
WideCharToMultiByte(CP_ACP, 0, pszBuf, -1, szAnsi, ARRAYSIZE(szAnsi), NULL, NULL);
|
|
MultiByteToWideChar(CP_ACP, 0, szAnsi, -1, szUnicode, ARRAYSIZE(szUnicode));
|
|
if (lstrcmp(szUnicode, pszBuf))
|
|
{
|
|
// Cannot convert losslessly from Unicode -> Ansi, so get the short path
|
|
|
|
lstrcpy(szUnicode, pszBuf);
|
|
SheShortenPath(szUnicode, TRUE);
|
|
lstrcpy(pszBuf, szUnicode);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
PathMakePresentable(pszBuf);
|
|
|
|
ASSERT(lstrlen(pszBuf) < MAX_PATH);
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Gets the displayable filename of the path. The filename
|
|
is placed in the provided buffer.
|
|
|
|
Returns: pointer to buffer
|
|
Cond: --
|
|
*/
|
|
LPTSTR PUBLIC PathGetDisplayName(
|
|
LPCTSTR pszPath,
|
|
LPTSTR pszBuf)
|
|
{
|
|
SHFILEINFO sfi;
|
|
|
|
if (SHGetFileInfo(pszPath, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME))
|
|
lstrcpy(pszBuf, sfi.szDisplayName);
|
|
else
|
|
lstrcpy(pszBuf, PathFindFileName(pszPath));
|
|
|
|
return pszBuf;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Checks if the attributes of the path. If it is a
|
|
directory and has the system bit set, and if the brfcase.dat
|
|
file exists in the directory, then return TRUE.
|
|
|
|
Worst case: performs two GetFileAttributes.
|
|
|
|
Returns: see above
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC PathCheckForBriefcase(
|
|
LPCTSTR pszPath,
|
|
DWORD dwAttrib) // if -1, then function gets the attributes
|
|
{
|
|
ASSERT(pszPath);
|
|
|
|
if (0xFFFFFFFF == dwAttrib)
|
|
{
|
|
dwAttrib = GetFileAttributes(pszPath);
|
|
if (0xFFFFFFFF == dwAttrib)
|
|
return FALSE;
|
|
}
|
|
|
|
if (IsFlagSet(dwAttrib, FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY) ||
|
|
IsFlagSet(dwAttrib, FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM))
|
|
{
|
|
TCHAR szT[MAX_PATH];
|
|
LPCTSTR pszDBName;
|
|
|
|
// Check for the existence of the brfcase.dat file.
|
|
//
|
|
if (IsLFNDrive(pszPath))
|
|
pszDBName = g_szDBName;
|
|
else
|
|
pszDBName = g_szDBNameShort;
|
|
|
|
if (PathsTooLong(pszPath, pszDBName))
|
|
return FALSE;
|
|
else
|
|
{
|
|
PathCombine(szT, pszPath, pszDBName);
|
|
return PathExists(szT);
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Returns TRUE if the path is to a briefcase root.
|
|
|
|
This function may hit the file-system to achieve
|
|
its goal.
|
|
|
|
Worst case: performs two GetFileAttributes.
|
|
|
|
Returns: TRUE if the path refers to a briefcase root.
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC PathIsBriefcase(
|
|
LPCTSTR pszPath)
|
|
{
|
|
UINT uRet;
|
|
|
|
ASSERT(pszPath);
|
|
|
|
// We perform our search by first looking in our cache
|
|
// of known briefcase paths (CPATH). If we don't find
|
|
// anything, then we proceed to iterate thru each
|
|
// component of the path, checking for these two things:
|
|
//
|
|
// 1) A directory with the system attribute
|
|
// 2) The existence of a brfcase.dat file in the directory.
|
|
//
|
|
uRet = CPATH_GetLocality(pszPath, NULL);
|
|
if (PL_FALSE == uRet)
|
|
{
|
|
uRet = PathCheckForBriefcase(pszPath, (DWORD)-1) ? PL_ROOT : PL_FALSE;
|
|
|
|
if (PL_ROOT == uRet)
|
|
{
|
|
int atom;
|
|
|
|
// Add this path to the briefcase path cache.
|
|
//
|
|
atom = Atom_Add(pszPath);
|
|
if (ATOM_ERR != atom)
|
|
CPATH_Replace(atom);
|
|
}
|
|
}
|
|
|
|
return PL_ROOT == uRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Gets the locality of the path, relative to any
|
|
briefcase. If PL_ROOT or PL_INSIDE is returned,
|
|
pszBuf will contain the path to the root of the
|
|
briefcase.
|
|
|
|
This function may hit the file-system to achieve
|
|
its goal.
|
|
|
|
Worst case: performs 2*n GetFileAttributes, where
|
|
n is the number of components in pszPath.
|
|
|
|
Returns: Path locality (PL_FALSE, PL_ROOT, PL_INSIDE)
|
|
|
|
Cond: --
|
|
*/
|
|
UINT PUBLIC PathGetLocality(
|
|
LPCTSTR pszPath,
|
|
LPTSTR pszBuf) // Buffer for root path
|
|
{
|
|
UINT uRet;
|
|
|
|
ASSERT(pszPath);
|
|
ASSERT(pszBuf);
|
|
|
|
*pszBuf = NULL_CHAR;
|
|
|
|
// pszPath may be:
|
|
// 1) a path to the briefcase folder itself
|
|
// 2) a path to a file or folder beneath the briefcase
|
|
// 3) a path to something unrelated to a briefcase
|
|
|
|
// We perform our search by first looking in our cache
|
|
// of known briefcase paths (CPATH). If we don't find
|
|
// anything, then we proceed to iterate thru each
|
|
// component of the path, checking for these two things:
|
|
//
|
|
// 1) A directory with the system attribute
|
|
// 2) The existence of a brfcase.dat file in the directory.
|
|
//
|
|
uRet = CPATH_GetLocality(pszPath, pszBuf);
|
|
if (PL_FALSE == uRet)
|
|
{
|
|
int cnt = 0;
|
|
|
|
lstrcpy(pszBuf, pszPath);
|
|
do
|
|
{
|
|
if (PathCheckForBriefcase(pszBuf, (DWORD)-1))
|
|
{
|
|
int atom;
|
|
|
|
uRet = cnt > 0 ? PL_INSIDE : PL_ROOT;
|
|
|
|
// Add this briefcase path to our cache
|
|
//
|
|
atom = Atom_Add(pszBuf);
|
|
if (ATOM_ERR != atom)
|
|
CPATH_Replace(atom);
|
|
|
|
break; // Done
|
|
}
|
|
|
|
cnt++;
|
|
|
|
} while (PathRemoveFileSpec(pszBuf));
|
|
|
|
if (PL_FALSE == uRet)
|
|
*pszBuf = NULL_CHAR;
|
|
}
|
|
|
|
return uRet;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Returns TRUE if the file/directory exists.
|
|
|
|
Returns: see above
|
|
Cond: --
|
|
*/
|
|
BOOL PUBLIC PathExists(
|
|
LPCTSTR pszPath)
|
|
{
|
|
return GetFileAttributes(pszPath) != 0xFFFFFFFF;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Finds the end of the root specification in a path.
|
|
|
|
input path output string
|
|
---------- -------------
|
|
c: <empty string>
|
|
c:\ <empty string>
|
|
c:\foo foo
|
|
c:\foo\bar foo\bar
|
|
\\pyrex\user <empty string>
|
|
\\pyrex\user\ <empty string>
|
|
\\pyrex\user\foo foo
|
|
\\pyrex\user\foo\bar foo\bar
|
|
|
|
Returns: pointer to first character after end of root spec.
|
|
|
|
Cond: --
|
|
*/
|
|
LPCTSTR PUBLIC PathFindEndOfRoot(
|
|
LPCTSTR pszPath)
|
|
{
|
|
LPCTSTR psz;
|
|
|
|
ASSERT(pszPath);
|
|
|
|
if (TEXT(':') == pszPath[1])
|
|
{
|
|
if (TEXT('\\') == pszPath[2])
|
|
psz = &pszPath[3];
|
|
else
|
|
psz = &pszPath[2];
|
|
}
|
|
else if (PathIsUNC(pszPath))
|
|
{
|
|
psz = PathFindNextComponentI(pszPath); // hop double-slash
|
|
psz = PathFindNextComponentI(psz); // hop server name
|
|
if (psz)
|
|
psz = PathFindNextComponentI(psz); // hop share name
|
|
|
|
if (!psz)
|
|
{
|
|
ASSERT(0); // There is no share name
|
|
psz = pszPath;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ASSERT(0);
|
|
psz = pszPath;
|
|
}
|
|
|
|
return psz;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
Purpose: Sends a notify message to the shell regarding a file-status
|
|
change.
|
|
Returns: --
|
|
Cond: --
|
|
*/
|
|
void PUBLIC PathNotifyShell(
|
|
LPCTSTR pszPath,
|
|
NOTIFYSHELLEVENT nse,
|
|
BOOL bDoNow) // TRUE: force the event to be processed right away
|
|
{
|
|
|
|
static LONG const rgShEvents[] =
|
|
{ SHCNE_CREATE, SHCNE_MKDIR, SHCNE_UPDATEITEM, SHCNE_UPDATEDIR };
|
|
|
|
ASSERT(pszPath);
|
|
ASSERT(nse < ARRAYSIZE(rgShEvents));
|
|
|
|
SHChangeNotify(rgShEvents[nse], SHCNF_PATH, pszPath, NULL);
|
|
|
|
if (bDoNow)
|
|
{
|
|
SHChangeNotify(0, SHCNF_FLUSHNOWAIT, NULL, NULL);
|
|
}
|
|
}
|
|
|