From: Jarkko Hietaniemi Date: Fri, 12 Apr 2002 19:57:05 +0000 (+0000) Subject: Integrate change #15879 from maint-5.6; X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?a=commitdiff_plain;h=bb27e7b65ce5dddfbcc195ca657661d5cc662d88;p=p5sagit%2Fp5-mst-13.2.git Integrate change #15879 from maint-5.6; 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..) --- diff --git a/MANIFEST b/MANIFEST index 9c69348..698e613 100644 --- 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 diff --git a/t/harness b/t/harness index a19363a..e9cf2ed 100644 --- a/t/harness +++ b/t/harness @@ -57,6 +57,7 @@ if (@ARGV) { push @tests, ; push @tests, ; push @tests, ; + push @tests, 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 index 0000000..d31a5b4 --- /dev/null +++ b/t/win32/longpath.t @@ -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; +} diff --git a/win32/win32.c b/win32/win32.c index 31a1496..fd44c5f 100644 --- a/win32/win32.c +++ b/win32/win32.c @@ -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; } }