[NTOSKRNL]
[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/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;
164 PUSHORT BackTracking = NULL;
165 USHORT ExpressionPosition = 0, NamePosition = 0, MatchingChars;
166 BOOLEAN BeyondName;
167 PAGED_CODE();
168
169 ASSERT(Name->Length);
170 ASSERT(Expression->Length);
171 ASSERT(!FsRtlDoesDbcsContainWildCards(Name));
172
173 while (NamePosition < Name->Length && ExpressionPosition < Expression->Length)
174 {
175 /* Basic check to test if chars are equal */
176 if ((Expression->Buffer[ExpressionPosition] == Name->Buffer[NamePosition]))
177 {
178 NamePosition++;
179 ExpressionPosition++;
180 }
181 /* Check cases that eat one char */
182 else if ((Expression->Buffer[ExpressionPosition] == '?') || (Expression->Buffer[ExpressionPosition] == ANSI_DOS_QM) ||
183 (Expression->Buffer[ExpressionPosition] == ANSI_DOS_DOT && Name->Buffer[NamePosition] == '.'))
184 {
185 NamePosition++;
186 ExpressionPosition++;
187 }
188 /* Test star */
189 else if (Expression->Buffer[ExpressionPosition] == '*')
190 {
191 /* Skip contigous stars */
192 while (ExpressionPosition + 1 < Expression->Length && Expression->Buffer[ExpressionPosition + 1] == '*')
193 {
194 ExpressionPosition++;
195 }
196
197 /* Save star position */
198 if (!BackTracking)
199 {
200 BackTracking = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
201 Expression->Length * sizeof(USHORT), 'nrSF');
202 }
203 BackTracking[++StarFound] = ExpressionPosition++;
204
205 /* If star is at the end, then eat all rest and leave */
206 if (ExpressionPosition == Expression->Length)
207 {
208 NamePosition = Name->Length;
209 break;
210 }
211 /* Allow null matching */
212 else if (Expression->Buffer[ExpressionPosition] != '?' &&
213 Expression->Buffer[ExpressionPosition] != Name->Buffer[NamePosition])
214 {
215 NamePosition++;
216 }
217 }
218 /* Check DOS_STAR */
219 else if (Expression->Buffer[ExpressionPosition] == ANSI_DOS_STAR)
220 {
221 /* We can only consume dot if that's not the last one
222 * Otherwise, we null match
223 */
224 if (Name->Buffer[NamePosition] == '.')
225 {
226 MatchingChars = NamePosition + 1;
227 while (MatchingChars < Name->Length)
228 {
229 if (Name->Buffer[MatchingChars] == '.')
230 {
231 NamePosition++;
232 break;
233 }
234 MatchingChars++;
235 }
236 }
237 else
238 {
239 /* XXX: Eat everything till the end */
240 if (ExpressionPosition + 1 == Expression->Length)
241 {
242 NamePosition = Name->Length;
243 }
244
245 /* Try to eat till the next matching char or . */
246 MatchingChars = NamePosition;
247 while (MatchingChars < Name->Length)
248 {
249 if (ExpressionPosition + 1 < Expression->Length &&
250 Name->Buffer[MatchingChars] == Expression->Buffer[ExpressionPosition + 1])
251 {
252 NamePosition = MatchingChars;
253 break;
254 }
255 else if (Name->Buffer[MatchingChars] == '.')
256 {
257 NamePosition = MatchingChars + 1;
258 break;
259 }
260 MatchingChars++;
261 }
262 }
263 ExpressionPosition++;
264 }
265 /* Check DOS_DOT */
266 else if (Expression->Buffer[ExpressionPosition] == DOS_DOT)
267 {
268 /* First try to find whether we are beyond last dot (beyond name) */
269 BeyondName = TRUE;
270 MatchingChars = NamePosition + 1;
271 while (MatchingChars < Name->Length)
272 {
273 if (Name->Buffer[MatchingChars] == '.')
274 {
275 BeyondName = FALSE;
276 break;
277 }
278 MatchingChars++;
279 }
280
281 /* If we are beyond name, we null match */
282 if (BeyondName)
283 {
284 ExpressionPosition++;
285 continue;
286 }
287 /* If not, we only match a dot */
288 else if (Name->Buffer[NamePosition] == '.')
289 {
290 NamePosition++;
291 ExpressionPosition++;
292 continue;
293 }
294 /* Otherwise, fail */
295 else
296 {
297 break;
298 }
299 }
300 /* If nothing match, try to backtrack */
301 else if (StarFound >= 0)
302 {
303 ExpressionPosition = BackTracking[StarFound--];
304 }
305 /* Otherwise, fail */
306 else
307 {
308 break;
309 }
310
311 /* Under certain circumstances, expression is over, but name isn't
312 * and we can backtrack, then, backtrack */
313 if (ExpressionPosition == Expression->Length &&
314 NamePosition != Name->Length && StarFound >= 0)
315 {
316 ExpressionPosition = BackTracking[StarFound--];
317 }
318 }
319 /* If we have nullable matching wc at the end of the string, eat them */
320 if (ExpressionPosition != Expression->Length && NamePosition == Name->Length)
321 {
322 while (ExpressionPosition < Expression->Length)
323 {
324 if (Expression->Buffer[ExpressionPosition] != ANSI_DOS_DOT &&
325 Expression->Buffer[ExpressionPosition] != '*' &&
326 Expression->Buffer[ExpressionPosition] != ANSI_DOS_STAR)
327 {
328 break;
329 }
330 ExpressionPosition++;
331 }
332 }
333
334 if (BackTracking)
335 {
336 ExFreePoolWithTag(BackTracking, 'nrSF');
337 }
338
339 return (ExpressionPosition == Expression->Length && NamePosition == Name->Length);
340 }
341
342 /*++
343 * @name FsRtlIsFatDbcsLegal
344 * @implemented
345 *
346 * Returns TRUE if the given DbcsName is a valid FAT filename (in 8.3)
347 *
348 * @param DbcsName
349 * The filename to check. It can also contains pathname.
350 *
351 * @param WildCardsPermissible
352 * If this is set to FALSE and if filename contains wildcard, the function
353 * will fail
354 *
355 * @param PathNamePermissible
356 * If this is set to FALSE and if the filename comes with a pathname, the
357 * function will fail
358 *
359 * @param LeadingBackslashPermissible
360 * If this is set to FALSE and if the filename starts with a backslash, the
361 * function will fail
362 *
363 * @return TRUE if the DbcsName is legal, FALSE otherwise
364 *
365 * @remarks None
366 *
367 *--*/
368 BOOLEAN
369 NTAPI
370 FsRtlIsFatDbcsLegal(IN ANSI_STRING DbcsName,
371 IN BOOLEAN WildCardsPermissible,
372 IN BOOLEAN PathNamePermissible,
373 IN BOOLEAN LeadingBackslashPermissible)
374 {
375 ANSI_STRING FirstPart, RemainingPart, Name;
376 BOOLEAN LastDot;
377 USHORT i;
378 PAGED_CODE();
379
380 /* Just quit if the string is empty */
381 if (!DbcsName.Length)
382 return FALSE;
383
384 /* DbcsName wasn't supposed to be started with \ */
385 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
386 return FALSE;
387 /* DbcsName was allowed to be started with \, but now, remove it */
388 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
389 {
390 DbcsName.Buffer = DbcsName.Buffer + 1;
391 DbcsName.Length = DbcsName.Length - 1;
392 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
393 }
394
395 /* Extract first part of the DbcsName to work on */
396 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
397 while (FirstPart.Length > 0)
398 {
399 /* Reset dots count */
400 LastDot = FALSE;
401
402 /* Accept special filename if wildcards are allowed */
403 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
404 {
405 if (FirstPart.Length == 2)
406 {
407 if (FirstPart.Buffer[1] == '.')
408 {
409 goto EndLoop;
410 }
411 }
412 else
413 {
414 goto EndLoop;
415 }
416 }
417
418 /* Filename must be 8.3 filename */
419 if (FirstPart.Length < 3 || FirstPart.Length > 12)
420 return FALSE;
421
422 /* Now, we will parse the filename to find everything bad in */
423 for (i = 0; i < FirstPart.Length; i++)
424 {
425 /* First make sure the character it's not the Lead DBCS */
426 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
427 {
428 if (i == (FirstPart.Length) - 1)
429 return FALSE;
430 i++;
431 }
432 /* Then check for bad characters */
433 else if (!FsRtlIsAnsiCharacterLegalFat(FirstPart.Buffer[i], WildCardsPermissible))
434 {
435 return FALSE;
436 }
437 else if (FirstPart.Buffer[i] == '.')
438 {
439 /* Filename can only contain one dot */
440 if (LastDot)
441 return FALSE;
442
443 LastDot = TRUE;
444
445 /* We mustn't have spaces before dot or at the end of the filename
446 * and no dot at the beginning of the filename */
447 if ((i == (FirstPart.Length) - 1) || i == 0)
448 return FALSE;
449
450 if (i > 0)
451 if (FirstPart.Buffer[i - 1] == ' ')
452 return FALSE;
453
454 /* Filename must be 8.3 filename and not 3.8 filename */
455 if ((FirstPart.Length - 1) - i > 3)
456 return FALSE;
457 }
458 }
459
460 /* Filename mustn't finish with a space */
461 if (FirstPart.Buffer[FirstPart.Length - 1] == ' ')
462 return FALSE;
463
464 EndLoop:
465 /* Preparing next loop */
466 Name.Buffer = RemainingPart.Buffer;
467 Name.Length = RemainingPart.Length;
468 Name.MaximumLength = RemainingPart.MaximumLength;
469
470 /* Call once again our dissect function */
471 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
472
473 /* We found a pathname, it wasn't allowed */
474 if (FirstPart.Length > 0 && !PathNamePermissible)
475 return FALSE;
476 }
477 return TRUE;
478 }
479
480 /*++
481 * @name FsRtlIsHpfsDbcsLegal
482 * @implemented
483 *
484 * Returns TRUE if the given DbcsName is a valid HPFS filename
485 *
486 * @param DbcsName
487 * The filename to check. It can also contains pathname.
488 *
489 * @param WildCardsPermissible
490 * If this is set to FALSE and if filename contains wildcard, the function
491 * will fail
492 *
493 * @param PathNamePermissible
494 * If this is set to FALSE and if the filename comes with a pathname, the
495 * function will fail
496 *
497 * @param LeadingBackslashPermissible
498 * If this is set to FALSE and if the filename starts with a backslash, the
499 * function will fail
500 *
501 * @return TRUE if the DbcsName is legal, FALSE otherwise
502 *
503 * @remarks None
504 *
505 *--*/
506 BOOLEAN
507 NTAPI
508 FsRtlIsHpfsDbcsLegal(IN ANSI_STRING DbcsName,
509 IN BOOLEAN WildCardsPermissible,
510 IN BOOLEAN PathNamePermissible,
511 IN BOOLEAN LeadingBackslashPermissible)
512 {
513 ANSI_STRING FirstPart, RemainingPart, Name;
514 USHORT i;
515 PAGED_CODE();
516
517 /* Just quit if the string is empty */
518 if (!DbcsName.Length)
519 return FALSE;
520
521 /* DbcsName wasn't supposed to be started with \ */
522 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
523 return FALSE;
524 /* DbcsName was allowed to be started with \, but now, remove it */
525 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
526 {
527 DbcsName.Buffer = DbcsName.Buffer + 1;
528 DbcsName.Length = DbcsName.Length - 1;
529 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
530 }
531
532 /* Extract first part of the DbcsName to work on */
533 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
534 while (FirstPart.Length > 0)
535 {
536 /* Accept special filename if wildcards are allowed */
537 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
538 {
539 if (FirstPart.Length == 2)
540 {
541 if (FirstPart.Buffer[1] == '.')
542 {
543 goto EndLoop;
544 }
545 }
546 else
547 {
548 goto EndLoop;
549 }
550 }
551
552 /* Filename must be 255 bytes maximum */
553 if (FirstPart.Length > 255)
554 return FALSE;
555
556 /* Now, we will parse the filename to find everything bad in */
557 for (i = 0; i < FirstPart.Length; i++)
558 {
559 /* First make sure the character it's not the Lead DBCS */
560 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
561 {
562 if (i == (FirstPart.Length) - 1)
563 return FALSE;
564 i++;
565 }
566 /* Then check for bad characters */
567 else if (!FsRtlIsAnsiCharacterLegalHpfs(FirstPart.Buffer[i], WildCardsPermissible))
568 {
569 return FALSE;
570 }
571 }
572
573 /* Filename mustn't finish with a space or a dot */
574 if ((FirstPart.Buffer[FirstPart.Length - 1] == ' ') ||
575 (FirstPart.Buffer[FirstPart.Length - 1] == '.'))
576 return FALSE;
577
578 EndLoop:
579 /* Preparing next loop */
580 Name.Buffer = RemainingPart.Buffer;
581 Name.Length = RemainingPart.Length;
582 Name.MaximumLength = RemainingPart.MaximumLength;
583
584 /* Call once again our dissect function */
585 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
586
587 /* We found a pathname, it wasn't allowed */
588 if (FirstPart.Length > 0 && !PathNamePermissible)
589 return FALSE;
590 }
591 return TRUE;
592 }