Skip to content

Commit 539f67e

Browse files
committed
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) ]
1 parent fe9a826 commit 539f67e

File tree

10 files changed

+259
-3
lines changed

10 files changed

+259
-3
lines changed

ext/standard/basic_functions.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,9 +207,10 @@ ZEND_END_ARG_INFO()
207207
/* }}} */
208208
/* {{{ main/streams/userspace.c */
209209
static
210-
ZEND_BEGIN_ARG_INFO(arginfo_stream_wrapper_register, 0)
210+
ZEND_BEGIN_ARG_INFO_EX(arginfo_stream_wrapper_register, 0, 0, 2)
211211
ZEND_ARG_INFO(0, protocol)
212212
ZEND_ARG_INFO(0, classname)
213+
ZEND_ARG_INFO(0, flags)
213214
ZEND_END_ARG_INFO()
214215

215216
static
@@ -221,6 +222,11 @@ static
221222
ZEND_BEGIN_ARG_INFO(arginfo_stream_wrapper_restore, 0)
222223
ZEND_ARG_INFO(0, protocol)
223224
ZEND_END_ARG_INFO()
225+
226+
static
227+
ZEND_BEGIN_ARG_INFO(arginfo_stream_is_local, 0)
228+
ZEND_ARG_INFO(0, stream)
229+
ZEND_END_ARG_INFO()
224230
/* }}} */
225231
/* {{{ array.c */
226232
static
@@ -3555,6 +3561,7 @@ zend_function_entry basic_functions[] = {
35553561
PHP_FE(stream_wrapper_restore, arginfo_stream_wrapper_restore)
35563562
PHP_FE(stream_get_wrappers, arginfo_stream_get_wrappers)
35573563
PHP_FE(stream_get_transports, arginfo_stream_get_transports)
3564+
PHP_FE(stream_is_local, arginfo_stream_is_local)
35583565
PHP_FE(get_headers, arginfo_get_headers)
35593566

35603567
#if HAVE_SYS_TIME_H || defined(PHP_WIN32)

ext/standard/streamsfuncs.c

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1341,6 +1341,37 @@ PHP_FUNCTION(stream_socket_enable_crypto)
13411341
}
13421342
/* }}} */
13431343

1344+
/* {{{ proto bool stream_is_local(resource stream|string url) U
1345+
*/
1346+
PHP_FUNCTION(stream_is_local)
1347+
{
1348+
zval *zstream;
1349+
php_stream *stream = NULL;
1350+
php_stream_wrapper *wrapper = NULL;
1351+
1352+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "z", &zstream) == FAILURE) {
1353+
RETURN_FALSE;
1354+
}
1355+
1356+
if(Z_TYPE_P(zstream) == IS_RESOURCE) {
1357+
php_stream_from_zval(stream, &zstream);
1358+
if(stream == NULL) {
1359+
RETURN_FALSE;
1360+
}
1361+
wrapper = stream->wrapper;
1362+
} else {
1363+
convert_to_string_ex(&zstream);
1364+
wrapper = php_stream_locate_url_wrapper(Z_STRVAL_P(zstream), NULL, STREAM_LOCATE_WRAPPERS_ONLY TSRMLS_CC);
1365+
}
1366+
1367+
if(!wrapper) {
1368+
RETURN_FALSE;
1369+
}
1370+
1371+
RETURN_BOOL(wrapper->is_url==0);
1372+
}
1373+
/* }}} */
1374+
13441375
#ifdef HAVE_SHUTDOWN
13451376
/* {{{ proto int stream_socket_shutdown(resource stream, int how)
13461377
causes all or part of a full-duplex connection on the socket associated

ext/standard/streamsfuncs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ PHP_FUNCTION(stream_filter_remove);
5555
PHP_FUNCTION(stream_socket_enable_crypto);
5656
PHP_FUNCTION(stream_socket_shutdown);
5757
PHP_FUNCTION(stream_socket_pair);
58+
PHP_FUNCTION(stream_is_local);
5859

5960
/*
6061
* Local variables:
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
--TEST--
2+
User streams and include()
3+
--INI--
4+
allow_url_fopen=1
5+
allow_url_include=0
6+
--FILE--
7+
<?php
8+
class test {
9+
private $data = '<?php echo "Hello World\n";?>';
10+
private $pos;
11+
12+
function stream_open($path, $mode, $options, &$opened_path)
13+
{
14+
if (strchr($mode, 'a'))
15+
$this->pos = strlen($this->data);
16+
else
17+
$this->po = 0;
18+
19+
return true;
20+
}
21+
22+
function stream_read($count)
23+
{
24+
$ret = substr($this->data, $this->pos, $count);
25+
$this->pos += strlen($ret);
26+
return $ret;
27+
}
28+
29+
function stream_tell()
30+
{
31+
return $this->pos;
32+
}
33+
34+
function stream_eof()
35+
{
36+
return $this->pos >= strlen($this->data);
37+
}
38+
39+
function stream_seek($offset, $whence)
40+
{
41+
switch($whence) {
42+
case SEEK_SET:
43+
if ($offset < $this->data && $offset >= 0) {
44+
$this->pos = $offset;
45+
return true;
46+
} else {
47+
return false;
48+
}
49+
break;
50+
case SEEK_CUR:
51+
if ($offset >= 0) {
52+
$this->pos += $offset;
53+
return true;
54+
} else {
55+
return false;
56+
}
57+
break;
58+
case SEEK_END:
59+
if (strlen($this->data) + $offset >= 0) {
60+
$this->pos = strlen($this->data) + $offset;
61+
return true;
62+
} else {
63+
return false;
64+
}
65+
break;
66+
default:
67+
return false;
68+
}
69+
}
70+
71+
}
72+
73+
stream_register_wrapper("test1", "test", STREAM_IS_URL);
74+
stream_register_wrapper("test2", "test");
75+
echo @file_get_contents("test1://hello"),"\n";
76+
@include "test1://hello";
77+
echo @file_get_contents("test2://hello"),"\n";
78+
@include "test2://hello";
79+
--EXPECT--
80+
<?php echo "Hello World\n";?>
81+
<?php echo "Hello World\n";?>
82+
Hello World
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
--TEST--
2+
local user streams must not be able to open() url's
3+
--INI--
4+
allow_url_fopen=1
5+
allow_url_include=0
6+
--FILE--
7+
<?php
8+
class test {
9+
private $data = '<?php echo "Hello World\n";?>';
10+
private $pos;
11+
private $stream = null;
12+
13+
function stream_open($path, $mode, $options, &$opened_path)
14+
{
15+
if (strpos($path, "test2://") === 0) {
16+
$this->stream = fopen("test1://".substr($path, 8), $mode);
17+
return !empty($this->stream);
18+
}
19+
if (strchr($mode, 'a'))
20+
$this->pos = strlen($this->data);
21+
else
22+
$this->po = 0;
23+
24+
return true;
25+
}
26+
27+
function stream_read($count)
28+
{
29+
if (!empty($this->stream)) {
30+
return fread($this->stream, $count);
31+
}
32+
$ret = substr($this->data, $this->pos, $count);
33+
$this->pos += strlen($ret);
34+
return $ret;
35+
}
36+
37+
function stream_tell()
38+
{
39+
if (!empty($this->stream)) {
40+
return ftell($this->stream);
41+
}
42+
return $this->pos;
43+
}
44+
45+
function stream_eof()
46+
{
47+
if (!empty($this->stream)) {
48+
return feof($this->stream);
49+
}
50+
return $this->pos >= strlen($this->data);
51+
}
52+
53+
function stream_seek($offset, $whence)
54+
{
55+
if (!empty($this->stream)) {
56+
return fseek($this->stream, $offset, $whence);
57+
}
58+
switch($whence) {
59+
case SEEK_SET:
60+
if ($offset < $this->data && $offset >= 0) {
61+
$this->pos = $offset;
62+
return true;
63+
} else {
64+
return false;
65+
}
66+
break;
67+
case SEEK_CUR:
68+
if ($offset >= 0) {
69+
$this->pos += $offset;
70+
return true;
71+
} else {
72+
return false;
73+
}
74+
break;
75+
case SEEK_END:
76+
if (strlen($this->data) + $offset >= 0) {
77+
$this->pos = strlen($this->data) + $offset;
78+
return true;
79+
} else {
80+
return false;
81+
}
82+
break;
83+
default:
84+
return false;
85+
}
86+
}
87+
88+
}
89+
90+
stream_register_wrapper("test1", "test", STREAM_IS_URL);
91+
stream_register_wrapper("test2", "test");
92+
echo @file_get_contents("test1://hello"),"\n";
93+
@include "test1://hello";
94+
echo @file_get_contents("test2://hello"),"\n";
95+
include "test2://hello";
96+
--EXPECTF--
97+
<?php echo "Hello World\n";?>
98+
<?php echo "Hello World\n";?>
99+
100+
Warning: fopen(): URL file-access is disabled in the server configuration in %sinclude_userstream_002.php on line 10
101+
102+
Warning: fopen(test1://hello): failed to open stream: no suitable wrapper could be found in %sinclude_userstream_002.php on line 10
103+
104+
Warning: include(test2://hello): failed to open stream: "test::stream_open" call failed in %sinclude_userstream_002.php on line 89
105+
106+
Warning: include(): Failed opening 'test2://hello' for inclusion (include_path='%s') in %sinclude_userstream_002.php on line 89

main/main.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,7 @@ int php_request_startup(TSRMLS_D)
11211121
PG(modules_activated) = 0;
11221122
PG(header_is_being_sent) = 0;
11231123
PG(connection_status) = PHP_CONNECTION_NORMAL;
1124+
PG(in_user_include) = 0;
11241125

11251126
zend_activate(TSRMLS_C);
11261127
sapi_activate(TSRMLS_C);

main/php_globals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ struct _php_core_globals {
156156
zend_bool com_initialized;
157157
#endif
158158
long max_input_nesting_level;
159+
zend_bool in_user_include;
159160
};
160161

161162

main/php_streams.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -499,6 +499,9 @@ END_EXTERN_C()
499499
/* get (or create) a persistent version of the stream */
500500
#define STREAM_OPEN_PERSISTENT 2048
501501

502+
/* don't check allow_url_fopen and allow_url_include */
503+
#define STREAM_DISABLE_URL_PROTECTION 0x00002000
504+
502505
/* Antique - no longer has meaning */
503506
#define IGNORE_URL_WIN 0
504507

@@ -550,6 +553,9 @@ PHPAPI HashTable *php_get_stream_filters_hash_global();
550553
END_EXTERN_C()
551554
#endif
552555

556+
/* Definitions for user streams */
557+
#define PHP_STREAM_IS_URL 1
558+
553559
/*
554560
* Local variables:
555561
* tab-width: 4

main/streams/streams.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1606,7 +1606,11 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, char
16061606
return &php_plain_files_wrapper;
16071607
}
16081608

1609-
if ((wrapperpp && (*wrapperpp)->is_url) && (!PG(allow_url_fopen) || ((options & STREAM_OPEN_FOR_INCLUDE) && !PG(allow_url_include))) ) {
1609+
if (wrapperpp && (*wrapperpp)->is_url &&
1610+
(options & STREAM_DISABLE_URL_PROTECTION) == 0 &&
1611+
(!PG(allow_url_fopen) ||
1612+
((options & STREAM_OPEN_FOR_INCLUDE) ||
1613+
PG(in_user_include)) && !PG(allow_url_include))) {
16101614
if (options & REPORT_ERRORS) {
16111615
php_error_docref(NULL TSRMLS_CC, E_WARNING, "URL file-access is disabled in the server configuration");
16121616
}

main/streams/userspace.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ PHP_MINIT_FUNCTION(user_streams)
8181
REGISTER_LONG_CONSTANT("STREAM_URL_STAT_QUIET", PHP_STREAM_URL_STAT_QUIET, CONST_CS|CONST_PERSISTENT);
8282
REGISTER_LONG_CONSTANT("STREAM_MKDIR_RECURSIVE", PHP_STREAM_MKDIR_RECURSIVE, CONST_CS|CONST_PERSISTENT);
8383

84+
REGISTER_LONG_CONSTANT("STREAM_IS_URL", PHP_STREAM_IS_URL, CONST_CS|CONST_PERSISTENT);
8485
return SUCCESS;
8586
}
8687

@@ -215,6 +216,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filena
215216
int call_result;
216217
php_stream *stream = NULL;
217218
zval *zcontext = NULL;
219+
zend_bool old_in_user_include;
218220

219221
/* Try to catch bad usage without preventing flexibility */
220222
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
223225
}
224226
FG(user_stream_current_filename) = filename;
225227

228+
/* if the user stream was registered as local and we are in include context,
229+
we add allow_url_include restrictions to allow_url_fopen ones */
230+
/* we need only is_url == 0 here since if is_url == 1 and remote wrappers
231+
were restricted we wouldn't get here */
232+
old_in_user_include = PG(in_user_include);
233+
if(uwrap->wrapper.is_url == 0 &&
234+
(options & STREAM_OPEN_FOR_INCLUDE) &&
235+
!PG(allow_url_include)) {
236+
PG(in_user_include) = 1;
237+
}
238+
226239
us = emalloc(sizeof(*us));
227240
us->wrapper = uwrap;
228241

@@ -258,6 +271,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filena
258271
FREE_ZVAL(us->object);
259272
efree(us);
260273
FG(user_stream_current_filename) = NULL;
274+
PG(in_user_include) = old_in_user_include;
261275
return NULL;
262276
} else {
263277
if (retval_ptr) {
@@ -339,6 +353,7 @@ static php_stream *user_wrapper_opener(php_stream_wrapper *wrapper, char *filena
339353

340354
FG(user_stream_current_filename) = NULL;
341355

356+
PG(in_user_include) = old_in_user_include;
342357
return stream;
343358
}
344359

@@ -436,8 +451,9 @@ PHP_FUNCTION(stream_wrapper_register)
436451
int protocol_len, classname_len;
437452
struct php_user_stream_wrapper * uwrap;
438453
int rsrc_id;
454+
long flags = 0;
439455

440-
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss", &protocol, &protocol_len, &classname, &classname_len) == FAILURE) {
456+
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "ss|l", &protocol, &protocol_len, &classname, &classname_len, &flags) == FAILURE) {
441457
RETURN_FALSE;
442458
}
443459

@@ -446,6 +462,7 @@ PHP_FUNCTION(stream_wrapper_register)
446462
uwrap->classname = estrndup(classname, classname_len);
447463
uwrap->wrapper.wops = &user_stream_wops;
448464
uwrap->wrapper.abstract = uwrap;
465+
uwrap->wrapper.is_url = ((flags & PHP_STREAM_IS_URL) != 0);
449466

450467
rsrc_id = ZEND_REGISTER_RESOURCE(NULL, uwrap, le_protocols);
451468

0 commit comments

Comments
 (0)