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