[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 ExpressionPosition++;
220 continue;
221 }
222 /* If not, we only match a dot */
223 else if (Name->Buffer[NamePosition] == L'.')
224 {
225 NamePosition++;
226 ExpressionPosition++;
227 continue;
228 }
229 /* Otherwise, fail */
230 else
231 {
232 break;
233 }
234 }
235 /* If nothing match, try to backtrack */
236 else if (StarFound >= 0)
237 {
238 ExpressionPosition = BackTracking[StarFound--];
239 }
240 /* Otherwise, fail */
241 else
242 {
243 break;
244 }
245
246 /* Under certain circumstances, expression is over, but name isn't
247 * and we can backtrack, then, backtrack */
248 if (ExpressionPosition == Expression->Length / sizeof(WCHAR) &&
249 NamePosition != Name->Length / sizeof(WCHAR) &&
250 StarFound >= 0)
251 {
252 ExpressionPosition = BackTracking[StarFound--];
253 }
254 }
255 /* If we have nullable matching wc at the end of the string, eat them */
256 if (ExpressionPosition != Expression->Length / sizeof(WCHAR) && NamePosition == Name->Length / sizeof(WCHAR))
257 {
258 while (ExpressionPosition < Expression->Length / sizeof(WCHAR))
259 {
260 if (Expression->Buffer[ExpressionPosition] != DOS_DOT &&
261 Expression->Buffer[ExpressionPosition] != L'*' &&
262 Expression->Buffer[ExpressionPosition] != DOS_STAR)
263 {
264 break;
265 }
266 ExpressionPosition++;
267 }
268 }
269
270 if (BackTracking)
271 {
272 ExFreePoolWithTag(BackTracking, 'nrSF');
273 }
274
275 return (ExpressionPosition == Expression->Length / sizeof(WCHAR) && NamePosition == Name->Length / sizeof(WCHAR));
276 }
277
278 /* PUBLIC FUNCTIONS **********************************************************/
279
280 /*++
281 * @name FsRtlAreNamesEqual
282 * @implemented
283 *
284 * Compare two strings to check if they match
285 *
286 * @param Name1
287 * First unicode string to compare
288 *
289 * @param Name2
290 * Second unicode string to compare
291 *
292 * @param IgnoreCase
293 * If TRUE, Case will be ignored when comparing strings
294 *
295 * @param UpcaseTable
296 * Table for upcase letters. If NULL is given, system one will be used
297 *
298 * @return TRUE if the strings are equal
299 *
300 * @remarks From Bo Branten's ntifs.h v25.
301 *
302 *--*/
303 BOOLEAN
304 NTAPI
305 FsRtlAreNamesEqual(IN PCUNICODE_STRING Name1,
306 IN PCUNICODE_STRING Name2,
307 IN BOOLEAN IgnoreCase,
308 IN PCWCH UpcaseTable OPTIONAL)
309 {
310 UNICODE_STRING UpcaseName1;
311 UNICODE_STRING UpcaseName2;
312 BOOLEAN StringsAreEqual, MemoryAllocated = FALSE;
313 USHORT i;
314 NTSTATUS Status;
315 PAGED_CODE();
316
317 /* Well, first check their size */
318 if (Name1->Length != Name2->Length) return FALSE;
319
320 /* Check if the caller didn't give an upcase table */
321 if ((IgnoreCase) && !(UpcaseTable))
322 {
323 /* Upcase the string ourselves */
324 Status = RtlUpcaseUnicodeString(&UpcaseName1, Name1, TRUE);
325 if (!NT_SUCCESS(Status)) RtlRaiseStatus(Status);
326
327 /* Upcase the second string too */
328 RtlUpcaseUnicodeString(&UpcaseName2, Name2, TRUE);
329 Name1 = &UpcaseName1;
330 Name2 = &UpcaseName2;
331
332 /* Make sure we go through the path below, but free the strings */
333 IgnoreCase = FALSE;
334 MemoryAllocated = TRUE;
335 }
336
337 /* Do a case-sensitive search */
338 if (!IgnoreCase)
339 {
340 /* Use a raw memory compare */
341 StringsAreEqual = RtlEqualMemory(Name1->Buffer,
342 Name2->Buffer,
343 Name1->Length);
344
345 /* Check if we allocated strings */
346 if (MemoryAllocated)
347 {
348 /* Free them */
349 RtlFreeUnicodeString(&UpcaseName1);
350 RtlFreeUnicodeString(&UpcaseName2);
351 }
352
353 /* Return the equality */
354 return StringsAreEqual;
355 }
356 else
357 {
358 /* Case in-sensitive search */
359 for (i = 0; i < Name1->Length / sizeof(WCHAR); i++)
360 {
361 /* Check if the character matches */
362 if (UpcaseTable[Name1->Buffer[i]] != UpcaseTable[Name2->Buffer[i]])
363 {
364 /* Non-match found! */
365 return FALSE;
366 }
367 }
368
369 /* We finished the loop so we are equal */
370 return TRUE;
371 }
372 }
373
374 /*++
375 * @name FsRtlDissectName
376 * @implemented
377 *
378 * Dissects a given path name into first and remaining part.
379 *
380 * @param Name
381 * Unicode string to dissect.
382 *
383 * @param FirstPart
384 * Pointer to user supplied UNICODE_STRING, that will later point
385 * to the first part of the original name.
386 *
387 * @param RemainingPart
388 * Pointer to user supplied UNICODE_STRING, that will later point
389 * to the remaining part of the original name.
390 *
391 * @return None
392 *
393 * @remarks Example:
394 * Name: \test1\test2\test3
395 * FirstPart: test1
396 * RemainingPart: test2\test3
397 *
398 *--*/
399 VOID
400 NTAPI
401 FsRtlDissectName(IN UNICODE_STRING Name,
402 OUT PUNICODE_STRING FirstPart,
403 OUT PUNICODE_STRING RemainingPart)
404 {
405 USHORT FirstPosition, i;
406 USHORT SkipFirstSlash = 0;
407 PAGED_CODE();
408
409 /* Zero the strings before continuing */
410 RtlZeroMemory(FirstPart, sizeof(UNICODE_STRING));
411 RtlZeroMemory(RemainingPart, sizeof(UNICODE_STRING));
412
413 /* Just quit if the string is empty */
414 if (!Name.Length) return;
415
416 /* Find first backslash */
417 FirstPosition = Name.Length / sizeof(WCHAR) ;
418 for (i = 0; i < Name.Length / sizeof(WCHAR); i++)
419 {
420 /* If we found one... */
421 if (Name.Buffer[i] == L'\\')
422 {
423 /* If it begins string, just notice it and continue */
424 if (i == 0)
425 {
426 SkipFirstSlash = 1;
427 }
428 else
429 {
430 /* Else, save its position and break out of the loop */
431 FirstPosition = i;
432 break;
433 }
434 }
435 }
436
437 /* Set up the first result string */
438 FirstPart->Buffer = Name.Buffer + SkipFirstSlash;
439 FirstPart->Length = (FirstPosition - SkipFirstSlash) * sizeof(WCHAR);
440 FirstPart->MaximumLength = FirstPart->Length;
441
442 /* And second one, if necessary */
443 if (FirstPosition < (Name.Length / sizeof(WCHAR)))
444 {
445 RemainingPart->Buffer = Name.Buffer + FirstPosition + 1;
446 RemainingPart->Length = Name.Length - (FirstPosition + 1) * sizeof(WCHAR);
447 RemainingPart->MaximumLength = RemainingPart->Length;
448 }
449 }
450
451 /*++
452 * @name FsRtlDoesNameContainWildCards
453 * @implemented
454 *
455 * Checks if the given string contains WildCards
456 *
457 * @param Name
458 * Pointer to a UNICODE_STRING containing Name to examine
459 *
460 * @return TRUE if Name contains wildcards, FALSE otherwise
461 *
462 * @remarks From Bo Branten's ntifs.h v12.
463 *
464 *--*/
465 BOOLEAN
466 NTAPI
467 FsRtlDoesNameContainWildCards(IN PUNICODE_STRING Name)
468 {
469 PWCHAR Ptr;
470 PAGED_CODE();
471
472 /* Loop through every character */
473 if (Name->Length)
474 {
475 Ptr = Name->Buffer + (Name->Length / sizeof(WCHAR)) - 1;
476 while ((Ptr >= Name->Buffer) && (*Ptr != L'\\'))
477 {
478 /* Check for Wildcard */
479 if (FsRtlIsUnicodeCharacterWild(*Ptr)) return TRUE;
480 Ptr--;
481 }
482 }
483
484 /* Nothing Found */
485 return FALSE;
486 }
487
488 /*++
489 * @name FsRtlIsNameInExpression
490 * @implemented
491 *
492 * Check if the Name string is in the Expression string.
493 *
494 * @param Expression
495 * The string in which we've to find Name. It can contain wildcards.
496 * If IgnoreCase is set to TRUE, this string MUST BE uppercase.
497 *
498 * @param Name
499 * The string to find. It cannot contain wildcards
500 *
501 * @param IgnoreCase
502 * If set to TRUE, case will be ignore with upcasing both strings
503 *
504 * @param UpcaseTable
505 * If not NULL, and if IgnoreCase is set to TRUE, it will be used to
506 * upcase the both strings
507 *
508 * @return TRUE if Name is in Expression, FALSE otherwise
509 *
510 * @remarks From Bo Branten's ntifs.h v12. This function should be
511 * rewritten to avoid recursion and better wildcard handling
512 * should be implemented (see FsRtlDoesNameContainWildCards).
513 *
514 *--*/
515 BOOLEAN
516 NTAPI
517 FsRtlIsNameInExpression(IN PUNICODE_STRING Expression,
518 IN PUNICODE_STRING Name,
519 IN BOOLEAN IgnoreCase,
520 IN PWCHAR UpcaseTable OPTIONAL)
521 {
522 BOOLEAN Result;
523 NTSTATUS Status;
524 UNICODE_STRING IntName;
525
526 if (IgnoreCase && !UpcaseTable)
527 {
528 Status = RtlUpcaseUnicodeString(&IntName, Name, TRUE);
529 if (!NT_SUCCESS(Status))
530 {
531 ExRaiseStatus(Status);
532 }
533 Name = &IntName;
534 IgnoreCase = FALSE;
535 }
536 else
537 {
538 IntName.Buffer = NULL;
539 }
540
541 Result = FsRtlIsNameInExpressionPrivate(Expression, Name, IgnoreCase, UpcaseTable);
542
543 if (IntName.Buffer != NULL)
544 {
545 RtlFreeUnicodeString(&IntName);
546 }
547
548 return Result;
549 }