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