Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ PHP NEWS
. Support reference values in Phar::mungServer(). (ndossche)
. Invalid values now throw in Phar::mungServer() instead of being silently
ignored. (ndossche)
. Support overridden methods in SplFileInfo for getMTime() and getPathname()
when building a phar. (ndossche)

- Reflection:
. Fixed bug GH-20217 (ReflectionClass::isIterable() incorrectly returns true
Expand Down
5 changes: 5 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ PHP 8.6 UPGRADE NOTES
IntlNumberRangeFormatter::IDENTITY_FALLBACK_RANGE identity fallbacks.
It is supported from icu 63.

- Phar:
. Overriding the getMTime() and getPathname() methods of SplFileInfo now
influences the result of the phar buildFrom family of functions.
This makes it possible to override the timestamp and names of files.

- Streams:
. Added stream socket context option so_reuseaddr that allows disabling
address reuse (SO_REUSEADDR) and explicitly uses SO_EXCLUSIVEADDRUSE on
Expand Down
3 changes: 2 additions & 1 deletion ext/phar/phar_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ typedef struct _phar_metadata_tracker {
typedef struct _phar_entry_info {
/* first bytes are exactly as in file */
uint32_t uncompressed_filesize;
/* modification time */
uint32_t timestamp;
uint32_t compressed_filesize;
uint32_t crc32;
Expand Down Expand Up @@ -464,7 +465,7 @@ void phar_entry_delref(phar_entry_data *idata);

phar_entry_info *phar_get_entry_info(phar_archive_data *phar, char *path, size_t path_len, char **error, bool security);
phar_entry_info *phar_get_entry_info_dir(phar_archive_data *phar, char *path, size_t path_len, char dir, char **error, bool security);
ZEND_ATTRIBUTE_NONNULL phar_entry_data *phar_get_or_create_entry_data(char *fname, size_t fname_len, char *path, size_t path_len, const char *mode, char allow_dir, char **error, bool security);
ZEND_ATTRIBUTE_NONNULL phar_entry_data *phar_get_or_create_entry_data(char *fname, size_t fname_len, char *path, size_t path_len, const char *mode, char allow_dir, char **error, bool security, uint32_t timestamp);
ZEND_ATTRIBUTE_NONNULL zend_result phar_get_entry_data(phar_entry_data **ret, char *fname, size_t fname_len, char *path, size_t path_len, const char *mode, char allow_dir, char **error, bool security);
ZEND_ATTRIBUTE_NONNULL_ARGS(1, 4) int phar_flush_ex(phar_archive_data *archive, zend_string *user_stub, bool is_default_stub, char **error);
ZEND_ATTRIBUTE_NONNULL int phar_flush(phar_archive_data *archive, char **error);
Expand Down
123 changes: 88 additions & 35 deletions ext/phar/phar_object.c
Original file line number Diff line number Diff line change
Expand Up @@ -1341,6 +1341,44 @@ struct _phar_t {
int count;
};

static zend_always_inline void phar_call_method_with_unwrap(zend_object *obj, const char *name, zval *rv)
{
zend_call_method_with_0_params(obj, obj->ce, NULL, name, rv);
if (Z_ISREF_P(rv)) {
zend_unwrap_reference(rv);
}
}

/* This is the same as phar_get_or_create_entry_data(), but allows overriding metadata via SplFileInfo. */
static phar_entry_data *phar_build_entry_data(char *fname, size_t fname_len, char *path, size_t path_len, char **error, zval *file_info)
{
uint32_t timestamp;

/* Expects an instance of SplFileInfo if it is an object, which is verified in phar_build(). */
if (Z_TYPE_P(file_info) == IS_OBJECT && Z_OBJCE_P(file_info)->type == ZEND_USER_CLASS) {
zval rv;
phar_call_method_with_unwrap(Z_OBJ_P(file_info), "getMTime", &rv);

if (UNEXPECTED(Z_TYPE(rv) != IS_LONG)) {
/* Either it's a tentative type failure, an exception happened, or the function returned false to indicate failure. */
*error = estrdup("getMTime() must return an int");
return NULL;
}

/* Sanity check bounds. See GH-14141. */
if (ZEND_LONG_UINT_OVFL(Z_LVAL(rv))) {
*error = estrdup("timestamp is limited to 32-bit");
return NULL;
}

timestamp = Z_LVAL(rv);
} else {
timestamp = time(NULL);
}

return phar_get_or_create_entry_data(fname, fname_len, path, path_len, "w+b", 0, error, true, timestamp);
}

static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
{
zval *value;
Expand All @@ -1351,7 +1389,7 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
php_stream *fp;
size_t fname_len;
size_t contents_len;
char *fname, *error = NULL, *base = ZSTR_VAL(p_obj->base), *save = NULL, *temp = NULL;
char *fname = NULL, *error = NULL, *base = ZSTR_VAL(p_obj->base), *save = NULL, *temp = NULL;
zend_string *opened;
char *str_key;
zend_class_entry *ce = p_obj->c;
Expand Down Expand Up @@ -1411,51 +1449,66 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
goto after_open_fp;
case IS_OBJECT:
if (instanceof_function(Z_OBJCE_P(value), spl_ce_SplFileInfo)) {
char *test = NULL;
spl_filesystem_object *intern = PHAR_FETCH_INTERNAL_EX(value);

if (!base_len) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Iterator %s returns an SplFileInfo object, so base directory must be specified", ZSTR_VAL(ce->name));
return ZEND_HASH_APPLY_STOP;
}

switch (intern->type) {
case SPL_FS_DIR: {
zend_string *test_str = spl_filesystem_object_get_path(intern);
fname_len = spprintf(&fname, 0, "%s%c%s", ZSTR_VAL(test_str), DEFAULT_SLASH, intern->u.dir.entry.d_name);
zend_string_release_ex(test_str, /* persistent */ false);
if (php_stream_stat_path(fname, &ssb) == 0 && S_ISDIR(ssb.sb.st_mode)) {
/* ignore directories */
efree(fname);
return ZEND_HASH_APPLY_KEEP;
}
zend_string *tmp_dir_str = NULL;

test = expand_filepath(fname, NULL);
efree(fname);
/* Take into account that SplFileObject may be overridden.
* The purpose here is to grab the path name of the file to add. */
if (Z_OBJCE_P(value)->type == ZEND_USER_CLASS) {
zval rv;
phar_call_method_with_unwrap(Z_OBJ_P(value), "getPathname", &rv);

if (test) {
fname = test;
fname_len = strlen(fname);
} else {
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path");
return ZEND_HASH_APPLY_STOP;
if (UNEXPECTED(Z_TYPE(rv) != IS_STRING)) {
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "getPathname() must return a string");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should be a type error, but I think there are other such problems in ext/phar so I'm happy to defer this to a latter PR.

return ZEND_HASH_APPLY_STOP;
}
tmp_dir_str = Z_STR(rv);
} else {
/* Not a user class, so we can grab the data internally quickly. */
switch (intern->type) {
case SPL_FS_DIR: {
zend_string *test_str = spl_filesystem_object_get_path(intern);
const char slash = DEFAULT_SLASH;
tmp_dir_str = zend_string_concat3(
ZSTR_VAL(test_str), ZSTR_LEN(test_str),
&slash, 1,
intern->u.dir.entry.d_name, strlen(intern->u.dir.entry.d_name)
);
zend_string_release_ex(test_str, /* persistent */ false);
break;
}
case SPL_FS_INFO:
case SPL_FS_FILE:
fname = expand_filepath(ZSTR_VAL(intern->file_name), NULL);
break;
}
}

save = fname;
goto phar_spl_fileinfo;
if (tmp_dir_str) {
if (php_stream_stat_path(ZSTR_VAL(tmp_dir_str), &ssb) == 0 && S_ISDIR(ssb.sb.st_mode)) {
/* ignore directories */
zend_string_release_ex(tmp_dir_str, /* persistent */ false);
return ZEND_HASH_APPLY_KEEP;
}
case SPL_FS_INFO:
case SPL_FS_FILE:
fname = expand_filepath(ZSTR_VAL(intern->file_name), NULL);
if (!fname) {
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path");
return ZEND_HASH_APPLY_STOP;
}

fname_len = strlen(fname);
save = fname;
goto phar_spl_fileinfo;
fname = expand_filepath(ZSTR_VAL(tmp_dir_str), NULL);
zend_string_release_ex(tmp_dir_str, /* persistent */ false);
}

if (!fname) {
zend_throw_exception_ex(spl_ce_UnexpectedValueException, 0, "Could not resolve file path");
return ZEND_HASH_APPLY_STOP;
}

fname_len = strlen(fname);
save = fname;
goto phar_spl_fileinfo;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess a follow-up refactoring is to get rid of this goto and assign the fname var in the corresponding switch case statement. As the GitHub UI didn't show me the label iniitally...

}
ZEND_FALLTHROUGH;
default:
Expand Down Expand Up @@ -1586,7 +1639,7 @@ static int phar_build(zend_object_iterator *iter, void *puser) /* {{{ */
return ZEND_HASH_APPLY_KEEP;
}

if (!(data = phar_get_or_create_entry_data(phar_obj->archive->fname, phar_obj->archive->fname_len, str_key, str_key_len, "w+b", 0, &error, true))) {
if (!(data = phar_build_entry_data(phar_obj->archive->fname, phar_obj->archive->fname_len, str_key, str_key_len, &error, value))) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s cannot be created: %s", str_key, error);
efree(error);

Expand Down Expand Up @@ -3544,7 +3597,7 @@ static void phar_add_file(phar_archive_data **pphar, zend_string *file_name, con
}
#endif

if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, filename, filename_len, "w+b", 0, &error, true))) {
if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, filename, filename_len, "w+b", 0, &error, true, time(NULL)))) {
if (error) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Entry %s does not exist and cannot be created: %s", filename, error);
efree(error);
Expand Down Expand Up @@ -3616,7 +3669,7 @@ static void phar_mkdir(phar_archive_data **pphar, zend_string *dir_name)
char *error;
phar_entry_data *data;

if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, ZSTR_VAL(dir_name), ZSTR_LEN(dir_name), "w+b", 2, &error, true))) {
if (!(data = phar_get_or_create_entry_data((*pphar)->fname, (*pphar)->fname_len, ZSTR_VAL(dir_name), ZSTR_LEN(dir_name), "w+b", 2, &error, true, time(NULL)))) {
if (error) {
zend_throw_exception_ex(spl_ce_BadMethodCallException, 0, "Directory %s does not exist and cannot be created: %s", ZSTR_VAL(dir_name), error);
efree(error);
Expand Down
2 changes: 1 addition & 1 deletion ext/phar/stream.c
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ static php_stream * phar_wrapper_open_url(php_stream_wrapper *wrapper, const cha
/* strip leading "/" */
internal_file = estrndup(ZSTR_VAL(resource->path) + 1, ZSTR_LEN(resource->path) - 1);
if (mode[0] == 'w' || (mode[0] == 'r' && mode[1] == '+')) {
if (NULL == (idata = phar_get_or_create_entry_data(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, strlen(internal_file), mode, 0, &error, true))) {
if (NULL == (idata = phar_get_or_create_entry_data(ZSTR_VAL(resource->host), ZSTR_LEN(resource->host), internal_file, strlen(internal_file), mode, 0, &error, true, time(NULL)))) {
if (error) {
php_stream_wrapper_log_error(wrapper, options, "%s", error);
efree(error);
Expand Down
81 changes: 81 additions & 0 deletions ext/phar/tests/buildFromIterator_user_overrides/getMTime.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
--TEST--
buildFromIterator with user overrides - getMTime()
--EXTENSIONS--
phar
--INI--
phar.readonly=0
phar.require_hash=0
--CREDITS--
Arne Blankerts
N. Dossche
--FILE--
<?php

class MySplFileInfo extends SplFileInfo {
public function getMTime(): int|false {
echo "[MTime]\n";
return 123;
}

public function getCTime(): int {
// This should not get called
echo "[CTime]\n";
return 0;
}

public function getATime(): int {
// This should not get called
echo "[ATime]\n";
return 0;
}
}

class MyIterator extends RecursiveDirectoryIterator {
public function current(): SplFileInfo {
echo "[ Found: " . parent::current()->getPathname() . " ]\n";
return new MySplFileInfo(parent::current()->getPathname());
}
}

$workdir = __DIR__.'/getMTime';
mkdir($workdir . '/content', recursive: true);
file_put_contents($workdir . '/content/hello.txt', "Hello world.");

$phar = new \Phar($workdir . '/test.phar');
$phar->startBuffering();
$phar->buildFromIterator(
new RecursiveIteratorIterator(
new MyIterator($workdir . '/content', FilesystemIterator::SKIP_DOTS)
),
$workdir
);
$phar->stopBuffering();


$result = new \Phar($workdir . '/test.phar', 0, 'test.phar');
var_dump($result['content/hello.txt']);
var_dump($result['content/hello.txt']->getATime());
var_dump($result['content/hello.txt']->getMTime());
var_dump($result['content/hello.txt']->getCTime());

?>
--CLEAN--
<?php
$workdir = __DIR__.'/getMTime';
@unlink($workdir . '/test.phar');
@unlink($workdir . '/content/hello.txt');
@rmdir($workdir . '/content');
@rmdir($workdir);
?>
--EXPECTF--
[ Found: %shello.txt ]
[MTime]
object(PharFileInfo)#%d (2) {
["pathName":"SplFileInfo":private]=>
string(%d) "phar://%shello.txt"
["fileName":"SplFileInfo":private]=>
string(%d) "hello.txt"
}
int(123)
int(123)
int(123)
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
--TEST--
buildFromIterator with user overrides - getMTime() by ref
--EXTENSIONS--
phar
--INI--
phar.readonly=0
phar.require_hash=0
--CREDITS--
Arne Blankerts
N. Dossche
--FILE--
<?php

class MySplFileInfo extends SplFileInfo {
public function &getMTime(): int|false {
static $data = 123;
echo "[MTime]\n";
return $data;
}
}

class MyIterator extends RecursiveDirectoryIterator {
public function current(): SplFileInfo {
echo "[ Found: " . parent::current()->getPathname() . " ]\n";
return new MySplFileInfo(parent::current()->getPathname());
}
}

$workdir = __DIR__.'/getMTime_byRef';
mkdir($workdir . '/content', recursive: true);
file_put_contents($workdir . '/content/hello.txt', "Hello world.");

$phar = new \Phar($workdir . '/test.phar');
$phar->startBuffering();
$phar->buildFromIterator(
new RecursiveIteratorIterator(
new MyIterator($workdir . '/content', FilesystemIterator::SKIP_DOTS)
),
$workdir
);
$phar->stopBuffering();


$result = new \Phar($workdir . '/test.phar', 0, 'test.phar');
var_dump($result['content/hello.txt']);
var_dump($result['content/hello.txt']->getATime());
var_dump($result['content/hello.txt']->getMTime());
var_dump($result['content/hello.txt']->getCTime());

?>
--CLEAN--
<?php
$workdir = __DIR__.'/getMTime_byRef';
@unlink($workdir . '/test.phar');
@unlink($workdir . '/content/hello.txt');
@rmdir($workdir . '/content');
@rmdir($workdir);
?>
--EXPECTF--
[ Found: %shello.txt ]
[MTime]
object(PharFileInfo)#%d (2) {
["pathName":"SplFileInfo":private]=>
string(%d) "phar://%shello.txt"
["fileName":"SplFileInfo":private]=>
string(%d) "hello.txt"
}
int(123)
int(123)
int(123)
Loading