627 lines
18 KiB
C++
627 lines
18 KiB
C++
/******************************************************************************
|
|
|
|
Copyright (c) 2002 Microsoft Corporation
|
|
|
|
Module Name:
|
|
safepath.cpp
|
|
|
|
Abstract:
|
|
Implements safe path function
|
|
|
|
******************************************************************************/
|
|
|
|
#include "stdafx.h"
|
|
#include <shlwapi.h>
|
|
|
|
// We use a little C++ precompiler trick to be able to code both ANSI & Unicode
|
|
// versions of the below functions in the same file with only one copy of the
|
|
// source code. This is what all the 'X' suffixes below are doing.
|
|
// During the first pass through the source file, we build ANSI source code.
|
|
// When we reach the bottom, we define a symbol & #include this source file,
|
|
// causing it to be compiled again. However, in this second pass, the symbol
|
|
// we defined causes it to be compiled as Unicode.
|
|
|
|
#undef XCHAR
|
|
#undef _X
|
|
#undef LPXSTR
|
|
#undef LPCXSTR
|
|
#undef StringCchCatExX
|
|
#undef StringCchCopyExX
|
|
#undef StringCchCopyNExX
|
|
#undef PathCchAppendX
|
|
#undef PathCchCombineX
|
|
#undef PathCchAddBackslashX
|
|
#undef PathCchAddExtensionX
|
|
#undef PathCchRenameExtensionX
|
|
#undef PathCchCanonicalizeX
|
|
#undef lstrlenX
|
|
#undef PathIsRelativeX
|
|
#undef PathIsRootX
|
|
#undef PathIsUNCX
|
|
#undef PathStripToRootX
|
|
#undef PathFindExtensionX
|
|
#undef StrChrX
|
|
#undef StrRChrX
|
|
#undef c_szDotExeX
|
|
#undef WUGetPCEndX
|
|
#undef WUGetPCStartX
|
|
#undef WUNearRootFixupsX
|
|
|
|
#if defined(SAFEPATH_UNICODEPASS)
|
|
|
|
static const WCHAR c_szDotExeW[] = L".exe";
|
|
|
|
// define Unicode versions
|
|
#define XCHAR WCHAR
|
|
#define _X(ch) L ## ch
|
|
#define LPXSTR LPWSTR
|
|
#define LPCXSTR LPCWSTR
|
|
#define StringCchCatExX StringCchCatExW
|
|
#define StringCchCopyExX StringCchCopyExW
|
|
#define StringCchCopyNExX StringCchCopyNExW
|
|
#define PathCchAppendX PathCchAppendW
|
|
#define PathCchCombineX PathCchCombineW
|
|
#define PathCchAddBackslashX PathCchAddBackslashW
|
|
#define PathCchAddExtensionX PathCchAddExtensionW
|
|
#define PathCchRenameExtensionX PathCchRenameExtensionW
|
|
#define PathCchCanonicalizeX PathCchCanonicalizeW
|
|
#define PathIsRelativeX PathIsRelativeW
|
|
#define PathIsRootX PathIsRootW
|
|
#define PathIsUNCX PathIsUNCW
|
|
#define PathStripToRootX PathStripToRootW
|
|
#define PathFindExtensionX PathFindExtensionW
|
|
#define StrChrX StrChrW
|
|
#define StrRChrX StrRChrW
|
|
#define lstrlenX lstrlenW
|
|
#define c_szDotExeX c_szDotExeW
|
|
#define WUGetPCEndX WUGetPCEndW
|
|
#define WUGetPCStartX WUGetPCStartW
|
|
#define WUNearRootFixupsX WUNearRootFixupsW
|
|
|
|
#else
|
|
|
|
static const CHAR c_szDotExeA[] = ".exe";
|
|
|
|
// define ANSI versions
|
|
#define XCHAR char
|
|
#define _X(ch) ch
|
|
#define LPXSTR LPSTR
|
|
#define LPCXSTR LPCSTR
|
|
#define StringCchCatExX StringCchCatExA
|
|
#define StringCchCopyExX StringCchCopyExA
|
|
#define StringCchCopyNExX StringCchCopyNExA
|
|
#define PathCchAppendX PathCchAppendA
|
|
#define PathCchCombineX PathCchCombineA
|
|
#define PathCchAddBackslashX PathCchAddBackslashA
|
|
#define PathCchAddExtensionX PathCchAddExtensionA
|
|
#define PathCchRenameExtensionX PathCchRenameExtensionA
|
|
#define PathCchCanonicalizeX PathCchCanonicalizeA
|
|
#define PathIsRelativeX PathIsRelativeA
|
|
#define PathIsRootX PathIsRootA
|
|
#define PathIsUNCX PathIsUNCA
|
|
#define PathStripToRootX PathStripToRootA
|
|
#define PathFindExtensionX PathFindExtensionA
|
|
#define StrChrX StrChrA
|
|
#define StrRChrX StrRChrA
|
|
#define lstrlenX lstrlenA
|
|
#define c_szDotExeX c_szDotExeA
|
|
#define WUGetPCEndX WUGetPCEndA
|
|
#define WUGetPCStartX WUGetPCStartA
|
|
#define WUNearRootFixupsX WUNearRootFixupsA
|
|
|
|
#endif
|
|
|
|
|
|
#define SAFEPATH_STRING_FLAGS (MISTSAFE_STRING_FLAGS | STRSAFE_NO_TRUNCATION)
|
|
#define CH_WHACK _X('\\')
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Utility functions
|
|
|
|
// **************************************************************************
|
|
// Return a pointer to the end of the next path componenent in the string.
|
|
// ie return a pointer to the next backslash or terminating NULL.
|
|
static inline
|
|
LPCXSTR WUGetPCEndX(LPCXSTR pszStart)
|
|
{
|
|
LPCXSTR pszEnd;
|
|
pszEnd = StrChrX(pszStart, CH_WHACK);
|
|
if (pszEnd == NULL)
|
|
pszEnd = pszStart + lstrlenX(pszStart);
|
|
return pszEnd;
|
|
}
|
|
|
|
// **************************************************************************
|
|
// 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).
|
|
static inline
|
|
LPXSTR WUGetPCStartX(LPXSTR pszStart, LPCXSTR pszCurrent)
|
|
{
|
|
LPXSTR pszBegin;
|
|
pszBegin = StrRChrX(pszStart, pszCurrent, CH_WHACK);
|
|
if (pszBegin == NULL)
|
|
pszBegin = pszStart;
|
|
return pszBegin;
|
|
}
|
|
|
|
// **************************************************************************
|
|
// Fix up a few special cases so that things roughly make sense.
|
|
static inline
|
|
void WUNearRootFixupsX(LPXSTR pszPath, DWORD cchPath, BOOL fUNC)
|
|
{
|
|
// Empty path?
|
|
if (cchPath > 1 && pszPath[0] == _X('\0'))
|
|
{
|
|
pszPath[0] = CH_WHACK;
|
|
pszPath[1] = _X('\0');
|
|
}
|
|
|
|
// Missing slash? (In the case of ANSI, be sure to check if the first
|
|
// character is a lead byte
|
|
else if (cchPath > 3 &&
|
|
#if !defined(SAFEPATH_UNICODEPASS)
|
|
IsDBCSLeadByte(pszPath[0]) == FALSE &&
|
|
#endif
|
|
pszPath[1] == _X(':') && pszPath[2] == _X('\0'))
|
|
{
|
|
pszPath[2] = _X('\\');
|
|
pszPath[3] = _X('\0');
|
|
}
|
|
|
|
// UNC root?
|
|
else if (cchPath > 2 &&
|
|
fUNC &&
|
|
pszPath[0] == _X('\\') && pszPath[1] == _X('\0'))
|
|
{
|
|
pszPath[1] = _X('\\');
|
|
pszPath[2] = _X('\0');
|
|
}
|
|
}
|
|
|
|
// **************************************************************************
|
|
static inline
|
|
LPXSTR AllocNewDest(LPXSTR pszDest, DWORD cchDest, LPXSTR *ppchDest, LPXSTR *ppszMax)
|
|
{
|
|
HRESULT hr;
|
|
LPXSTR pszNewDest = NULL;
|
|
DWORD cchToCopy;
|
|
|
|
pszNewDest = (LPXSTR)HeapAlloc(GetProcessHeap(), 0, cchDest * sizeof(XCHAR));
|
|
if (pszNewDest == NULL)
|
|
{
|
|
SetLastError(ERROR_OUTOFMEMORY);
|
|
goto done;
|
|
}
|
|
|
|
cchToCopy = (DWORD)(DWORD_PTR)(*ppchDest - pszDest);
|
|
|
|
hr = StringCchCopyNExX(pszNewDest, cchDest, pszDest, cchToCopy,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, pszNewDest);
|
|
SetLastError(HRESULT_CODE(hr));
|
|
pszNewDest = NULL;
|
|
goto done;
|
|
}
|
|
|
|
*ppchDest = pszNewDest + cchToCopy;
|
|
*ppszMax = pszNewDest + cchDest - 1;
|
|
|
|
done:
|
|
return pszNewDest;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Exported functions
|
|
|
|
// **************************************************************************
|
|
HRESULT PathCchCanonicalizeX(LPXSTR pszDest, DWORD cchDest, LPCXSTR pszSrc)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
LPCXSTR pchSrc, pchPCEnd;
|
|
LPXSTR pszMax = pszDest + cchDest - 1;
|
|
LPXSTR pchDest;
|
|
LPXSTR pszDestReal = pszDest;
|
|
DWORD cchPC;
|
|
BOOL fUNC, fRoot;
|
|
|
|
if (pszDest == NULL || cchDest == 0 || pszSrc == NULL)
|
|
{
|
|
hr = STRSAFE_E_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
pchSrc = pszSrc;
|
|
pchDest = pszDestReal;
|
|
|
|
// Need to keep track of whether we have a UNC path so we can potentially
|
|
// fix it up below
|
|
fUNC = PathIsUNCX(pszSrc);
|
|
|
|
while (*pchSrc != _T('\0'))
|
|
{
|
|
pchPCEnd = WUGetPCEndX(pchSrc);
|
|
cchPC = (DWORD)(DWORD_PTR)(pchPCEnd - pchSrc) + 1;
|
|
|
|
// is it a backslash?
|
|
if (cchPC == 1 && *pchSrc == CH_WHACK)
|
|
{
|
|
if (pchDest + 1 > pszMax)
|
|
{
|
|
// source string too big for the buffer. Put a NULL at the end
|
|
// to ensure that it is NULL terminated.
|
|
pszDestReal[cchDest - 1] = 0;
|
|
hr = STRSAFE_E_INSUFFICIENT_BUFFER;
|
|
goto done;
|
|
}
|
|
|
|
// Just copy it.
|
|
*pchDest++ = CH_WHACK;
|
|
pchSrc++;
|
|
}
|
|
|
|
// ok, how about a dot?
|
|
else if (cchPC == 2 && *pchSrc == _X('.'))
|
|
{
|
|
if (pszDest == pszSrc && pszDestReal == pszDest)
|
|
{
|
|
pszDestReal = AllocNewDest(pszDest, cchDest, &pchDest, &pszMax);
|
|
if (pszDestReal == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// Are we at the end?
|
|
if (*(pchSrc + 1) == 0)
|
|
{
|
|
pchSrc++;
|
|
|
|
// remove the last slash we copied (if we've copied one), but
|
|
// don't make a malformed root
|
|
if (pchDest > pszDestReal && PathIsRootX(pszDestReal) == FALSE)
|
|
pchDest--;
|
|
}
|
|
else
|
|
{
|
|
pchSrc += 2;
|
|
}
|
|
}
|
|
|
|
// any double dots?
|
|
else if (cchPC == 3 && *pchSrc == _X('.') && *(pchSrc + 1) == _X('.'))
|
|
{
|
|
if (pszDest == pszSrc && pszDestReal == pszDest)
|
|
{
|
|
pszDestReal = AllocNewDest(pszDest, cchDest, &pchDest, &pszMax);
|
|
if (pszDestReal == NULL)
|
|
{
|
|
hr = HRESULT_FROM_WIN32(GetLastError());
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
// make sure we aren't already at the root. If not, just remove
|
|
// the previous path component
|
|
if (PathIsRootX(pszDestReal) == FALSE)
|
|
{
|
|
pchDest = WUGetPCStartX(pszDestReal, pchDest - 1);
|
|
}
|
|
|
|
// we are at the root- however, we must make sure to skip the
|
|
// backslash at the end of the ..\ so we don't copy another
|
|
// one (otherwise, C:\..\FOO would become C:\\FOO)
|
|
else
|
|
{
|
|
if (*(pchSrc + 2) == CH_WHACK)
|
|
pchSrc++;
|
|
}
|
|
|
|
// skip ".."
|
|
pchSrc += 2;
|
|
}
|
|
|
|
// just choose 'none of the above'...
|
|
else
|
|
{
|
|
if (pchDest != pchSrc)
|
|
{
|
|
DWORD cchAvail;
|
|
|
|
cchAvail = cchDest - (DWORD)(DWORD_PTR)(pchDest - pszDestReal);
|
|
|
|
hr = StringCchCopyNExX(pchDest, cchAvail, pchSrc, cchPC,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
}
|
|
|
|
pchDest += (cchPC - 1);
|
|
pchSrc += (cchPC - 1);
|
|
}
|
|
|
|
// make sure we always have a NULL terminated string
|
|
if (pszDestReal != pszSrc)
|
|
*pchDest = _X('\0');
|
|
}
|
|
|
|
// Check for weirdo root directory stuff.
|
|
WUNearRootFixupsX(pszDestReal, cchDest, fUNC);
|
|
|
|
if (pszDest != pszDestReal)
|
|
{
|
|
hr = StringCchCopyExX(pszDest, cchDest, pszDestReal,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
}
|
|
|
|
done:
|
|
if (pszDest != pszDestReal && pszDestReal != NULL)
|
|
HeapFree(GetProcessHeap(), 0, pszDestReal);
|
|
|
|
return hr;
|
|
}
|
|
|
|
// **************************************************************************
|
|
HRESULT PathCchRenameExtensionX(LPXSTR pszPath, DWORD cchPath, LPCXSTR pszExt)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
LPXSTR pszOldExt;
|
|
DWORD cchPathWithoutExt;
|
|
|
|
if (pszPath == NULL || pszExt == NULL)
|
|
{
|
|
hr = STRSAFE_E_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
// This function returns a pointer to the end of the string if there
|
|
// is no extension. This is exactly what we want cuz we will want
|
|
// to add an extension to the end of the string if none exists.
|
|
pszOldExt = PathFindExtensionX(pszPath);
|
|
cchPathWithoutExt = (DWORD)(DWORD_PTR)(pszOldExt - pszPath);
|
|
|
|
hr = StringCchCopyExX(pszOldExt, cchPath - cchPathWithoutExt, pszExt,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
// **************************************************************************
|
|
HRESULT PathCchAddExtensionX(LPXSTR pszPath, DWORD cchPath, LPCXSTR pszExt)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
LPXSTR pszOldExt;
|
|
|
|
if (pszPath == NULL)
|
|
{
|
|
hr = STRSAFE_E_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
// since we're *adding* an extension here, don't want to do anything if
|
|
// one already exists
|
|
pszOldExt = PathFindExtensionX(pszPath);
|
|
if (*pszOldExt == _T('\0'))
|
|
{
|
|
if (pszExt == NULL)
|
|
pszExt = c_szDotExeX;
|
|
|
|
hr = StringCchCatExX(pszPath, cchPath, pszExt,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
// **************************************************************************
|
|
HRESULT PathCchAddBackslashX(LPXSTR pszPath, DWORD cchPathBuff)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
LPCXSTR psz;
|
|
DWORD cch;
|
|
|
|
if (pszPath == NULL)
|
|
{
|
|
hr = STRSAFE_E_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
cch = lstrlenX(pszPath);
|
|
|
|
if (cch == 0)
|
|
goto done;
|
|
|
|
#if defined(SAFEPATH_UNICODEPASS)
|
|
psz = &pszPath[cch - 1];
|
|
#else
|
|
psz = CharPrevA(pszPath, &pszPath[cch]);
|
|
#endif
|
|
|
|
// if the end of the base string does not have a backslash, then add one
|
|
if (*psz != CH_WHACK)
|
|
{
|
|
// make sure we have enough space for the backslash in the buffer
|
|
if (cch + 1 >= cchPathBuff)
|
|
{
|
|
hr = STRSAFE_E_INSUFFICIENT_BUFFER;
|
|
goto done;
|
|
}
|
|
|
|
pszPath[cch++] = CH_WHACK;
|
|
pszPath[cch] = _X('\0');
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
// **************************************************************************
|
|
HRESULT PathCchCombineX(LPXSTR pszPath, DWORD cchPathBuff, LPCXSTR pszPrefix,
|
|
LPCXSTR pszSuffix)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
|
|
if (pszPath == NULL || cchPathBuff == 0)
|
|
{
|
|
hr = STRSAFE_E_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
|
|
// if both fields are NULL, just bail now.
|
|
if (pszPrefix == NULL && pszSuffix == NULL)
|
|
{
|
|
pszPath[0] = L'\0';
|
|
goto done;
|
|
}
|
|
|
|
if ((pszPrefix == NULL || *pszPrefix == _X('\0')) &&
|
|
(pszSuffix == NULL || *pszSuffix == _X('\0')))
|
|
{
|
|
if (cchPathBuff > 1)
|
|
{
|
|
pszPath[0] = _X('\\');
|
|
pszPath[1] = _X('\0');
|
|
}
|
|
else
|
|
{
|
|
hr = STRSAFE_E_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
goto done;
|
|
}
|
|
|
|
// if all we have is the suffix, just copy it
|
|
if (pszPrefix == NULL || *pszPrefix == _X('\0'))
|
|
{
|
|
hr = StringCchCopyExX(pszPath, cchPathBuff, pszSuffix,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
}
|
|
else
|
|
{
|
|
// if all we have is the prefix, just copy it
|
|
if (pszSuffix == NULL || *pszSuffix == _X('\0'))
|
|
{
|
|
hr = StringCchCopyExX(pszPath, cchPathBuff, pszPrefix,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
}
|
|
|
|
// if we have a relative path for the suffix, then we just combine
|
|
// the two and insert a backslash between them if necessary
|
|
else if (PathIsRelativeX(pszSuffix))
|
|
{
|
|
hr = StringCchCopyExX(pszPath, cchPathBuff, pszPrefix,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
hr = PathCchAddBackslashX(pszPath, cchPathBuff);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
hr = StringCchCatExX(pszPath, cchPathBuff, pszSuffix,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
}
|
|
|
|
// if the suffix starts with a backslash then just strip off
|
|
// everything except for the root of the prefix and append the
|
|
// suffix
|
|
else if (*pszSuffix == CH_WHACK && PathIsUNCX(pszSuffix) == FALSE)
|
|
{
|
|
hr = StringCchCopyExX(pszPath, cchPathBuff, pszPrefix,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
// this is safe to call as it will only reduce the size of the
|
|
// string
|
|
PathStripToRootX(pszPath);
|
|
|
|
hr = PathCchAddBackslashX(pszPath, cchPathBuff);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
|
|
// make sure to skip the backslash while appending
|
|
hr = StringCchCatExX(pszPath, cchPathBuff, pszSuffix + 1,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
}
|
|
|
|
// we'll, likely the suffix is a full path (local or UNC), so
|
|
// ignore the prefix
|
|
else
|
|
{
|
|
hr = StringCchCopyExX(pszPath, cchPathBuff, pszSuffix,
|
|
NULL, NULL, SAFEPATH_STRING_FLAGS);
|
|
if (FAILED(hr))
|
|
goto done;
|
|
}
|
|
}
|
|
|
|
hr = PathCchCanonicalizeX(pszPath, cchPathBuff, pszPath);
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
// **************************************************************************
|
|
HRESULT PathCchAppendX(LPXSTR pszPath, DWORD cchPathBuff, LPCXSTR pszNew)
|
|
{
|
|
HRESULT hr = NOERROR;
|
|
DWORD dwOffset = 0;
|
|
DWORD cch, cchNew;
|
|
|
|
if (pszPath == NULL)
|
|
{
|
|
hr = STRSAFE_E_INVALID_PARAMETER;
|
|
goto done;
|
|
}
|
|
|
|
if (pszNew != NULL)
|
|
{
|
|
// skip all initial backslashes in pszNew
|
|
while (*pszNew == CH_WHACK)
|
|
{
|
|
pszNew++;
|
|
}
|
|
|
|
hr = PathCchCombineX(pszPath, cchPathBuff, pszPath, pszNew);
|
|
|
|
}
|
|
else
|
|
{
|
|
hr = E_FAIL;
|
|
}
|
|
|
|
done:
|
|
return hr;
|
|
}
|
|
|
|
|
|
|
|
|
|
// make the unicode pass through the file
|
|
#if !defined(SAFEPATH_UNICODEPASS)
|
|
#define SAFEPATH_UNICODEPASS
|
|
#include "safepath.cpp"
|
|
#endif
|
|
|
|
|