/*++ Copyright (c) 1991 Microsoft Corporation Module Name: NameSup.c Abstract: This module implements the Ntfs Name support routines Author: Gary Kimura [GaryKi] & Tom Miller [TomM] 20-Feb-1990 Revision History: --*/ #include "NtfsProc.h" #define Dbg (DEBUG_TRACE_NAMESUP) #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, NtfsCollateNames) #pragma alloc_text(PAGE, NtfsIsFatNameValid) #pragma alloc_text(PAGE, NtfsIsFileNameValid) #pragma alloc_text(PAGE, NtfsParseName) #pragma alloc_text(PAGE, NtfsParsePath) #pragma alloc_text(PAGE, NtfsUpcaseName) #endif #define MAX_CHARS_IN_8_DOT_3 (12) PARSE_TERMINATION_REASON NtfsParsePath ( IN UNICODE_STRING Path, IN BOOLEAN WildCardsPermissible, OUT PUNICODE_STRING FirstPart, OUT PNTFS_NAME_DESCRIPTOR Name, OUT PUNICODE_STRING RemainingPart ) /*++ Routine Description: This routine takes as input a path. Each component of the path is checked until either: - The end of the path has been reached, or - A well formed complex name is excountered, or - An illegal character is encountered, or - A complex name component is malformed At this point the return value is set to one of the three reasons above, and the arguments are set as follows: FirstPart: All the components up to one containing an illegal character or colon character. May be the whole path. Name: The "pieces" of a component containing an illegal character or colon character. This name is actually a struncture containing the four pieces of a name, "file name, attribute type, attribute name, version number." In the example below, they are shown separated by plus signs. RemainingPart: All the remaining components. A preceding or trailing backslash is ignored during processing and stripped in either FirstPart or RemainingPart. Following are some examples of this routine's actions. Path FirstPart Name Remaining ================ ========= ============ ========= \nt\pri\os \nt\pri\os \nt\pri\os\ \nt\pri\os nt\pri\os \nt\pri\os \nt\pr"\os \nt pr" os \nt\pri\os:contr::3\ntfs \nt\pri os + contr + + 3 ntfs \nt\pri\os\circle:pict:circ \nt\pri\os circle + pict + circ Arguments: Path - This unicode string descibes the path to parse. Note that path here may only describe a single component. WildCardsPermissible - This parameter tells us if wild card characters should be considered legal. FirstPart - This unicode string will receive portion of the path, up to a component boundry, successfully parsed before the parse terminated. Note that store for this string comes from the Path parameter. Name - This is the name we were parsing when we reached our termination condition. It is a srtucture of strings that receive the file name, attribute type, attribute name, and version number respectively. It wil be filled in only to the extent that the parse succeeded. For example, in the case we encounter an illegal character in the attribute type field, only the file name field will be filled in. This may signal a special control file, and this possibility must be investigated by the file system. RemainingPart - This string will receive any portion of the path, starting at the first component boundry after the termination name, not parsed. It will often be an empty string. ReturnValue: An enumerated type with one of the following values: EndOfPathReached - The path was fully parsed. Only first part is filled in. NonSimpleName - A component of the path containing a legal, well formed non-simple name was encountered. IllegalCharacterInName - An illegal character was encountered. Parsing stops immediately. MalFormedName - A non-simple name did not conform to the correct format. This may be a result of too many fields, or a malformed version number. AttributeOnly - A component of the path containing a legal well formed non-simple name was encountered which does not have a file name. VersionNumberPresent - A component of the path containing a legal well formed non-simple name was encountered which contains a version number. --*/ { UNICODE_STRING FirstName; BOOLEAN WellFormed; BOOLEAN MoreNamesInPath; BOOLEAN FirstIteration; BOOLEAN FoundIllegalCharacter; PARSE_TERMINATION_REASON TerminationReason; PAGED_CODE(); // // Initialize some loacal variables and OUT parameters. // FirstIteration = TRUE; MoreNamesInPath = TRUE; // // Clear the fieldspresent flag in the name descriptor. // Name->FieldsPresent = 0; // // By default, set the returned first part to start at the beginning of // the input buffer and include a leading backslash. // FirstPart->Buffer = Path.Buffer; if (Path.Buffer[0] == L'\\') { FirstPart->Length = 2; FirstPart->MaximumLength = 2; } else { FirstPart->Length = 0; FirstPart->MaximumLength = 0; } // // Do the first check outside the loop in case we are given a backslash // by itself. // if (FirstPart->Length == Path.Length) { RemainingPart->Length = 0; RemainingPart->Buffer = &Path.Buffer[Path.Length >> 1]; return EndOfPathReached; } // // Crack the path, checking each componant // while (MoreNamesInPath) { FsRtlDissectName( Path, &FirstName, RemainingPart ); MoreNamesInPath = (BOOLEAN)(RemainingPart->Length != 0); // // If this is not the last name in the path, then attributes // and version numbers are not allowed. If this is the last // name then propagate the callers arguments. // WellFormed = NtfsParseName( FirstName, WildCardsPermissible, &FoundIllegalCharacter, Name ); // // Check the cases when we will break out of this loop, ie. if the // the name was not well formed or it was non-simple. // if ( !WellFormed || (Name->FieldsPresent != FILE_NAME_PRESENT_FLAG) // // TEMPCODE TRAILING_DOT // || (Name->FileName.Length != Name->FileName.MaximumLength) ) { break; } // // We will continue parsing this string, so consider the current // FirstName to be parsed and add it to FirstPart. Also reset // the Name->FieldsPresent variable. // if ( FirstIteration ) { FirstPart->Length += FirstName.Length; FirstIteration = FALSE; } else { FirstPart->Length += (sizeof(WCHAR) + FirstName.Length); } FirstPart->MaximumLength = FirstPart->Length; Path = *RemainingPart; } // // At this point FirstPart, Name, and RemainingPart should all be set // correctly. It remains, only to generate the correct return value. // if ( !WellFormed ) { if ( FoundIllegalCharacter ) { TerminationReason = IllegalCharacterInName; } else { TerminationReason = MalFormedName; } } else { if ( Name->FieldsPresent == FILE_NAME_PRESENT_FLAG ) { // // TEMPCODE TRAILING_DOT // if (Name->FileName.Length != Name->FileName.MaximumLength) { TerminationReason = NonSimpleName; } else { TerminationReason = EndOfPathReached; } } else if (FlagOn( Name->FieldsPresent, VERSION_NUMBER_PRESENT_FLAG )) { TerminationReason = VersionNumberPresent; } else if (!FlagOn( Name->FieldsPresent, FILE_NAME_PRESENT_FLAG )) { TerminationReason = AttributeOnly; } else { TerminationReason = NonSimpleName; } } return TerminationReason; } BOOLEAN NtfsParseName ( IN const UNICODE_STRING Name, IN BOOLEAN WildCardsPermissible, OUT PBOOLEAN FoundIllegalCharacter, OUT PNTFS_NAME_DESCRIPTOR ParsedName ) /*++ Routine Description: This routine takes as input a single name component. It is processed into file name, attribute type, attribute name, and version number fields. If the name is well formed according to the following rules: A. An NTFS name may not contain any of the following characters: 0x0000-0x001F " / < > ? | * B. An Ntfs name can take any of the following forms: ::T :A :A:T N N:::V N::T N::T:V N:A N:A::V N:A:T N:A:T:V If a version number is present, there must be a file name. We specifically note the legal names without a filename component (AttributeOnly) and any name with a version number (VersionNumberPresent). Incidently, N corresponds to file name, T to attribute type, A to attribute name, and V to version number. TRUE is returned. If FALSE is returned, then the OUT parameter FoundIllegalCharacter will be set appropriatly. Note that the buffer space for ParsedName comes from Name. Arguments: Name - This is the single path element input name. WildCardsPermissible - This determines if wild cards characters should be considered legal FoundIllegalCharacter - This parameter will receive a TRUE if the the function returns FALSE because of encountering an illegal character. ParsedName - Recieves the pieces of the processed name. Note that the storage for all the string from the input Name. ReturnValue: TRUE if the Name is well formed, and FALSE otherwise. --*/ { ULONG Index; ULONG NameLength; ULONG FieldCount; ULONG FieldIndexes[5]; UCHAR ValidCharFlags = FSRTL_NTFS_LEGAL; PULONG Fields; BOOLEAN IsNameValid = TRUE; PAGED_CODE(); // // Initialize some OUT parameters and local variables. // *FoundIllegalCharacter = FALSE; Fields = &ParsedName->FieldsPresent; *Fields = 0; FieldCount = 1; FieldIndexes[0] = 0xFFFFFFFF; // We add on to this later... // // For starters, zero length names are invalid. // NameLength = Name.Length / sizeof(WCHAR); if ( NameLength == 0 ) { return FALSE; } // // Now name must correspond to a legal single Ntfs Name. // for (Index = 0; Index < NameLength; Index += 1) { WCHAR Char; Char = Name.Buffer[Index]; // // First check that file names are well formed in terms of colons. // if ( Char == L':' ) { // // A colon can't be the last character, and we can't have // more than three colons. // if ( (Index == NameLength - 1) || (FieldCount >= 4) ) { IsNameValid = FALSE; break; } FieldIndexes[FieldCount] = Index; FieldCount += 1; ValidCharFlags = FSRTL_NTFS_STREAM_LEGAL; continue; } // // Now check for wild card characters if they weren't allowed, // and other illegal characters. // if ((Char <= 0xff) && !FsRtlTestAnsiCharacter( Char, TRUE, WildCardsPermissible, ValidCharFlags )) { IsNameValid = FALSE; *FoundIllegalCharacter = TRUE; break; } } // // If we ran into a problem with one of the fields, don't try to load // up that field into the out parameter. // if ( !IsNameValid ) { FieldCount -= 1; // // Set the end of the last field to the current Index. // } else { FieldIndexes[FieldCount] = Index; } // // Now we load up the OUT parmeters // while ( FieldCount != 0 ) { ULONG StartIndex; ULONG EndIndex; USHORT Length; // // Add one here since this is actually the position of the colon. // StartIndex = FieldIndexes[FieldCount - 1] + 1; EndIndex = FieldIndexes[FieldCount]; Length = (USHORT)((EndIndex - StartIndex) * sizeof(WCHAR)); // // If this field is empty, skip it // if ( Length == 0 ) { FieldCount -= 1; continue; } // // Now depending of the field, extract the appropriate information. // if ( FieldCount == 1 ) { UNICODE_STRING TempName; TempName.Buffer = &Name.Buffer[StartIndex]; TempName.Length = Length; TempName.MaximumLength = Length; // // If the resulting length is 0, forget this entry. // if (TempName.Length == 0) { FieldCount -= 1; continue; } SetFlag(*Fields, FILE_NAME_PRESENT_FLAG); ParsedName->FileName = TempName; } else if ( FieldCount == 2) { SetFlag(*Fields, ATTRIBUTE_NAME_PRESENT_FLAG); ParsedName->AttributeName.Buffer = &Name.Buffer[StartIndex]; ParsedName->AttributeName.Length = Length; ParsedName->AttributeName.MaximumLength = Length; } else if ( FieldCount == 3) { SetFlag(*Fields, ATTRIBUTE_TYPE_PRESENT_FLAG); ParsedName->AttributeType.Buffer = &Name.Buffer[StartIndex]; ParsedName->AttributeType.Length = Length; ParsedName->AttributeType.MaximumLength = Length; } else if ( FieldCount == 4) { ULONG VersionNumber; STRING VersionNumberA; UNICODE_STRING VersionNumberU; NTSTATUS Status; UCHAR *endp = NULL; VersionNumberU.Buffer = &Name.Buffer[StartIndex]; VersionNumberU.Length = Length; VersionNumberU.MaximumLength = Length; // // Note that the resulting Ansi string is null terminated. // Status = RtlUnicodeStringToCountedOemString( &VersionNumberA, &VersionNumberU, TRUE ); // // If something went wrong (most likely ran out of pool), raise. // if ( !NT_SUCCESS(Status) ) { ExRaiseStatus( Status ); } VersionNumber = 0; //**** strtoul( VersionNumberA.Buffer, &endp, 0 ); RtlFreeOemString( &VersionNumberA ); if ( (VersionNumber == MAXULONG) || (endp != NULL) ) { IsNameValid = FALSE; } else { SetFlag( *Fields, VERSION_NUMBER_PRESENT_FLAG ); ParsedName->VersionNumber = VersionNumber; } } FieldCount -= 1; } // // Check for special malformed cases. // if (FlagOn( *Fields, VERSION_NUMBER_PRESENT_FLAG ) && !FlagOn( *Fields, FILE_NAME_PRESENT_FLAG )) { IsNameValid = FALSE; } return IsNameValid; } VOID NtfsUpcaseName ( IN PWCH UpcaseTable, IN ULONG UpcaseTableSize, IN OUT PUNICODE_STRING Name ) /*++ Routine Description: This routine upcases a string. Arguments: UpcaseTable - Pointer to an array of Unicode upcased characters indexed by the Unicode character to be upcased. UpcaseTableSize - Size of the Upcase table in unicode characters Name - Supplies the string to upcase Return Value: None. --*/ { ULONG i; ULONG Length; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsUpcaseName\n") ); DebugTrace( 0, Dbg, ("Name = %Z\n", Name) ); Length = Name->Length / sizeof(WCHAR); for (i=0; i < Length; i += 1) { if ((ULONG)Name->Buffer[i] < UpcaseTableSize) { Name->Buffer[i] = UpcaseTable[ (ULONG)Name->Buffer[i] ]; } } DebugTrace( 0, Dbg, ("Upcased Name = %Z\n", Name) ); DebugTrace( -1, Dbg, ("NtfsUpcaseName -> VOID\n") ); return; } FSRTL_COMPARISON_RESULT NtfsCollateNames ( IN PCWCH UpcaseTable, IN ULONG UpcaseTableSize, IN PCUNICODE_STRING Expression, IN PCUNICODE_STRING Name, IN FSRTL_COMPARISON_RESULT WildIs, IN BOOLEAN IgnoreCase ) /*++ Routine Description: This routine compares an expression with a name lexigraphically for LessThan, EqualTo, or GreaterThan. If the expression does not contain any wildcards, this procedure does a complete comparison. If the expression does contain wild cards, then the comparison is only done up to the first wildcard character. Name may not contain wild cards. The wildcard character compares as less then all other characters. So the wildcard name "*.*" will always compare less than all all strings. Arguments: UpcaseTable - Pointer to an array of Unicode upcased characters indexed by the Unicode character to be upcased. UpcaseTableSize - Size of the Upcase table in unicode characters Expression - Supplies the first name expression to compare, optionally with wild cards. Note that caller must have already upcased the name (this will make lookup faster). Name - Supplies the second name to compare - no wild cards allowed. The caller must have already upcased the name. WildIs - Determines what Result is returned if a wild card is encountered in the Expression String. For example, to find the start of an expression in the Btree, LessThan should be supplied; then GreaterThan should be supplied to find the end of the expression in the tree. IgnoreCase - TRUE if case should be ignored for the comparison Return Value: FSRTL_COMPARISON_RESULT - LessThan if Expression < Name EqualTo if Expression == Name GreaterThan if Expression > Name --*/ { WCHAR ConstantChar; WCHAR ExpressionChar; ULONG i; ULONG Length; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsCollateNames\n") ); DebugTrace( 0, Dbg, ("Expression = %Z\n", Expression) ); DebugTrace( 0, Dbg, ("Name = %Z\n", Name) ); DebugTrace( 0, Dbg, ("WildIs = %08lx\n", WildIs) ); DebugTrace( 0, Dbg, ("IgnoreCase = %02lx\n", IgnoreCase) ); // // Calculate the length in wchars that we need to compare. This will // be the smallest length of the two strings. // if (Expression->Length < Name->Length) { Length = Expression->Length / sizeof(WCHAR); } else { Length = Name->Length / sizeof(WCHAR); } // // Now we'll just compare the elements in the names until we can decide // their lexicagrahical ordering, checking for wild cards in // LocalExpression (from Expression). // // If an upcase table was specified, the compare is done case insensitive. // for (i = 0; i < Length; i += 1) { ConstantChar = Name->Buffer[i]; ExpressionChar = Expression->Buffer[i]; if ( IgnoreCase ) { if (ConstantChar < UpcaseTableSize) { ConstantChar = UpcaseTable[(ULONG)ConstantChar]; } if (ExpressionChar < UpcaseTableSize) { ExpressionChar = UpcaseTable[(ULONG)ExpressionChar]; } } if ( FsRtlIsUnicodeCharacterWild(ExpressionChar) ) { DebugTrace( -1, Dbg, ("NtfsCollateNames -> %08lx (Wild)\n", WildIs) ); return WildIs; } if ( ExpressionChar < ConstantChar ) { DebugTrace( -1, Dbg, ("NtfsCollateNames -> LessThan\n") ); return LessThan; } if ( ExpressionChar > ConstantChar ) { DebugTrace( -1, Dbg, ("NtfsCollateNames -> GreaterThan\n") ); return GreaterThan; } } // // We've gone through the entire short match and they're equal // so we need to now check which one is shorter, or, if // LocalExpression is longer, we need to see if the next character is // wild! (For example, an enumeration of "ABC*", must return // "ABC". // if (Expression->Length < Name->Length) { DebugTrace( -1, Dbg, ("NtfsCollateNames -> LessThan (length)\n") ); return LessThan; } if (Expression->Length > Name->Length) { if (FsRtlIsUnicodeCharacterWild(Expression->Buffer[i])) { DebugTrace( -1, Dbg, ("NtfsCollateNames -> %08lx (trailing wild)\n", WildIs) ); return WildIs; } DebugTrace( -1, Dbg, ("NtfsCollateNames -> GreaterThan (length)\n") ); return GreaterThan; } DebugTrace( -1, Dbg, ("NtfsCollateNames -> EqualTo\n") ); return EqualTo; } BOOLEAN NtfsIsFileNameValid ( IN PUNICODE_STRING FileName, IN BOOLEAN WildCardsPermissible ) /*++ Routine Description: This routine checks if the specified file name is valid. Note that only the file name part of the name is allowed, ie. no colons are permitted. Arguments: FileName - Supplies the name to check. WildCardsPermissible - Tells us if wild card characters are ok. Return Value: BOOLEAN - TRUE if the name is valid, FALSE otherwise. --*/ { ULONG Index; ULONG NameLength; BOOLEAN AllDots = TRUE; BOOLEAN IsNameValid = TRUE; PAGED_CODE(); DebugTrace( +1, Dbg, ("NtfsIsFileNameValid\n") ); DebugTrace( 0, Dbg, ("FileName = %Z\n", FileName) ); DebugTrace( 0, Dbg, ("WildCardsPermissible = %s\n", WildCardsPermissible ? "TRUE" : "FALSE") ); // // It better be a valid unicode string. // if ((FileName->Length == 0) || FlagOn( FileName->Length, 1 )) { IsNameValid = FALSE; } else { // // Check if corresponds to a legal single Ntfs Name. // NameLength = FileName->Length / sizeof(WCHAR); for (Index = 0; Index < NameLength; Index += 1) { WCHAR Char; Char = FileName->Buffer[Index]; // // Check for wild card characters if they weren't allowed, and // check for the other illegal characters including the colon and // backslash characters since this can only be a single component. // if ( ((Char <= 0xff) && !FsRtlIsAnsiCharacterLegalNtfs(Char, WildCardsPermissible)) || (Char == L':') || (Char == L'\\') ) { IsNameValid = FALSE; break; } // // Remember if this is not a '.' character. // if (Char != L'.') { AllDots = FALSE; } } // // The names '.' and '..' are also invalid. // if (AllDots && (NameLength == 1 || NameLength == 2)) { IsNameValid = FALSE; } } DebugTrace( -1, Dbg, ("NtfsIsFileNameValid -> %s\n", IsNameValid ? "TRUE" : "FALSE") ); return IsNameValid; } BOOLEAN NtfsIsFatNameValid ( IN PUNICODE_STRING FileName, IN BOOLEAN WildCardsPermissible ) /*++ Routine Description: This routine checks if the specified file name is conformant to the Fat 8.3 file naming rules. Arguments: FileName - Supplies the name to check. WildCardsPermissible - Tells us if wild card characters are ok. Return Value: BOOLEAN - TRUE if the name is valid, FALSE otherwise. --*/ { BOOLEAN Results; STRING DbcsName; USHORT i; CHAR Buffer[24]; WCHAR wc; PAGED_CODE(); // // If the name is more than 24 bytes then it can't be a valid Fat name. // if (FileName->Length > 24) { return FALSE; } // // We will do some extra checking ourselves because we really want to be // fairly restrictive of what an 8.3 name contains. That way // we will then generate an 8.3 name for some nomially valid 8.3 // names (e.g., names that contain DBCS characters). The extra characters // we'll filter off are those characters less than and equal to the space // character and those beyond lowercase z. // if (FlagOn( NtfsData.Flags,NTFS_FLAGS_ALLOW_EXTENDED_CHAR )) { for (i = 0; i < FileName->Length / sizeof( WCHAR ); i += 1) { wc = FileName->Buffer[i]; if ((wc <= 0x0020) || (wc == 0x007c)) { return FALSE; } } } else { for (i = 0; i < FileName->Length / sizeof( WCHAR ); i += 1) { wc = FileName->Buffer[i]; if ((wc <= 0x0020) || (wc >= 0x007f) || (wc == 0x007c)) { return FALSE; } } } // // The characters match up okay so now build up the dbcs string to call // the fsrtl routine to check for legal 8.3 formation // Results = FALSE; DbcsName.MaximumLength = 24; DbcsName.Buffer = Buffer; if (NT_SUCCESS(RtlUnicodeStringToCountedOemString( &DbcsName, FileName, FALSE))) { if (FsRtlIsFatDbcsLegal( DbcsName, WildCardsPermissible, FALSE, FALSE )) { Results = TRUE; } } // // And return to our caller // return Results; }