Skip to content

Commit e91d76b

Browse files
committed
Buh #69477 ext/zip: Allow extracting to paths with dirs ending with dot
The function `php_zip_make_relative_path()` used to stop on the first right-most occurrence of './' in the ZIP entry path. As a result, the `extractTo()` method didn't extract entries like `foo/bar./baz/file.txt` into correct location.
1 parent 6adfb8c commit e91d76b

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

ext/zip/php_zip.c

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,11 +117,21 @@ static char * php_zip_make_relative_path(char *path, size_t path_len) /* {{{ */
117117
return path;
118118
}
119119

120-
if (i >= 2 && (path[i -1] == '.' || path[i -1] == ':')) {
121-
/* i is the position of . or :, add 1 for / */
120+
if (i >= 1 && path[i - 1] == ':') {
122121
path_begin = path + i + 1;
123122
break;
124123
}
124+
125+
if (i == 2 && path[i - 2] == '.' && path[i - 1] == '.') {
126+
path_begin = path + 3;
127+
break;
128+
}
129+
130+
if (i >= 3 && IS_SLASH(path[i - 3]) && path[i - 2] == '.' && path[i - 1] == '.') {
131+
path_begin = path + i + 1;
132+
break;
133+
}
134+
125135
i--;
126136
}
127137

ext/zip/tests/bug69477.phpt

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
--TEST--
2+
Bug #69477 (ZipArchive::extractTo() truncates path segments ending with dot)
3+
--SKIPIF--
4+
<?php
5+
if(!extension_loaded('zip')) die('skip');
6+
?>
7+
--FILE--
8+
<?php
9+
include __DIR__ . '/utils.inc';
10+
$dir = __DIR__ . '/bug69477';
11+
if (file_exists($dir)) rmdir_rf($dir);
12+
mkdir($dir);
13+
$zipfile = $dir . '/abc.zip';
14+
15+
$archive = new ZipArchive();
16+
17+
if (!$archive->open($zipfile, ZipArchive::CREATE)) {
18+
exit('failed: unable to create archive');
19+
}
20+
21+
// (string) Entry path in the ZIP => (string) Expected actual target path
22+
$paths = [
23+
'.a/b/c/file01.txt' => '.a/b/c/file01.txt',
24+
'a./b/c/file02.txt' => 'a./b/c/file02.txt',
25+
'a/.b/c/file03.txt' => 'a/.b/c/file03.txt',
26+
'a/b./c/file04.txt' => 'a/b./c/file04.txt',
27+
'a/b../c/file05.txt' => 'a/b../c/file05.txt',
28+
'a/b.../c/file06.txt' => 'a/b.../c/file06.txt',
29+
'a/..b/c/file07.txt' => 'a/..b/c/file07.txt',
30+
'a/...b/c/file08.txt' => 'a/...b/c/file08.txt',
31+
'a/../b./c./file09.txt' => 'b./c./file09.txt',
32+
'//../b./c./file10.txt' => 'b./c./file10.txt',
33+
'/../b./c./file11.txt' => 'b./c./file11.txt',
34+
'C:/a./b./file12.txt' => 'a./b./file12.txt',
35+
'a/b:/c/file13.txt' => 'c/file13.txt',
36+
];
37+
38+
foreach ($paths as $zippath => $realpath) {
39+
$archive->addFromString($zippath, $zippath . ' => ' . $realpath);
40+
}
41+
42+
$archive->close();
43+
44+
$archive2 = new ZipArchive();
45+
46+
if (!$archive2->open($zipfile)) {
47+
exit('failed: unable to open archive2');
48+
}
49+
50+
$archive2->extractTo($dir);
51+
$archive2->close();
52+
53+
foreach ($paths as $zippath => $realpath) {
54+
if (!is_readable($dir . '/' . $realpath) || file_get_contents($dir . '/' . $realpath) !== $zippath . ' => ' . $realpath) {
55+
exit('failed: ' . $zippath);
56+
}
57+
}
58+
59+
echo 'ok';
60+
?>
61+
--CLEAN--
62+
<?php
63+
include __DIR__ . '/utils.inc';
64+
$dir = __DIR__ . '/bug69477';
65+
rmdir_rf($dir);
66+
?>
67+
--EXPECT--
68+
ok

0 commit comments

Comments
 (0)