From: Dmitry Stogov Date: Tue, 3 Jul 2007 10:22:55 +0000 (+0000) Subject: Added ability to create local or remote (URL) user streams X-Git-Tag: BEFORE_IMPORT_OF_MYSQLND~327 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=f766275b7a1485ad8ce327dbe51badc4157a3620;p=php Added ability to create local or remote (URL) user streams Local user streams must not be able to open(), URLs if allow_url_include is off Implemented new function stream_is_local() [ - stream_wrapper_register() extended with additional optional argument "flags" of type long. This time only one flag is implemented - STREAM_IS_URL, that means that userstream wrapper is remote (URL). By default stream is local. - stream_is_local() is a new function that accepts stream and tell if this stream is local or remote (URL) ] --- diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 0f594d4927..cbd989ea13 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -205,9 +205,10 @@ ZEND_END_ARG_INFO() /* }}} */ /* {{{ main/streams/userspace.c */ static -ZEND_BEGIN_ARG_INFO(arginfo_stream_wrapper_register, 0) +ZEND_BEGIN_ARG_INFO_EX(arginfo_stream_wrapper_register, 0, 0, 2) ZEND_ARG_INFO(0, protocol) ZEND_ARG_INFO(0, classname) + ZEND_ARG_INFO(0, flags) ZEND_END_ARG_INFO() static @@ -2346,6 +2347,11 @@ static ZEND_BEGIN_ARG_INFO(arginfo_stream_get_wrappers, 0) ZEND_END_ARG_INFO() +static +ZEND_BEGIN_ARG_INFO(arginfo_stream_is_local, 0) + ZEND_ARG_INFO(0, stream) +ZEND_END_ARG_INFO() + static ZEND_BEGIN_ARG_INFO_EX(arginfo_stream_select, 0, 0, 4) ZEND_ARG_INFO(1, read_streams) /* ARRAY_INFO(1, read_streams, 1) */ @@ -3596,6 +3602,7 @@ zend_function_entry basic_functions[] = { /* {{{ */ PHP_FE(stream_wrapper_restore, arginfo_stream_wrapper_restore) PHP_FE(stream_get_wrappers, arginfo_stream_get_wrappers) PHP_FE(stream_get_transports, arginfo_stream_get_transports) + PHP_FE(stream_is_local, arginfo_stream_is_local) PHP_FE(get_headers, arginfo_get_headers) #if HAVE_SYS_TIME_H || defined(PHP_WIN32) diff --git a/ext/standard/streamsfuncs.c b/ext/standard/streamsfuncs.c index 327806da64..6aa34e95f4 100644 --- a/ext/standard/streamsfuncs.c +++ b/ext/standard/streamsfuncs.c @@ -1647,6 +1647,37 @@ PHP_FUNCTION(stream_resolve_include_path) } /* }}} */ +/* {{{ proto bool stream_is_local(resource stream|string url) U +*/ +PHP_FUNCTION(stream_is_local) +{ + zval *zstream; + php_stream *stream = NULL; + php_stream_wrapper *wrapper = NULL; + + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zstream) == FAILURE) { + RETURN_FALSE; + } + + if(Z_TYPE_P(zstream) == IS_RESOURCE) { + php_stream_from_zval(stream, &zstream); + if(stream == NULL) { + RETURN_FALSE; + } + wrapper = stream->wrapper; + } else { + convert_to_string_ex(&zstream); + wrapper = php_stream_locate_url_wrapper(Z_STRVAL_P(zstream), NULL, STREAM_LOCATE_WRAPPERS_ONLY TSRMLS_CC); + } + + if(!wrapper) { + RETURN_FALSE; + } + + RETURN_BOOL(wrapper->is_url==0); +} +/* }}} */ + #ifdef HAVE_SHUTDOWN /* {{{ proto int stream_socket_shutdown(resource stream, int how) U causes all or part of a full-duplex connection on the socket associated diff --git a/ext/standard/streamsfuncs.h b/ext/standard/streamsfuncs.h index 45893bd9b9..5350d0fe81 100644 --- a/ext/standard/streamsfuncs.h +++ b/ext/standard/streamsfuncs.h @@ -57,6 +57,7 @@ PHP_FUNCTION(stream_socket_enable_crypto); PHP_FUNCTION(stream_socket_shutdown); PHP_FUNCTION(stream_socket_pair); PHP_FUNCTION(stream_resolve_include_path); +PHP_FUNCTION(stream_is_local); /* * Local variables: diff --git a/ext/standard/tests/file/include_userstream_001.phpt b/ext/standard/tests/file/include_userstream_001.phpt new file mode 100755 index 0000000000..d805afd204 --- /dev/null +++ b/ext/standard/tests/file/include_userstream_001.phpt @@ -0,0 +1,82 @@ +--TEST-- +User streams and include() +--INI-- +allow_url_fopen=1 +allow_url_include=0 +--FILE-- +'; + private $pos; + + function stream_open($path, $mode, $options, &$opened_path) + { + if (strchr($mode, 'a')) + $this->pos = strlen($this->data); + else + $this->po = 0; + + return true; + } + + function stream_read($count) + { + $ret = substr($this->data, $this->pos, $count); + $this->pos += strlen($ret); + return $ret; + } + + function stream_tell() + { + return $this->pos; + } + + function stream_eof() + { + return $this->pos >= strlen($this->data); + } + + function stream_seek($offset, $whence) + { + switch($whence) { + case SEEK_SET: + if ($offset < $this->data && $offset >= 0) { + $this->pos = $offset; + return true; + } else { + return false; + } + break; + case SEEK_CUR: + if ($offset >= 0) { + $this->pos += $offset; + return true; + } else { + return false; + } + break; + case SEEK_END: + if (strlen($this->data) + $offset >= 0) { + $this->pos = strlen($this->data) + $offset; + return true; + } else { + return false; + } + break; + default: + return false; + } + } + +} + +stream_register_wrapper("test1", "test", STREAM_IS_URL); +stream_register_wrapper("test2", "test"); +echo @file_get_contents("test1://hello"),"\n"; +@include "test1://hello"; +echo @file_get_contents("test2://hello"),"\n"; +@include "test2://hello"; +--EXPECT-- + + +Hello World diff --git a/ext/standard/tests/file/include_userstream_002.phpt b/ext/standard/tests/file/include_userstream_002.phpt new file mode 100755 index 0000000000..d422a82769 --- /dev/null +++ b/ext/standard/tests/file/include_userstream_002.phpt @@ -0,0 +1,106 @@ +--TEST-- +local user streams must not be able to open() url's +--INI-- +allow_url_fopen=1 +allow_url_include=0 +--FILE-- +'; + private $pos; + private $stream = null; + + function stream_open($path, $mode, $options, &$opened_path) + { + if (strpos($path, "test2://") === 0) { + $this->stream = fopen("test1://".substr($path, 8), $mode); + return !empty($this->stream); + } + if (strchr($mode, 'a')) + $this->pos = strlen($this->data); + else + $this->po = 0; + + return true; + } + + function stream_read($count) + { + if (!empty($this->stream)) { + return fread($this->stream, $count); + } + $ret = substr($this->data, $this->pos, $count); + $this->pos += strlen($ret); + return $ret; + } + + function stream_tell() + { + if (!empty($this->stream)) { + return ftell($this->stream); + } + return $this->pos; + } + + function stream_eof() + { + if (!empty($this->stream)) { + return feof($this->stream); + } + return $this->pos >= strlen($this->data); + } + + function stream_seek($offset, $whence) + { + if (!empty($this->stream)) { + return fseek($this->stream, $offset, $whence); + } + switch($whence) { + case SEEK_SET: + if ($offset < $this->data && $offset >= 0) { + $this->pos = $offset; + return true; + } else { + return false; + } + break; + case SEEK_CUR: + if ($offset >= 0) { + $this->pos += $offset; + return true; + } else { + return false; + } + break; + case SEEK_END: + if (strlen($this->data) + $offset >= 0) { + $this->pos = strlen($this->data) + $offset; + return true; + } else { + return false; + } + break; + default: + return false; + } + } + +} + +stream_register_wrapper("test1", "test", STREAM_IS_URL); +stream_register_wrapper("test2", "test"); +echo @file_get_contents("test1://hello"),"\n"; +@include "test1://hello"; +echo @file_get_contents("test2://hello"),"\n"; +include "test2://hello"; +--EXPECTF-- + + + +Warning: fopen(): test1:// wrapper is disabled in the server configuration in %sinclude_userstream_002.php on line 10 + +Warning: fopen(test1://hello): failed to open stream: no suitable wrapper could be found in %sinclude_userstream_002.php on line 10 + +Warning: include(test2://hello): failed to open stream: "test::stream_open" call failed in %sinclude_userstream_002.php on line 89 + +Warning: include(): Failed opening 'test2://hello' for inclusion (include_path='%s') in %sinclude_userstream_002.php on line 89 diff --git a/main/main.c b/main/main.c index 7abf5d0aee..8a3b350bdb 100644 --- a/main/main.c +++ b/main/main.c @@ -1232,6 +1232,7 @@ int php_request_startup(TSRMLS_D) PG(modules_activated) = 0; PG(header_is_being_sent) = 0; PG(connection_status) = PHP_CONNECTION_NORMAL; + PG(in_user_include) = 0; zend_activate(TSRMLS_C); sapi_activate(TSRMLS_C); diff --git a/main/php_globals.h b/main/php_globals.h index 04da9289c2..0dbdb49fbb 100644 --- a/main/php_globals.h +++ b/main/php_globals.h @@ -142,6 +142,7 @@ struct _php_core_globals { zend_bool com_initialized; #endif long max_input_nesting_level; + zend_bool in_user_include; }; diff --git a/main/php_streams.h b/main/php_streams.h index bcb5cad45a..77808b1d4a 100755 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -619,6 +619,9 @@ END_EXTERN_C() /* use glob stream for directory open in plain files stream */ #define STREAM_USE_GLOB_DIR_OPEN 0x00001000 +/* use glob stream for directory open in plain files stream */ +#define STREAM_DISABLE_URL_PROTECTION 0x00002000 + /* Antique - no longer has meaning */ #define IGNORE_URL_WIN 0 @@ -669,7 +672,7 @@ PHPAPI int _php_stream_make_seekable(php_stream *origstream, php_stream **newstr PHP_INI_MH(OnUpdateAllowUrl); PHPAPI int php_stream_wrapper_is_allowed(const char *wrapper, int wrapper_len, const char *setting TSRMLS_DC); -#define php_stream_allow_url_fopen(wrapper, wrapper_len) php_stream_wrapper_is_allowed((wrapper), (wrapper_len), PG(allow_url_fopen_list) TSRMLS_CC) +#define php_stream_allow_url_fopen(wrapper, wrapper_len) php_stream_wrapper_is_allowed((wrapper), (wrapper_len), PG(in_user_include)?PG(allow_url_include_list):PG(allow_url_fopen_list) TSRMLS_CC) #define php_stream_allow_url_include(wrapper, wrapper_len) php_stream_wrapper_is_allowed((wrapper), (wrapper_len), PG(allow_url_include_list) TSRMLS_CC) /* Give other modules access to the url_stream_wrappers_hash and stream_filters_hash */ @@ -683,6 +686,8 @@ extern php_stream_wrapper_ops *php_stream_user_wrapper_ops; END_EXTERN_C() #endif +/* Definitions for user streams */ +#define PHP_STREAM_IS_URL 1 /* * Local variables: * tab-width: 4 diff --git a/main/streams/streams.c b/main/streams/streams.c index 24496d7c54..e484b7f9cb 100755 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2163,8 +2163,9 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char return plain_files_wrapper; } - if (!php_stream_allow_url_fopen(protocol, n) || - ((options & STREAM_OPEN_FOR_INCLUDE) && !php_stream_allow_url_include(protocol, n)) ) { + if (((options & STREAM_DISABLE_URL_PROTECTION) == 0) && + (!php_stream_allow_url_fopen(protocol, n) || + ((options & STREAM_OPEN_FOR_INCLUDE) && !php_stream_allow_url_include(protocol, n)))) { if (options & REPORT_ERRORS) { /* protocol[n] probably isn't '\0' */ char *protocol_dup = estrndup(protocol, n); @@ -2797,7 +2798,7 @@ PHPAPI int _php_stream_path_encode(php_stream_wrapper *wrapper, return FAILURE; } - wrapper = php_stream_locate_url_wrapper(scheme, NULL, options TSRMLS_CC); + wrapper = php_stream_locate_url_wrapper(scheme, NULL, options | STREAM_DISABLE_URL_PROTECTION TSRMLS_CC); efree(scheme); if (!wrapper) { *pathenc = NULL; diff --git a/main/streams/userspace.c b/main/streams/userspace.c index 29271d72c0..dfc2900942 100644 --- a/main/streams/userspace.c +++ b/main/streams/userspace.c @@ -81,6 +81,7 @@ PHP_MINIT_FUNCTION(user_streams) REGISTER_LONG_CONSTANT("STREAM_URL_STAT_QUIET", PHP_STREAM_URL_STAT_QUIET, CONST_CS|CONST_PERSISTENT); REGISTER_LONG_CONSTANT("STREAM_MKDIR_RECURSIVE", PHP_STREAM_MKDIR_RECURSIVE, CONST_CS|CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("STREAM_IS_URL", PHP_STREAM_IS_URL, CONST_CS|CONST_PERSISTENT); return SUCCESS; } @@ -215,6 +216,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filena int call_result; php_stream *stream = NULL; zval *zcontext = NULL; + zend_bool old_in_user_include; /* Try to catch bad usage without preventing flexibility */ if (FG(user_stream_current_filename) != NULL && strcmp(filename, FG(user_stream_current_filename)) == 0) { @@ -223,6 +225,17 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filena } FG(user_stream_current_filename) = filename; + /* if the user stream was registered as local and we are in include context, + we add allow_url_include restrictions to allow_url_fopen ones */ + /* we need only is_url == 0 here since if is_url == 1 and remote wrappers + were restricted we wouldn't get here */ + old_in_user_include = PG(in_user_include); + if(uwrap->wrapper.is_url == 0 && + (options & STREAM_OPEN_FOR_INCLUDE) && + (PG(allow_url_include_list) == NULL || strlen(PG(allow_url_include_list)) !=1 || PG(allow_url_include_list)[0] != '*')) { + PG(in_user_include) = 1; + } + us = emalloc(sizeof(*us)); us->wrapper = uwrap; @@ -258,6 +271,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filena FREE_ZVAL(us->object); efree(us); FG(user_stream_current_filename) = NULL; + PG(in_user_include) = old_in_user_include; return NULL; } else { if (retval_ptr) { @@ -338,7 +352,8 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filena zval_ptr_dtor(&zfilename); FG(user_stream_current_filename) = NULL; - + + PG(in_user_include) = old_in_user_include; return stream; } @@ -436,8 +451,9 @@ PHP_FUNCTION(stream_wrapper_register) int protocol_len, classname_len; struct php_user_stream_wrapper * uwrap; int rsrc_id; + long flags = 0; - if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &protocol, &protocol_len, &classname, &classname_len) == FAILURE) { + if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &protocol, &protocol_len, &classname, &classname_len, &flags) == FAILURE) { RETURN_FALSE; } @@ -446,6 +462,7 @@ PHP_FUNCTION(stream_wrapper_register) uwrap->classname = estrndup(classname, classname_len); uwrap->wrapper.wops = &user_stream_wops; uwrap->wrapper.abstract = uwrap; + uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0); rsrc_id = ZEND_REGISTER_RESOURCE(NULL, uwrap, le_protocols);