summaryrefslogtreecommitdiff
path: root/src/palrt/path.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/palrt/path.cpp')
-rw-r--r--src/palrt/path.cpp642
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;
+}
+
+
+