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