3b1afd801ec4e9bd2cdb7881fa276d350cf5f942
[reactos.git] / ntoskrnl / fsrtl / dbcsname.c
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 (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 = (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 }