//--------------------------------------------------------------------------- // // 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: c:\ c:\foo foo c:\foo\bar foo\bar \\pyrex\user \\pyrex\user\ \\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); } }