-
Notifications
You must be signed in to change notification settings - Fork 8k
Support custom SplFileInfo objects in Phar::buildFromIterator() #14143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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; | ||
|
|
@@ -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; | ||
|
|
@@ -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"); | ||
| 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; | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
| } | ||
| ZEND_FALLTHROUGH; | ||
| default: | ||
|
|
@@ -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); | ||
|
|
||
|
|
@@ -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); | ||
|
|
@@ -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); | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
81 changes: 81 additions & 0 deletions
81
ext/phar/tests/buildFromIterator_user_overrides/getMTime.phpt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
70 changes: 70 additions & 0 deletions
70
ext/phar/tests/buildFromIterator_user_overrides/getMTime_byRef.phpt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.