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