From 352b64f75558f4a0a03f94be74e0797643158a47 Mon Sep 17 00:00:00 2001 From: Francois Laupretre Date: Wed, 23 Dec 2015 15:09:53 +0100 Subject: [PATCH] Implement new 'cache_key' streams operation C API: php_stream_cache_key() PHP API: file_cache_key() Bump PHP_API_VERSION and enclose 'cache_key' definitions in conditional blocks Check key prefix (debug mode only) Implement new 'cache_key' streams operation C API: php_stream_cache_key() PHP API: file_cache_key() Bump PHP_API_VERSION and enclose 'cache_key' definitions in conditional blocks Check key prefix (debug mode only) Minor fixes (PHP_API_VERSION checks) Add key prefix check for userspace wrappers --- UPGRADING | 1 + ext/phar/stream.c | 14 +++- ext/phar/stream.h | 1 + ext/phar/tests/phar_buildfromiterator10.phpt | 4 +- ext/phar/tests/phar_is_cacheable_001.phpt | 19 +++++ ext/standard/basic_functions.c | 8 ++ ext/standard/file.c | 23 +++++ ext/standard/file.h | 1 + .../http_url_is_not_cacheable_001.phpt | 18 ++++ .../streams/plain_file_is_cacheable_001.phpt | 17 ++++ .../streams/stream_cache_key_user_001.phpt | 22 +++++ .../streams/stream_cache_key_user_002.phpt | 22 +++++ .../streams/stream_cache_key_user_004.phpt | 18 ++++ .../streams/stream_cache_key_user_005.phpt | 24 ++++++ main/php_streams.h | 9 ++ main/streams/plain_wrapper.c | 9 +- main/streams/streams.c | 48 +++++++++++ main/streams/userspace.c | 83 ++++++++++++++++++- 18 files changed, 334 insertions(+), 7 deletions(-) create mode 100644 ext/phar/tests/phar_is_cacheable_001.phpt create mode 100644 ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt create mode 100644 ext/standard/tests/streams/plain_file_is_cacheable_001.phpt create mode 100644 ext/standard/tests/streams/stream_cache_key_user_001.phpt create mode 100644 ext/standard/tests/streams/stream_cache_key_user_002.phpt create mode 100644 ext/standard/tests/streams/stream_cache_key_user_004.phpt create mode 100644 ext/standard/tests/streams/stream_cache_key_user_005.phpt diff --git a/UPGRADING b/UPGRADING index 5fb5bf0035682..b6ea09cd68662 100644 --- a/UPGRADING +++ b/UPGRADING @@ -90,6 +90,7 @@ PHP 7.2 UPGRADE NOTES ======================================== 2. New Features ======================================== + . Added cache_key() streams operation. - Core: . It is now possible to remove argument type annotations when overriding an diff --git a/ext/phar/stream.c b/ext/phar/stream.c index 97e1dc6b979d7..d3bcac5dae89d 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -45,7 +45,8 @@ php_stream_wrapper_ops phar_stream_wops = { phar_wrapper_rename, /* rename */ phar_wrapper_mkdir, /* create directory */ phar_wrapper_rmdir, /* remove directory */ - NULL + NULL, /* Metadata handling */ + phar_wrapper_cache_key /* cache_key */ }; php_stream_wrapper php_stream_phar_wrapper = { @@ -967,6 +968,17 @@ static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from } /* }}} */ +/** + * Called by the opcode cache to get the key to use when caching this URL + */ +static zend_string *phar_wrapper_cache_key(php_stream_wrapper *wrapper, zend_string *url, int options, php_stream_context *context) /* {{{ */ +{ + /* Phar URLs are always cacheable */ + zend_string_addref(url); + return url; +} +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/ext/phar/stream.h b/ext/phar/stream.h index d763f71039b4c..b3c0444b58dd3 100644 --- a/ext/phar/stream.h +++ b/ext/phar/stream.h @@ -28,6 +28,7 @@ static php_stream* phar_wrapper_open_url(php_stream_wrapper *wrapper, const char static int phar_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from, const char *url_to, int options, php_stream_context *context); static int phar_wrapper_unlink(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context); static int phar_wrapper_stat(php_stream_wrapper *wrapper, const char *url, int flags, php_stream_statbuf *ssb, php_stream_context *context); +static zend_string *phar_wrapper_cache_key(php_stream_wrapper *wrapper, zend_string *url, int options, php_stream_context *context); /* file/stream handlers */ static size_t phar_stream_write(php_stream *stream, const char *buf, size_t count); diff --git a/ext/phar/tests/phar_buildfromiterator10.phpt b/ext/phar/tests/phar_buildfromiterator10.phpt index e6b9c025da8c8..8aea9e02d9094 100644 --- a/ext/phar/tests/phar_buildfromiterator10.phpt +++ b/ext/phar/tests/phar_buildfromiterator10.phpt @@ -29,13 +29,15 @@ unlink(dirname(__FILE__) . '/buildfromiterator10.phar'); __HALT_COMPILER(); ?> --EXPECTF-- -array(35) { +array(36) { ["phar_ctx_001.phpt"]=> string(%d) "%sphar_ctx_001.phpt" ["phar_get_supported_signatures_001.phpt"]=> string(%d) "%sphar_get_supported_signatures_001.phpt" ["phar_get_supported_signatures_002.phpt"]=> string(%d) "%sphar_get_supported_signatures_002.phpt" + ["phar_is_cacheable_001.phpt"]=> + string(%d) "%sphar_is_cacheable_001.phpt" ["phar_oo_001.phpt"]=> string(%d) "%sphar_oo_001.phpt" ["phar_oo_002.phpt"]=> diff --git a/ext/phar/tests/phar_is_cacheable_001.phpt b/ext/phar/tests/phar_is_cacheable_001.phpt new file mode 100644 index 0000000000000..eeafdbd95edc3 --- /dev/null +++ b/ext/phar/tests/phar_is_cacheable_001.phpt @@ -0,0 +1,19 @@ +--TEST-- +Phar URI is cacheable and key is URI +--SKIPIF-- + +--FILE-- + +==DONE== +--EXPECTF-- +string(28) "phar:///Foo/Bar/Moo.phar.php" +==DONE== diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 8f73153bbaf3f..d3f9eecf28f09 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -1220,6 +1220,13 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_INFO(arginfo_sys_get_temp_dir, 0) ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_INFO_EX(arginfo_file_cache_key, 0, 0, 1) + ZEND_ARG_INFO(0, filename) + ZEND_ARG_INFO(0, options) + ZEND_ARG_INFO(0, context) +ZEND_END_ARG_INFO() + /* }}} */ /* {{{ filestat.c */ ZEND_BEGIN_ARG_INFO(arginfo_disk_total_space, 0) @@ -3405,6 +3412,7 @@ const zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(output_reset_rewrite_vars, arginfo_output_reset_rewrite_vars) PHP_FE(sys_get_temp_dir, arginfo_sys_get_temp_dir) + PHP_FE(file_cache_key, arginfo_file_cache_key) #ifdef PHP_WIN32 PHP_FE(sapi_windows_cp_set, arginfo_sapi_windows_cp_set) diff --git a/ext/standard/file.c b/ext/standard/file.c index d82c2c513e928..8cdbfb0053bb1 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -2519,6 +2519,29 @@ PHP_FUNCTION(sys_get_temp_dir) } /* }}} */ +/* {{{ proto bool file_cache_key(string filename [, int options [, resource context]]) + Determine if a path can be cached and the key to use */ +PHP_FUNCTION(file_cache_key) +{ + zend_string *filename; + zend_long options = 0; + zval *zcontext = NULL; + zend_string *ret; + + /* Parse arguments */ + if (zend_parse_parameters(ZEND_NUM_ARGS(), "S|lr!", &filename + , &options, &zcontext) == FAILURE) { + return; + } + + ret = php_stream_cache_key(filename, options, php_stream_context_from_zval(zcontext, 0)); + if (ret) { + RETVAL_STR(ret); + } + /* Default: return null */ +} +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/ext/standard/file.h b/ext/standard/file.h index 4a014c7617a8d..85bc59115bf59 100644 --- a/ext/standard/file.h +++ b/ext/standard/file.h @@ -71,6 +71,7 @@ PHP_FUNCTION(fnmatch); PHP_NAMED_FUNCTION(php_if_ftruncate); PHP_NAMED_FUNCTION(php_if_fstat); PHP_FUNCTION(sys_get_temp_dir); +PHP_FUNCTION(file_cache_key); PHP_MINIT_FUNCTION(user_streams); diff --git a/ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt b/ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt new file mode 100644 index 0000000000000..b171bbbdef36d --- /dev/null +++ b/ext/standard/tests/streams/http_url_is_not_cacheable_001.phpt @@ -0,0 +1,18 @@ +--TEST-- +HTTP URL is not cacheable +--FILE-- + +--EXPECTF-- +NULL +NULL diff --git a/ext/standard/tests/streams/plain_file_is_cacheable_001.phpt b/ext/standard/tests/streams/plain_file_is_cacheable_001.phpt new file mode 100644 index 0000000000000..d630733c7061a --- /dev/null +++ b/ext/standard/tests/streams/plain_file_is_cacheable_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +Plain file is cacheable +--FILE-- + +--EXPECTF-- +string(16) "/Foo/Bar/Moo.php" +string(23) "file:///Foo/Bar/Moo.php" diff --git a/ext/standard/tests/streams/stream_cache_key_user_001.phpt b/ext/standard/tests/streams/stream_cache_key_user_001.phpt new file mode 100644 index 0000000000000..6129ec20d9da8 --- /dev/null +++ b/ext/standard/tests/streams/stream_cache_key_user_001.phpt @@ -0,0 +1,22 @@ +--TEST-- +Valid cache key returned from userspace wrapper +--FILE-- + +--EXPECTF-- +string(26) "mystream://random/path:key" diff --git a/ext/standard/tests/streams/stream_cache_key_user_002.phpt b/ext/standard/tests/streams/stream_cache_key_user_002.phpt new file mode 100644 index 0000000000000..1d2e2e8f7adba --- /dev/null +++ b/ext/standard/tests/streams/stream_cache_key_user_002.phpt @@ -0,0 +1,22 @@ +--TEST-- +Null cache key returned from userspace wrapper +--FILE-- + +--EXPECTF-- +NULL diff --git a/ext/standard/tests/streams/stream_cache_key_user_004.phpt b/ext/standard/tests/streams/stream_cache_key_user_004.phpt new file mode 100644 index 0000000000000..ab8c33b882eec --- /dev/null +++ b/ext/standard/tests/streams/stream_cache_key_user_004.phpt @@ -0,0 +1,18 @@ +--TEST-- +Unimplemented cache_key() method in userspace wrapper +--FILE-- + +--EXPECTF-- +NULL diff --git a/ext/standard/tests/streams/stream_cache_key_user_005.phpt b/ext/standard/tests/streams/stream_cache_key_user_005.phpt new file mode 100644 index 0000000000000..12518f2722ec4 --- /dev/null +++ b/ext/standard/tests/streams/stream_cache_key_user_005.phpt @@ -0,0 +1,24 @@ +--TEST-- +Invalid key returned by userspace cache_key() method +--FILE-- + +--EXPECTF-- +Fatal error: file_cache_key(): cache_key: Key (invalid_key) should start with same prefix as URL (mystream://random/path) in %s on line %d + diff --git a/main/php_streams.h b/main/php_streams.h index d10d087fb4956..957d5bb5e6766 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -158,6 +158,12 @@ typedef struct _php_stream_wrapper_ops { int (*stream_rmdir)(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context); /* Metadata handling */ int (*stream_metadata)(php_stream_wrapper *wrapper, const char *url, int options, void *value, php_stream_context *context); + + /* This operation was introduced in PHP 7.2 (PHP_API_VERSION >= 20160731) */ + /* Asks whether an URL is cacheable and the key to use */ + /* Note: Returned string must be zend_string_release()d by the caller */ + zend_string *(*stream_cache_key)(php_stream_wrapper *wrapper, zend_string *url, + int options, php_stream_context *context); } php_stream_wrapper_ops; struct _php_stream_wrapper { @@ -374,6 +380,9 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in PHPAPI int _php_stream_set_option(php_stream *stream, int option, int value, void *ptrparam); #define php_stream_set_option(stream, option, value, ptrvalue) _php_stream_set_option((stream), (option), (value), (ptrvalue)) +PHPAPI zend_string *_php_stream_cache_key(zend_string *path, int options, php_stream_context *context); +#define php_stream_cache_key(path, options, context) _php_stream_cache_key((path), (options), (context)) + #define php_stream_set_chunk_size(stream, size) _php_stream_set_option((stream), PHP_STREAM_OPTION_SET_CHUNK_SIZE, (size), NULL) END_EXTERN_C() diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 4cd6cca854735..8c6d9bc7be93c 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -1412,6 +1412,12 @@ static int php_plain_files_metadata(php_stream_wrapper *wrapper, const char *url return 1; } +static zend_string *php_plain_files_cache_key(php_stream_wrapper *wrapper, zend_string *url, int option, php_stream_context *context) +{ + /* Plain files are always cacheable*/ + zend_string_addref(url); + return url; +} static php_stream_wrapper_ops php_plain_files_wrapper_ops = { php_plain_files_stream_opener, @@ -1424,7 +1430,8 @@ static php_stream_wrapper_ops php_plain_files_wrapper_ops = { php_plain_files_rename, php_plain_files_mkdir, php_plain_files_rmdir, - php_plain_files_metadata + php_plain_files_metadata, + php_plain_files_cache_key }; PHPAPI php_stream_wrapper php_plain_files_wrapper = { diff --git a/main/streams/streams.c b/main/streams/streams.c index 25898cd2c4f59..c3d36c30f9fb6 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2320,6 +2320,54 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in } /* }}} */ +/* {{{ php_stream_cache_key + */ +/* Note: If not NULL, the returned zend_string must be released by the caller */ + +PHPAPI zend_string *_php_stream_cache_key(zend_string *path, int options, php_stream_context *context) +{ + php_stream_wrapper *wrapper = NULL; + zend_string *ret = NULL; + + if (!path) { + return NULL; + } + + wrapper = php_stream_locate_url_wrapper(ZSTR_VAL(path), NULL, options); + + if (wrapper && wrapper->wops->stream_cache_key) { + ret = wrapper->wops->stream_cache_key(wrapper, + path, options, context); + if (ret) { + if (ZSTR_LEN(ret) == 0) { + /* Discard empty key as not cacheable */ + zend_string_release(ret); + ret = NULL; + } else { +#ifdef PHP_DEBUG + if (path != ret) { + /* Check that returned key starts with the same '://' */ + /* prefix as input path */ + const char *p1; + size_t n; + + p1=php_memnstr(ZSTR_VAL(path), "://", sizeof("://") - 1 + , ZSTR_VAL(path) + ZSTR_LEN(path)); + if (p1) { /* Ignore non-protocol-prefixed strings */ + n=p1-ZSTR_VAL(path)+3; /* Prefix length */ + ZEND_ASSERT((n <= ZSTR_LEN(ret)) + && (!strncmp(ZSTR_VAL(ret), ZSTR_VAL(path), n))); + } + } +#endif + } + } + } + + return ret; +} +/* }}} */ + /* * Local variables: * tab-width: 4 diff --git a/main/streams/userspace.c b/main/streams/userspace.c index da39dcaf7c2bc..c00b12c2fc337 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -52,6 +52,7 @@ static int user_wrapper_rename(php_stream_wrapper *wrapper, const char *url_from static int user_wrapper_mkdir(php_stream_wrapper *wrapper, const char *url, int mode, int options, php_stream_context *context); static int user_wrapper_rmdir(php_stream_wrapper *wrapper, const char *url, int options, php_stream_context *context); static int user_wrapper_metadata(php_stream_wrapper *wrapper, const char *url, int option, void *value, php_stream_context *context); +static zend_string *user_wrapper_cache_key(php_stream_wrapper *wrapper, zend_string *url, int option, php_stream_context *context); static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char *filename, const char *mode, int options, zend_string **opened_path, php_stream_context *context STREAMS_DC); @@ -66,7 +67,8 @@ static php_stream_wrapper_ops user_stream_wops = { user_wrapper_rename, user_wrapper_mkdir, user_wrapper_rmdir, - user_wrapper_metadata + user_wrapper_metadata, + user_wrapper_cache_key }; @@ -148,6 +150,7 @@ typedef struct _php_userstream_data php_userstream_data_t; #define USERSTREAM_SET_OPTION "stream_set_option" #define USERSTREAM_TRUNCATE "stream_truncate" #define USERSTREAM_METADATA "stream_metadata" +#define USERSTREAM_CACHE_KEY "stream_cache_key" /* {{{ class should have methods like these: @@ -279,6 +282,11 @@ typedef struct _php_userstream_data php_userstream_data_t; return true / false; } + function stream_cache_key(string $path, int $options) + { + return string or null; + } + }}} **/ static void user_stream_create_object(struct php_user_stream_wrapper *uwrap, php_stream_context *context, zval *object) @@ -362,7 +370,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, const char * return NULL; } - /* call it's stream_open method - set up params first */ + /* call its stream_open method - set up params first */ ZVAL_STRING(&args[0], filename); ZVAL_STRING(&args[1], mode); ZVAL_LONG(&args[2], options); @@ -444,7 +452,7 @@ static php_stream *user_wrapper_opendir(php_stream_wrapper *wrapper, const char return NULL; } - /* call it's dir_open method - set up params first */ + /* call its dir_open method - set up params first */ ZVAL_STRING(&args[0], filename); ZVAL_LONG(&args[1], options); @@ -1359,7 +1367,7 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i return ret; } - /* call it's stat_url method - set up params first */ + /* call its stat_url method - set up params first */ ZVAL_STRING(&args[0], url); ZVAL_LONG(&args[1], flags); @@ -1394,6 +1402,73 @@ static int user_wrapper_stat_url(php_stream_wrapper *wrapper, const char *url, i return ret; } +static zend_string *user_wrapper_cache_key(php_stream_wrapper *wrapper, zend_string *url, int flags, php_stream_context *context) +{ + struct php_user_stream_wrapper *uwrap = (struct php_user_stream_wrapper*)wrapper->abstract; + zval zfuncname, zretval, *zp; + zval args[2]; + int call_result; + zval object; + zend_string *ret; + const char *p1; + size_t prefix_len; + + /* create an instance of our class */ + user_stream_create_object(uwrap, context, &object); + if (Z_TYPE(object) == IS_UNDEF) { + return 0; + } + + /* call its cache_key method - set up params first */ + ZVAL_STR(&args[0], url); + ZVAL_LONG(&args[1], flags); + + ZVAL_STRING(&zfuncname, USERSTREAM_CACHE_KEY); + + call_result = call_user_function_ex(NULL, + &object, + &zfuncname, + &zretval, + 2, args, + 0, NULL ); + + if (call_result == SUCCESS) { + if (Z_TYPE(zretval) == IS_NULL) { + ret = NULL; /* Not cacheable */ + } else { + zp = &zretval; + ZVAL_DEREF(zp); + convert_to_string(zp); /* Convert returned key to string */ + ret = Z_STR_P(zp); + + /* Check that url and key have the same '://' prefix */ + if (url != ret) { + p1=php_memnstr(ZSTR_VAL(url), "://", sizeof("://") - 1 + , ZSTR_VAL(url) + ZSTR_LEN(url)); + prefix_len=0; + if (p1) { + prefix_len=p1-ZSTR_VAL(url)+3; /* Prefix length */ + } + if ((!p1) || (prefix_len > ZSTR_LEN(ret)) || strncmp(ZSTR_VAL(ret), ZSTR_VAL(url), prefix_len)) { + php_error_docref(NULL, E_ERROR, "cache_key: Key (%s) should start with same prefix as URL (%s)", + ZSTR_VAL(ret), ZSTR_VAL(url)); + zend_string_release(ret); + ret = NULL; /* Discard key */ + } + } + } + } else { + /* No error if method is not implemented. URL is just not cacheable. */ + ret = NULL; + } + + /* clean up */ + zval_ptr_dtor(&object); + /* Don't cleanup zretval */ + zval_ptr_dtor(&zfuncname); + + return ret; +} static size_t php_userstreamop_readdir(php_stream *stream, char *buf, size_t count) {