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