mirror of
https://github.com/SSCLI/sscli_20021101
synced 2026-06-08 12:28:57 +00:00
9fa3874800
Moved the original file to the archive subfolder.
1034 lines
28 KiB
C++
1034 lines
28 KiB
C++
// ==++==
|
|
//
|
|
//
|
|
// Copyright (c) 2002 Microsoft Corporation. All rights reserved.
|
|
//
|
|
// The use and distribution terms for this software are contained in the file
|
|
// named license.txt, which can be found in the root of this distribution.
|
|
// By using this software in any fashion, you are agreeing to be bound by the
|
|
// terms of this license.
|
|
//
|
|
// You must not remove this notice, or any other, from this software.
|
|
//
|
|
//
|
|
// ==--==
|
|
// ===========================================================================
|
|
// File: path.cpp
|
|
//
|
|
// Path APIs ported from shlwapi (especially for Fusion)
|
|
// ===========================================================================
|
|
|
|
#include "shlwapip.h"
|
|
|
|
#define CH_SLASH TEXT('/')
|
|
#define CH_WHACK TEXT('\\')
|
|
|
|
//
|
|
// Inline function to check for a double-backslash at the
|
|
// beginning of a string
|
|
//
|
|
|
|
static __inline BOOL DBL_BSLASH(LPCWSTR psz)
|
|
{
|
|
return (psz[0] == TEXT('\\') && psz[1] == TEXT('\\'));
|
|
}
|
|
|
|
//
|
|
// Inline function to check for a path separator character.
|
|
//
|
|
|
|
static __inline BOOL IsPathSeparator(WCHAR ch)
|
|
{
|
|
return (ch == CH_SLASH || ch == CH_WHACK);
|
|
}
|
|
|
|
// 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. ==> "."
|
|
//
|
|
STDAPI_(LPWSTR) PathFindExtensionW(LPCWSTR pszPath)
|
|
{
|
|
LPCWSTR pszDot = NULL;
|
|
|
|
RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFindExtension: caller passed bad pszPath");
|
|
|
|
if (pszPath)
|
|
{
|
|
for (; *pszPath; pszPath++)
|
|
{
|
|
switch (*pszPath)
|
|
{
|
|
case TEXT('.'):
|
|
pszDot = pszPath; // remember the last dot
|
|
break;
|
|
|
|
case CH_WHACK:
|
|
case CH_SLASH:
|
|
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 ? (LPWSTR)pszDot : (LPWSTR)pszPath;
|
|
}
|
|
|
|
// 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 (!lstrcmpi(pPath + 1, TEXT(":\\")))
|
|
{
|
|
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 == TEXT('\\'))
|
|
{
|
|
//
|
|
// 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 == TEXT(':')) // 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) != TEXT('\0'))
|
|
{
|
|
// Nope.
|
|
*(pT+1) = TEXT('\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 > lpszEnd)
|
|
{
|
|
lpszEnd = lpszSlash;
|
|
}
|
|
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).
|
|
//
|
|
LPCWSTR PCStart(LPCWSTR lpszStart, LPCWSTR lpszEnd)
|
|
{
|
|
LPCWSTR lpszBegin = StrRChr(lpszStart, lpszEnd, CH_WHACK);
|
|
LPCWSTR lpszSlash = StrRChr(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] == TEXT('\0'))
|
|
{
|
|
// Fix up.
|
|
lpszPath[0] = CH_WHACK;
|
|
lpszPath[1] = TEXT('\0');
|
|
}
|
|
// Check for missing slash.
|
|
if (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('\\'); // already checked in if guard
|
|
lpszPath[1] = TEXT('\\');
|
|
lpszPath[2] = TEXT('\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, TCHAR, 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 = TEXT('\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.
|
|
*lpchDst = CH_WHACK;
|
|
lpchDst++;
|
|
lpchSrc++;
|
|
}
|
|
else if (cchPC == 2 && *lpchSrc == TEXT('.')) // Check for dots.
|
|
{
|
|
// Skip it...
|
|
// Are we at the end?
|
|
if (*(lpchSrc+1) == TEXT('\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 == TEXT('.') && *(lpchSrc + 1) == TEXT('.')) // 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.
|
|
lstrcpyn(lpchDst, lpchSrc, cchPC);
|
|
lpchDst += cchPC - 1;
|
|
lpchSrc += cchPC - 1;
|
|
}
|
|
|
|
// Keep everything nice and tidy.
|
|
*lpchDst = TEXT('\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;
|
|
}
|
|
|
|
// 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.
|
|
//
|
|
STDAPI_(LPWSTR) PathRemoveBackslashW(LPWSTR lpszPath)
|
|
{
|
|
RIPMSG(lpszPath && IS_VALID_STRING_PTR(lpszPath, -1), "PathRemoveBackslash: caller passed bad lpszPath");
|
|
|
|
if (lpszPath)
|
|
{
|
|
int len = lstrlen(lpszPath)-1;
|
|
|
|
if (!PathIsRootW(lpszPath) && IsPathSeparator(lpszPath[len]))
|
|
lpszPath[len] = TEXT('\0');
|
|
|
|
return lpszPath + len;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
|
|
/*----------------------------------------------------------
|
|
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_PATH), "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_PATH];
|
|
LPWSTR pszT;
|
|
|
|
*szTemp = TEXT('\0');
|
|
|
|
if (lpszDir && *lpszDir)
|
|
{
|
|
if (!lpszFile || *lpszFile==TEXT('\0'))
|
|
{
|
|
lstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp)); // lpszFile is empty
|
|
}
|
|
else if (PathIsRelativeW(lpszFile))
|
|
{
|
|
lstrcpyn(szTemp, lpszDir, ARRAYSIZE(szTemp));
|
|
pszT = PathAddBackslashW(szTemp);
|
|
if (pszT)
|
|
{
|
|
int iRemaining = (int)(ARRAYSIZE(szTemp) - (pszT - szTemp));
|
|
|
|
if (lstrlen(lpszFile) < iRemaining)
|
|
{
|
|
lstrcpyn(pszT, lpszFile, iRemaining);
|
|
}
|
|
else
|
|
{
|
|
*szTemp = TEXT('\0');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
*szTemp = TEXT('\0');
|
|
}
|
|
}
|
|
else if (IsPathSeparator(*lpszFile) && !PathIsUNCW(lpszFile))
|
|
{
|
|
lstrcpyn(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
|
|
lstrcpyn(pszT, lpszFile+1, (int)(ARRAYSIZE(szTemp) - (pszT - szTemp)));
|
|
}
|
|
else
|
|
{
|
|
*szTemp = TEXT('\0');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lstrcpyn(szTemp, lpszFile, ARRAYSIZE(szTemp)); // already fully qualified file part
|
|
}
|
|
}
|
|
else if (lpszFile && *lpszFile)
|
|
{
|
|
lstrcpyn(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 = TEXT('\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 = lstrlen(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 = TEXT('\0');
|
|
}
|
|
}
|
|
|
|
lpszRet = lpszEnd;
|
|
}
|
|
|
|
return lpszRet;
|
|
}
|
|
|
|
|
|
// 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\
|
|
// c:\ -> c:\
|
|
// c: -> c:
|
|
// foo -> foo
|
|
//
|
|
STDAPI_(LPWSTR) PathFindFileNameW(LPCWSTR pPath)
|
|
{
|
|
LPCWSTR pT = pPath;
|
|
|
|
RIPMSG(pPath && IS_VALID_STRING_PTR(pPath, -1), "PathFindFileName: caller passed bad pPath");
|
|
|
|
if (pPath)
|
|
{
|
|
for ( ; *pPath; pPath++)
|
|
{
|
|
if ((pPath[0] == TEXT('\\') || pPath[0] == TEXT(':') || pPath[0] == TEXT('/'))
|
|
&& pPath[1] && pPath[1] != TEXT('\\') && pPath[1] != TEXT('/'))
|
|
pT = pPath + 1;
|
|
}
|
|
}
|
|
|
|
return (LPWSTR)pT; // const -> non const
|
|
}
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// 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;
|
|
}
|
|
|
|
|
|
|
|
//---------------------------------------------------------------------------
|
|
// Returns 0 through 25 (corresponding to 'A' through 'Z') if the path has
|
|
// a drive letter, otherwise returns -1.
|
|
//
|
|
//
|
|
STDAPI_(int) PathGetDriveNumberW(LPCWSTR lpsz)
|
|
{
|
|
RIPMSG(lpsz && IS_VALID_STRING_PTR(lpsz, -1), "PathGetDriveNumber: caller passed bad lpsz");
|
|
|
|
if (lpsz)
|
|
{
|
|
if (lpsz[0] != TEXT('\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"
|
|
//
|
|
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] == TEXT(':'))
|
|
{
|
|
// 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;
|
|
}
|
|
|
|
#define StrIntlEqNI(s1, s2, len) _StrCmpLocaleW(NORM_IGNORECASE, s1, len, s2, len)
|
|
|
|
int _StrCmpLocaleW(DWORD dwFlags, LPCWSTR psz1, int cch1, LPCWSTR psz2, int cch2)
|
|
{
|
|
int i = CompareStringW(GetThreadLocale(), dwFlags, psz1, cch1, psz2, cch2);
|
|
if (!i)
|
|
{
|
|
i = CompareStringW(LOCALE_USER_DEFAULT, dwFlags, psz1, cch1, psz2, cch2);
|
|
}
|
|
return i - CSTR_EQUAL;
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
STDAPI_(int) PathCommonPrefixW(LPCWSTR pszFile1, LPCWSTR pszFile2, LPWSTR pszPath)
|
|
{
|
|
RIPMSG(pszFile1 && IS_VALID_STRING_PTR(pszFile1, -1), "PathCommonPrefix: caller passed bad pszFile1");
|
|
RIPMSG(pszFile2 && IS_VALID_STRING_PTR(pszFile2, -1), "PathCommonPrefix: caller passed bad pszFile2");
|
|
RIPMSG(!pszPath || IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathCommonPrefix: caller passed bad pszPath");
|
|
|
|
if (pszFile1 && pszFile2)
|
|
{
|
|
LPCWSTR psz1, psz2, pszNext1, pszNext2, pszCommon;
|
|
int cch;
|
|
|
|
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)
|
|
{
|
|
pszNext1 = StrSlash(psz1);
|
|
pszNext2 = StrSlash(psz2);
|
|
|
|
cch = (int) (pszNext1 - psz1);
|
|
|
|
if (cch != (pszNext2 - psz2))
|
|
break; // lengths of segments not equal
|
|
|
|
if (StrIntlEqNI(psz1, psz2, cch))
|
|
pszCommon = pszNext1;
|
|
else
|
|
break;
|
|
|
|
ASSERT(*pszNext1 == TEXT('\0') || IsPathSeparator(*pszNext1));
|
|
ASSERT(*pszNext2 == TEXT('\0') || IsPathSeparator(*pszNext2));
|
|
|
|
if (*pszNext1 == TEXT('\0'))
|
|
break;
|
|
|
|
psz1 = pszNext1 + 1;
|
|
|
|
if (*pszNext2 == TEXT('\0'))
|
|
break;
|
|
|
|
psz2 = pszNext2 + 1;
|
|
}
|
|
|
|
if (pszCommon)
|
|
{
|
|
cch = (int) (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;
|
|
}
|
|
|
|
return 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.
|
|
//
|
|
STDAPI_(LPWSTR) PathFindNextComponentW(LPCWSTR pszPath)
|
|
{
|
|
RIPMSG(pszPath && IS_VALID_STRING_PTR(pszPath, -1), "PathFindNextComponent: caller passed bad pszPath");
|
|
|
|
if (pszPath)
|
|
{
|
|
LPWSTR pszLastSlash;
|
|
|
|
// Are we at the end of a path.
|
|
if (!*pszPath)
|
|
{
|
|
// Yep, quit.
|
|
return NULL;
|
|
}
|
|
// Find the next slash.
|
|
pszLastSlash = StrSlash(pszPath);
|
|
|
|
// Is there a slash?
|
|
if (!pszLastSlash)
|
|
{
|
|
// No - Return a ptr to the NULL.
|
|
return (LPWSTR)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;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static const WCHAR c_szDot[] = TEXT(".");
|
|
static const WCHAR c_szDotDot[] = TEXT("..");
|
|
|
|
#ifdef PLATFORM_UNIX
|
|
static const WCHAR c_szDotDotSlash[] = TEXT("../");
|
|
#else
|
|
static const WCHAR c_szDotDotSlash[] = TEXT("..\\");
|
|
#endif
|
|
|
|
|
|
// 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
|
|
//
|
|
STDAPI_(BOOL) PathRelativePathToW(LPWSTR pszPath, LPCWSTR pszFrom, DWORD dwAttrFrom, LPCWSTR pszTo, DWORD dwAttrTo)
|
|
{
|
|
#ifdef DEBUG
|
|
WCHAR szFromCopy[MAX_PATH];
|
|
WCHAR szToCopy[MAX_PATH];
|
|
|
|
RIPMSG(pszPath && IS_VALID_WRITE_BUFFER(pszPath, TCHAR, MAX_PATH), "PathRelativePathTo: caller passed bad pszPath");
|
|
RIPMSG(pszFrom && IS_VALID_STRING_PTR(pszFrom, -1), "PathRelativePathTo: caller passed bad pszFrom");
|
|
RIPMSG(pszTo && IS_VALID_STRING_PTR(pszTo, -1), "PathRelativePathTo: caller passed bad pszTo");
|
|
|
|
// we make copies of the pszFrom and pszTo buffers in case one of the strings they are passing is a pointer
|
|
// inside pszPath buffer. If this were the case, it would be trampled when we call DEBUGWhackPathBuffer().
|
|
if (pszFrom)
|
|
{
|
|
lstrcpyn(szFromCopy, pszFrom, ARRAYSIZE(szFromCopy));
|
|
pszFrom = szFromCopy;
|
|
}
|
|
|
|
if (pszTo)
|
|
{
|
|
lstrcpyn(szToCopy, pszTo, ARRAYSIZE(szToCopy));
|
|
pszTo = szToCopy;
|
|
}
|
|
#endif // DEBUG
|
|
|
|
|
|
if (pszPath && pszFrom && pszTo)
|
|
{
|
|
WCHAR szFrom[MAX_PATH], szTo[MAX_PATH];
|
|
LPWSTR psz;
|
|
UINT cchCommon;
|
|
|
|
*pszPath = 0; // assume none
|
|
|
|
lstrcpyn(szFrom, pszFrom, ARRAYSIZE(szFrom));
|
|
lstrcpyn(szTo, pszTo, ARRAYSIZE(szTo));
|
|
|
|
if (!(dwAttrFrom & FILE_ATTRIBUTE_DIRECTORY))
|
|
PathRemoveFileSpecW(szFrom);
|
|
|
|
if (!(dwAttrTo & FILE_ATTRIBUTE_DIRECTORY))
|
|
PathRemoveFileSpecW(szTo);
|
|
|
|
cchCommon = PathCommonPrefixW(szFrom, szTo, NULL);
|
|
if (cchCommon == 0)
|
|
return FALSE;
|
|
|
|
psz = szFrom + cchCommon;
|
|
|
|
if (*psz)
|
|
{
|
|
// build ..\.. part of the path
|
|
if (IsPathSeparator(*psz))
|
|
psz++; // skip slash
|
|
while (*psz)
|
|
{
|
|
psz = PathFindNextComponentW(psz);
|
|
// WARNING: 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 (!IsPathSeparator(pszTo[cchCommon]))
|
|
cchCommon--;
|
|
|
|
if ((lstrlen(pszPath) + lstrlen(pszTo + cchCommon)) >= MAX_PATH)
|
|
{
|
|
*pszPath = 0;
|
|
return FALSE;
|
|
}
|
|
|
|
ASSERT(IsPathSeparator(pszTo[cchCommon]));
|
|
lstrcat(pszPath, pszTo + cchCommon);
|
|
}
|
|
|
|
ASSERT(PathIsRelative(pszPath));
|
|
ASSERT(lstrlen(pszPath) < MAX_PATH);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
return FALSE;
|
|
}
|