1cda629441e0ebe0f2e6299a49c4576a46913eae
[reactos.git] / reactos / ntoskrnl / fsrtl / name.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 name parsing and other support routines for FSDs
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
7 * Filip Navara (navaraf@reactos.org)
8 * Pierre Schweitzer (pierre.schweitzer@reactos.org)
9 * Aleksey Bragin (aleksey@reactos.org)
10 */
11
12 /* INCLUDES ******************************************************************/
13
14 #include <ntoskrnl.h>
15 #define NDEBUG
16 #include <debug.h>
17
18 /* PRIVATE FUNCTIONS *********************************************************/
19 BOOLEAN
20 NTAPI
21 FsRtlIsNameInExpressionPrivate(IN PUNICODE_STRING Expression,
22 IN PUNICODE_STRING Name,
23 IN BOOLEAN IgnoreCase,
24 IN PWCHAR UpcaseTable OPTIONAL)
25 {
26 SHORT StarFound = -1, DosStarFound = -1;
27 PUSHORT BackTracking = NULL, DosBackTracking = NULL;
28 UNICODE_STRING IntExpression;
29 USHORT ExpressionPosition = 0, NamePosition = 0, MatchingChars, LastDot;
30 WCHAR CompareChar;
31 PAGED_CODE();
32
33 /* Check if we were given strings at all */
34 if (!Name->Length || !Expression->Length)
35 {
36 /* Return TRUE if both strings are empty, otherwise FALSE */
37 if (Name->Length == 0 && Expression->Length == 0)
38 return TRUE;
39 else
40 return FALSE;
41 }
42
43 /* Check for a shortcut: just one wildcard */
44 if (Expression->Length == sizeof(WCHAR))
45 {
46 if (Expression->Buffer[0] == L'*')
47 return TRUE;
48 }
49
50 ASSERT(!IgnoreCase || UpcaseTable);
51
52 /* Another shortcut, wildcard followed by some string */
53 if (Expression->Buffer[0] == L'*')
54 {
55 /* Copy Expression to our local variable */
56 IntExpression = *Expression;
57
58 /* Skip the first char */
59 IntExpression.Buffer++;
60 IntExpression.Length -= sizeof(WCHAR);
61
62 /* Continue only if the rest of the expression does NOT contain
63 any more wildcards */
64 if (!FsRtlDoesNameContainWildCards(&IntExpression))
65 {
66 /* Check for a degenerate case */
67 if (Name->Length < (Expression->Length - sizeof(WCHAR)))
68 return FALSE;
69
70 /* Calculate position */
71 NamePosition = (Name->Length - IntExpression.Length) / sizeof(WCHAR);
72
73 /* Compare */
74 if (!IgnoreCase)
75 {
76 /* We can just do a byte compare */
77 return RtlEqualMemory(IntExpression.Buffer,
78 Name->Buffer + NamePosition,
79 IntExpression.Length);
80 }
81 else
82 {
83 /* Not so easy, need to upcase and check char by char */
84 for (ExpressionPosition = 0; ExpressionPosition < (IntExpression.Length / sizeof(WCHAR)); ExpressionPosition++)
85 {
86 /* Assert that expression is already upcased! */
87 ASSERT(IntExpression.Buffer[ExpressionPosition] == UpcaseTable[IntExpression.Buffer[ExpressionPosition]]);
88
89 /* Now compare upcased name char with expression */
90 if (UpcaseTable[Name->Buffer[NamePosition + ExpressionPosition]] !=
91 IntExpression.Buffer[ExpressionPosition])
92 {
93 return FALSE;
94 }
95 }
96
97 /* It matches */
98 return TRUE;
99 }
100 }
101 }
102
103 while ((NamePosition < Name->Length / sizeof(WCHAR)) &&
104 (ExpressionPosition < Expression->Length / sizeof(WCHAR)))
105 {
106 /* Basic check to test if chars are equal */
107 CompareChar = IgnoreCase ? UpcaseTable[Name->Buffer[NamePosition]] :
108 Name->Buffer[NamePosition];
109 if (Expression->Buffer[ExpressionPosition] == CompareChar)
110 {
111 NamePosition++;
112 ExpressionPosition++;
113 }
114 /* Check cases that eat one char */
115 else if (Expression->Buffer[ExpressionPosition] == L'?')
116 {
117 NamePosition++;
118 ExpressionPosition++;
119 }
120 /* Test star */
121 else if (Expression->Buffer[ExpressionPosition] == L'*')
122 {
123 /* Skip contigous stars */
124 while ((ExpressionPosition + 1 < (USHORT)(Expression->Length / sizeof(WCHAR))) &&
125 (Expression->Buffer[ExpressionPosition + 1] == L'*'))
126 {
127 ExpressionPosition++;
128 }
129
130 /* Save star position */
131 if (!BackTracking)
132 {
133 BackTracking = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
134 (Expression->Length / sizeof(WCHAR)) * sizeof(USHORT),
135 'nrSF');
136 }
137 BackTracking[++StarFound] = ExpressionPosition++;
138
139 /* If star is at the end, then eat all rest and leave */
140 if (ExpressionPosition == Expression->Length / sizeof(WCHAR))
141 {
142 NamePosition = Name->Length / sizeof(WCHAR);
143 break;
144 }
145 /* Allow null matching */
146 else if (Expression->Buffer[ExpressionPosition] != L'?' &&
147 Expression->Buffer[ExpressionPosition] != Name->Buffer[NamePosition])
148 {
149 NamePosition++;
150 }
151 }
152 /* Check DOS_STAR */
153 else if (Expression->Buffer[ExpressionPosition] == DOS_STAR)
154 {
155 /* Skip contigous stars */
156 while ((ExpressionPosition + 1 < (USHORT)(Expression->Length / sizeof(WCHAR))) &&
157 (Expression->Buffer[ExpressionPosition + 1] == DOS_STAR))
158 {
159 ExpressionPosition++;
160 }
161
162 /* Look for last dot */
163 MatchingChars = 0;
164 LastDot = (USHORT)-1;
165 while (MatchingChars < Name->Length / sizeof(WCHAR))
166 {
167 if (Name->Buffer[MatchingChars] == L'.')
168 {
169 LastDot = MatchingChars;
170 if (LastDot > NamePosition)
171 break;
172 }
173
174 MatchingChars++;
175 }
176
177 /* If we don't have dots or we didn't find last yet
178 * start eating everything
179 */
180 if (MatchingChars != Name->Length || LastDot == (USHORT)-1)
181 {
182 if (!DosBackTracking) DosBackTracking = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
183 (Expression->Length / sizeof(WCHAR)) * sizeof(USHORT),
184 'nrSF');
185 DosBackTracking[++DosStarFound] = ExpressionPosition++;
186
187 /* Not the same char, start exploring */
188 if (Expression->Buffer[ExpressionPosition] != Name->Buffer[NamePosition])
189 NamePosition++;
190 }
191 else
192 {
193 /* Else, if we are at last dot, eat it - otherwise, null match */
194 if (Name->Buffer[NamePosition] == '.')
195 NamePosition++;
196
197 ExpressionPosition++;
198 }
199 }
200 /* Check DOS_DOT */
201 else if (Expression->Buffer[ExpressionPosition] == DOS_DOT)
202 {
203 /* We only match dots */
204 if (Name->Buffer[NamePosition] == L'.')
205 {
206 NamePosition++;
207 }
208 /* Try to explore later on for null matching */
209 else if ((ExpressionPosition + 1 < (USHORT)(Expression->Length / sizeof(WCHAR))) &&
210 (Name->Buffer[NamePosition] == Expression->Buffer[ExpressionPosition + 1]))
211 {
212 NamePosition++;
213 }
214 ExpressionPosition++;
215 }
216 /* Check DOS_QM */
217 else if (Expression->Buffer[ExpressionPosition] == DOS_QM)
218 {
219 /* We match everything except dots */
220 if (Name->Buffer[NamePosition] != L'.')
221 {
222 NamePosition++;
223 }
224 ExpressionPosition++;
225 }
226 /* If nothing match, try to backtrack */
227 else if (StarFound >= 0)
228 {
229 ExpressionPosition = BackTracking[StarFound--];
230 }
231 else if (DosStarFound >= 0)
232 {
233 ExpressionPosition = DosBackTracking[DosStarFound--];
234 }
235 /* Otherwise, fail */
236 else
237 {
238 break;
239 }
240
241 /* Under certain circumstances, expression is over, but name isn't
242 * and we can backtrack, then, backtrack */
243 if (ExpressionPosition == Expression->Length / sizeof(WCHAR) &&
244 NamePosition != Name->Length / sizeof(WCHAR) &&
245 StarFound >= 0)
246 {
247 ExpressionPosition = BackTracking[StarFound--];
248 }
249 }
250 /* If we have nullable matching wc at the end of the string, eat them */
251 if (ExpressionPosition != Expression->Length / sizeof(WCHAR) && NamePosition == Name->Length / sizeof(WCHAR))
252 {
253 while (ExpressionPosition < Expression->Length / sizeof(WCHAR))
254 {
255 if (Expression->Buffer[ExpressionPosition] != DOS_DOT &&
256 Expression->Buffer[ExpressionPosition] != L'*' &&
257 Expression->Buffer[ExpressionPosition] != DOS_STAR)
258 {
259 break;
260 }
261 ExpressionPosition++;
262 }
263 }
264
265 if (BackTracking)
266 {
267 ExFreePoolWithTag(BackTracking, 'nrSF');
268 }
269 if (DosBackTracking)
270 {
271 ExFreePoolWithTag(DosBackTracking, 'nrSF');
272 }
273
274 return (ExpressionPosition == Expression->Length / sizeof(WCHAR) && NamePosition == Name->Length / sizeof(WCHAR));
275 }
276
277 /* PUBLIC FUNCTIONS **********************************************************/
278
279 /*++
280 * @name FsRtlAreNamesEqual
281 * @implemented
282 *
283 * Compare two strings to check if they match
284 *
285 * @param Name1
286 * First unicode string to compare
287 *
288 * @param Name2
289 * Second unicode string to compare
290 *
291 * @param IgnoreCase
292 * If TRUE, Case will be ignored when comparing strings
293 *
294 * @param UpcaseTable
295 * Table for upcase letters. If NULL is given, system one will be used
296 *
297 * @return TRUE if the strings are equal
298 *
299 * @remarks From Bo Branten's ntifs.h v25.
300 *
301 *--*/
302 BOOLEAN
303 NTAPI
304 FsRtlAreNamesEqual(IN PCUNICODE_STRING Name1,
305 IN PCUNICODE_STRING Name2,
306 IN BOOLEAN IgnoreCase,
307 IN PCWCH UpcaseTable OPTIONAL)
308 {
309 UNICODE_STRING UpcaseName1;
310 UNICODE_STRING UpcaseName2;
311 BOOLEAN StringsAreEqual, MemoryAllocated = FALSE;
312 USHORT i;
313 NTSTATUS Status;
314 PAGED_CODE();
315
316 /* Well, first check their size */
317 if (Name1->Length != Name2->Length) return FALSE;
318
319 /* Check if the caller didn't give an upcase table */
320 if ((IgnoreCase) && !(UpcaseTable))
321 {
322 /* Upcase the string ourselves */
323 Status = RtlUpcaseUnicodeString(&UpcaseName1, Name1, TRUE);
324 if (!NT_SUCCESS(Status)) RtlRaiseStatus(Status);
325
326 /* Upcase the second string too */
327 RtlUpcaseUnicodeString(&UpcaseName2, Name2, TRUE);
328 Name1 = &UpcaseName1;
329 Name2 = &UpcaseName2;
330
331 /* Make sure we go through the path below, but free the strings */
332 IgnoreCase = FALSE;
333 MemoryAllocated = TRUE;
334 }
335
336 /* Do a case-sensitive search */
337 if (!IgnoreCase)
338 {
339 /* Use a raw memory compare */
340 StringsAreEqual = RtlEqualMemory(Name1->Buffer,
341 Name2->Buffer,
342 Name1->Length);
343
344 /* Check if we allocated strings */
345 if (MemoryAllocated)
346 {
347 /* Free them */
348 RtlFreeUnicodeString(&UpcaseName1);
349 RtlFreeUnicodeString(&UpcaseName2);
350 }
351
352 /* Return the equality */
353 return StringsAreEqual;
354 }
355 else
356 {
357 /* Case in-sensitive search */
358 for (i = 0; i < Name1->Length / sizeof(WCHAR); i++)
359 {
360 /* Check if the character matches */
361 if (UpcaseTable[Name1->Buffer[i]] != UpcaseTable[Name2->Buffer[i]])
362 {
363 /* Non-match found! */
364 return FALSE;
365 }
366 }
367
368 /* We finished the loop so we are equal */
369 return TRUE;
370 }
371 }
372
373 /*++
374 * @name FsRtlDissectName
375 * @implemented
376 *
377 * Dissects a given path name into first and remaining part.
378 *
379 * @param Name
380 * Unicode string to dissect.
381 *
382 * @param FirstPart
383 * Pointer to user supplied UNICODE_STRING, that will later point
384 * to the first part of the original name.
385 *
386 * @param RemainingPart
387 * Pointer to user supplied UNICODE_STRING, that will later point
388 * to the remaining part of the original name.
389 *
390 * @return None
391 *
392 * @remarks Example:
393 * Name: \test1\test2\test3
394 * FirstPart: test1
395 * RemainingPart: test2\test3
396 *
397 *--*/
398 VOID
399 NTAPI
400 FsRtlDissectName(IN UNICODE_STRING Name,
401 OUT PUNICODE_STRING FirstPart,
402 OUT PUNICODE_STRING RemainingPart)
403 {
404 USHORT FirstPosition, i;
405 USHORT SkipFirstSlash = 0;
406 PAGED_CODE();
407
408 /* Zero the strings before continuing */
409 RtlZeroMemory(FirstPart, sizeof(UNICODE_STRING));
410 RtlZeroMemory(RemainingPart, sizeof(UNICODE_STRING));
411
412 /* Just quit if the string is empty */
413 if (!Name.Length) return;
414
415 /* Find first backslash */
416 FirstPosition = Name.Length / sizeof(WCHAR) ;
417 for (i = 0; i < Name.Length / sizeof(WCHAR); i++)
418 {
419 /* If we found one... */
420 if (Name.Buffer[i] == L'\\')
421 {
422 /* If it begins string, just notice it and continue */
423 if (i == 0)
424 {
425 SkipFirstSlash = 1;
426 }
427 else
428 {
429 /* Else, save its position and break out of the loop */
430 FirstPosition = i;
431 break;
432 }
433 }
434 }
435
436 /* Set up the first result string */
437 FirstPart->Buffer = Name.Buffer + SkipFirstSlash;
438 FirstPart->Length = (FirstPosition - SkipFirstSlash) * sizeof(WCHAR);
439 FirstPart->MaximumLength = FirstPart->Length;
440
441 /* And second one, if necessary */
442 if (FirstPosition < (Name.Length / sizeof(WCHAR)))
443 {
444 RemainingPart->Buffer = Name.Buffer + FirstPosition + 1;
445 RemainingPart->Length = Name.Length - (FirstPosition + 1) * sizeof(WCHAR);
446 RemainingPart->MaximumLength = RemainingPart->Length;
447 }
448 }
449
450 /*++
451 * @name FsRtlDoesNameContainWildCards
452 * @implemented
453 *
454 * Checks if the given string contains WildCards
455 *
456 * @param Name
457 * Pointer to a UNICODE_STRING containing Name to examine
458 *
459 * @return TRUE if Name contains wildcards, FALSE otherwise
460 *
461 * @remarks From Bo Branten's ntifs.h v12.
462 *
463 *--*/
464 BOOLEAN
465 NTAPI
466 FsRtlDoesNameContainWildCards(IN PUNICODE_STRING Name)
467 {
468 PWCHAR Ptr;
469 PAGED_CODE();
470
471 /* Loop through every character */
472 if (Name->Length)
473 {
474 Ptr = Name->Buffer + (Name->Length / sizeof(WCHAR)) - 1;
475 while ((Ptr >= Name->Buffer) && (*Ptr != L'\\'))
476 {
477 /* Check for Wildcard */
478 if (FsRtlIsUnicodeCharacterWild(*Ptr)) return TRUE;
479 Ptr--;
480 }
481 }
482
483 /* Nothing Found */
484 return FALSE;
485 }
486
487 /*++
488 * @name FsRtlIsNameInExpression
489 * @implemented
490 *
491 * Check if the Name string is in the Expression string.
492 *
493 * @param Expression
494 * The string in which we've to find Name. It can contain wildcards.
495 * If IgnoreCase is set to TRUE, this string MUST BE uppercase.
496 *
497 * @param Name
498 * The string to find. It cannot contain wildcards
499 *
500 * @param IgnoreCase
501 * If set to TRUE, case will be ignore with upcasing both strings
502 *
503 * @param UpcaseTable
504 * If not NULL, and if IgnoreCase is set to TRUE, it will be used to
505 * upcase the both strings
506 *
507 * @return TRUE if Name is in Expression, FALSE otherwise
508 *
509 * @remarks From Bo Branten's ntifs.h v12. This function should be
510 * rewritten to avoid recursion and better wildcard handling
511 * should be implemented (see FsRtlDoesNameContainWildCards).
512 *
513 *--*/
514 BOOLEAN
515 NTAPI
516 FsRtlIsNameInExpression(IN PUNICODE_STRING Expression,
517 IN PUNICODE_STRING Name,
518 IN BOOLEAN IgnoreCase,
519 IN PWCHAR UpcaseTable OPTIONAL)
520 {
521 BOOLEAN Result;
522 NTSTATUS Status;
523 UNICODE_STRING IntName;
524
525 if (IgnoreCase && !UpcaseTable)
526 {
527 Status = RtlUpcaseUnicodeString(&IntName, Name, TRUE);
528 if (!NT_SUCCESS(Status))
529 {
530 ExRaiseStatus(Status);
531 }
532 Name = &IntName;
533 IgnoreCase = FALSE;
534 }
535 else
536 {
537 IntName.Buffer = NULL;
538 }
539
540 Result = FsRtlIsNameInExpressionPrivate(Expression, Name, IgnoreCase, UpcaseTable);
541
542 if (IntName.Buffer != NULL)
543 {
544 RtlFreeUnicodeString(&IntName);
545 }
546
547 return Result;
548 }