[NTOSKRNL]
[reactos.git] / reactos / ntoskrnl / fsrtl / dbcsname.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 DBCS parsing and other support routines for FSDs
6 * PROGRAMMERS: Alex Ionescu (alex.ionescu@reactos.org)
7 * Pierre Schweitzer (pierre.schweitzer@reactos.org)
8 */
9
10 /* INCLUDES ******************************************************************/
11
12 #include <ntoskrnl.h>
13 #define NDEBUG
14 #include <debug.h>
15
16 /* PUBLIC FUNCTIONS **********************************************************/
17
18 /*++
19 * @name FsRtlDissectDbcs
20 * @implemented
21 *
22 * Dissects a given path name into first and remaining part.
23 *
24 * @param Name
25 * ANSI string to dissect.
26 *
27 * @param FirstPart
28 * Pointer to user supplied ANSI_STRING, that will later point
29 * to the first part of the original name.
30 *
31 * @param RemainingPart
32 * Pointer to user supplied ANSI_STRING, that will later point
33 * to the remaining part of the original name.
34 *
35 * @return None
36 *
37 * @remarks Example:
38 * Name: \test1\test2\test3
39 * FirstPart: test1
40 * RemainingPart: test2\test3
41 *
42 *--*/
43 VOID
44 NTAPI
45 FsRtlDissectDbcs(IN ANSI_STRING Name,
46 OUT PANSI_STRING FirstPart,
47 OUT PANSI_STRING RemainingPart)
48 {
49 USHORT FirstPosition, i;
50 USHORT SkipFirstSlash = 0;
51 PAGED_CODE();
52
53 /* Zero the strings before continuing */
54 RtlZeroMemory(FirstPart, sizeof(ANSI_STRING));
55 RtlZeroMemory(RemainingPart, sizeof(ANSI_STRING));
56
57 /* Just quit if the string is empty */
58 if (!Name.Length) return;
59
60 /* Find first backslash */
61 FirstPosition = Name.Length;
62 for (i = 0; i < Name.Length; i++)
63 {
64 /* First make sure the character it's not the Lead DBCS */
65 if (FsRtlIsLeadDbcsCharacter(Name.Buffer[i]))
66 {
67 i++;
68 }
69 /* If we found one... */
70 else if (Name.Buffer[i] == '\\')
71 {
72 /* If it begins string, just notice it and continue */
73 if (i == 0)
74 {
75 SkipFirstSlash = 1;
76 }
77 else
78 {
79 /* Else, save its position and break out of the loop */
80 FirstPosition = i;
81 break;
82 }
83 }
84 }
85
86 /* Set up the first result string */
87 FirstPart->Buffer = Name.Buffer + SkipFirstSlash;
88 FirstPart->Length = (FirstPosition - SkipFirstSlash);
89 FirstPart->MaximumLength = FirstPart->Length;
90
91 /* And second one, if necessary */
92 if (FirstPosition < (Name.Length))
93 {
94 RemainingPart->Buffer = Name.Buffer + FirstPosition + 1;
95 RemainingPart->Length = Name.Length - (FirstPosition + 1);
96 RemainingPart->MaximumLength = RemainingPart->Length;
97 }
98 }
99
100 /*++
101 * @name FsRtlDoesDbcsContainWildCards
102 * @implemented
103 *
104 * Returns TRUE if the given DbcsName contains wildcards such as *, ?,
105 * ANSI_DOS_STAR, ANSI_DOS_DOT, and ANSI_DOS_QM
106 *
107 * @param Name
108 * The Name to check
109 *
110 * @return TRUE if there are wildcards, FALSE otherwise
111 *
112 * @remarks None
113 *
114 *--*/
115 BOOLEAN
116 NTAPI
117 FsRtlDoesDbcsContainWildCards(IN PANSI_STRING Name)
118 {
119 USHORT i;
120 PAGED_CODE();
121
122 /* Check every character */
123 for (i = 0; i < Name->Length; i++)
124 {
125 /* First make sure it's not the Lead DBCS */
126 if (FsRtlIsLeadDbcsCharacter(Name->Buffer[i]))
127 {
128 i++;
129 }
130 else if (FsRtlIsAnsiCharacterWild(Name->Buffer[i]))
131 {
132 /* Now return if it has a wildcard */
133 return TRUE;
134 }
135 }
136
137 /* We didn't return above...so none found */
138 return FALSE;
139 }
140
141 /*++
142 * @name FsRtlIsDbcsInExpression
143 * @implemented
144 *
145 * Check if the Name string is in the Expression string.
146 *
147 * @param Expression
148 * The string in which we've to find Name. It can contains wildcards
149 *
150 * @param Name
151 * The string to find. It cannot contain wildcards.
152 *
153 * @return TRUE if Name is found in Expression, FALSE otherwise
154 *
155 * @remarks
156 *
157 *--*/
158 BOOLEAN
159 NTAPI
160 FsRtlIsDbcsInExpression(IN PANSI_STRING Expression,
161 IN PANSI_STRING Name)
162 {
163 SHORT StarFound = -1;
164 PUSHORT BackTracking = NULL;
165 USHORT ExpressionPosition = 0, NamePosition = 0, MatchingChars;
166 PAGED_CODE();
167
168 ASSERT(Name->Length);
169 ASSERT(Expression->Length);
170 ASSERT(!FsRtlDoesDbcsContainWildCards(Name));
171
172 while (NamePosition < Name->Length && ExpressionPosition < Expression->Length)
173 {
174 /* Basic check to test if chars are equal */
175 if ((Expression->Buffer[ExpressionPosition] == Name->Buffer[NamePosition]))
176 {
177 NamePosition++;
178 ExpressionPosition++;
179 }
180 /* Check cases that eat one char */
181 else if ((Expression->Buffer[ExpressionPosition] == '?') || (Expression->Buffer[ExpressionPosition] == ANSI_DOS_QM) ||
182 (Expression->Buffer[ExpressionPosition] == ANSI_DOS_DOT && Name->Buffer[NamePosition] == '.'))
183 {
184 NamePosition++;
185 ExpressionPosition++;
186 }
187 /* Test star */
188 else if (Expression->Buffer[ExpressionPosition] == '*')
189 {
190 /* Skip contigous stars */
191 while (ExpressionPosition + 1 < Expression->Length && Expression->Buffer[ExpressionPosition + 1] == '*')
192 {
193 ExpressionPosition++;
194 }
195
196 /* Save star position */
197 if (!BackTracking)
198 {
199 BackTracking = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
200 Expression->Length * sizeof(USHORT), 'nrSF');
201 }
202 BackTracking[++StarFound] = ExpressionPosition++;
203
204 /* If star is at the end, then eat all rest and leave */
205 if (ExpressionPosition == Expression->Length)
206 {
207 NamePosition = Name->Length;
208 break;
209 }
210 /* Allow null matching */
211 else if (Expression->Buffer[ExpressionPosition] != '?' &&
212 Expression->Buffer[ExpressionPosition] != Name->Buffer[NamePosition])
213 {
214 NamePosition++;
215 }
216 }
217 /* Check DOS_STAR */
218 else if (Expression->Buffer[ExpressionPosition] == ANSI_DOS_STAR)
219 {
220 MatchingChars = NamePosition;
221 while (MatchingChars < Name->Length)
222 {
223 if (Name->Buffer[MatchingChars] == '.')
224 {
225 NamePosition = MatchingChars + 1;
226 }
227 MatchingChars++;
228 }
229 ExpressionPosition++;
230 }
231 /* If nothing match, try to backtrack */
232 else if (StarFound >= 0)
233 {
234 ExpressionPosition = BackTracking[StarFound--];
235 }
236 /* Otherwise, fail */
237 else
238 {
239 break;
240 }
241
242 /* Under certain circumstances, expression is over, but name isn't
243 * and we can backtrack, then, backtrack */
244 if (ExpressionPosition == Expression->Length &&
245 NamePosition != Name->Length && StarFound >= 0)
246 {
247 ExpressionPosition = BackTracking[StarFound--];
248 }
249 }
250 /* If we have nullable matching wc at the end of the string, eat them */
251 if (ExpressionPosition != Expression->Length && NamePosition == Name->Length)
252 {
253 while (ExpressionPosition < Expression->Length)
254 {
255 if (Expression->Buffer[ExpressionPosition] != ANSI_DOS_DOT &&
256 Expression->Buffer[ExpressionPosition] != '*' &&
257 Expression->Buffer[ExpressionPosition] != ANSI_DOS_STAR)
258 {
259 break;
260 }
261 ExpressionPosition++;
262 }
263 }
264
265 if (BackTracking)
266 {
267 ExFreePoolWithTag(BackTracking, 'nrSF');
268 }
269
270 return (ExpressionPosition == Expression->Length && NamePosition == Name->Length);
271 }
272
273 /*++
274 * @name FsRtlIsFatDbcsLegal
275 * @implemented
276 *
277 * Returns TRUE if the given DbcsName is a valid FAT filename (in 8.3)
278 *
279 * @param DbcsName
280 * The filename to check. It can also contains pathname.
281 *
282 * @param WildCardsPermissible
283 * If this is set to FALSE and if filename contains wildcard, the function
284 * will fail
285 *
286 * @param PathNamePermissible
287 * If this is set to FALSE and if the filename comes with a pathname, the
288 * function will fail
289 *
290 * @param LeadingBackslashPermissible
291 * If this is set to FALSE and if the filename starts with a backslash, the
292 * function will fail
293 *
294 * @return TRUE if the DbcsName is legal, FALSE otherwise
295 *
296 * @remarks None
297 *
298 *--*/
299 BOOLEAN
300 NTAPI
301 FsRtlIsFatDbcsLegal(IN ANSI_STRING DbcsName,
302 IN BOOLEAN WildCardsPermissible,
303 IN BOOLEAN PathNamePermissible,
304 IN BOOLEAN LeadingBackslashPermissible)
305 {
306 ANSI_STRING FirstPart, RemainingPart, Name;
307 BOOLEAN LastDot;
308 USHORT i;
309 PAGED_CODE();
310
311 /* Just quit if the string is empty */
312 if (!DbcsName.Length)
313 return FALSE;
314
315 /* DbcsName wasn't supposed to be started with \ */
316 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
317 return FALSE;
318 /* DbcsName was allowed to be started with \, but now, remove it */
319 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
320 {
321 DbcsName.Buffer = DbcsName.Buffer + 1;
322 DbcsName.Length = DbcsName.Length - 1;
323 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
324 }
325
326 /* Extract first part of the DbcsName to work on */
327 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
328 while (FirstPart.Length > 0)
329 {
330 /* Reset dots count */
331 LastDot = FALSE;
332
333 /* Accept special filename if wildcards are allowed */
334 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
335 {
336 if (FirstPart.Length == 2)
337 {
338 if (FirstPart.Buffer[1] == '.')
339 {
340 goto EndLoop;
341 }
342 }
343 else
344 {
345 goto EndLoop;
346 }
347 }
348
349 /* Filename must be 8.3 filename */
350 if (FirstPart.Length < 3 || FirstPart.Length > 12)
351 return FALSE;
352
353 /* Now, we will parse the filename to find everything bad in */
354 for (i = 0; i < FirstPart.Length; i++)
355 {
356 /* First make sure the character it's not the Lead DBCS */
357 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
358 {
359 if (i == (FirstPart.Length) - 1)
360 return FALSE;
361 i++;
362 }
363 /* Then check for bad characters */
364 else if (!FsRtlIsAnsiCharacterLegalFat(FirstPart.Buffer[i], WildCardsPermissible))
365 {
366 return FALSE;
367 }
368 else if (FirstPart.Buffer[i] == '.')
369 {
370 /* Filename can only contain one dot */
371 if (LastDot)
372 return FALSE;
373
374 LastDot = TRUE;
375
376 /* We mustn't have spaces before dot or at the end of the filename
377 * and no dot at the beginning of the filename */
378 if ((i == (FirstPart.Length) - 1) || i == 0)
379 return FALSE;
380
381 if (i > 0)
382 if (FirstPart.Buffer[i - 1] == ' ')
383 return FALSE;
384
385 /* Filename must be 8.3 filename and not 3.8 filename */
386 if ((FirstPart.Length - 1) - i > 3)
387 return FALSE;
388 }
389 }
390
391 /* Filename mustn't finish with a space */
392 if (FirstPart.Buffer[FirstPart.Length - 1] == ' ')
393 return FALSE;
394
395 EndLoop:
396 /* Preparing next loop */
397 Name.Buffer = RemainingPart.Buffer;
398 Name.Length = RemainingPart.Length;
399 Name.MaximumLength = RemainingPart.MaximumLength;
400
401 /* Call once again our dissect function */
402 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
403
404 /* We found a pathname, it wasn't allowed */
405 if (FirstPart.Length > 0 && !PathNamePermissible)
406 return FALSE;
407 }
408 return TRUE;
409 }
410
411 /*++
412 * @name FsRtlIsHpfsDbcsLegal
413 * @implemented
414 *
415 * Returns TRUE if the given DbcsName is a valid HPFS filename
416 *
417 * @param DbcsName
418 * The filename to check. It can also contains pathname.
419 *
420 * @param WildCardsPermissible
421 * If this is set to FALSE and if filename contains wildcard, the function
422 * will fail
423 *
424 * @param PathNamePermissible
425 * If this is set to FALSE and if the filename comes with a pathname, the
426 * function will fail
427 *
428 * @param LeadingBackslashPermissible
429 * If this is set to FALSE and if the filename starts with a backslash, the
430 * function will fail
431 *
432 * @return TRUE if the DbcsName is legal, FALSE otherwise
433 *
434 * @remarks None
435 *
436 *--*/
437 BOOLEAN
438 NTAPI
439 FsRtlIsHpfsDbcsLegal(IN ANSI_STRING DbcsName,
440 IN BOOLEAN WildCardsPermissible,
441 IN BOOLEAN PathNamePermissible,
442 IN BOOLEAN LeadingBackslashPermissible)
443 {
444 ANSI_STRING FirstPart, RemainingPart, Name;
445 USHORT i;
446 PAGED_CODE();
447
448 /* Just quit if the string is empty */
449 if (!DbcsName.Length)
450 return FALSE;
451
452 /* DbcsName wasn't supposed to be started with \ */
453 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
454 return FALSE;
455 /* DbcsName was allowed to be started with \, but now, remove it */
456 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
457 {
458 DbcsName.Buffer = DbcsName.Buffer + 1;
459 DbcsName.Length = DbcsName.Length - 1;
460 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
461 }
462
463 /* Extract first part of the DbcsName to work on */
464 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
465 while (FirstPart.Length > 0)
466 {
467 /* Accept special filename if wildcards are allowed */
468 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
469 {
470 if (FirstPart.Length == 2)
471 {
472 if (FirstPart.Buffer[1] == '.')
473 {
474 goto EndLoop;
475 }
476 }
477 else
478 {
479 goto EndLoop;
480 }
481 }
482
483 /* Filename must be 255 bytes maximum */
484 if (FirstPart.Length > 255)
485 return FALSE;
486
487 /* Now, we will parse the filename to find everything bad in */
488 for (i = 0; i < FirstPart.Length; i++)
489 {
490 /* First make sure the character it's not the Lead DBCS */
491 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
492 {
493 if (i == (FirstPart.Length) - 1)
494 return FALSE;
495 i++;
496 }
497 /* Then check for bad characters */
498 else if (!FsRtlIsAnsiCharacterLegalHpfs(FirstPart.Buffer[i], WildCardsPermissible))
499 {
500 return FALSE;
501 }
502 }
503
504 /* Filename mustn't finish with a space or a dot */
505 if ((FirstPart.Buffer[FirstPart.Length - 1] == ' ') ||
506 (FirstPart.Buffer[FirstPart.Length - 1] == '.'))
507 return FALSE;
508
509 EndLoop:
510 /* Preparing next loop */
511 Name.Buffer = RemainingPart.Buffer;
512 Name.Length = RemainingPart.Length;
513 Name.MaximumLength = RemainingPart.MaximumLength;
514
515 /* Call once again our dissect function */
516 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
517
518 /* We found a pathname, it wasn't allowed */
519 if (FirstPart.Length > 0 && !PathNamePermissible)
520 return FALSE;
521 }
522 return TRUE;
523 }