Branching for 0.3.15 release after two days of no response from a certain sphere...
[reactos.git] / 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 else if (Expression->Buffer[ExpressionPosition] != '?')
211 {
212 NamePosition++;
213 }
214 }
215 /* Check DOS_STAR */
216 else if (Expression->Buffer[ExpressionPosition] == ANSI_DOS_STAR)
217 {
218 MatchingChars = NamePosition;
219 while (MatchingChars < Name->Length)
220 {
221 if (Name->Buffer[MatchingChars] == '.')
222 {
223 NamePosition = MatchingChars + 1;
224 }
225 MatchingChars++;
226 }
227 ExpressionPosition++;
228 }
229 /* If nothing match, try to backtrack */
230 else if (StarFound >= 0)
231 {
232 ExpressionPosition = BackTracking[StarFound--];
233 }
234 /* Otherwise, fail */
235 else
236 {
237 break;
238 }
239
240 /* Under certain circumstances, expression is over, but name isn't
241 * and we can backtrack, then, backtrack */
242 if (ExpressionPosition == Expression->Length &&
243 NamePosition != Name->Length && StarFound >= 0)
244 {
245 ExpressionPosition = BackTracking[StarFound--];
246 }
247 }
248 if (ExpressionPosition + 1 == Expression->Length && NamePosition == Name->Length &&
249 Expression->Buffer[ExpressionPosition] == ANSI_DOS_DOT)
250 {
251 ExpressionPosition++;
252 }
253
254 if (BackTracking)
255 {
256 ExFreePoolWithTag(BackTracking, 'nrSF');
257 }
258
259 return (ExpressionPosition == Expression->Length && NamePosition == Name->Length);
260 }
261
262 /*++
263 * @name FsRtlIsFatDbcsLegal
264 * @implemented
265 *
266 * Returns TRUE if the given DbcsName is a valid FAT filename (in 8.3)
267 *
268 * @param DbcsName
269 * The filename to check. It can also contains pathname.
270 *
271 * @param WildCardsPermissible
272 * If this is set to FALSE and if filename contains wildcard, the function
273 * will fail
274 *
275 * @param PathNamePermissible
276 * If this is set to FALSE and if the filename comes with a pathname, the
277 * function will fail
278 *
279 * @param LeadingBackslashPermissible
280 * If this is set to FALSE and if the filename starts with a backslash, the
281 * function will fail
282 *
283 * @return TRUE if the DbcsName is legal, FALSE otherwise
284 *
285 * @remarks None
286 *
287 *--*/
288 BOOLEAN
289 NTAPI
290 FsRtlIsFatDbcsLegal(IN ANSI_STRING DbcsName,
291 IN BOOLEAN WildCardsPermissible,
292 IN BOOLEAN PathNamePermissible,
293 IN BOOLEAN LeadingBackslashPermissible)
294 {
295 ANSI_STRING FirstPart, RemainingPart, Name;
296 BOOLEAN LastDot;
297 USHORT i;
298 PAGED_CODE();
299
300 /* Just quit if the string is empty */
301 if (!DbcsName.Length)
302 return FALSE;
303
304 /* DbcsName wasn't supposed to be started with \ */
305 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
306 return FALSE;
307 /* DbcsName was allowed to be started with \, but now, remove it */
308 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
309 {
310 DbcsName.Buffer = DbcsName.Buffer + 1;
311 DbcsName.Length = DbcsName.Length - 1;
312 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
313 }
314
315 /* Extract first part of the DbcsName to work on */
316 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
317 while (FirstPart.Length > 0)
318 {
319 /* Reset dots count */
320 LastDot = FALSE;
321
322 /* Accept special filename if wildcards are allowed */
323 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
324 {
325 if (FirstPart.Length == 2)
326 {
327 if (FirstPart.Buffer[1] == '.')
328 {
329 goto EndLoop;
330 }
331 }
332 else
333 {
334 goto EndLoop;
335 }
336 }
337
338 /* Filename must be 8.3 filename */
339 if (FirstPart.Length < 3 || FirstPart.Length > 12)
340 return FALSE;
341
342 /* Now, we will parse the filename to find everything bad in */
343 for (i = 0; i < FirstPart.Length; i++)
344 {
345 /* First make sure the character it's not the Lead DBCS */
346 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
347 {
348 if (i == (FirstPart.Length) - 1)
349 return FALSE;
350 i++;
351 }
352 /* Then check for bad characters */
353 else if (!FsRtlIsAnsiCharacterLegalFat(FirstPart.Buffer[i], WildCardsPermissible))
354 {
355 return FALSE;
356 }
357 else if (FirstPart.Buffer[i] == '.')
358 {
359 /* Filename can only contain one dot */
360 if (LastDot)
361 return FALSE;
362
363 LastDot = TRUE;
364
365 /* We mustn't have spaces before dot or at the end of the filename
366 * and no dot at the beginning of the filename */
367 if ((i == (FirstPart.Length) - 1) || i == 0)
368 return FALSE;
369
370 if (i > 0)
371 if (FirstPart.Buffer[i - 1] == ' ')
372 return FALSE;
373
374 /* Filename must be 8.3 filename and not 3.8 filename */
375 if ((FirstPart.Length - 1) - i > 3)
376 return FALSE;
377 }
378 }
379
380 /* Filename mustn't finish with a space */
381 if (FirstPart.Buffer[FirstPart.Length - 1] == ' ')
382 return FALSE;
383
384 EndLoop:
385 /* Preparing next loop */
386 Name.Buffer = RemainingPart.Buffer;
387 Name.Length = RemainingPart.Length;
388 Name.MaximumLength = RemainingPart.MaximumLength;
389
390 /* Call once again our dissect function */
391 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
392
393 /* We found a pathname, it wasn't allowed */
394 if (FirstPart.Length > 0 && !PathNamePermissible)
395 return FALSE;
396 }
397 return TRUE;
398 }
399
400 /*++
401 * @name FsRtlIsHpfsDbcsLegal
402 * @implemented
403 *
404 * Returns TRUE if the given DbcsName is a valid HPFS filename
405 *
406 * @param DbcsName
407 * The filename to check. It can also contains pathname.
408 *
409 * @param WildCardsPermissible
410 * If this is set to FALSE and if filename contains wildcard, the function
411 * will fail
412 *
413 * @param PathNamePermissible
414 * If this is set to FALSE and if the filename comes with a pathname, the
415 * function will fail
416 *
417 * @param LeadingBackslashPermissible
418 * If this is set to FALSE and if the filename starts with a backslash, the
419 * function will fail
420 *
421 * @return TRUE if the DbcsName is legal, FALSE otherwise
422 *
423 * @remarks None
424 *
425 *--*/
426 BOOLEAN
427 NTAPI
428 FsRtlIsHpfsDbcsLegal(IN ANSI_STRING DbcsName,
429 IN BOOLEAN WildCardsPermissible,
430 IN BOOLEAN PathNamePermissible,
431 IN BOOLEAN LeadingBackslashPermissible)
432 {
433 ANSI_STRING FirstPart, RemainingPart, Name;
434 USHORT i;
435 PAGED_CODE();
436
437 /* Just quit if the string is empty */
438 if (!DbcsName.Length)
439 return FALSE;
440
441 /* DbcsName wasn't supposed to be started with \ */
442 if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
443 return FALSE;
444 /* DbcsName was allowed to be started with \, but now, remove it */
445 else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
446 {
447 DbcsName.Buffer = DbcsName.Buffer + 1;
448 DbcsName.Length = DbcsName.Length - 1;
449 DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
450 }
451
452 /* Extract first part of the DbcsName to work on */
453 FsRtlDissectDbcs(DbcsName, &FirstPart, &RemainingPart);
454 while (FirstPart.Length > 0)
455 {
456 /* Accept special filename if wildcards are allowed */
457 if (WildCardsPermissible && (FirstPart.Length == 1 || FirstPart.Length == 2) && FirstPart.Buffer[0] == '.')
458 {
459 if (FirstPart.Length == 2)
460 {
461 if (FirstPart.Buffer[1] == '.')
462 {
463 goto EndLoop;
464 }
465 }
466 else
467 {
468 goto EndLoop;
469 }
470 }
471
472 /* Filename must be 255 bytes maximum */
473 if (FirstPart.Length > 255)
474 return FALSE;
475
476 /* Now, we will parse the filename to find everything bad in */
477 for (i = 0; i < FirstPart.Length; i++)
478 {
479 /* First make sure the character it's not the Lead DBCS */
480 if (FsRtlIsLeadDbcsCharacter(FirstPart.Buffer[i]))
481 {
482 if (i == (FirstPart.Length) - 1)
483 return FALSE;
484 i++;
485 }
486 /* Then check for bad characters */
487 else if (!FsRtlIsAnsiCharacterLegalHpfs(FirstPart.Buffer[i], WildCardsPermissible))
488 {
489 return FALSE;
490 }
491 }
492
493 /* Filename mustn't finish with a space or a dot */
494 if ((FirstPart.Buffer[FirstPart.Length - 1] == ' ') ||
495 (FirstPart.Buffer[FirstPart.Length - 1] == '.'))
496 return FALSE;
497
498 EndLoop:
499 /* Preparing next loop */
500 Name.Buffer = RemainingPart.Buffer;
501 Name.Length = RemainingPart.Length;
502 Name.MaximumLength = RemainingPart.MaximumLength;
503
504 /* Call once again our dissect function */
505 FsRtlDissectDbcs(Name, &FirstPart, &RemainingPart);
506
507 /* We found a pathname, it wasn't allowed */
508 if (FirstPart.Length > 0 && !PathNamePermissible)
509 return FALSE;
510 }
511 return TRUE;
512 }