Skip to content

Commit 403bf75

Browse files
committed
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4: ext/phar: Fix .phar-prefixed non-magic directory handling (#22372)
2 parents c46e63f + 2a339c2 commit 403bf75

6 files changed

Lines changed: 130 additions & 35 deletions

File tree

NEWS

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ PHP NEWS
2121
. Fixed memory leak when calling IntlListFormatter::__construct() twice.
2222
(Weilin Du)
2323

24+
- Phar:
25+
. Fixed inconsistent handling of the magic ".phar" directory. Paths such as
26+
"/.phar" remain protected, while non-magic paths that merely start with
27+
".phar" are handled consistently across file and directory creation,
28+
copying, ArrayAccess, stream lookup, directory iteration and extraction.
29+
(Weilin Du)
30+
2431
- Reflection:
2532
. Fixed bug GH-22324 (Ignore leading namespace separator in
2633
ReflectionParameter::__construct()). (jorgsowa)

ext/phar/dirstream.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ static php_stream *phar_make_dirstream(const char *dir, size_t dirlen, const Has
158158
ALLOC_HASHTABLE(data);
159159
zend_hash_init(data, 64, NULL, NULL, 0);
160160

161-
if ((*dir == '/' && dirlen == 1 && (manifest->nNumOfElements == 0)) || (dirlen >= sizeof(".phar")-1 && !memcmp(dir, ".phar", sizeof(".phar")-1))) {
161+
if ((*dir == '/' && dirlen == 1 && (manifest->nNumOfElements == 0)) || phar_path_is_magic_phar_ex(dir, dirlen)) {
162162
/* make empty root directory for empty phar */
163163
/* make empty directory for .phar magic directory */
164164
return php_stream_alloc(&phar_dir_ops, data, NULL, "r");
@@ -175,7 +175,7 @@ static php_stream *phar_make_dirstream(const char *dir, size_t dirlen, const Has
175175

176176
if (*dir == '/') {
177177
/* root directory */
178-
if (zend_string_starts_with_literal(str_key, ".phar")) {
178+
if (phar_is_magic_phar(str_key)) {
179179
/* do not add any magic entries to this directory */
180180
continue;
181181
}

ext/phar/phar_internal.h

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,31 @@ static inline bool phar_validate_alias(const char *alias, size_t alias_len) /* {
380380
}
381381
/* }}} */
382382

383+
static inline bool phar_path_is_magic_phar_ex(const char *path, size_t path_len) /* {{{ */
384+
{
385+
if (path_len > 0 && path[0] == '/') {
386+
path++;
387+
path_len--;
388+
}
389+
390+
if (path_len < sizeof(".phar") - 1 || memcmp(path, ".phar", sizeof(".phar") - 1) != 0) {
391+
return false;
392+
}
393+
394+
if (path_len == sizeof(".phar") - 1) {
395+
return true;
396+
}
397+
398+
return path[sizeof(".phar") - 1] == '/' || path[sizeof(".phar") - 1] == '\\';
399+
}
400+
/* }}} */
401+
402+
static inline bool phar_is_magic_phar(const zend_string *path) /* {{{ */
403+
{
404+
return phar_path_is_magic_phar_ex(ZSTR_VAL(path), ZSTR_LEN(path));
405+
}
406+
/* }}} */
407+
383408
static inline void phar_set_inode(phar_entry_info *entry) /* {{{ */
384409
{
385410
char tmp[MAXPATHLEN];

ext/phar/phar_object.c

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,7 +1619,7 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
16191619
return ZEND_HASH_APPLY_STOP;
16201620
}
16211621
after_open_fp:
1622-
if (str_key_len >= sizeof(".phar")-1 && !memcmp(str_key, ".phar", sizeof(".phar")-1)) {
1622+
if (phar_path_is_magic_phar_ex(str_key, str_key_len)) {
16231623
/* silently skip any files that would be added to the magic .phar directory */
16241624
if (save) {
16251625
efree(save);
@@ -3468,14 +3468,14 @@ PHP_METHOD(Phar, copy)
34683468
RETURN_THROWS();
34693469
}
34703470

3471-
if (zend_string_starts_with_literal(old_file, ".phar")) {
3471+
if (phar_is_magic_phar(old_file)) {
34723472
/* can't copy a meta file */
34733473
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
34743474
"file \"%s\" cannot be copied to file \"%s\", cannot copy Phar meta-file in %s", ZSTR_VAL(old_file), ZSTR_VAL(new_file), phar_obj->archive->fname);
34753475
RETURN_THROWS();
34763476
}
34773477

3478-
if (zend_string_starts_with_literal(new_file, ".phar")) {
3478+
if (phar_is_magic_phar(new_file)) {
34793479
/* can't copy a meta file */
34803480
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0,
34813481
"file \"%s\" cannot be copied to file \"%s\", cannot copy to Phar meta-file in %s", ZSTR_VAL(old_file), ZSTR_VAL(new_file), phar_obj->archive->fname);
@@ -3561,11 +3561,8 @@ PHP_METHOD(Phar, offsetExists)
35613561
}
35623562
}
35633563

3564-
if (zend_string_starts_with_literal(file_name, ".phar")) {
3565-
/* none of these are real files, so they don't exist */
3566-
RETURN_FALSE;
3567-
}
3568-
RETURN_TRUE;
3564+
/* none of these are real files, so they don't exist */
3565+
RETURN_BOOL(!phar_is_magic_phar(file_name));
35693566
} else {
35703567
/* If the info class is not based on PharFileInfo, directories are not directly instantiable */
35713568
if (UNEXPECTED(!instanceof_function(phar_obj->spl.info_class, phar_ce_entry))) {
@@ -3608,7 +3605,7 @@ PHP_METHOD(Phar, offsetGet)
36083605
RETURN_THROWS();
36093606
}
36103607

3611-
if (zend_string_starts_with_literal(file_name, ".phar")) {
3608+
if (phar_is_magic_phar(file_name)) {
36123609
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot directly get any files or directories in magic \".phar\" directory");
36133610
RETURN_THROWS();
36143611
}
@@ -3639,16 +3636,9 @@ static void phar_add_file(phar_archive_data **pphar, zend_string *file_name, con
36393636
ALLOCA_FLAG(filename_use_heap)
36403637
#endif
36413638

3642-
if (
3643-
zend_string_starts_with_literal(file_name, ".phar")
3644-
|| zend_string_starts_with_literal(file_name, "/.phar")
3645-
) {
3646-
size_t prefix_len = (ZSTR_VAL(file_name)[0] == '/') + sizeof(".phar")-1;
3647-
char next_char = ZSTR_VAL(file_name)[prefix_len];
3648-
if (next_char == '/' || next_char == '\\' || next_char == '\0') {
3649-
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create any files in magic \".phar\" directory");
3650-
return;
3651-
}
3639+
if (phar_is_magic_phar(file_name)) {
3640+
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create any files in magic \".phar\" directory");
3641+
return;
36523642
}
36533643

36543644
/* TODO How to handle Windows path normalisation with zend_string ? */
@@ -3795,7 +3785,7 @@ PHP_METHOD(Phar, offsetSet)
37953785
RETURN_THROWS();
37963786
}
37973787

3798-
if (zend_string_starts_with_literal(file_name, ".phar")) {
3788+
if (phar_is_magic_phar(file_name)) {
37993789
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot set any files or directories in magic \".phar\" directory");
38003790
RETURN_THROWS();
38013791
}
@@ -3862,16 +3852,9 @@ PHP_METHOD(Phar, addEmptyDir)
38623852

38633853
PHAR_ARCHIVE_OBJECT();
38643854

3865-
if (
3866-
zend_string_starts_with_literal(dir_name, ".phar")
3867-
|| zend_string_starts_with_literal(dir_name, "/.phar")
3868-
) {
3869-
size_t prefix_len = (ZSTR_VAL(dir_name)[0] == '/') + sizeof(".phar")-1;
3870-
char next_char = ZSTR_VAL(dir_name)[prefix_len];
3871-
if (next_char == '/' || next_char == '\\' || next_char == '\0') {
3872-
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory");
3873-
RETURN_THROWS();
3874-
}
3855+
if (phar_is_magic_phar(dir_name)) {
3856+
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Cannot create a directory in magic \".phar\" directory");
3857+
RETURN_THROWS();
38753858
}
38763859

38773860
phar_mkdir(&phar_obj->archive, dir_name);
@@ -4177,7 +4160,7 @@ static zend_result phar_extract_file(bool overwrite, phar_entry_info *entry, cha
41774160
return SUCCESS;
41784161
}
41794162

4180-
if (zend_string_starts_with_literal(entry->filename, ".phar")) {
4163+
if (phar_is_magic_phar(entry->filename)) {
41814164
return SUCCESS;
41824165
}
41834166
/* strip .. from path and restrict it to be under dest directory */
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
--TEST--
2+
Phar: .phar-prefixed non-magic directories are accessible
3+
--EXTENSIONS--
4+
phar
5+
--INI--
6+
phar.readonly=0
7+
phar.require_hash=0
8+
--FILE--
9+
<?php
10+
$fname = __DIR__ . '/' . basename(__FILE__, '.php') . '.phar.php';
11+
$pname = 'phar://' . $fname;
12+
13+
$phar = new Phar($fname);
14+
$phar['.pharx/array.txt'] = 'array';
15+
$phar->addFromString('.pharx/from-string.txt', 'from-string');
16+
$phar->addFromString('/.phary/leading.txt', 'leading');
17+
$phar->copy('.pharx/array.txt', '.pharx/copy.txt');
18+
19+
var_dump(isset($phar['.pharx/array.txt']));
20+
echo $phar['.pharx/array.txt']->getContent(), "\n";
21+
echo file_get_contents($pname . '/.pharx/from-string.txt'), "\n";
22+
echo file_get_contents($pname . '/.phary/leading.txt'), "\n";
23+
echo file_get_contents($pname . '/.pharx/copy.txt'), "\n";
24+
25+
$root = [];
26+
$dh = opendir($pname . '/');
27+
while (false !== ($entry = readdir($dh))) {
28+
$root[] = $entry;
29+
}
30+
closedir($dh);
31+
sort($root);
32+
var_dump($root);
33+
34+
$subdir = [];
35+
$dh = opendir($pname . '/.pharx');
36+
while (false !== ($entry = readdir($dh))) {
37+
$subdir[] = $entry;
38+
}
39+
closedir($dh);
40+
sort($subdir);
41+
var_dump($subdir);
42+
43+
try {
44+
$phar->addFromString('.phar/still-magic.txt', 'no');
45+
} catch (Throwable $e) {
46+
echo $e->getMessage(), "\n";
47+
}
48+
49+
try {
50+
$phar->addEmptyDir('/.phar');
51+
} catch (Throwable $e) {
52+
echo $e->getMessage(), "\n";
53+
}
54+
?>
55+
--CLEAN--
56+
<?php
57+
@unlink(__DIR__ . '/' . basename(__FILE__, '.clean.php') . '.phar.php');
58+
?>
59+
--EXPECT--
60+
bool(true)
61+
array
62+
from-string
63+
leading
64+
array
65+
array(2) {
66+
[0]=>
67+
string(6) ".pharx"
68+
[1]=>
69+
string(6) ".phary"
70+
}
71+
array(3) {
72+
[0]=>
73+
string(9) "array.txt"
74+
[1]=>
75+
string(8) "copy.txt"
76+
[2]=>
77+
string(15) "from-string.txt"
78+
}
79+
Cannot create any files in magic ".phar" directory
80+
Cannot create a directory in magic ".phar" directory

ext/phar/util.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ zend_result phar_mount_entry(phar_archive_data *phar, char *filename, size_t fil
210210
return FAILURE;
211211
}
212212

213-
if (path_len >= sizeof(".phar")-1 && !memcmp(path, ".phar", sizeof(".phar")-1)) {
213+
if (phar_path_is_magic_phar_ex(path, path_len)) {
214214
/* no creating magic phar files by mounting them */
215215
return FAILURE;
216216
}
@@ -1271,7 +1271,7 @@ phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, si
12711271
*error = NULL;
12721272
}
12731273

1274-
if (security && path_len >= sizeof(".phar")-1 && !memcmp(path, ".phar", sizeof(".phar")-1)) {
1274+
if (security && phar_path_is_magic_phar_ex(path, path_len)) {
12751275
if (error) {
12761276
spprintf(error, 4096, "phar error: cannot directly access magic \".phar\" directory or files within it");
12771277
}

0 commit comments

Comments
 (0)