* Sync up to trunk HEAD (r62975).
[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/name.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, Name;
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 /* DbcsName wasn't supposed to be started with \ */
444 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
445 return FALSE;
446 /* DbcsName was allowed to be started with \, but now, remove it */
447 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
448 {
449 DbcsName.Buffer = DbcsName.Buffer + 1;
450 DbcsName.Length = DbcsName.Length - 1;
451 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
452 }
453
454 /* Extract first part of the DbcsName to work on */
455 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
456 while (FirstPart.Length > 0)
457 {
458 /* Reset dots count */
459 LastDot = FALSE;
460
461 /* Accept special filename if wildcards are allowed */
462 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
463 {
464 if (FirstPart.Length == 2)
465 {
466 if (FirstPart.Buffer[1] == '.')
467 {
468 goto EndLoop;
469 }
470 }
471 else
472 {
473 goto EndLoop;
474 }
475 }
476
477 /* Filename must be 8.3 filename */
478 if (FirstPart.Length < 3 || FirstPart.Length > 12)
479 return FALSE;
480
481 /* Now, we will parse the filename to find everything bad in */
482 for (i = 0; i < FirstPart.Length; i++)
483 {
484 /* First make sure the character it's not the Lead DBCS */
485 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
486 {
487 if (i == (FirstPart.Length) - 1)
488 return FALSE;
489 i++;
490 }
491 /* Then check for bad characters */
492 else if (!FsRtlIsAnsiCharacterLegalFat(FirstPart.Buffer[i], WildCardsPermissible))
493 {
494 return FALSE;
495 }
496 else if (FirstPart.Buffer[i] == '.')
497 {
498 /* Filename can only contain one dot */
499 if (LastDot)
500 return FALSE;
501
502 LastDot = TRUE;
503
504 /* We mustn't have spaces before dot or at the end of the filename
505 * and no dot at the beginning of the filename */
506 if ((i == (FirstPart.Length) - 1) || i == 0)
507 return FALSE;
508
509 if (i > 0)
510 if (FirstPart.Buffer[i - 1] == ' ')
511 return FALSE;
512
513 /* Filename must be 8.3 filename and not 3.8 filename */
514 if ((FirstPart.Length - 1) - i > 3)
515 return FALSE;
516 }
517 }
518
519 /* Filename mustn't finish with a space */
520 if (FirstPart.Buffer[FirstPart.Length - 1] == ' ')
521 return FALSE;
522
523 EndLoop:
524 /* Preparing next loop */
525 Name.Buffer = RemainingPart.Buffer;
526 Name.Length = RemainingPart.Length;
527 Name.MaximumLength = RemainingPart.MaximumLength;
528
529 /* Call once again our dissect function */
530 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
531
532 /* We found a pathname, it wasn't allowed */
533 if (FirstPart.Length > 0 && !PathNamePermissible)
534 return FALSE;
535 }
536 return TRUE;
537 }
538
539 /*++
540 * @name FsRtlIsHpfsDbcsLegal
541 * @implemented
542 *
543 * Returns TRUE if the given DbcsName is a valid HPFS filename
544 *
545 * @param DbcsName
546 * The filename to check. It can also contains pathname.
547 *
548 * @param WildCardsPermissible
549 * If this is set to FALSE and if filename contains wildcard, the function
550 * will fail
551 *
552 * @param PathNamePermissible
553 * If this is set to FALSE and if the filename comes with a pathname, the
554 * function will fail
555 *
556 * @param LeadingBackslashPermissible
557 * If this is set to FALSE and if the filename starts with a backslash, the
558 * function will fail
559 *
560 * @return TRUE if the DbcsName is legal, FALSE otherwise
561 *
562 * @remarks None
563 *
564 *--*/
565 BOOLEAN
566 NTAPI
567 FsRtlIsHpfsDbcsLegal(IN ANSI_STRING DbcsName,
568 IN BOOLEAN WildCardsPermissible,
569 IN BOOLEAN PathNamePermissible,
570 IN BOOLEAN LeadingBackslashPermissible)
571 {
572 ANSI_STRING FirstPart, RemainingPart, Name;
573 USHORT i;
574 PAGED_CODE();
575
576 /* Just quit if the string is empty */
577 if (!DbcsName.Length)
578 return FALSE;
579
580 /* DbcsName wasn't supposed to be started with \ */
581 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
582 return FALSE;
583 /* DbcsName was allowed to be started with \, but now, remove it */
584 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
585 {
586 DbcsName.Buffer = DbcsName.Buffer + 1;
587 DbcsName.Length = DbcsName.Length - 1;
588 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
589 }
590
591 /* Extract first part of the DbcsName to work on */
592 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
593 while (FirstPart.Length > 0)
594 {
595 /* Accept special filename if wildcards are allowed */
596 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
597 {
598 if (FirstPart.Length == 2)
599 {
600 if (FirstPart.Buffer[1] == '.')
601 {
602 goto EndLoop;
603 }
604 }
605 else
606 {
607 goto EndLoop;
608 }
609 }
610
611 /* Filename must be 255 bytes maximum */
612 if (FirstPart.Length > 255)
613 return FALSE;
614
615 /* Now, we will parse the filename to find everything bad in */
616 for (i = 0; i < FirstPart.Length; i++)
617 {
618 /* First make sure the character it's not the Lead DBCS */
619 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
620 {
621 if (i == (FirstPart.Length) - 1)
622 return FALSE;
623 i++;
624 }
625 /* Then check for bad characters */
626 else if (!FsRtlIsAnsiCharacterLegalHpfs(FirstPart.Buffer[i], WildCardsPermissible))
627 {
628 return FALSE;
629 }
630 }
631
632 /* Filename mustn't finish with a space or a dot */
633 if ((FirstPart.Buffer[FirstPart.Length - 1] == ' ') ||
634 (FirstPart.Buffer[FirstPart.Length - 1] == '.'))
635 return FALSE;
636
637 EndLoop:
638 /* Preparing next loop */
639 Name.Buffer = RemainingPart.Buffer;
640 Name.Length = RemainingPart.Length;
641 Name.MaximumLength = RemainingPart.MaximumLength;
642
643 /* Call once again our dissect function */
644 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
645
646 /* We found a pathname, it wasn't allowed */
647 if (FirstPart.Length > 0 && !PathNamePermissible)
648 return FALSE;
649 }
650 return TRUE;
651 }