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