Hi, I found some time to whip up a quick patch against 5.3.
This patch allows adding stream wrappers to include_path on both windows and unix. This means one can set an include_path to ".:/usr/local/lib/php:phar:///path/to/ZF.phar/lib" or the windows equivalent ".;C:\php5;phar://C:/php5/ZF.phar/lib" Any stream wrapper, userspace or built-in, will work as long as it satisfies security constraints and implements url_stat(), stream_open(), stat(), and read() or the C equivalents. In other words, this test I already had created for pecl/phar works: <?php $fname = dirname(__FILE__) . '/tempmanifest1.phar.php'; $a = new Phar($fname); $a['file1.php'] = 'file1.php '; $a['test/file1.php'] = 'test/file1.php '; unset($a); set_include_path('.' . PATH_SEPARATOR . 'phar://' . $fname); include 'file1.php'; set_include_path('.' . PATH_SEPARATOR . 'phar://' . $fname . '/test'); include 'file1.php'; include 'file2.php'; ?> ===DONE=== with output: --EXPECTF-- file1.php test/file1.php Warning: include(file2.php): failed to open stream: No such file or directory in %sinclude_path.php on line %d Warning: include(): Failed opening 'file2.php' for inclusion (include_path='.:phar:///home/cellog/workspace/php5/ext/phar/tests/tempmanifest1.phar.php/test') in %sinclude_path.php on line %d ===DONE=== The internals of the patch borrows logic from php_stream_locate_url_wrapper to quickly detect a stream wrapper. It excludes the data stream wrapper out of hand as this has no business in include_path for obvious reasons. It also fully supports disabled allow_url_fopen/allow_url_include by passing STREAM_OPEN_FOR_INCLUDE to the eventual php_stream_locate_url_wrapper call. The most important part of the patch is the way it finds a stream wrapper on unix. Because PATH_SEPARATOR is ":" on unix, the include_path scanner will stop after "phar" in the sample include_paths above. The new scanner checks for a valid stream wrapper (a sequence of alpha-numeric, +, -, or . characters followed by ://) and seeks to the next : if found. My primary concern with the patch is performance. I'm certain it can be tweaked. I extracted the call to url_stat from php_stream_stat(), which removes the possibility of hitting stat cache but eliminates the redundant call to php_stream_locate_url_wrapper. If it is indeed faster to use php_stream_stat(), that's one easy optimization. In any case, it adds an additional scan of each include_path component to detect stream wrappers, which is called on each include. This may be the biggest candidate for optimization, as a simple cache of paths with stream wrapper/not could be created whenever include_path is set and used instead of scanning the string every time. By the way, there may be some unsafe scans in php_stream_locate_url_wrapper, specifically line 1531 in PHP_5_3: if ((*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || !memcmp("data", path, 4))) { It doesn't seem to verify that path is at least 4 or there is at least 2 extra characters after p before checking for "//" or "data" Thanks, Greg
Index: main/fopen_wrappers.c =================================================================== RCS file: /repository/php-src/main/fopen_wrappers.c,v retrieving revision 1.175.2.3.2.13.2.7 diff -u -r1.175.2.3.2.13.2.7 fopen_wrappers.c --- main/fopen_wrappers.c 5 Mar 2008 13:34:12 -0000 1.175.2.3.2.13.2.7 +++ main/fopen_wrappers.c 7 Mar 2008 04:48:16 -0000 @@ -447,6 +447,7 @@ char resolved_path[MAXPATHLEN]; char trypath[MAXPATHLEN]; char *ptr, *end; + php_stream_wrapper *wrapper; if (!filename) { return NULL; @@ -463,20 +464,43 @@ } } - ptr = path; + ptr = (char *) path; while (ptr && *ptr) { + const char *p; + int n = 0, len, is_stream_wrapper = 0, maybe_stream = 1; + end = strchr(ptr, DEFAULT_DIR_SEPARATOR); +#ifndef PHP_WIN32 + /* search for stream wrapper */ + if (end - ptr <= 1) { + maybe_stream = 0; + goto not_stream; + } + for (p = ptr; p < end && (isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'); p++) { + n++; + } + + if (n == end - ptr && *p && !strncmp("//", p+1, 2)) { + is_stream_wrapper = 1; + /* seek to real end of include_path portion */ + end = strchr(end + 1, DEFAULT_DIR_SEPARATOR); + } else { + maybe_stream = 0; + } +not_stream: +#endif if (end) { if ((end-ptr) + 1 + filename_length + 1 >= MAXPATHLEN) { ptr = end + 1; continue; } memcpy(trypath, ptr, end-ptr); + len = end-ptr; trypath[end-ptr] = '/'; memcpy(trypath+(end-ptr)+1, filename, filename_length+1); ptr = end+1; } else { - int len = strlen(ptr); + len = strlen(ptr); if (len + 1 + filename_length + 1 >= MAXPATHLEN) { break; @@ -486,6 +510,35 @@ memcpy(trypath+len+1, filename, filename_length+1); ptr = NULL; } + + if (!is_stream_wrapper && maybe_stream) { + /* search for stream wrapper */ + for (p = trypath; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { + n++; + } + } + + if (is_stream_wrapper || (n < len - 3 && (*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || !memcmp("data", trypath, 4)))) { + char *actual; + + wrapper = php_stream_locate_url_wrapper(trypath, &actual, STREAM_OPEN_FOR_INCLUDE TSRMLS_CC); + if (wrapper == &php_plain_files_wrapper) { + strncpy(trypath, actual, MAXPATHLEN); + } else if (!wrapper) { + /* if wrapper is NULL, there was a mal-formed include_path stream wrapper, so skip this ptr */ + continue; + } else { + if (wrapper->wops->url_stat) { + php_stream_statbuf ssb; + + if (SUCCESS == wrapper->wops->url_stat(wrapper, trypath, 0, &ssb, NULL TSRMLS_CC)) { + return estrdup(trypath); + } + } + continue; + } + } + if (tsrm_realpath(trypath, resolved_path TSRMLS_CC)) { return estrdup(resolved_path); } @@ -496,6 +549,8 @@ if (zend_is_executing(TSRMLS_C)) { char *exec_fname = zend_get_executed_filename(TSRMLS_C); int exec_fname_length = strlen(exec_fname); + const char *p; + int n = 0; while ((--exec_fname_length >= 0) && !IS_SLASH(exec_fname[exec_fname_length])); if (exec_fname && exec_fname[0] != '[' && @@ -503,6 +558,26 @@ exec_fname_length + 1 + filename_length + 1 < MAXPATHLEN) { memcpy(trypath, exec_fname, exec_fname_length + 1); memcpy(trypath+exec_fname_length + 1, filename, filename_length+1); + + /* search for stream wrapper */ + for (p = trypath; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { + n++; + } + if (n < exec_fname_length - 3 && (*p == ':') && (n > 1) && (!strncmp("//", p+1, 2) || !memcmp("data", trypath, 4))) { + char *actual; + + wrapper = php_stream_locate_url_wrapper(trypath, &actual, STREAM_OPEN_FOR_INCLUDE TSRMLS_CC); + if (wrapper == &php_plain_files_wrapper) { + /* this should never technically happen, but we'll leave it here for completeness */ + strncpy(trypath, actual, MAXPATHLEN); + } else if (!wrapper) { + /* if wrapper is NULL, there was a mal-formed include_path stream wrapper + this also should be impossible */ + return NULL; + } else { + return estrdup(trypath); + } + } if (tsrm_realpath(trypath, resolved_path TSRMLS_CC)) { return estrdup(resolved_path); } Index: main/main.c =================================================================== RCS file: /repository/php-src/main/main.c,v retrieving revision 1.640.2.23.2.57.2.11 diff -u -r1.640.2.23.2.57.2.11 main.c --- main/main.c 5 Mar 2008 13:34:12 -0000 1.640.2.23.2.57.2.11 +++ main/main.c 7 Mar 2008 04:48:17 -0000 @@ -1077,8 +1077,15 @@ PHPAPI int php_stream_open_for_zend_ex(const char *filename, zend_file_handle *handle, int mode TSRMLS_DC) /* {{{ */ { php_stream *stream; + char *resolved_path; - stream = php_stream_open_wrapper((char *)filename, "rb", mode, &handle->opened_path); + resolved_path = zend_resolve_path((char *)filename, strlen(filename) TSRMLS_CC); + if (resolved_path) { + stream = php_stream_open_wrapper(resolved_path, "rb", mode, &handle->opened_path); + efree(resolved_path); + } else { + stream = php_stream_open_wrapper((char *)filename, "rb", mode, &handle->opened_path); + } if (stream) { handle->type = ZEND_HANDLE_STREAM;
-- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php