windows-nt/Source/XPSP1/NT/ds/netapi/netlib/canon.c
2020-09-26 16:20:57 +08:00

561 lines
14 KiB
C

/*++
Copyright (c) 1991-92 Microsoft Corporation
Module Name:
canon.c
Abstract:
Code to 'canonicalize' a path name. This code may be replaced by OS or FS
code sometime in the future, so keep it separate from the rest of the net
canonicalization code.
We do not canonicalize a path with reference to a specific drive. Therefore,
we can't use rules about the number of characters or format of a path
component (eg. FAT filename rules). We leave this up to the file system. The
CanonicalizePathName function in this module will make a path name look
presentable, nothing more
Contents:
CanonicalizePathName
(ConvertPathCharacters)
(ParseLocalDevicePrefix)
(ConvertPathMacros)
(BackUpPath)
Author:
Richard L Firth (rfirth) 02-Jan-1992
Revision History:
--*/
#include "nticanon.h"
#include <tstring.h> // NetpInitOemString().
const TCHAR text_AUX[] = TEXT("AUX");
const TCHAR text_COM[] = TEXT("COM");
const TCHAR text_DEV[] = TEXT("DEV");
const TCHAR text_LPT[] = TEXT("LPT");
const TCHAR text_PRN[] = TEXT("PRN");
//
// prototypes
//
STATIC
VOID
ConvertPathCharacters(
IN LPTSTR Path
);
STATIC
BOOL
ConvertDeviceName(
IN OUT LPTSTR PathName
);
STATIC
BOOL
ParseLocalDevicePrefix(
IN OUT LPTSTR* DeviceName
);
STATIC
BOOL
ConvertPathMacros(
IN OUT LPTSTR Path
);
STATIC
LPTSTR
BackUpPath(
IN LPTSTR Stopper,
IN LPTSTR Path
);
//
// routines
//
NET_API_STATUS
CanonicalizePathName(
IN LPTSTR PathPrefix OPTIONAL,
IN LPTSTR PathName,
OUT LPTSTR Buffer,
IN DWORD BufferSize,
OUT LPDWORD RequiredSize OPTIONAL
)
/*++
Routine Description:
Given a path name, this function will 'canonicalize' it - that is, convert
it to a standard form. We attempt to accomplish here what path canonicalization
accomplished in LANMAN. The following is done:
* all macros in the input filename (\., .\, \.., ..\) are removed and
replaced by path components
* any required translations are performed on the path specification:
* unix-style / converted to dos-style \
* specific transliteration. NOTE: the input case is NOT converted.
The underlying file system may be case insensitive. Just pass
through the path as presented by the caller
* device names (ie name space controlled by us) is canonicalized by
converting device names to UPPER CASE and removing trailing colon
in all but disk devices
What is NOT done:
* starts with a drive specifier (eg. D:) or a sharepoint specifier
(eg \\computername\sharename)
* all path components required to fully specify the required path or
file are included in the output path specification
NOTES: 1. This function only uses local naming rules. It does not gurantee
to 'correctly' canonicalize a remote path name
2. Character validation is not done - this is left to the underlying
file system
Arguments:
PathPrefix - an OPTIONAL parameter. If non-NULL, points to a string which
is to be prepended to PathName before canonicalization of
the concatenated strings. This will typically be another
drive or path
PathName - input path to canonicalize. May be already fully qualified,
or may be one of the following:
- relative local path name (eg foo\bar)
- remote path name (eg \\computer\share\foo\bar\filename.ext)
- device name (eg LPT1:)
Buffer - place to store the canonicalized name
BufferSize - size (in bytes) of Buffer
RequiredSize- OPTIONAL parameter. If supplied AND Buffer was not sufficient
to hold the results of the canonicalization then will contain
the size of buffer necessary to retrieve canonicalized version
of PathName (optionally prefixed by PathPrefix)
Return Value:
DWORD
Success - NERR_Success
Failure - ERROR_INVALID_NAME
There is a fundamental problem with PathName (like too
many ..\ macros) or the name is too long
NERR_BufTooSmall
Buffer is too small to hold the canonicalized path
--*/
{
TCHAR pathBuffer[MAX_PATH*2 + 1];
DWORD prefixLen;
DWORD pathLen;
if (ARGUMENT_PRESENT(PathPrefix)) {
prefixLen = STRLEN(PathPrefix);
if (prefixLen) {
// Make sure we don't overrun our buffer
if (prefixLen > MAX_PATH*2 ) {
return ERROR_INVALID_NAME;
}
STRCPY(pathBuffer, PathPrefix);
if (!IS_PATH_SEPARATOR(pathBuffer[prefixLen - 1])) {
STRCAT(pathBuffer, TEXT("\\"));
++prefixLen;
}
if (IS_PATH_SEPARATOR(*PathName)) {
++PathName;
}
}
} else {
prefixLen = 0;
pathBuffer[0] = 0;
}
pathLen = STRLEN(PathName);
if (pathLen + prefixLen > MAX_PATH*2 - 1) {
return ERROR_INVALID_NAME;
}
STRCAT(pathBuffer, PathName);
ConvertPathCharacters(pathBuffer);
if (!ConvertDeviceName(pathBuffer)) {
if (!ConvertPathMacros(pathBuffer)) {
return ERROR_INVALID_NAME;
}
}
pathLen = STRSIZE(pathBuffer);
if (pathLen > BufferSize) {
if (ARGUMENT_PRESENT(RequiredSize)) {
*RequiredSize = pathLen;
}
return NERR_BufTooSmall;
}
STRCPY(Buffer, pathBuffer);
return NERR_Success;
}
STATIC
VOID
ConvertPathCharacters(
IN LPTSTR Path
)
/*++
Routine Description:
Converts non-standard path component characters to their canonical
counterparts. Currently all this routine does is convert / to \. It may
be enhanced in future to perform case conversion
Arguments:
Path - pointer to path buffer to transform. Performs conversion in place
Return Value:
None.
--*/
{
while (*Path) {
if (*Path == TCHAR_FWDSLASH) {
*Path = TCHAR_BACKSLASH;
}
++Path;
}
}
STATIC
BOOL
ConvertDeviceName(
IN OUT LPTSTR PathName
)
/*++
Routine Description:
If PathBuffer contains a device name of AUX or PRN (case insensitive),
convert to COM1 and LPT1 resp. If PathBuffer is a device and has a local
device prefix (\dev\ (LM20 style) or \\.\) then skips it, but leaves the
prefix in the buffer.
Device names (including DISK devices) will be UPPERCASEd, whatever that
means for other locales.
ASSUMES: Disk Device is single CHARACTER, followed by ':' (optionally
followed by rest of path)
Arguments:
PathName - pointer to buffer containing possible device name. Performs
conversion in place
Return Value:
BOOL
TRUE - PathName is a DOS device name
FALSE - PathName not a DOS device
--*/
{
BOOL isDeviceName = FALSE;
#ifndef UNICODE
UNICODE_STRING PathName_U;
OEM_STRING PathName_A;
PWSTR PathName_W;
NetpInitOemString(&PathName_A, PathName);
RtlOemStringToUnicodeString(&PathName_U, &PathName_A, TRUE);
PathName_W = PathName_U.Buffer;
if (RtlIsDosDeviceName_U(PathName_W)) {
LPTSTR deviceName = PathName;
DWORD deviceLength;
ParseLocalDevicePrefix(&deviceName);
deviceLength = STRLEN(deviceName) - 1;
if (deviceName[deviceLength] == TCHAR_COLON) {
deviceName[deviceLength] = 0;
--deviceLength;
}
if (!STRICMP(deviceName, text_PRN)) {
STRCPY(deviceName, text_LPT);
STRCAT(deviceName, TEXT("1"));
} else if (!STRICMP(deviceName, text_AUX)) {
STRCPY(deviceName, text_COM);
STRCAT(deviceName, TEXT("1"));
}
isDeviceName = TRUE;
STRUPR(deviceName);
} else {
switch (RtlDetermineDosPathNameType_U(PathName_W)) {
case RtlPathTypeDriveRelative:
case RtlPathTypeDriveAbsolute:
*PathName = TOUPPER(*PathName);
}
}
RtlFreeUnicodeString(&PathName_U);
#else
if (RtlIsDosDeviceName_U(PathName)) {
LPTSTR deviceName = PathName;
DWORD deviceLength;
ParseLocalDevicePrefix(&deviceName);
deviceLength = STRLEN(deviceName) - 1;
if (deviceName[deviceLength] == TCHAR_COLON) {
deviceName[deviceLength] = 0;
--deviceLength;
}
if (!STRICMP(deviceName, text_PRN)) {
STRCPY(deviceName, text_LPT);
STRCAT(deviceName, TEXT("1"));
} else if (!STRICMP(deviceName, text_AUX)) {
STRCPY(deviceName, text_COM);
STRCAT(deviceName, TEXT("1"));
}
isDeviceName = TRUE;
STRUPR(deviceName);
} else {
switch (RtlDetermineDosPathNameType_U(PathName)) {
case RtlPathTypeDriveRelative:
case RtlPathTypeDriveAbsolute:
*PathName = TOUPPER(*PathName);
}
}
#endif
return isDeviceName;
}
STATIC
BOOL
ParseLocalDevicePrefix(
IN OUT LPTSTR* DeviceName
)
/*++
Routine Description:
If a device name starts with a local device name specifier - "\\.\" or
"\DEV\" - then move DeviceName past the prefix and return TRUE, else FALSE
Arguments:
DeviceName - pointer to string containing potential local device name,
prefixed by "\\.\" or "\DEV\". If the local device prefix
is present the string pointer is advanced past it to the
device name proper
Return Value:
BOOL
TRUE - DeviceName has a local device prefix. DeviceName now points at
the name after the prefix
FALSE - DeviceName doesn't have a local device prefix
--*/
{
LPTSTR devName = *DeviceName;
if (IS_PATH_SEPARATOR(*devName)) {
++devName;
if (!STRNICMP(devName, text_DEV, 3)) {
devName += 3;
} else if (IS_PATH_SEPARATOR(*devName)) {
++devName;
if (*devName == TCHAR_DOT) {
++devName;
} else {
return FALSE;
}
} else {
return FALSE;
}
if (IS_PATH_SEPARATOR(*devName)) {
++devName;
*DeviceName = devName;
return TRUE;
}
}
return FALSE;
}
STATIC
BOOL
ConvertPathMacros(
IN OUT LPTSTR Path
)
/*++
Routine Description:
Removes path macros (\.. and \.) and replaces them with the correct level
of path components. This routine expects path macros to appear in a path
like this:
<path>\.
<path>\.\<more-path>
<path>\..
<path>\..\<more-path>
I.e. a macro will either be terminated by the End-Of-String character (\0)
or another path separator (\).
Assumes Path has \ for path separator, not /
Arguments:
Path - pointer to a string containing a path to convert. Path must
contain all the path components that will appear in the result
E.g. Path = "d:\alpha\beta\gamma\..\delta\..\..\zeta\foo\bar"
will result in Path = "d:\zeta\foo\bar"
Path should contain back slashes (\) for path separators if the
correct results are to be produced
Return Value:
TRUE - Path converted
FALSE - Path contained an error
--*/
{
LPTSTR ptr = Path;
LPTSTR lastSlash = NULL;
LPTSTR previousLastSlash = NULL;
TCHAR ch;
//
// if this path is UNC then move the pointer past the computer name to the
// start of the (supposed) share name. Treat the remnants as a relative path
//
if (IS_PATH_SEPARATOR(Path[0]) && IS_PATH_SEPARATOR(Path[1])) {
Path += 2;
while (!IS_PATH_SEPARATOR(*Path) && *Path) {
++Path;
}
if (!*Path) {
return FALSE; // we had \\computername which is bad
}
++Path; // past \ into share name
if (IS_PATH_SEPARATOR(*Path)) {
return FALSE; // we had \\computername\\ which is bad
}
}
ptr = Path;
//
// remove all \., .\, \.. and ..\ from path
//
while ((ch = *ptr) != TCHAR_EOS) {
if (ch == TCHAR_BACKSLASH) {
if (lastSlash == ptr - 1) {
return FALSE;
}
previousLastSlash = lastSlash;
lastSlash = ptr;
} else if ((ch == TCHAR_DOT) && ((lastSlash == ptr - 1) || (ptr == Path))) {
TCHAR nextCh = *(ptr + 1);
if (nextCh == TCHAR_DOT) {
TCHAR nextCh = *(ptr + 2);
if ((nextCh == TCHAR_BACKSLASH) || (nextCh == TCHAR_EOS)) {
if (!previousLastSlash) {
return FALSE;
}
STRCPY(previousLastSlash, ptr + 2);
if (nextCh == TCHAR_EOS) {
break;
}
ptr = lastSlash = previousLastSlash;
previousLastSlash = BackUpPath(Path, ptr - 1);
}
} else if (nextCh == TCHAR_BACKSLASH) {
LPTSTR src = lastSlash ? ptr + 1 : ptr + 2;
LPTSTR dst = lastSlash ? lastSlash : ptr;
STRCPY(dst, src);
continue; // at current character position
} else if (nextCh == TCHAR_EOS) {
*(lastSlash ? lastSlash : ptr) = TCHAR_EOS;
break;
}
}
++ptr;
}
//
// path may be empty
//
return TRUE;
}
STATIC
LPTSTR
BackUpPath(
IN LPTSTR Stopper,
IN LPTSTR Path
)
/*++
Routine Description:
Searches backwards in a string for a path separator character (back-slash)
Arguments:
Stopper - pointer past which Path cannot be backed up
Path - pointer to path to back up
Return Value:
Pointer to backed-up path, or NULL if an error occurred
--*/
{
while ((*Path != TCHAR_BACKSLASH) && (Path != Stopper)) {
--Path;
}
return (*Path == TCHAR_BACKSLASH) ? Path : NULL;
}