|
|
/******************************************************************************
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
|