545 lines
14 KiB
C++
545 lines
14 KiB
C++
//+---------------------------------------------------------------------------
|
|
//
|
|
// Microsoft Windows
|
|
// Copyright (C) Microsoft Corporation, 1992 - 1994.
|
|
//
|
|
// File: longpath.cxx
|
|
//
|
|
// Contents: GetLongPathName implementation
|
|
//
|
|
// History: 25-Aug-94 DrewB Created from Win32 sources for
|
|
// GetShortPathName
|
|
// 06-Sep-94 DrewB Rewrote using Win32 for portability
|
|
// 11-Nov-94 BruceMa Use this version for both Chicago
|
|
// and NT
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
#include <ole2int.h>
|
|
#include <widewrap.h>
|
|
#include <longname.h>
|
|
|
|
#define ARGUMENT_PRESENT2(p) ((p) != NULL)
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: IsLongComponent, public
|
|
//
|
|
// Synopsis: Determines whether the current path component is a legal
|
|
// 8.3 name or not. If not, it is considered to be a long
|
|
// component.
|
|
//
|
|
// Arguments: [pwcsPath] - Path to check
|
|
// [ppwcsEnd] - Return for end of component pointer
|
|
//
|
|
// Returns: BOOL
|
|
//
|
|
// Modifies: [ppwcsEnd]
|
|
//
|
|
// History: 28-Aug-94 DrewB Created
|
|
//
|
|
// Notes: An empty path is considered to be long
|
|
// The following characters are not valid in file name domain:
|
|
// * + , : ; < = > ? [ ] |
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
BOOL IsLongComponent(LPCWSTR pwcsPath,
|
|
PWSTR *ppwcsEnd)
|
|
{
|
|
LPWSTR pwcEnd, pwcDot;
|
|
BOOL fLongNameFound;
|
|
WCHAR wc;
|
|
|
|
pwcEnd = (LPWSTR)pwcsPath;
|
|
fLongNameFound = FALSE;
|
|
pwcDot = NULL;
|
|
|
|
while (TRUE)
|
|
{
|
|
wc = *pwcEnd;
|
|
|
|
if (wc == L'\\' || wc == 0)
|
|
{
|
|
*ppwcsEnd = pwcEnd;
|
|
|
|
// We're at a component terminator, so make the
|
|
// determination of whether what we've seen is a long
|
|
// name or short one
|
|
|
|
// If we've aready seen illegal characters or invalid
|
|
// structure for a short name, don't bother to check lengths
|
|
if (pwcEnd-pwcsPath > 0 && !fLongNameFound)
|
|
{
|
|
// If this component fits in 8.3 then it is a short name
|
|
if ((!pwcDot && (ULONG)(pwcEnd - pwcsPath) <= 8) ||
|
|
(pwcDot && ((ULONG)(pwcEnd - pwcDot) <= 3 + 1 &&
|
|
(ULONG)(pwcEnd - pwcsPath) <= 8 + 3 + 1)))
|
|
{
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
// Handle dots
|
|
if (wc == L'.')
|
|
{
|
|
// If two or more '.' or the base name is longer than
|
|
// 8 characters or no base name at all, it is an illegal dos
|
|
// file name
|
|
if (pwcDot != NULL ||
|
|
((ULONG)(pwcEnd - pwcsPath)) > 8 ||
|
|
(pwcEnd == pwcsPath && *(pwcEnd + 1) != L'\\'))
|
|
{
|
|
fLongNameFound = TRUE;
|
|
}
|
|
|
|
pwcDot = pwcEnd;
|
|
}
|
|
|
|
// Check for characters which aren't valid in short names
|
|
else if (wc <= L' ' ||
|
|
wc == L'*' ||
|
|
wc == L'+' ||
|
|
wc == L',' ||
|
|
wc == L':' ||
|
|
wc == L';' ||
|
|
wc == L'<' ||
|
|
wc == L'=' ||
|
|
wc == L'>' ||
|
|
wc == L'?' ||
|
|
wc == L'[' ||
|
|
wc == L']' ||
|
|
wc == L'|')
|
|
{
|
|
fLongNameFound = TRUE;
|
|
}
|
|
|
|
pwcEnd++;
|
|
}
|
|
}
|
|
|
|
//
|
|
// The following code was stolen from NT's RTL in curdir.c
|
|
//
|
|
|
|
#define IS_PATH_SEPARATOR(wch) \
|
|
((wch) == L'\\' || (wch) == L'/')
|
|
|
|
typedef enum
|
|
{
|
|
PATH_TYPE_UNKNOWN,
|
|
PATH_TYPE_UNC_ABSOLUTE,
|
|
PATH_TYPE_LOCAL_DEVICE,
|
|
PATH_TYPE_ROOT_LOCAL_DEVICE,
|
|
PATH_TYPE_DRIVE_ABSOLUTE,
|
|
PATH_TYPE_DRIVE_RELATIVE,
|
|
PATH_TYPE_ROOTED,
|
|
PATH_TYPE_RELATIVE
|
|
} PATH_TYPE;
|
|
|
|
PATH_TYPE
|
|
DetermineDosPathNameType(
|
|
IN PCWSTR DosFileName
|
|
)
|
|
|
|
/*++
|
|
|
|
Routine Description:
|
|
|
|
This function examines the Dos format file name and determines the
|
|
type of file name (i.e. UNC, DriveAbsolute, Current Directory
|
|
rooted, or Relative.
|
|
|
|
Arguments:
|
|
|
|
DosFileName - Supplies the Dos format file name whose type is to be
|
|
determined.
|
|
|
|
Return Value:
|
|
|
|
PATH_TYPE_UNKNOWN - The path type can not be determined
|
|
|
|
PATH_TYPE_UNC_ABSOLUTE - The path specifies a Unc absolute path
|
|
in the format \\server-name\sharename\rest-of-path
|
|
|
|
PATH_TYPE_LOCAL_DEVICE - The path specifies a local device in the format
|
|
\\.\rest-of-path this can be used for any device where the nt and
|
|
Win32 names are the same. For example mailslots.
|
|
|
|
PATH_TYPE_ROOT_LOCAL_DEVICE - The path specifies the root of the local
|
|
devices in the format \\.
|
|
|
|
PATH_TYPE_DRIVE_ABSOLUTE - The path specifies a drive letter absolute
|
|
path in the form drive:\rest-of-path
|
|
|
|
PATH_TYPE_DRIVE_RELATIVE - The path specifies a drive letter relative
|
|
path in the form drive:rest-of-path
|
|
|
|
PATH_TYPE_ROOTED - The path is rooted relative to the current disk
|
|
designator (either Unc disk, or drive). The form is \rest-of-path.
|
|
|
|
PATH_TYPE_RELATIVE - The path is relative (i.e. not absolute or rooted).
|
|
|
|
--*/
|
|
|
|
{
|
|
PATH_TYPE ReturnValue;
|
|
|
|
if ( IS_PATH_SEPARATOR(*DosFileName) )
|
|
{
|
|
if ( IS_PATH_SEPARATOR(*(DosFileName+1)) )
|
|
{
|
|
if ( DosFileName[2] == L'.' )
|
|
{
|
|
if ( IS_PATH_SEPARATOR(*(DosFileName+3)) )
|
|
{
|
|
ReturnValue = PATH_TYPE_LOCAL_DEVICE;
|
|
}
|
|
else if ( (*(DosFileName+3)) == 0 )
|
|
{
|
|
ReturnValue = PATH_TYPE_ROOT_LOCAL_DEVICE;
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = PATH_TYPE_UNC_ABSOLUTE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = PATH_TYPE_UNC_ABSOLUTE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = PATH_TYPE_ROOTED;
|
|
}
|
|
}
|
|
else if (*(DosFileName+1) == L':')
|
|
{
|
|
if (IS_PATH_SEPARATOR(*(DosFileName+2)))
|
|
{
|
|
ReturnValue = PATH_TYPE_DRIVE_ABSOLUTE;
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = PATH_TYPE_DRIVE_RELATIVE;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = PATH_TYPE_RELATIVE;
|
|
}
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
//+---------------------------------------------------------------------------
|
|
//
|
|
// Function: GetLongPathName, public
|
|
//
|
|
// Synopsis: Expand each component of the given path into its
|
|
// long form
|
|
//
|
|
// Arguments: [pwcsPath] - Path
|
|
// [pwcsLongPath] - Long path return buffer
|
|
// [cchLongPath] - Size of return buffer in characters
|
|
//
|
|
// Returns: 0 for errors
|
|
// Number of characters needed for buffer if buffer is too small
|
|
// includes NULL terminator
|
|
// Length of long path, doesn't include NULL terminator
|
|
//
|
|
// Modifies: [pwcsLongPath]
|
|
//
|
|
// History: 28-Aug-94 DrewB Created
|
|
// 11-Nov-94 BruceMa Modifed to use for Chicago at
|
|
// FindFirstFile
|
|
//
|
|
// Notes: The source and destination buffers can be the same memory
|
|
// Doesn't handle paths with internal . and .., although
|
|
// they are handled at the beginning
|
|
//
|
|
//----------------------------------------------------------------------------
|
|
|
|
ULONG
|
|
APIENTRY
|
|
GetLongPathNameW(LPCWSTR pwcsPath,
|
|
LPWSTR pwcsLongPath,
|
|
ULONG cchLongPath)
|
|
{
|
|
PATH_TYPE pt;
|
|
HANDLE h;
|
|
LPWSTR pwcsLocalLongPath;
|
|
ULONG cchReturn, cb, cch, cchOutput;
|
|
LPWSTR pwcStart = NULL;
|
|
LPWSTR pwcEnd;
|
|
LPWSTR pwcLong;
|
|
WCHAR wcSave;
|
|
BOOL fLong;
|
|
WIN32_FIND_DATA wfd;
|
|
cchReturn = 0;
|
|
pwcsLocalLongPath = NULL;
|
|
|
|
|
|
if (!ARGUMENT_PRESENT2(pwcsPath))
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
__try
|
|
{
|
|
//
|
|
// First, run down the string checking for tilde's. Any path
|
|
// that has a short name section to it will have a tilde. If
|
|
// there are no tilde's, then we already have the long path,
|
|
// so we can return the string.
|
|
//
|
|
fLong = TRUE;
|
|
for (pwcLong = (LPWSTR)pwcsPath; *pwcLong != 0; pwcLong++)
|
|
{
|
|
if (*pwcLong == L'~')
|
|
{
|
|
fLong = FALSE;
|
|
}
|
|
}
|
|
//
|
|
// This derives the number of characters, including the NULL
|
|
//
|
|
cch = (pwcLong - pwcsPath) + 1;
|
|
|
|
//
|
|
// If it isn't a long path already, then we are going to have
|
|
// to parse it.
|
|
//
|
|
if (!fLong)
|
|
{
|
|
// Decide the path type, we want find out the position of
|
|
// the first character of the first name
|
|
pt = DetermineDosPathNameType(pwcsPath);
|
|
switch(pt)
|
|
{
|
|
// Form: "\\server_name\share_name\rest_of_the_path"
|
|
case PATH_TYPE_UNC_ABSOLUTE:
|
|
if ((pwcStart = wcschr(pwcsPath + 2, L'\\')) != NULL &&
|
|
(pwcStart = wcschr(pwcStart + 1, L'\\')) != NULL)
|
|
{
|
|
pwcStart++;
|
|
}
|
|
else
|
|
{
|
|
pwcStart = NULL;
|
|
}
|
|
break;
|
|
|
|
// Form: "\\.\rest_of_the_path"
|
|
case PATH_TYPE_LOCAL_DEVICE:
|
|
pwcStart = (LPWSTR)pwcsPath + 4;
|
|
break;
|
|
|
|
// Form: "\\."
|
|
case PATH_TYPE_ROOT_LOCAL_DEVICE:
|
|
pwcStart = NULL;
|
|
break;
|
|
|
|
// Form: "D:\rest_of_the_path"
|
|
case PATH_TYPE_DRIVE_ABSOLUTE:
|
|
pwcStart = (LPWSTR)pwcsPath + 3;
|
|
break;
|
|
|
|
// Form: "rest_of_the_path"
|
|
case PATH_TYPE_RELATIVE:
|
|
pwcStart = (LPWSTR) pwcsPath;
|
|
goto EatDots;
|
|
|
|
// Form: "D:rest_of_the_path"
|
|
case PATH_TYPE_DRIVE_RELATIVE:
|
|
pwcStart = (LPWSTR)pwcsPath+2;
|
|
|
|
EatDots:
|
|
// Handle .\ and ..\ cases
|
|
while (*pwcStart != 0 && *pwcStart == L'.')
|
|
{
|
|
if (pwcStart[1] == L'\\')
|
|
{
|
|
pwcStart += 2;
|
|
}
|
|
else if (pwcStart[1] == L'.' && pwcStart[2] == L'\\')
|
|
{
|
|
pwcStart += 3;
|
|
}
|
|
else
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
|
|
// Form: "\rest_of_the_path"
|
|
case PATH_TYPE_ROOTED:
|
|
pwcStart = (LPWSTR)pwcsPath + 1;
|
|
break;
|
|
|
|
default:
|
|
pwcStart = NULL;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// In the special case where we have no work to do, exit quickly
|
|
// This saves a lot of instructions for trivial cases
|
|
// In one case the path as given requires no processing
|
|
// The middle case, we determine there were no tilde's in the path
|
|
// In the other, the path only has one component and it is already
|
|
// long
|
|
///
|
|
if (pwcStart == NULL ||
|
|
(fLong == TRUE) ||
|
|
((fLong = IsLongComponent(pwcStart, &pwcEnd)) &&
|
|
*pwcEnd == 0))
|
|
{
|
|
// Nothing to convert, copy down the source string
|
|
// to the buffer if necessary
|
|
|
|
if (pwcStart != NULL)
|
|
{
|
|
cch = (ULONG)(pwcEnd - pwcsPath + 1);
|
|
}
|
|
|
|
if (cchLongPath >= cch)
|
|
{
|
|
// If there's an output buffer which is different from
|
|
// the input buffer, fill it in
|
|
if (ARGUMENT_PRESENT2(pwcsLongPath) &&
|
|
pwcsLongPath != pwcsPath)
|
|
{
|
|
memcpy(pwcsLongPath, pwcsPath, cch * sizeof(WCHAR));
|
|
}
|
|
|
|
cchReturn = cch - 1;
|
|
goto gsnTryExit;
|
|
}
|
|
else
|
|
{
|
|
cchReturn = cch;
|
|
goto gsnTryExit;
|
|
}
|
|
}
|
|
|
|
// Make a local buffer so that we won't overlap the
|
|
// source pathname in case the long name is longer than the
|
|
// source name.
|
|
if (cchLongPath > 0 && ARGUMENT_PRESENT2(pwcsLongPath))
|
|
{
|
|
pwcsLocalLongPath = (PWCHAR)PrivMemAlloc(cchLongPath * sizeof(WCHAR));
|
|
if (pwcsLocalLongPath == NULL)
|
|
{
|
|
goto gsnTryExit;
|
|
}
|
|
}
|
|
|
|
// Set up pointer to copy output to
|
|
pwcLong = pwcsLocalLongPath;
|
|
cchOutput = 0;
|
|
|
|
// Copy the portions of the path that we skipped initially
|
|
cch = pwcStart-pwcsPath;
|
|
cchOutput += cch;
|
|
if (cchOutput <= cchLongPath && ARGUMENT_PRESENT2(pwcsLongPath))
|
|
{
|
|
memcpy(pwcLong, pwcsPath, cch*sizeof(WCHAR));
|
|
pwcLong += cch;
|
|
}
|
|
|
|
for (;;)
|
|
{
|
|
// Determine whether the current component is long or short
|
|
cch = pwcEnd-pwcStart+1;
|
|
cb = cch*sizeof(WCHAR);
|
|
|
|
if (fLong)
|
|
{
|
|
// If the component is already long, just copy it into
|
|
// the output. Copy the terminating character along with it
|
|
// so the output remains properly punctuated
|
|
|
|
cchOutput += cch;
|
|
if (cchOutput <= cchLongPath && ARGUMENT_PRESENT2(pwcsLongPath))
|
|
{
|
|
memcpy(pwcLong, pwcStart, cb);
|
|
pwcLong += cch;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
WCHAR wcsTmp[MAX_PATH];
|
|
|
|
// For a short component we need to determine the
|
|
// long name, if there is one. The only way to
|
|
// do this reliably is to enumerate for the child
|
|
|
|
wcSave = *pwcEnd;
|
|
*pwcEnd = 0;
|
|
|
|
h = FindFirstFile(pwcsPath, &wfd);
|
|
*pwcEnd = wcSave;
|
|
|
|
if (h == INVALID_HANDLE_VALUE)
|
|
{
|
|
goto gsnTryExit;
|
|
}
|
|
|
|
FindClose(h);
|
|
|
|
lstrcpyW(wcsTmp, wfd.cFileName);
|
|
|
|
// Copy the filename returned by the query into the output
|
|
// Copy the terminator from the original component into
|
|
// the output to maintain punctuation
|
|
cch = lstrlenW(wcsTmp)+1;
|
|
cchOutput += cch;
|
|
if (cchOutput <= cchLongPath && ARGUMENT_PRESENT2(pwcsLongPath))
|
|
{
|
|
memcpy(pwcLong, wcsTmp, (cch-1)*sizeof(WCHAR));
|
|
pwcLong += cch;
|
|
*(pwcLong-1) = *pwcEnd;
|
|
}
|
|
}
|
|
|
|
if (*pwcEnd == 0)
|
|
{
|
|
break;
|
|
}
|
|
|
|
// Update start pointer to next component
|
|
pwcStart = pwcEnd+1;
|
|
fLong = IsLongComponent(pwcStart, &pwcEnd);
|
|
}
|
|
|
|
// Copy local output buffer to given output buffer if necessary
|
|
if (cchLongPath >= cchOutput && ARGUMENT_PRESENT2(pwcsLongPath))
|
|
{
|
|
memcpy(pwcsLongPath, pwcsLocalLongPath, cchOutput * sizeof(WCHAR));
|
|
cchReturn = cchOutput-1;
|
|
}
|
|
else
|
|
{
|
|
cchReturn = cchOutput;
|
|
}
|
|
|
|
gsnTryExit:;
|
|
}
|
|
__finally
|
|
{
|
|
if (pwcsLocalLongPath != NULL)
|
|
{
|
|
PrivMemFree(pwcsLocalLongPath);
|
|
pwcsLocalLongPath = NULL;
|
|
}
|
|
}
|
|
|
|
return cchReturn;
|
|
}
|