Integrate change #15879 from maint-5.6;
Jarkko Hietaniemi [Fri, 12 Apr 2002 19:57:05 +0000 (19:57 +0000)]
Win32::GetLongPathName() did not return valid results if there
were "." and ".." components in the path; also fix a potential
buffer overflow if the long path happens to be longer than
MAX_PATH (this can presumably happen if they use \\?\... style
paths); add a rather limited testsuite that exercises just the
edge cases
p4raw-link: @15879 on //depot/maint-5.6/perl: a15439704ef1059bf178ec4b1820fee7b2af7173

p4raw-id: //depot/perl@15880
p4raw-branched: from //depot/maint-5.6/perl@15877 'branch in'
t/win32/longpath.t
p4raw-integrated: from //depot/maint-5.6/perl@15877 'ignore' MANIFEST
(@12747..) 'merge in' t/harness (@11427..) win32/win32.c
(@13145..)

MANIFEST
t/harness
t/win32/longpath.t [new file with mode: 0755]
win32/win32.c

index 9c69348..698e613 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -2552,6 +2552,7 @@ t/uni/lower.t                     See if Unicode casing works
 t/uni/sprintf.t                        See if Unicode sprintf works
 t/uni/title.t                  See if Unicode casing works
 t/uni/upper.t                  See if Unicode casing works
+t/win32/longpath.t             Test if Win32::GetLongPathName() works
 t/x2p/s2p.t                    See if s2p/psed work
 taint.c                                Tainting code
 thrdvar.h                      Per-thread variables
index a19363a..e9cf2ed 100644 (file)
--- a/t/harness
+++ b/t/harness
@@ -57,6 +57,7 @@ if (@ARGV) {
         push @tests, <op/*.t>;
         push @tests, <uni/*.t>;
         push @tests, <lib/*.t>;
+       push @tests, <win32/*.t> if $^O eq 'MSWin32';
        use File::Spec;
        my $updir = File::Spec->updir;
        my $mani  = File::Spec->catfile(File::Spec->updir, "MANIFEST");
diff --git a/t/win32/longpath.t b/t/win32/longpath.t
new file mode 100755 (executable)
index 0000000..d31a5b4
--- /dev/null
@@ -0,0 +1,52 @@
+#!perl -w
+
+# tests for Win32::GetLongPathName()
+
+$^O =~ /^MSWin/ or print("1..0 # not win32\n" ), exit;
+
+my @paths = qw(
+    /
+    //
+    .
+    ..
+    c:
+    c:/
+    c:./
+    c:/.
+    c:/..
+    c:./..
+    //./
+    //.
+    //..
+    //./..
+);
+push @paths, map { my $x = $_; $x =~ s,/,\\,g; $x } @paths;
+push @paths, qw(
+    ../\
+    c:.\\../\
+    c:/\..//
+    c://.\/./\
+    \\.\\../\
+    //\..//
+    //.\/./\
+);
+
+my $drive = $ENV{SystemDrive};
+if ($drive) {
+    for (@paths) {
+       s/^c:/$drive/;
+    }
+    push @paths, $ENV{SystemRoot} if $ENV{SystemRoot};
+}
+my %expect;
+@expect{@paths} = map { my $x = $_; $x =~ s,(.[/\\])[/\\]+,$1,g; $x } @paths;
+
+print "1.." . @paths . "\n";
+my $i = 1;
+for (@paths) {
+    my $got = Win32::GetLongPathName($_);
+    print "# '$_' => expect '$expect{$_}' => got '$got'\n";
+    print "not " unless $expect{$_} eq $got;
+    print "ok $i\n";
+    ++$i;
+}
index 31a1496..fd44c5f 100644 (file)
@@ -1284,6 +1284,18 @@ win32_stat(const char *path, struct stat *sbuf)
     return res;
 }
 
+#define isSLASH(c) ((c) == '/' || (c) == '\\')
+#define SKIP_SLASHES(s) \
+    STMT_START {                               \
+       while (*(s) && isSLASH(*(s)))           \
+           ++(s);                              \
+    } STMT_END
+#define COPY_NONSLASHES(d,s) \
+    STMT_START {                               \
+       while (*(s) && !isSLASH(*(s)))          \
+           *(d)++ = *(s)++;                    \
+    } STMT_END
+
 /* Find the longname of a given path.  path is destructively modified.
  * It should have space for at least MAX_PATH characters. */
 DllExport char *
@@ -1299,61 +1311,74 @@ win32_longpath(char *path)
        return Nullch;
 
     /* drive prefix */
-    if (isALPHA(path[0]) && path[1] == ':' &&
-       (path[2] == '/' || path[2] == '\\'))
-    {
+    if (isALPHA(path[0]) && path[1] == ':') {
        start = path + 2;
        *tmpstart++ = path[0];
        *tmpstart++ = ':';
     }
     /* UNC prefix */
-    else if ((path[0] == '/' || path[0] == '\\') &&
-            (path[1] == '/' || path[1] == '\\'))
-    {
+    else if (isSLASH(path[0]) && isSLASH(path[1])) {
        start = path + 2;
        *tmpstart++ = path[0];
        *tmpstart++ = path[1];
-       /* copy machine name */
-       while (*start && *start != '/' && *start != '\\')
-           *tmpstart++ = *start++;
+       SKIP_SLASHES(start);
+       COPY_NONSLASHES(tmpstart,start);        /* copy machine name */
        if (*start) {
-           *tmpstart++ = *start;
-           start++;
-           /* copy share name */
-           while (*start && *start != '/' && *start != '\\')
-               *tmpstart++ = *start++;
+           *tmpstart++ = *start++;
+           SKIP_SLASHES(start);
+           COPY_NONSLASHES(tmpstart,start);    /* copy share name */
        }
     }
-    sep = *start++;
-    if (sep == '/' || sep == '\\')
-       *tmpstart++ = sep;
     *tmpstart = '\0';
-    while (sep) {
-       /* walk up to slash */
-       while (*start && *start != '/' && *start != '\\')
-           ++start;
+    while (*start) {
+       /* copy initial slash, if any */
+       if (isSLASH(*start)) {
+           *tmpstart++ = *start++;
+           *tmpstart = '\0';
+           SKIP_SLASHES(start);
+       }
+
+       /* FindFirstFile() expands "." and "..", so we need to pass
+        * those through unmolested */
+       if (*start == '.'
+           && (!start[1] || isSLASH(start[1])
+               || (start[1] == '.' && (!start[2] || isSLASH(start[2])))))
+       {
+           COPY_NONSLASHES(tmpstart,start);    /* copy "." or ".." */
+           *tmpstart = '\0';
+           continue;
+       }
+
+       /* if this is the end, bust outta here */
+       if (!*start)
+           break;
 
-       /* discard doubled slashes */
-       while (*start && (start[1] == '/' || start[1] == '\\'))
+       /* now we're at a non-slash; walk up to next slash */
+       while (*start && !isSLASH(*start))
            ++start;
-       sep = *start;
 
        /* stop and find full name of component */
+       sep = *start;
        *start = '\0';
        fhand = FindFirstFile(path,&fdata);
+       *start = sep;
        if (fhand != INVALID_HANDLE_VALUE) {
-           strcpy(tmpstart, fdata.cFileName);
-           tmpstart += strlen(fdata.cFileName);
-           if (sep)
-               *tmpstart++ = sep;
-           *tmpstart = '\0';
-           *start++ = sep;
-           FindClose(fhand);
+           STRLEN len = strlen(fdata.cFileName);
+           if ((STRLEN)(tmpbuf + sizeof(tmpbuf) - tmpstart) > len) {
+               strcpy(tmpstart, fdata.cFileName);
+               tmpstart += len;
+               FindClose(fhand);
+           }
+           else {
+               FindClose(fhand);
+               errno = ERANGE;
+               return Nullch;
+           }
        }
        else {
            /* failed a step, just return without side effects */
            /*PerlIO_printf(Perl_debug_log, "Failed to find %s\n", path);*/
-           *start = sep;
+           errno = EINVAL;
            return Nullch;
        }
     }