diff options
Diffstat (limited to 'src/palrt/path.cpp')
-rw-r--r-- | src/palrt/path.cpp | 642 |
1 files changed, 642 insertions, 0 deletions
diff --git a/src/palrt/path.cpp b/src/palrt/path.cpp new file mode 100644 index 0000000000..1d610cd4fb --- /dev/null +++ b/src/palrt/path.cpp @@ -0,0 +1,642 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. +// + +// +// =========================================================================== +// File: path.cpp +// +// Path APIs ported from shlwapi (especially for Fusion) +// =========================================================================== + +#include "common.h" + + + +#define CH_SLASH W('/') +#define CH_WHACK W('\\') + +// +// Inline function to check for a double-backslash at the +// beginning of a string +// + +static __inline BOOL DBL_BSLASH(LPCWSTR psz) +{ + return (psz[0] == W('\\') && psz[1] == W('\\')); +} + +// +// Inline function to check for a path separator character. +// + +static __inline BOOL IsPathSeparator(WCHAR ch) +{ + return (ch == CH_SLASH || ch == CH_WHACK); +} + +__inline BOOL ChrCmpW_inline(WCHAR w1, WCHAR wMatch) +{ + return(!(w1 == wMatch)); +} + +STDAPI_(LPWSTR) StrRChrW(LPCWSTR lpStart, LPCWSTR lpEnd, WCHAR wMatch) +{ + LPCWSTR lpFound = NULL; + + RIPMSG(lpStart && IS_VALID_STRING_PTRW(lpStart, -1), "StrRChrW: caller passed bad lpStart"); + RIPMSG(!lpEnd || lpEnd <= lpStart + lstrlenW(lpStart), "StrRChrW: caller passed bad lpEnd"); + // don't need to check for NULL lpStart + + if (!lpEnd) + lpEnd = lpStart + lstrlenW(lpStart); + + for ( ; lpStart < lpEnd; lpStart++) + { + if (!ChrCmpW_inline(*lpStart, wMatch)) + lpFound = lpStart; + } + return ((LPWSTR)lpFound); +} + + +// check if a path is a root +// +// returns: +// TRUE +// "\" "X:\" "\\" "\\foo" "\\foo\bar" +// +// FALSE for others including "\\foo\bar\" (!) +// +STDAPI_(BOOL) PathIsRootW(LPCWSTR pPath) +{ + RIPMSG(pPath && IS_VALID_STRING_PTR(pPath, -1), "PathIsRoot: caller passed bad pPath"); + + if (!pPath || !*pPath) + { + return FALSE; + } + + if (!lstrcmpiW(pPath + 1, W(":\\"))) + { + return TRUE; // "X:\" case + } + + if (IsPathSeparator(*pPath) && (*(pPath + 1) == 0)) + { + return TRUE; // "/" or "\" case + } + + if (DBL_BSLASH(pPath)) // smells like UNC name + { + LPCWSTR p; + int cBackslashes = 0; + + for (p = pPath + 2; *p; p++) + { + if (*p == W('\\')) + { + // + // return FALSE for "\\server\share\dir" + // so just check if there is more than one slash + // + // "\\server\" without a share name causes + // problems for WNet APIs. we should return + // FALSE for this as well + // + if ((++cBackslashes > 1) || !*(p+1)) + return FALSE; + } + } + // end of string with only 1 more backslash + // must be a bare UNC, which looks like a root dir + return TRUE; + } + return FALSE; +} + +/* +// rips the last part of the path off including the backslash +// C:\foo -> C:\ +// C:\foo\bar -> C:\foo +// C:\foo\ -> C:\foo +// \\x\y\x -> \\x\y +// \\x\y -> \\x +// \\x -> \\ (Just the double slash!) +// \foo -> \ (Just the slash!) +// +// in/out: +// pFile fully qualified path name +// returns: +// TRUE we stripped something +// FALSE didn't strip anything (root directory case) +// +*/ +STDAPI_(BOOL) PathRemoveFileSpecW(LPWSTR pFile) +{ + RIPMSG(pFile && IS_VALID_STRING_PTR(pFile, -1), "PathRemoveFileSpec: caller passed bad pFile"); + + if (pFile) + { + LPWSTR pT; + LPWSTR pT2 = pFile; + + for (pT = pT2; *pT2; pT2++) + { + if (IsPathSeparator(*pT2)) + { + pT = pT2; // last "\" found, (we will strip here) + } + else if (*pT2 == W(':')) // skip ":\" so we don't + { + if (IsPathSeparator(pT2[1])) // strip the "\" from "C:\" + { + pT2++; + } + pT = pT2 + 1; + } + } + + if (*pT == 0) + { + // didn't strip anything + return FALSE; + } + else if (((pT == pFile) && IsPathSeparator(*pT)) || // is it the "\foo" case? + ((pT == pFile+1) && (*pT == CH_WHACK && *pFile == CH_WHACK))) // or the "\\bar" case? + { + // Is it just a '\'? + if (*(pT+1) != W('\0')) + { + // Nope. + *(pT+1) = W('\0'); + return TRUE; // stripped something + } + else + { + // Yep. + return FALSE; + } + } + else + { + *pT = 0; + return TRUE; // stripped something + } + } + return FALSE; +} + +// +// Return a pointer to the end of the next path component in the string. +// ie return a pointer to the next backslash or terminating NULL. +// +LPCWSTR GetPCEnd(LPCWSTR lpszStart) +{ + LPCWSTR lpszEnd; + LPCWSTR lpszSlash; + + lpszEnd = StrChr(lpszStart, CH_WHACK); + lpszSlash = StrChr(lpszStart, CH_SLASH); + if ((lpszSlash && lpszSlash < lpszEnd) || + !lpszEnd) + { + lpszEnd = lpszSlash; + } + if (!lpszEnd) + { + lpszEnd = lpszStart + lstrlenW(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). +// +LPCWSTR PCStart(LPCWSTR lpszStart, LPCWSTR lpszEnd) +{ + LPCWSTR lpszBegin = StrRChrW(lpszStart, lpszEnd, CH_WHACK); + LPCWSTR lpszSlash = StrRChrW(lpszStart, lpszEnd, CH_SLASH); + if (lpszSlash > lpszBegin) + { + lpszBegin = lpszSlash; + } + if (!lpszBegin) + { + lpszBegin = lpszStart; + } + return lpszBegin; +} + +// +// Fix up a few special cases so that things roughly make sense. +// +void NearRootFixups(LPWSTR lpszPath, BOOL fUNC) +{ + // Check for empty path. + if (lpszPath[0] == W('\0')) + { + // Fix up. +#ifndef PLATFORM_UNIX + lpszPath[0] = CH_WHACK; +#else + lpszPath[0] = CH_SLASH; +#endif + lpszPath[1] = W('\0'); + } + // Check for missing slash. + if (lpszPath[1] == W(':') && lpszPath[2] == W('\0')) + { + // Fix up. + lpszPath[2] = W('\\'); + lpszPath[3] = W('\0'); + } + // Check for UNC root. + if (fUNC && lpszPath[0] == W('\\') && lpszPath[1] == W('\0')) + { + // Fix up. + //lpszPath[0] = W('\\'); // already checked in if guard + lpszPath[1] = W('\\'); + lpszPath[2] = W('\0'); + } +} + +/*---------------------------------------------------------- +Purpose: Canonicalize a path. + +Returns: +Cond: -- +*/ +STDAPI_(BOOL) PathCanonicalizeW(LPWSTR lpszDst, LPCWSTR lpszSrc) +{ + LPCWSTR lpchSrc; + LPCWSTR lpchPCEnd; // Pointer to end of path component. + LPWSTR lpchDst; + BOOL fUNC; + int cchPC; + + RIPMSG(lpszDst && IS_VALID_WRITE_BUFFER(lpszDst, WCHAR, MAX_PATH), "PathCanonicalize: caller passed bad lpszDst"); + RIPMSG(lpszSrc && IS_VALID_STRING_PTR(lpszSrc, -1), "PathCanonicalize: caller passed bad lpszSrc"); + RIPMSG(lpszDst != lpszSrc, "PathCanonicalize: caller passed the same buffer for lpszDst and lpszSrc"); + + if (!lpszDst || !lpszSrc) + { + SetLastError(ERROR_INVALID_PARAMETER); + return FALSE; + } + + *lpszDst = W('\0'); + + fUNC = PathIsUNCW(lpszSrc); // Check for UNCness. + + // Init. + lpchSrc = lpszSrc; + lpchDst = lpszDst; + + while (*lpchSrc) + { + lpchPCEnd = GetPCEnd(lpchSrc); + cchPC = (int) (lpchPCEnd - lpchSrc)+1; + + if (cchPC == 1 && IsPathSeparator(*lpchSrc)) // Check for slashes. + { + // Just copy them. +#ifndef PLATFORM_UNIX + *lpchDst = CH_WHACK; +#else + *lpchDst = CH_SLASH; +#endif + lpchDst++; + lpchSrc++; + } + else if (cchPC == 2 && *lpchSrc == W('.')) // Check for dots. + { + // Skip it... + // Are we at the end? + if (*(lpchSrc+1) == W('\0')) + { + lpchSrc++; + + // remove the last slash we copied (if we've copied one), but don't make a mal-formed root + if ((lpchDst > lpszDst) && !PathIsRootW(lpszDst)) + lpchDst--; + } + else + { + lpchSrc += 2; + } + } + else if (cchPC == 3 && *lpchSrc == W('.') && *(lpchSrc + 1) == W('.')) // Check for dot dot. + { + // make sure we aren't already at the root + if (!PathIsRootW(lpszDst)) + { + // Go up... Remove the previous path component. + lpchDst = (LPWSTR)PCStart(lpszDst, lpchDst - 1); + } + else + { + // When we can't back up, skip the trailing backslash + // so we don't copy one again. (C:\..\FOO would otherwise + // turn into C:\\FOO). + if (IsPathSeparator(*(lpchSrc + 2))) + { + lpchSrc++; + } + } + + // skip ".." + lpchSrc += 2; + } + else // Everything else + { + // Just copy it. + lstrcpynW(lpchDst, lpchSrc, cchPC); + lpchDst += cchPC - 1; + lpchSrc += cchPC - 1; + } + + // Keep everything nice and tidy. + *lpchDst = W('\0'); + } + + // Check for weirdo root directory stuff. + NearRootFixups(lpszDst, fUNC); + + return TRUE; +} + +// Modifies: +// pszRoot +// +// Returns: +// TRUE if a drive root was found +// FALSE otherwise +// +STDAPI_(BOOL) PathStripToRootW(LPWSTR pszRoot) +{ + RIPMSG(pszRoot && IS_VALID_STRING_PTR(pszRoot, -1), "PathStripToRoot: caller passed bad pszRoot"); + + if (pszRoot) + { + while (!PathIsRootW(pszRoot)) + { + if (!PathRemoveFileSpecW(pszRoot)) + { + // If we didn't strip anything off, + // must be current drive + return FALSE; + } + } + return TRUE; + } + return FALSE; +} + + + +/*---------------------------------------------------------- +Purpose: Concatenate lpszDir and lpszFile into a properly formed + path and canonicalize any relative path pieces. + + lpszDest and lpszFile can be the same buffer + lpszDest and lpszDir can be the same buffer + +Returns: pointer to lpszDest +*/ +STDAPI_(LPWSTR) PathCombineW(LPWSTR lpszDest, LPCWSTR lpszDir, LPCWSTR lpszFile) +{ +#ifdef DEBUG + RIPMSG(lpszDest && IS_VALID_WRITE_BUFFER(lpszDest, TCHAR, MAX_LONGPATH), "PathCombine: caller passed bad lpszDest"); + RIPMSG(!lpszDir || IS_VALID_STRING_PTR(lpszDir, -1), "PathCombine: caller passed bad lpszDir"); + RIPMSG(!lpszFile || IS_VALID_STRING_PTR(lpszFile, -1), "PathCombine: caller passed bad lpszFile"); + RIPMSG(lpszDir || lpszFile, "PathCombine: caller neglected to pass lpszDir or lpszFile"); +#endif // DEBUG + + + if (lpszDest) + { + TCHAR szTemp[MAX_LONGPATH]; + LPWSTR pszT; + + *szTemp = W('\0'); + + if (lpszDir && *lpszDir) + { + if (!lpszFile || *lpszFile==W('\0')) + { + lstrcpynW(szTemp, lpszDir, ARRAYSIZE(szTemp)); // lpszFile is empty + } + else if (PathIsRelativeW(lpszFile)) + { + lstrcpynW(szTemp, lpszDir, ARRAYSIZE(szTemp)); + pszT = PathAddBackslashW(szTemp); + if (pszT) + { + int iRemaining = (int)(ARRAYSIZE(szTemp) - (pszT - szTemp)); + + if (lstrlenW(lpszFile) < iRemaining) + { + lstrcpynW(pszT, lpszFile, iRemaining); + } + else + { + *szTemp = W('\0'); + } + } + else + { + *szTemp = W('\0'); + } + } + else if (IsPathSeparator(*lpszFile) && !PathIsUNCW(lpszFile)) + { + lstrcpynW(szTemp, lpszDir, ARRAYSIZE(szTemp)); + // FEATURE: Note that we do not check that an actual root is returned; + // it is assumed that we are given valid parameters + PathStripToRootW(szTemp); + + pszT = PathAddBackslashW(szTemp); + if (pszT) + { + // Skip the backslash when copying + // Note: We don't support strings longer than 4GB, but that's + // okay because we already barf at MAX_PATH + lstrcpynW(pszT, lpszFile+1, (int)(ARRAYSIZE(szTemp) - (pszT - szTemp))); + } + else + { + *szTemp = W('\0'); + } + } + else + { + lstrcpynW(szTemp, lpszFile, ARRAYSIZE(szTemp)); // already fully qualified file part + } + } + else if (lpszFile && *lpszFile) + { + lstrcpynW(szTemp, lpszFile, ARRAYSIZE(szTemp)); // no dir just use file. + } + + // + // if szTemp has something in it we succeeded. Also if szTemp is empty and + // the input strings are empty we succeed and PathCanonicalize() will + // return "\" + // + if (*szTemp || ((lpszDir || lpszFile) && !((lpszDir && *lpszDir) || (lpszFile && *lpszFile)))) + { + PathCanonicalizeW(lpszDest, szTemp); // this deals with .. and . stuff + // returns "\" on empty szTemp + } + else + { + *lpszDest = W('\0'); // set output buffer to empty string. + lpszDest = NULL; // return failure. + } + } + + return lpszDest; +} + +// add a backslash to a qualified path +// +// in: +// lpszPath path (A:, C:\foo, etc) +// +// out: +// lpszPath A:\, C:\foo\ ; +// +// returns: +// pointer to the NULL that terminates the path +// +STDAPI_(LPWSTR) PathAddBackslashW(LPWSTR lpszPath) +{ + LPWSTR lpszRet = NULL; + + RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathAddBackslash: caller passed bad lpszPath"); + + if (lpszPath) + { + int ichPath = lstrlenW(lpszPath); + LPWSTR lpszEnd = lpszPath + ichPath; + + if (ichPath) + { + + // Get the end of the source directory + switch(*(lpszEnd-1)) + { + case CH_SLASH: + case CH_WHACK: + break; + + default: + // try to keep us from tromping over MAX_PATH in size. + // if we find these cases, return NULL. Note: We need to + // check those places that call us to handle their GP fault + // if they try to use the NULL! + if (ichPath >= (MAX_PATH - 2)) // -2 because ichPath doesn't include NULL, and we're adding a CH_WHACK. + { + return(NULL); + } + + *lpszEnd++ = CH_WHACK; + *lpszEnd = W('\0'); + } + } + + lpszRet = lpszEnd; + } + + return lpszRet; +} + + + + +//--------------------------------------------------------------------------- +// Returns TRUE if the given string is a UNC path. +// +// TRUE +// "\\foo\bar" +// "\\foo" <- careful +// "\\" +// FALSE +// "\foo" +// "foo" +// "c:\foo" +// +// +STDAPI_(BOOL) PathIsUNCW(LPCWSTR pszPath) +{ + RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathIsUNC: caller passed bad pszPath"); + + if (pszPath) + { + return DBL_BSLASH(pszPath); + } + return FALSE; +} + + + + + +//--------------------------------------------------------------------------- +// Return TRUE if the path isn't absoulte. +// +// TRUE +// "foo.exe" +// ".\foo.exe" +// "..\boo\foo.exe" +// +// FALSE +// "\foo" +// "c:bar" <- be careful +// "c:\bar" +// "\\foo\bar" +// +STDAPI_(BOOL) PathIsRelativeW(LPCWSTR lpszPath) +{ + RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathIsRelative: caller passed bad lpszPath"); + + if (!lpszPath || *lpszPath == 0) + { + // The NULL path is assumed relative + return TRUE; + } + + if (IsPathSeparator(lpszPath[0])) + { + // Does it begin with a slash ? + return FALSE; + } + else if (lpszPath[1] == W(':')) + { + // Does it begin with a drive and a colon ? + return FALSE; + } + else + { + // Probably relative. + return TRUE; + } +} + +// find the next slash or null terminator +LPWSTR StrSlash(LPCWSTR psz) +{ + for (; *psz && !IsPathSeparator(*psz); psz++); + + // Cast to a non-const string to mimic the behavior + // of wcschr/StrChr and strchr. + return (LPWSTR) psz; +} + + + |