[NTOSKRNL] Fix some possible overruns in FsRtlIsNameInExpressionPrivate + add a test...
[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 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 }