1 /* 2 * PROJECT: ReactOS Kernel 3 * LICENSE: GPL - See COPYING in the top level directory 4 * FILE: ntoskrnl/fsrtl/dbcsname.c 5 * PURPOSE: Provides DBCS parsing and other support routines for FSDs 6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org) 7 * Pierre Schweitzer (pierre.schweitzer@reactos.org) 8 */ 9 10 /* INCLUDES ******************************************************************/ 11 12 #include <ntoskrnl.h> 13 #define NDEBUG 14 #include <debug.h> 15 16 /* PUBLIC FUNCTIONS **********************************************************/ 17 18 /*++ 19 * @name FsRtlDissectDbcs 20 * @implemented 21 * 22 * Dissects a given path name into first and remaining part. 23 * 24 * @param Name 25 * ANSI string to dissect. 26 * 27 * @param FirstPart 28 * Pointer to user supplied ANSI_STRING, that will later point 29 * to the first part of the original name. 30 * 31 * @param RemainingPart 32 * Pointer to user supplied ANSI_STRING, that will later point 33 * to the remaining part of the original name. 34 * 35 * @return None 36 * 37 * @remarks Example: 38 * Name: \test1\test2\test3 39 * FirstPart: test1 40 * RemainingPart: test2\test3 41 * 42 *--*/ 43 VOID 44 NTAPI 45 FsRtlDissectDbcs(IN ANSI_STRING Name, 46 OUT PANSI_STRING FirstPart, 47 OUT PANSI_STRING RemainingPart) 48 { 49 USHORT FirstPosition, i; 50 USHORT SkipFirstSlash = 0; 51 PAGED_CODE(); 52 53 /* Zero the strings before continuing */ 54 RtlZeroMemory(FirstPart, sizeof(ANSI_STRING)); 55 RtlZeroMemory(RemainingPart, sizeof(ANSI_STRING)); 56 57 /* Just quit if the string is empty */ 58 if (!Name.Length) return; 59 60 /* Find first backslash */ 61 FirstPosition = Name.Length; 62 for (i = 0; i < Name.Length; i++) 63 { 64 /* First make sure the character it's not the Lead DBCS */ 65 if (FsRtlIsLeadDbcsCharacter(Name.Buffer[i])) 66 { 67 i++; 68 } 69 /* If we found one... */ 70 else if (Name.Buffer[i] == '\\') 71 { 72 /* If it begins string, just notice it and continue */ 73 if (i == 0) 74 { 75 SkipFirstSlash = 1; 76 } 77 else 78 { 79 /* Else, save its position and break out of the loop */ 80 FirstPosition = i; 81 break; 82 } 83 } 84 } 85 86 /* Set up the first result string */ 87 FirstPart->Buffer = Name.Buffer + SkipFirstSlash; 88 FirstPart->Length = (FirstPosition - SkipFirstSlash); 89 FirstPart->MaximumLength = FirstPart->Length; 90 91 /* And second one, if necessary */ 92 if (FirstPosition < (Name.Length)) 93 { 94 RemainingPart->Buffer = Name.Buffer + FirstPosition + 1; 95 RemainingPart->Length = Name.Length - (FirstPosition + 1); 96 RemainingPart->MaximumLength = RemainingPart->Length; 97 } 98 } 99 100 /*++ 101 * @name FsRtlDoesDbcsContainWildCards 102 * @implemented 103 * 104 * Returns TRUE if the given DbcsName contains wildcards such as *, ?, 105 * ANSI_DOS_STAR, ANSI_DOS_DOT, and ANSI_DOS_QM 106 * 107 * @param Name 108 * The Name to check 109 * 110 * @return TRUE if there are wildcards, FALSE otherwise 111 * 112 * @remarks None 113 * 114 *--*/ 115 BOOLEAN 116 NTAPI 117 FsRtlDoesDbcsContainWildCards(IN PANSI_STRING Name) 118 { 119 USHORT i; 120 PAGED_CODE(); 121 122 /* Check every character */ 123 for (i = 0; i < Name->Length; i++) 124 { 125 /* First make sure it's not the Lead DBCS */ 126 if (FsRtlIsLeadDbcsCharacter(Name->Buffer[i])) 127 { 128 i++; 129 } 130 else if (FsRtlIsAnsiCharacterWild(Name->Buffer[i])) 131 { 132 /* Now return if it has a wildcard */ 133 return TRUE; 134 } 135 } 136 137 /* We didn't return above...so none found */ 138 return FALSE; 139 } 140 141 /*++ 142 * @name FsRtlIsDbcsInExpression 143 * @implemented 144 * 145 * Check if the Name string is in the Expression string. 146 * 147 * @param Expression 148 * The string in which we've to find Name. It can contains wildcards 149 * 150 * @param Name 151 * The string to find. It cannot contain wildcards. 152 * 153 * @return TRUE if Name is found in Expression, FALSE otherwise 154 * 155 * @remarks 156 * 157 *--*/ 158 BOOLEAN 159 NTAPI 160 FsRtlIsDbcsInExpression(IN PANSI_STRING Expression, 161 IN PANSI_STRING Name) 162 { 163 USHORT Offset, Position, BackTrackingPosition, OldBackTrackingPosition; 164 USHORT BackTrackingBuffer[16], OldBackTrackingBuffer[16] = {0}; 165 PUSHORT BackTrackingSwap, BackTracking = BackTrackingBuffer, OldBackTracking = OldBackTrackingBuffer; 166 ULONG BackTrackingBufferSize = RTL_NUMBER_OF(BackTrackingBuffer); 167 PVOID AllocatedBuffer = NULL; 168 USHORT ExpressionPosition, NamePosition = 0, MatchingChars = 1; 169 USHORT NameChar = 0, ExpressionChar; 170 BOOLEAN EndOfName = FALSE; 171 BOOLEAN Result; 172 BOOLEAN DontSkipDot; 173 PAGED_CODE(); 174 175 ASSERT(Name->Length); 176 ASSERT(Expression->Length); 177 ASSERT(!FsRtlDoesDbcsContainWildCards(Name)); 178 179 /* Check if we were given strings at all */ 180 if (!Name->Length || !Expression->Length) 181 { 182 /* Return TRUE if both strings are empty, otherwise FALSE */ 183 if (Name->Length == 0 && Expression->Length == 0) 184 return TRUE; 185 else 186 return FALSE; 187 } 188 189 /* Check for a shortcut: just one wildcard */ 190 if (Expression->Length == sizeof(CHAR)) 191 { 192 if (Expression->Buffer[0] == '*') 193 return TRUE; 194 } 195 196 //ASSERT(FsRtlDoesDbcsContainWildCards(Expression)); 197 198 /* Another shortcut, wildcard followed by some string */ 199 if (Expression->Buffer[0] == '*') 200 { 201 /* Copy Expression to our local variable */ 202 ANSI_STRING IntExpression = *Expression; 203 204 /* Skip the first char */ 205 IntExpression.Buffer++; 206 IntExpression.Length -= sizeof(CHAR); 207 208 /* Continue only if the rest of the expression does NOT contain 209 any more wildcards */ 210 if (!FsRtlDoesDbcsContainWildCards(&IntExpression)) 211 { 212 /* Check for a degenerate case */ 213 if (Name->Length < (Expression->Length - sizeof(CHAR))) 214 return FALSE; 215 216 /* Calculate position */ 217 NamePosition = (Name->Length - IntExpression.Length) / sizeof(CHAR); 218 219 /* Check whether we are breaking a two chars char (DBCS) */ 220 if (NlsMbOemCodePageTag) 221 { 222 MatchingChars = 0; 223 224 while (MatchingChars < NamePosition) 225 { 226 /* Check if current char is DBCS lead char, if so, jump by two chars */ 227 MatchingChars += FsRtlIsLeadDbcsCharacter(Name->Buffer[MatchingChars]) ? 2 : 1; 228 } 229 230 /* If so, deny */ 231 if (MatchingChars > NamePosition) 232 return FALSE; 233 } 234 235 /* Compare */ 236 return RtlEqualMemory(IntExpression.Buffer, 237 (Name->Buffer + NamePosition), 238 IntExpression.Length); 239 } 240 } 241 242 /* Name parsing loop */ 243 for (; !EndOfName; MatchingChars = BackTrackingPosition) 244 { 245 /* Reset positions */ 246 OldBackTrackingPosition = BackTrackingPosition = 0; 247 248 if (NamePosition >= Name->Length) 249 { 250 EndOfName = TRUE; 251 if (MatchingChars && OldBackTracking[MatchingChars - 1] == Expression->Length * 2) 252 break; 253 } 254 else 255 { 256 /* If lead byte present */ 257 if (FsRtlIsLeadDbcsCharacter(Name->Buffer[NamePosition])) 258 { 259 NameChar = Name->Buffer[NamePosition] + 260 (0x100 * Name->Buffer[NamePosition + 1]); 261 NamePosition += sizeof(USHORT); 262 } 263 else 264 { 265 NameChar = Name->Buffer[NamePosition]; 266 NamePosition += sizeof(UCHAR); 267 } 268 } 269 270 while (MatchingChars > OldBackTrackingPosition) 271 { 272 ExpressionPosition = (OldBackTracking[OldBackTrackingPosition++] + 1) / 2; 273 274 /* Expression parsing loop */ 275 for (Offset = 0; ExpressionPosition < Expression->Length; ) 276 { 277 ExpressionPosition += Offset; 278 279 if (ExpressionPosition == Expression->Length) 280 { 281 BackTracking[BackTrackingPosition++] = Expression->Length * 2; 282 break; 283 } 284 285 /* If buffer too small */ 286 if (BackTrackingPosition > BackTrackingBufferSize - 3) 287 { 288 /* We should only ever get here once! */ 289 ASSERT(AllocatedBuffer == NULL); 290 ASSERT((BackTracking == BackTrackingBuffer) || (BackTracking == OldBackTrackingBuffer)); 291 ASSERT((OldBackTracking == BackTrackingBuffer) || (OldBackTracking == OldBackTrackingBuffer)); 292 293 /* Calculate buffer size */ 294 BackTrackingBufferSize = Expression->Length * 2 + 1; 295 296 /* Allocate memory for both back-tracking buffers */ 297 AllocatedBuffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE, 298 2 * BackTrackingBufferSize * sizeof(USHORT), 299 'nrSF'); 300 if (AllocatedBuffer == NULL) 301 { 302 DPRINT1("Failed to allocate BackTracking buffer. BackTrackingBufferSize = =x%lx\n", 303 BackTrackingBufferSize); 304 Result = FALSE; 305 goto Exit; 306 } 307 308 /* Copy BackTracking content. Note that it can point to either BackTrackingBuffer or OldBackTrackingBuffer */ 309 RtlCopyMemory(AllocatedBuffer, 310 BackTracking, 311 RTL_NUMBER_OF(BackTrackingBuffer) * sizeof(USHORT)); 312 313 /* Place current Backtracking is at the start of the new buffer */ 314 BackTracking = AllocatedBuffer; 315 316 /* Copy OldBackTracking content */ 317 RtlCopyMemory(&BackTracking[BackTrackingBufferSize], 318 OldBackTracking, 319 RTL_NUMBER_OF(OldBackTrackingBuffer) * sizeof(USHORT)); 320 321 /* Place current OldBackTracking after current BackTracking in the buffer */ 322 OldBackTracking = &BackTracking[BackTrackingBufferSize]; 323 } 324 325 /* If lead byte present */ 326 if (FsRtlIsLeadDbcsCharacter(Expression->Buffer[ExpressionPosition])) 327 { 328 ExpressionChar = Expression->Buffer[ExpressionPosition] + 329 (0x100 * Expression->Buffer[ExpressionPosition + 1]); 330 Offset = sizeof(USHORT); 331 } 332 else 333 { 334 ExpressionChar = Expression->Buffer[ExpressionPosition]; 335 Offset = sizeof(UCHAR); 336 } 337 338 /* Basic check to test if chars are equal */ 339 if (ExpressionChar == NameChar && !EndOfName) 340 { 341 BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2; 342 } 343 /* Check cases that eat one char */ 344 else if (ExpressionChar == '?' && !EndOfName) 345 { 346 BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2; 347 } 348 /* Test star */ 349 else if (ExpressionChar == '*') 350 { 351 BackTracking[BackTrackingPosition++] = ExpressionPosition * 2; 352 BackTracking[BackTrackingPosition++] = (ExpressionPosition * 2) + 1; 353 continue; 354 } 355 /* Check DOS_STAR */ 356 else if (ExpressionChar == ANSI_DOS_STAR) 357 { 358 /* Look for last dot */ 359 DontSkipDot = TRUE; 360 if (!EndOfName && NameChar == '.') 361 { 362 for (Position = NamePosition; Position < Name->Length; ) 363 { 364 /* If lead byte not present */ 365 if (!FsRtlIsLeadDbcsCharacter(Name->Buffer[Position])) 366 { 367 if (Name->Buffer[Position] == '.') 368 { 369 DontSkipDot = FALSE; 370 break; 371 } 372 373 Position += sizeof(UCHAR); 374 } 375 else 376 { 377 Position += sizeof(USHORT); 378 } 379 } 380 } 381 382 if (EndOfName || NameChar != '.' || !DontSkipDot) 383 BackTracking[BackTrackingPosition++] = ExpressionPosition * 2; 384 385 BackTracking[BackTrackingPosition++] = (ExpressionPosition * 2) + 1; 386 continue; 387 } 388 /* Check DOS_DOT */ 389 else if (ExpressionChar == ANSI_DOS_DOT) 390 { 391 if (EndOfName) continue; 392 393 if (NameChar == '.') 394 BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2; 395 } 396 /* Check DOS_QM */ 397 else if (ExpressionChar == ANSI_DOS_QM) 398 { 399 if (EndOfName || NameChar == '.') continue; 400 401 BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2; 402 } 403 404 /* Leave from loop */ 405 break; 406 } 407 408 for (Position = 0; MatchingChars > OldBackTrackingPosition && Position < BackTrackingPosition; Position++) 409 { 410 while (MatchingChars > OldBackTrackingPosition && 411 BackTracking[Position] > OldBackTracking[OldBackTrackingPosition]) 412 { 413 ++OldBackTrackingPosition; 414 } 415 } 416 } 417 418 /* Swap pointers */ 419 BackTrackingSwap = BackTracking; 420 BackTracking = OldBackTracking; 421 OldBackTracking = BackTrackingSwap; 422 } 423 424 /* Store result value */ 425 Result = MatchingChars && (OldBackTracking[MatchingChars - 1] == Expression->Length * 2); 426 427 Exit: 428 429 /* Frees the memory if necessary */ 430 if (AllocatedBuffer != NULL) 431 { 432 ExFreePoolWithTag(AllocatedBuffer, 'nrSF'); 433 } 434 435 return Result; 436 } 437 438 /*++ 439 * @name FsRtlIsFatDbcsLegal 440 * @implemented 441 * 442 * Returns TRUE if the given DbcsName is a valid FAT filename (in 8.3) 443 * 444 * @param DbcsName 445 * The filename to check. It can also contains pathname. 446 * 447 * @param WildCardsPermissible 448 * If this is set to FALSE and if filename contains wildcard, the function 449 * will fail 450 * 451 * @param PathNamePermissible 452 * If this is set to FALSE and if the filename comes with a pathname, the 453 * function will fail 454 * 455 * @param LeadingBackslashPermissible 456 * If this is set to FALSE and if the filename starts with a backslash, the 457 * function will fail 458 * 459 * @return TRUE if the DbcsName is legal, FALSE otherwise 460 * 461 * @remarks None 462 * 463 *--*/ 464 BOOLEAN 465 NTAPI 466 FsRtlIsFatDbcsLegal(IN ANSI_STRING DbcsName, 467 IN BOOLEAN WildCardsPermissible, 468 IN BOOLEAN PathNamePermissible, 469 IN BOOLEAN LeadingBackslashPermissible) 470 { 471 ANSI_STRING FirstPart, RemainingPart; 472 BOOLEAN LastDot; 473 USHORT i; 474 PAGED_CODE(); 475 476 /* Just quit if the string is empty */ 477 if (!DbcsName.Length) 478 return FALSE; 479 480 /* Accept special filename if wildcards are allowed */ 481 if (WildCardsPermissible && (DbcsName.Length == 1 || DbcsName.Length == 2) && DbcsName.Buffer[0] == '.') 482 { 483 if (DbcsName.Length == 2) 484 { 485 if (DbcsName.Buffer[1] == '.') 486 return TRUE; 487 } 488 else 489 { 490 return TRUE; 491 } 492 } 493 494 /* DbcsName wasn't supposed to be started with \ */ 495 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\') 496 return FALSE; 497 /* DbcsName was allowed to be started with \, but now, remove it */ 498 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\') 499 { 500 DbcsName.Buffer = DbcsName.Buffer + 1; 501 DbcsName.Length = DbcsName.Length - 1; 502 DbcsName.MaximumLength = DbcsName.MaximumLength - 1; 503 } 504 505 if (PathNamePermissible) 506 { 507 /* We copy the buffer for FsRtlDissectDbcs call */ 508 RemainingPart.Buffer = DbcsName.Buffer; 509 RemainingPart.Length = DbcsName.Length; 510 RemainingPart.MaximumLength = DbcsName.MaximumLength; 511 512 while (RemainingPart.Length > 0) 513 { 514 if (RemainingPart.Buffer[0] == '\\') 515 return FALSE; 516 517 /* Call once again our dissect function */ 518 FsRtlDissectDbcs(RemainingPart, &FirstPart, &RemainingPart); 519 520 if (!FsRtlIsFatDbcsLegal(FirstPart, 521 WildCardsPermissible, 522 FALSE, 523 FALSE)) 524 { 525 return FALSE; 526 } 527 } 528 529 return TRUE; 530 } 531 532 if (WildCardsPermissible && FsRtlDoesDbcsContainWildCards(&DbcsName)) 533 { 534 for (i = 0; i < DbcsName.Length; i++) 535 { 536 /* First make sure the character it's not the Lead DBCS */ 537 if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i])) 538 { 539 i++; 540 } 541 /* Then check for bad characters */ 542 else if (!FsRtlIsAnsiCharacterLegalFat(DbcsName.Buffer[i], TRUE)) 543 { 544 return FALSE; 545 } 546 } 547 548 return TRUE; 549 } 550 551 /* Filename must be 8.3 filename */ 552 if (DbcsName.Length > 12) 553 return FALSE; 554 555 /* Reset dots count */ 556 LastDot = FALSE; 557 558 for (i = 0; i < DbcsName.Length; i++) 559 { 560 /* First make sure the character it's not the Lead DBCS */ 561 if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i])) 562 { 563 if (!LastDot && (i >= 7)) 564 return FALSE; 565 566 if (i == (DbcsName.Length - 1)) 567 return FALSE; 568 569 i++; 570 continue; 571 } 572 /* Then check for bad characters */ 573 else if (!FsRtlIsAnsiCharacterLegalFat(DbcsName.Buffer[i], WildCardsPermissible)) 574 { 575 return FALSE; 576 } 577 else if (DbcsName.Buffer[i] == '.') 578 { 579 /* Filename can only contain one dot */ 580 if (LastDot) 581 return FALSE; 582 583 LastDot = TRUE; 584 585 /* We mustn't have spaces before dot or at the end of the filename 586 * and no dot at the beginning of the filename */ 587 if (i == (DbcsName.Length - 1) || i == 0) 588 return FALSE; 589 590 /* Filename must be 8.3 filename and not 3.8 filename */ 591 if ((DbcsName.Length - 1) - i > 3) 592 return FALSE; 593 594 if ((i > 0) && DbcsName.Buffer[i - 1] == ' ') 595 return FALSE; 596 } 597 /* Filename mustn't finish with a space */ 598 else if (DbcsName.Buffer[i] == ' ' && i == (DbcsName.Length - 1)) 599 { 600 return FALSE; 601 } 602 603 if (!LastDot && (i >= 8)) 604 return FALSE; 605 } 606 607 return TRUE; 608 } 609 610 /*++ 611 * @name FsRtlIsHpfsDbcsLegal 612 * @implemented 613 * 614 * Returns TRUE if the given DbcsName is a valid HPFS filename 615 * 616 * @param DbcsName 617 * The filename to check. It can also contains pathname. 618 * 619 * @param WildCardsPermissible 620 * If this is set to FALSE and if filename contains wildcard, the function 621 * will fail 622 * 623 * @param PathNamePermissible 624 * If this is set to FALSE and if the filename comes with a pathname, the 625 * function will fail 626 * 627 * @param LeadingBackslashPermissible 628 * If this is set to FALSE and if the filename starts with a backslash, the 629 * function will fail 630 * 631 * @return TRUE if the DbcsName is legal, FALSE otherwise 632 * 633 * @remarks None 634 * 635 *--*/ 636 BOOLEAN 637 NTAPI 638 FsRtlIsHpfsDbcsLegal(IN ANSI_STRING DbcsName, 639 IN BOOLEAN WildCardsPermissible, 640 IN BOOLEAN PathNamePermissible, 641 IN BOOLEAN LeadingBackslashPermissible) 642 { 643 ANSI_STRING FirstPart, RemainingPart; 644 USHORT i; 645 PAGED_CODE(); 646 647 /* Just quit if the string is empty */ 648 if (!DbcsName.Length) 649 return FALSE; 650 651 /* Accept special filename if wildcards are allowed */ 652 if (WildCardsPermissible && (DbcsName.Length == 1 || DbcsName.Length == 2) && DbcsName.Buffer[0] == '.') 653 { 654 if (DbcsName.Length == 2) 655 { 656 if (DbcsName.Buffer[1] == '.') 657 return TRUE; 658 } 659 else 660 { 661 return TRUE; 662 } 663 } 664 665 /* DbcsName wasn't supposed to be started with \ */ 666 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\') 667 return FALSE; 668 /* DbcsName was allowed to be started with \, but now, remove it */ 669 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\') 670 { 671 DbcsName.Buffer = DbcsName.Buffer + 1; 672 DbcsName.Length = DbcsName.Length - 1; 673 DbcsName.MaximumLength = DbcsName.MaximumLength - 1; 674 } 675 676 if (PathNamePermissible) 677 { 678 /* We copy the buffer for FsRtlDissectDbcs call */ 679 RemainingPart.Buffer = DbcsName.Buffer; 680 RemainingPart.Length = DbcsName.Length; 681 RemainingPart.MaximumLength = DbcsName.MaximumLength; 682 683 while (RemainingPart.Length > 0) 684 { 685 if (RemainingPart.Buffer[0] == '\\') 686 return FALSE; 687 688 /* Call once again our dissect function */ 689 FsRtlDissectDbcs(RemainingPart, &FirstPart, &RemainingPart); 690 691 if (!FsRtlIsHpfsDbcsLegal(FirstPart, 692 WildCardsPermissible, 693 FALSE, 694 FALSE)) 695 { 696 return FALSE; 697 } 698 } 699 700 return TRUE; 701 } 702 703 if (DbcsName.Length > 255) 704 return FALSE; 705 706 for (i = 0; i < DbcsName.Length; i++) 707 { 708 /* First make sure the character it's not the Lead DBCS */ 709 if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i])) 710 { 711 if (i == (DbcsName.Length - 1)) 712 return FALSE; 713 i++; 714 } 715 /* Then check for bad characters */ 716 else if (!FsRtlIsAnsiCharacterLegalHpfs(DbcsName.Buffer[i], WildCardsPermissible)) 717 { 718 return FALSE; 719 } 720 /* Filename mustn't finish with a space or a dot */ 721 else if ((DbcsName.Buffer[i] == ' ' || DbcsName.Buffer[i] == '.') && i == (DbcsName.Length - 1)) 722 { 723 return FALSE; 724 } 725 } 726 727 return TRUE; 728 } 729