8ecd3b4593a329a55530ccf68d16968b2f278bbb
[reactos.git] / reactos / 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 SHORT StarFound = -1, DosStarFound = -1;
164 PUSHORT BackTracking = NULL, DosBackTracking = NULL;
165 USHORT ExpressionPosition = 0, NamePosition = 0, MatchingChars, LastDot;
166 PAGED_CODE();
167
168 ASSERT(Name->Length);
169 ASSERT(Expression->Length);
170 ASSERT(!FsRtlDoesDbcsContainWildCards(Name));
171
172 /* Check if we were given strings at all */
173 if (!Name->Length || !Expression->Length)
174 {
175 /* Return TRUE if both strings are empty, otherwise FALSE */
176 if (Name->Length == 0 && Expression->Length == 0)
177 return TRUE;
178 else
179 return FALSE;
180 }
181
182 /* Check for a shortcut: just one wildcard */
183 if (Expression->Length == sizeof(CHAR))
184 {
185 if (Expression->Buffer[0] == '*')
186 return TRUE;
187 }
188
189 //ASSERT(FsRtlDoesDbcsContainWildCards(Expression));
190
191 /* Another shortcut, wildcard followed by some string */
192 if (Expression->Buffer[0] == '*')
193 {
194 /* Copy Expression to our local variable */
195 ANSI_STRING IntExpression = *Expression;
196
197 /* Skip the first char */
198 IntExpression.Buffer++;
199 IntExpression.Length -= sizeof(CHAR);
200
201 /* Continue only if the rest of the expression does NOT contain
202 any more wildcards */
203 if (!FsRtlDoesDbcsContainWildCards(&IntExpression))
204 {
205 /* Check for a degenerate case */
206 if (Name->Length < (Expression->Length - sizeof(CHAR)))
207 return FALSE;
208
209 /* Calculate position */
210 NamePosition = (Name->Length - IntExpression.Length) / sizeof(CHAR);
211
212 /* Check whether we are breaking a two chars char (DBCS) */
213 if (NlsMbOemCodePageTag)
214 {
215 MatchingChars = 0;
216
217 while (MatchingChars < NamePosition)
218 {
219 /* Check if current char is DBCS lead char, if so, jump by two chars */
220 MatchingChars += FsRtlIsLeadDbcsCharacter(Name->Buffer[MatchingChars]) ? 2 : 1;
221 }
222
223 /* If so, deny */
224 if (MatchingChars > NamePosition)
225 return FALSE;
226 }
227
228 /* Compare */
229 return RtlEqualMemory(IntExpression.Buffer,
230 (Name->Buffer + NamePosition),
231 IntExpression.Length);
232 }
233 }
234
235 while (NamePosition < Name->Length && ExpressionPosition < Expression->Length)
236 {
237 /* Basic check to test if chars are equal */
238 if ((Expression->Buffer[ExpressionPosition] == Name->Buffer[NamePosition]))
239 {
240 NamePosition++;
241 ExpressionPosition++;
242 }
243 /* Check cases that eat one char */
244 else if (Expression->Buffer[ExpressionPosition] == '?')
245 {
246 NamePosition++;
247 ExpressionPosition++;
248 }
249 /* Test star */
250 else if (Expression->Buffer[ExpressionPosition] == '*')
251 {
252 /* Skip contigous stars */
253 while (ExpressionPosition + 1 < Expression->Length && Expression->Buffer[ExpressionPosition + 1] == '*')
254 {
255 ExpressionPosition++;
256 }
257
258 /* Save star position */
259 if (!BackTracking)
260 {
261 BackTracking = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
262 Expression->Length * sizeof(USHORT), 'nrSF');
263 }
264 BackTracking[++StarFound] = ExpressionPosition++;
265
266 /* If star is at the end, then eat all rest and leave */
267 if (ExpressionPosition == Expression->Length)
268 {
269 NamePosition = Name->Length;
270 break;
271 }
272 /* Allow null matching */
273 else if (Expression->Buffer[ExpressionPosition] != '?' &&
274 Expression->Buffer[ExpressionPosition] != Name->Buffer[NamePosition])
275 {
276 NamePosition++;
277 }
278 }
279 /* Check DOS_STAR */
280 else if (Expression->Buffer[ExpressionPosition] == ANSI_DOS_STAR)
281 {
282 /* Skip contigous stars */
283 while (ExpressionPosition + 1 < Expression->Length && Expression->Buffer[ExpressionPosition + 1] == ANSI_DOS_STAR)
284 {
285 ExpressionPosition++;
286 }
287
288 /* Look for last dot */
289 MatchingChars = 0;
290 LastDot = (USHORT)-1;
291 while (MatchingChars < Name->Length)
292 {
293 if (Name->Buffer[MatchingChars] == '.')
294 {
295 LastDot = MatchingChars;
296 if (LastDot > NamePosition)
297 break;
298 }
299
300 MatchingChars++;
301 }
302
303 /* If we don't have dots or we didn't find last yet
304 * start eating everything
305 */
306 if (MatchingChars != Name->Length || LastDot == (USHORT)-1)
307 {
308 if (!DosBackTracking) DosBackTracking = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
309 Expression->Length * sizeof(USHORT), 'nrSF');
310 DosBackTracking[++DosStarFound] = ExpressionPosition++;
311
312 /* Not the same char, start exploring */
313 if (Expression->Buffer[ExpressionPosition] != Name->Buffer[NamePosition])
314 NamePosition++;
315 }
316 else
317 {
318 /* Else, if we are at last dot, eat it - otherwise, null match */
319 if (Name->Buffer[NamePosition] == '.')
320 NamePosition++;
321
322 ExpressionPosition++;
323 }
324 }
325 /* Check DOS_DOT */
326 else if (Expression->Buffer[ExpressionPosition] == ANSI_DOS_DOT)
327 {
328 /* We only match dots */
329 if (Name->Buffer[NamePosition] == '.')
330 {
331 NamePosition++;
332 }
333 /* Try to explore later on for null matching */
334 else if (ExpressionPosition + 1 < Expression->Length &&
335 Name->Buffer[NamePosition] == Expression->Buffer[ExpressionPosition + 1])
336 {
337 NamePosition++;
338 }
339 ExpressionPosition++;
340 }
341 /* Check DOS_QM */
342 else if (Expression->Buffer[ExpressionPosition] == ANSI_DOS_QM)
343 {
344 /* We match everything except dots */
345 if (Name->Buffer[NamePosition] != '.')
346 {
347 NamePosition++;
348 }
349 ExpressionPosition++;
350 }
351 /* If nothing match, try to backtrack */
352 else if (StarFound >= 0)
353 {
354 ExpressionPosition = BackTracking[StarFound--];
355 }
356 else if (DosStarFound >= 0)
357 {
358 ExpressionPosition = DosBackTracking[DosStarFound--];
359 }
360 /* Otherwise, fail */
361 else
362 {
363 break;
364 }
365
366 /* Under certain circumstances, expression is over, but name isn't
367 * and we can backtrack, then, backtrack */
368 if (ExpressionPosition == Expression->Length &&
369 NamePosition != Name->Length && StarFound >= 0)
370 {
371 ExpressionPosition = BackTracking[StarFound--];
372 }
373 }
374 /* If we have nullable matching wc at the end of the string, eat them */
375 if (ExpressionPosition != Expression->Length && NamePosition == Name->Length)
376 {
377 while (ExpressionPosition < Expression->Length)
378 {
379 if (Expression->Buffer[ExpressionPosition] != ANSI_DOS_DOT &&
380 Expression->Buffer[ExpressionPosition] != '*' &&
381 Expression->Buffer[ExpressionPosition] != ANSI_DOS_STAR)
382 {
383 break;
384 }
385 ExpressionPosition++;
386 }
387 }
388
389 if (BackTracking)
390 {
391 ExFreePoolWithTag(BackTracking, 'nrSF');
392 }
393 if (DosBackTracking)
394 {
395 ExFreePoolWithTag(DosBackTracking, 'nrSF');
396 }
397
398 return (ExpressionPosition == Expression->Length && NamePosition == Name->Length);
399 }
400
401 /*++
402 * @name FsRtlIsFatDbcsLegal
403 * @implemented
404 *
405 * Returns TRUE if the given DbcsName is a valid FAT filename (in 8.3)
406 *
407 * @param DbcsName
408 * The filename to check. It can also contains pathname.
409 *
410 * @param WildCardsPermissible
411 * If this is set to FALSE and if filename contains wildcard, the function
412 * will fail
413 *
414 * @param PathNamePermissible
415 * If this is set to FALSE and if the filename comes with a pathname, the
416 * function will fail
417 *
418 * @param LeadingBackslashPermissible
419 * If this is set to FALSE and if the filename starts with a backslash, the
420 * function will fail
421 *
422 * @return TRUE if the DbcsName is legal, FALSE otherwise
423 *
424 * @remarks None
425 *
426 *--*/
427 BOOLEAN
428 NTAPI
429 FsRtlIsFatDbcsLegal(IN ANSI_STRING DbcsName,
430 IN BOOLEAN WildCardsPermissible,
431 IN BOOLEAN PathNamePermissible,
432 IN BOOLEAN LeadingBackslashPermissible)
433 {
434 ANSI_STRING FirstPart, RemainingPart;
435 BOOLEAN LastDot;
436 USHORT i;
437 PAGED_CODE();
438
439 /* Just quit if the string is empty */
440 if (!DbcsName.Length)
441 return FALSE;
442
443 /* Accept special filename if wildcards are allowed */
444 if (WildCardsPermissible && (DbcsName.Length == 1 || DbcsName.Length == 2) && DbcsName.Buffer[0] == '.')
445 {
446 if (DbcsName.Length == 2)
447 {
448 if (DbcsName.Buffer[1] == '.')
449 return TRUE;
450 }
451 else
452 {
453 return TRUE;
454 }
455 }
456
457 /* DbcsName wasn't supposed to be started with \ */
458 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
459 return FALSE;
460 /* DbcsName was allowed to be started with \, but now, remove it */
461 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
462 {
463 DbcsName.Buffer = DbcsName.Buffer + 1;
464 DbcsName.Length = DbcsName.Length - 1;
465 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
466 }
467
468 if (PathNamePermissible)
469 {
470 /* We copy the buffer for FsRtlDissectDbcs call */
471 RemainingPart.Buffer = DbcsName.Buffer;
472 RemainingPart.Length = DbcsName.Length;
473 RemainingPart.MaximumLength = DbcsName.MaximumLength;
474
475 while (RemainingPart.Length > 0)
476 {
477 if (RemainingPart.Buffer[0] == '\\')
478 return FALSE;
479
480 /* Call once again our dissect function */
481 FsRtlDissectDbcs(RemainingPart, &FirstPart, &RemainingPart);
482
483 if (!FsRtlIsFatDbcsLegal(FirstPart,
484 WildCardsPermissible,
485 FALSE,
486 FALSE))
487 {
488 return FALSE;
489 }
490 }
491
492 return TRUE;
493 }
494
495 if (WildCardsPermissible && FsRtlDoesDbcsContainWildCards(&DbcsName))
496 {
497 for (i = 0; i < DbcsName.Length; i++)
498 {
499 /* First make sure the character it's not the Lead DBCS */
500 if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i]))
501 {
502 i++;
503 }
504 /* Then check for bad characters */
505 else if (!FsRtlIsAnsiCharacterLegalFat(DbcsName.Buffer[i], TRUE))
506 {
507 return FALSE;
508 }
509 }
510
511 return TRUE;
512 }
513
514 /* Filename must be 8.3 filename */
515 if (DbcsName.Length > 12)
516 return FALSE;
517
518 /* Reset dots count */
519 LastDot = FALSE;
520
521 for (i = 0; i < DbcsName.Length; i++)
522 {
523 /* First make sure the character it's not the Lead DBCS */
524 if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i]))
525 {
526 if (!LastDot && (i >= 7))
527 return FALSE;
528
529 if (i == (DbcsName.Length - 1))
530 return FALSE;
531
532 i++;
533 continue;
534 }
535 /* Then check for bad characters */
536 else if (!FsRtlIsAnsiCharacterLegalFat(DbcsName.Buffer[i], WildCardsPermissible))
537 {
538 return FALSE;
539 }
540 else if (DbcsName.Buffer[i] == '.')
541 {
542 /* Filename can only contain one dot */
543 if (LastDot)
544 return FALSE;
545
546 LastDot = TRUE;
547
548 /* We mustn't have spaces before dot or at the end of the filename
549 * and no dot at the beginning of the filename */
550 if (i == (DbcsName.Length - 1) || i == 0)
551 return FALSE;
552
553 /* Filename must be 8.3 filename and not 3.8 filename */
554 if ((DbcsName.Length - 1) - i > 3)
555 return FALSE;
556
557 if ((i > 0) && DbcsName.Buffer[i - 1] == ' ')
558 return FALSE;
559 }
560 /* Filename mustn't finish with a space */
561 else if (DbcsName.Buffer[i] == ' ' && i == (DbcsName.Length - 1))
562 {
563 return FALSE;
564 }
565
566 if (!LastDot && (i >= 8))
567 return FALSE;
568 }
569
570 return TRUE;
571 }
572
573 /*++
574 * @name FsRtlIsHpfsDbcsLegal
575 * @implemented
576 *
577 * Returns TRUE if the given DbcsName is a valid HPFS filename
578 *
579 * @param DbcsName
580 * The filename to check. It can also contains pathname.
581 *
582 * @param WildCardsPermissible
583 * If this is set to FALSE and if filename contains wildcard, the function
584 * will fail
585 *
586 * @param PathNamePermissible
587 * If this is set to FALSE and if the filename comes with a pathname, the
588 * function will fail
589 *
590 * @param LeadingBackslashPermissible
591 * If this is set to FALSE and if the filename starts with a backslash, the
592 * function will fail
593 *
594 * @return TRUE if the DbcsName is legal, FALSE otherwise
595 *
596 * @remarks None
597 *
598 *--*/
599 BOOLEAN
600 NTAPI
601 FsRtlIsHpfsDbcsLegal(IN ANSI_STRING DbcsName,
602 IN BOOLEAN WildCardsPermissible,
603 IN BOOLEAN PathNamePermissible,
604 IN BOOLEAN LeadingBackslashPermissible)
605 {
606 ANSI_STRING FirstPart, RemainingPart;
607 USHORT i;
608 PAGED_CODE();
609
610 /* Just quit if the string is empty */
611 if (!DbcsName.Length)
612 return FALSE;
613
614 /* Accept special filename if wildcards are allowed */
615 if (WildCardsPermissible && (DbcsName.Length == 1 || DbcsName.Length == 2) && DbcsName.Buffer[0] == '.')
616 {
617 if (DbcsName.Length == 2)
618 {
619 if (DbcsName.Buffer[1] == '.')
620 return TRUE;
621 }
622 else
623 {
624 return TRUE;
625 }
626 }
627
628 /* DbcsName wasn't supposed to be started with \ */
629 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
630 return FALSE;
631 /* DbcsName was allowed to be started with \, but now, remove it */
632 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
633 {
634 DbcsName.Buffer = DbcsName.Buffer + 1;
635 DbcsName.Length = DbcsName.Length - 1;
636 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
637 }
638
639 if (PathNamePermissible)
640 {
641 /* We copy the buffer for FsRtlDissectDbcs call */
642 RemainingPart.Buffer = DbcsName.Buffer;
643 RemainingPart.Length = DbcsName.Length;
644 RemainingPart.MaximumLength = DbcsName.MaximumLength;
645
646 while (RemainingPart.Length > 0)
647 {
648 if (RemainingPart.Buffer[0] == '\\')
649 return FALSE;
650
651 /* Call once again our dissect function */
652 FsRtlDissectDbcs(RemainingPart, &FirstPart, &RemainingPart);
653
654 if (!FsRtlIsHpfsDbcsLegal(FirstPart,
655 WildCardsPermissible,
656 FALSE,
657 FALSE))
658 {
659 return FALSE;
660 }
661 }
662
663 return TRUE;
664 }
665
666 if (DbcsName.Length > 255)
667 return FALSE;
668
669 for (i = 0; i < DbcsName.Length; i++)
670 {
671 /* First make sure the character it's not the Lead DBCS */
672 if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i]))
673 {
674 if (i == (DbcsName.Length - 1))
675 return FALSE;
676 i++;
677 }
678 /* Then check for bad characters */
679 else if (!FsRtlIsAnsiCharacterLegalHpfs(DbcsName.Buffer[i], WildCardsPermissible))
680 {
681 return FALSE;
682 }
683 /* Filename mustn't finish with a space or a dot */
684 else if ((DbcsName.Buffer[i] == ' ' || DbcsName.Buffer[i] == '.') && i == (DbcsName.Length - 1))
685 {
686 return FALSE;
687 }
688 }
689
690 return TRUE;
691 }