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