xref: /reactos/ntoskrnl/fsrtl/dbcsname.c (revision 84344399)
1 /*
2  * PROJECT:         ReactOS Kernel
3  * LICENSE:         GPL - See COPYING in the top level directory
4  * FILE:            ntoskrnl/fsrtl/dbcsname.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     USHORT Offset, Position, BackTrackingPosition, OldBackTrackingPosition;
164     USHORT BackTrackingBuffer[16], OldBackTrackingBuffer[16] = {0};
165     PUSHORT BackTrackingSwap, BackTracking = BackTrackingBuffer, OldBackTracking = OldBackTrackingBuffer;
166     ULONG BackTrackingBufferSize = RTL_NUMBER_OF(BackTrackingBuffer);
167     PVOID AllocatedBuffer = NULL;
168     USHORT ExpressionPosition, NamePosition = 0, MatchingChars = 1;
169     USHORT NameChar = 0, ExpressionChar;
170     BOOLEAN EndOfName = FALSE;
171     BOOLEAN Result;
172     BOOLEAN DontSkipDot;
173     PAGED_CODE();
174 
175     ASSERT(Name->Length);
176     ASSERT(Expression->Length);
177     ASSERT(!FsRtlDoesDbcsContainWildCards(Name));
178 
179     /* Check if we were given strings at all */
180     if (!Name->Length || !Expression->Length)
181     {
182         /* Return TRUE if both strings are empty, otherwise FALSE */
183         if (Name->Length == 0 && Expression->Length == 0)
184             return TRUE;
185         else
186             return FALSE;
187     }
188 
189     /* Check for a shortcut: just one wildcard */
190     if (Expression->Length == sizeof(CHAR))
191     {
192         if (Expression->Buffer[0] == '*')
193             return TRUE;
194     }
195 
196     //ASSERT(FsRtlDoesDbcsContainWildCards(Expression));
197 
198     /* Another shortcut, wildcard followed by some string */
199     if (Expression->Buffer[0] == '*')
200     {
201         /* Copy Expression to our local variable */
202         ANSI_STRING IntExpression = *Expression;
203 
204         /* Skip the first char */
205         IntExpression.Buffer++;
206         IntExpression.Length -= sizeof(CHAR);
207 
208         /* Continue only if the rest of the expression does NOT contain
209            any more wildcards */
210         if (!FsRtlDoesDbcsContainWildCards(&IntExpression))
211         {
212             /* Check for a degenerate case */
213             if (Name->Length < (Expression->Length - sizeof(CHAR)))
214                 return FALSE;
215 
216             /* Calculate position */
217             NamePosition = (Name->Length - IntExpression.Length) / sizeof(CHAR);
218 
219             /* Check whether we are breaking a two chars char (DBCS) */
220             if (NlsMbOemCodePageTag)
221             {
222                 MatchingChars = 0;
223 
224                 while (MatchingChars < NamePosition)
225                 {
226                     /* Check if current char is DBCS lead char, if so, jump by two chars */
227                     MatchingChars += FsRtlIsLeadDbcsCharacter(Name->Buffer[MatchingChars]) ? 2 : 1;
228                 }
229 
230                 /* If so, deny */
231                 if (MatchingChars > NamePosition)
232                     return FALSE;
233             }
234 
235             /* Compare */
236             return RtlEqualMemory(IntExpression.Buffer,
237                                   (Name->Buffer + NamePosition),
238                                   IntExpression.Length);
239         }
240     }
241 
242     /* Name parsing loop */
243     for (; !EndOfName; MatchingChars = BackTrackingPosition)
244     {
245         /* Reset positions */
246         OldBackTrackingPosition = BackTrackingPosition = 0;
247 
248         if (NamePosition >= Name->Length)
249         {
250             EndOfName = TRUE;
251             if (MatchingChars && OldBackTracking[MatchingChars - 1] == Expression->Length * 2)
252                 break;
253         }
254         else
255         {
256             /* If lead byte present */
257             if (FsRtlIsLeadDbcsCharacter(Name->Buffer[NamePosition]))
258             {
259                 NameChar = Name->Buffer[NamePosition] +
260                            (0x100 * Name->Buffer[NamePosition + 1]);
261                 NamePosition += sizeof(USHORT);
262             }
263             else
264             {
265                 NameChar = Name->Buffer[NamePosition];
266                 NamePosition += sizeof(UCHAR);
267             }
268         }
269 
270         while (MatchingChars > OldBackTrackingPosition)
271         {
272             ExpressionPosition = (OldBackTracking[OldBackTrackingPosition++] + 1) / 2;
273 
274             /* Expression parsing loop */
275             for (Offset = 0; ExpressionPosition < Expression->Length; )
276             {
277                 ExpressionPosition += Offset;
278 
279                 if (ExpressionPosition == Expression->Length)
280                 {
281                     BackTracking[BackTrackingPosition++] = Expression->Length * 2;
282                     break;
283                 }
284 
285                 /* If buffer too small */
286                 if (BackTrackingPosition > BackTrackingBufferSize - 3)
287                 {
288                     /* We should only ever get here once! */
289                     ASSERT(AllocatedBuffer == NULL);
290                     ASSERT((BackTracking == BackTrackingBuffer) || (BackTracking == OldBackTrackingBuffer));
291                     ASSERT((OldBackTracking == BackTrackingBuffer) || (OldBackTracking == OldBackTrackingBuffer));
292 
293                     /* Calculate buffer size */
294                     BackTrackingBufferSize = Expression->Length * 2 + 1;
295 
296                     /* Allocate memory for both back-tracking buffers */
297                     AllocatedBuffer = ExAllocatePoolWithTag(PagedPool | POOL_RAISE_IF_ALLOCATION_FAILURE,
298                                                             2 * BackTrackingBufferSize * sizeof(USHORT),
299                                                             'nrSF');
300                     if (AllocatedBuffer == NULL)
301                     {
302                         DPRINT1("Failed to allocate BackTracking buffer. BackTrackingBufferSize = =x%lx\n",
303                                 BackTrackingBufferSize);
304                         Result = FALSE;
305                         goto Exit;
306                     }
307 
308                     /* Copy BackTracking content. Note that it can point to either BackTrackingBuffer or OldBackTrackingBuffer */
309                     RtlCopyMemory(AllocatedBuffer,
310                                   BackTracking,
311                                   RTL_NUMBER_OF(BackTrackingBuffer) * sizeof(USHORT));
312 
313                     /* Place current Backtracking is at the start of the new buffer */
314                     BackTracking = AllocatedBuffer;
315 
316                     /* Copy OldBackTracking content */
317                     RtlCopyMemory(&BackTracking[BackTrackingBufferSize],
318                                   OldBackTracking,
319                                   RTL_NUMBER_OF(OldBackTrackingBuffer) * sizeof(USHORT));
320 
321                     /* Place current OldBackTracking after current BackTracking in the buffer */
322                     OldBackTracking = &BackTracking[BackTrackingBufferSize];
323                 }
324 
325                 /* If lead byte present */
326                 if (FsRtlIsLeadDbcsCharacter(Expression->Buffer[ExpressionPosition]))
327                 {
328                     ExpressionChar = Expression->Buffer[ExpressionPosition] +
329                                      (0x100 * Expression->Buffer[ExpressionPosition + 1]);
330                     Offset = sizeof(USHORT);
331                 }
332                 else
333                 {
334                     ExpressionChar = Expression->Buffer[ExpressionPosition];
335                     Offset = sizeof(UCHAR);
336                 }
337 
338                 /* Basic check to test if chars are equal */
339                 if (ExpressionChar == NameChar && !EndOfName)
340                 {
341                     BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2;
342                 }
343                 /* Check cases that eat one char */
344                 else if (ExpressionChar == '?' && !EndOfName)
345                 {
346                     BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2;
347                 }
348                 /* Test star */
349                 else if (ExpressionChar == '*')
350                 {
351                     BackTracking[BackTrackingPosition++] = ExpressionPosition * 2;
352                     BackTracking[BackTrackingPosition++] = (ExpressionPosition * 2) + 1;
353                     continue;
354                 }
355                 /* Check DOS_STAR */
356                 else if (ExpressionChar == ANSI_DOS_STAR)
357                 {
358                     /* Look for last dot */
359                     DontSkipDot = TRUE;
360                     if (!EndOfName && NameChar == '.')
361                     {
362                         for (Position = NamePosition; Position < Name->Length; )
363                         {
364                             /* If lead byte not present */
365                             if (!FsRtlIsLeadDbcsCharacter(Name->Buffer[Position]))
366                             {
367                                 if (Name->Buffer[Position] == '.')
368                                 {
369                                     DontSkipDot = FALSE;
370                                     break;
371                                 }
372 
373                                 Position += sizeof(UCHAR);
374                             }
375                             else
376                             {
377                                 Position += sizeof(USHORT);
378                             }
379                         }
380                     }
381 
382                     if (EndOfName || NameChar != '.' || !DontSkipDot)
383                         BackTracking[BackTrackingPosition++] = ExpressionPosition * 2;
384 
385                     BackTracking[BackTrackingPosition++] = (ExpressionPosition * 2) + 1;
386                     continue;
387                 }
388                 /* Check DOS_DOT */
389                 else if (ExpressionChar == ANSI_DOS_DOT)
390                 {
391                     if (EndOfName) continue;
392 
393                     if (NameChar == '.')
394                         BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2;
395                 }
396                 /* Check DOS_QM */
397                 else if (ExpressionChar == ANSI_DOS_QM)
398                 {
399                     if (EndOfName || NameChar == '.') continue;
400 
401                     BackTracking[BackTrackingPosition++] = (ExpressionPosition + Offset) * 2;
402                 }
403 
404                 /* Leave from loop */
405                 break;
406             }
407 
408             for (Position = 0; MatchingChars > OldBackTrackingPosition && Position < BackTrackingPosition; Position++)
409             {
410                 while (MatchingChars > OldBackTrackingPosition &&
411                        BackTracking[Position] > OldBackTracking[OldBackTrackingPosition])
412                 {
413                     ++OldBackTrackingPosition;
414                 }
415             }
416         }
417 
418         /* Swap pointers */
419         BackTrackingSwap = BackTracking;
420         BackTracking = OldBackTracking;
421         OldBackTracking = BackTrackingSwap;
422     }
423 
424     /* Store result value */
425     Result = MatchingChars && (OldBackTracking[MatchingChars - 1] == Expression->Length * 2);
426 
427 Exit:
428 
429     /* Frees the memory if necessary */
430     if (AllocatedBuffer != NULL)
431     {
432         ExFreePoolWithTag(AllocatedBuffer, 'nrSF');
433     }
434 
435     return Result;
436 }
437 
438 /*++
439  * @name FsRtlIsFatDbcsLegal
440  * @implemented
441  *
442  * Returns TRUE if the given DbcsName is a valid FAT filename (in 8.3)
443  *
444  * @param DbcsName
445  *        The filename to check. It can also contains pathname.
446  *
447  * @param WildCardsPermissible
448  *        If this is set to FALSE and if filename contains wildcard, the function
449  *        will fail
450  *
451  * @param PathNamePermissible
452  *        If this is set to FALSE and if the filename comes with a pathname, the
453  *        function will fail
454  *
455  * @param LeadingBackslashPermissible
456  *        If this is set to FALSE and if the filename starts with a backslash, the
457  *        function will fail
458  *
459  * @return TRUE if the DbcsName is legal, FALSE otherwise
460  *
461  * @remarks None
462  *
463  *--*/
464 BOOLEAN
465 NTAPI
466 FsRtlIsFatDbcsLegal(IN ANSI_STRING DbcsName,
467                     IN BOOLEAN WildCardsPermissible,
468                     IN BOOLEAN PathNamePermissible,
469                     IN BOOLEAN LeadingBackslashPermissible)
470 {
471     ANSI_STRING FirstPart, RemainingPart;
472     BOOLEAN LastDot;
473     USHORT i;
474     PAGED_CODE();
475 
476     /* Just quit if the string is empty */
477     if (!DbcsName.Length)
478         return FALSE;
479 
480     /* Accept special filename if wildcards are allowed */
481     if (WildCardsPermissible && (DbcsName.Length == 1 || DbcsName.Length == 2) && DbcsName.Buffer[0] == '.')
482     {
483         if (DbcsName.Length == 2)
484         {
485             if (DbcsName.Buffer[1] == '.')
486                 return TRUE;
487         }
488         else
489         {
490             return TRUE;
491         }
492     }
493 
494     /* DbcsName wasn't supposed to be started with \ */
495     if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
496         return FALSE;
497     /* DbcsName was allowed to be started with \, but now, remove it */
498     else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
499     {
500         DbcsName.Buffer = DbcsName.Buffer + 1;
501         DbcsName.Length = DbcsName.Length - 1;
502         DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
503     }
504 
505     if (PathNamePermissible)
506     {
507         /* We copy the buffer for FsRtlDissectDbcs call */
508         RemainingPart.Buffer = DbcsName.Buffer;
509         RemainingPart.Length = DbcsName.Length;
510         RemainingPart.MaximumLength = DbcsName.MaximumLength;
511 
512         while (RemainingPart.Length > 0)
513         {
514             if (RemainingPart.Buffer[0] == '\\')
515                 return FALSE;
516 
517             /* Call once again our dissect function */
518             FsRtlDissectDbcs(RemainingPart, &FirstPart, &RemainingPart);
519 
520             if (!FsRtlIsFatDbcsLegal(FirstPart,
521                                      WildCardsPermissible,
522                                      FALSE,
523                                      FALSE))
524             {
525                 return FALSE;
526             }
527         }
528 
529         return TRUE;
530     }
531 
532     if (WildCardsPermissible && FsRtlDoesDbcsContainWildCards(&DbcsName))
533     {
534         for (i = 0; i < DbcsName.Length; i++)
535         {
536             /* First make sure the character it's not the Lead DBCS */
537             if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i]))
538             {
539                 i++;
540             }
541             /* Then check for bad characters */
542             else if (!FsRtlIsAnsiCharacterLegalFat(DbcsName.Buffer[i], TRUE))
543             {
544                 return FALSE;
545             }
546         }
547 
548         return TRUE;
549     }
550 
551     /* Filename must be 8.3 filename */
552     if (DbcsName.Length > 12)
553         return FALSE;
554 
555     /* Reset dots count */
556     LastDot = FALSE;
557 
558     for (i = 0; i < DbcsName.Length; i++)
559     {
560         /* First make sure the character it's not the Lead DBCS */
561         if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i]))
562         {
563             if (!LastDot && (i >= 7))
564                 return FALSE;
565 
566             if (i == (DbcsName.Length - 1))
567                 return FALSE;
568 
569             i++;
570             continue;
571         }
572         /* Then check for bad characters */
573         else if (!FsRtlIsAnsiCharacterLegalFat(DbcsName.Buffer[i], WildCardsPermissible))
574         {
575             return FALSE;
576         }
577         else if (DbcsName.Buffer[i] == '.')
578         {
579             /* Filename can only contain one dot */
580             if (LastDot)
581                 return FALSE;
582 
583             LastDot = TRUE;
584 
585             /* We mustn't have spaces before dot or at the end of the filename
586              * and no dot at the beginning of the filename */
587             if (i == (DbcsName.Length - 1) || i == 0)
588                 return FALSE;
589 
590             /* Filename must be 8.3 filename and not 3.8 filename */
591             if ((DbcsName.Length - 1) - i > 3)
592                 return FALSE;
593 
594             if ((i > 0) && DbcsName.Buffer[i - 1] == ' ')
595                 return FALSE;
596         }
597         /* Filename mustn't finish with a space */
598         else if (DbcsName.Buffer[i] == ' ' && i == (DbcsName.Length - 1))
599         {
600             return FALSE;
601         }
602 
603         if (!LastDot && (i >= 8))
604             return FALSE;
605     }
606 
607     return TRUE;
608 }
609 
610 /*++
611  * @name FsRtlIsHpfsDbcsLegal
612  * @implemented
613  *
614  * Returns TRUE if the given DbcsName is a valid HPFS filename
615  *
616  * @param DbcsName
617  *        The filename to check. It can also contains pathname.
618  *
619  * @param WildCardsPermissible
620  *        If this is set to FALSE and if filename contains wildcard, the function
621  *        will fail
622  *
623  * @param PathNamePermissible
624  *        If this is set to FALSE and if the filename comes with a pathname, the
625  *        function will fail
626  *
627  * @param LeadingBackslashPermissible
628  *        If this is set to FALSE and if the filename starts with a backslash, the
629  *        function will fail
630  *
631  * @return TRUE if the DbcsName is legal, FALSE otherwise
632  *
633  * @remarks None
634  *
635  *--*/
636 BOOLEAN
637 NTAPI
638 FsRtlIsHpfsDbcsLegal(IN ANSI_STRING DbcsName,
639                      IN BOOLEAN WildCardsPermissible,
640                      IN BOOLEAN PathNamePermissible,
641                      IN BOOLEAN LeadingBackslashPermissible)
642 {
643     ANSI_STRING FirstPart, RemainingPart;
644     USHORT i;
645     PAGED_CODE();
646 
647     /* Just quit if the string is empty */
648     if (!DbcsName.Length)
649         return FALSE;
650 
651     /* Accept special filename if wildcards are allowed */
652     if (WildCardsPermissible && (DbcsName.Length == 1 || DbcsName.Length == 2) && DbcsName.Buffer[0] == '.')
653     {
654         if (DbcsName.Length == 2)
655         {
656             if (DbcsName.Buffer[1] == '.')
657                 return TRUE;
658         }
659         else
660         {
661             return TRUE;
662         }
663     }
664 
665     /* DbcsName wasn't supposed to be started with \ */
666     if (!LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
667         return FALSE;
668     /* DbcsName was allowed to be started with \, but now, remove it */
669     else if (LeadingBackslashPermissible && DbcsName.Buffer[0] == '\\')
670     {
671         DbcsName.Buffer = DbcsName.Buffer + 1;
672         DbcsName.Length = DbcsName.Length - 1;
673         DbcsName.MaximumLength = DbcsName.MaximumLength - 1;
674     }
675 
676     if (PathNamePermissible)
677     {
678         /* We copy the buffer for FsRtlDissectDbcs call */
679         RemainingPart.Buffer = DbcsName.Buffer;
680         RemainingPart.Length = DbcsName.Length;
681         RemainingPart.MaximumLength = DbcsName.MaximumLength;
682 
683         while (RemainingPart.Length > 0)
684         {
685             if (RemainingPart.Buffer[0] == '\\')
686                 return FALSE;
687 
688             /* Call once again our dissect function */
689             FsRtlDissectDbcs(RemainingPart, &FirstPart, &RemainingPart);
690 
691             if (!FsRtlIsHpfsDbcsLegal(FirstPart,
692                                       WildCardsPermissible,
693                                       FALSE,
694                                       FALSE))
695             {
696                 return FALSE;
697             }
698         }
699 
700         return TRUE;
701     }
702 
703     if (DbcsName.Length > 255)
704         return FALSE;
705 
706     for (i = 0; i < DbcsName.Length; i++)
707     {
708         /* First make sure the character it's not the Lead DBCS */
709         if (FsRtlIsLeadDbcsCharacter(DbcsName.Buffer[i]))
710         {
711             if (i == (DbcsName.Length - 1))
712                 return FALSE;
713             i++;
714         }
715         /* Then check for bad characters */
716         else if (!FsRtlIsAnsiCharacterLegalHpfs(DbcsName.Buffer[i], WildCardsPermissible))
717         {
718             return FALSE;
719         }
720         /* Filename mustn't finish with a space or a dot */
721         else if ((DbcsName.Buffer[i] == ' ' || DbcsName.Buffer[i] == '.') && i == (DbcsName.Length - 1))
722         {
723             return FALSE;
724         }
725     }
726 
727     return TRUE;
728 }
729