Skip to content

Commit b0cab05

Browse files
committed
Add tests for uncastable streams and dataloss streams
Moreover, stop using php_stream_can_cast() just before a php_stream_cast()
1 parent 18067c8 commit b0cab05

14 files changed

+342
-25
lines changed

ext/posix/posix.c

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -426,10 +426,15 @@ static int php_posix_stream_get_fd(zval *zfp, zend_long *fd) /* {{{ */
426426
if (stream == NULL) {
427427
return 0;
428428
}
429-
if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT) == SUCCESS) {
430-
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT, (void*)fd, 0);
431-
} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD) == SUCCESS) {
432-
php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)fd, 0);
429+
430+
/* get the fd.
431+
* NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag when casting.
432+
* It is only used here so that the buffered data warning is not displayed.
433+
*/
434+
if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
435+
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&fd, 0);
436+
} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
437+
php_stream_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void*)&fd, 0);
433438
} else {
434439
php_error_docref(NULL, E_WARNING, "Could not use stream of type '%s'",
435440
stream->ops->label);

ext/posix/tests/posix_fpathconf.phpt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ try {
1515
} catch (\TypeError $e) {
1616
echo $e->getMessage() . "\n";
1717
}
18-
$fd = fopen(sys_get_temp_dir(), "r");
18+
$fd = fopen(__DIR__, "r");
1919
var_dump(posix_fpathconf($fd, POSIX_PC_PATH_MAX));
2020
fclose($fd);
2121
?>

ext/posix/tests/posix_isatty.phpt

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,11 @@
22
posix_isatty(): Basic tests
33
--EXTENSIONS--
44
posix
5-
--SKIPIF--
6-
<?php
7-
if (!function_exists('posix_isatty')) die('skip posix_isatty() not found');
8-
?>
95
--FILE--
106
<?php
117

12-
var_dump(posix_isatty(0));
8+
var_dump(posix_isatty(STDIN));
139

1410
?>
15-
--EXPECTF--
16-
bool(%s)
11+
--EXPECT--
12+
bool(false)
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
posix_isatty(): casting stream does not emit data loss and should not emit warnings
3+
--EXTENSIONS--
4+
posix
5+
--SKIPIF--
6+
<?php
7+
require __DIR__ . '/../../standard/tests/http/server.inc'; http_server_skipif();
8+
?>
9+
--INI--
10+
allow_url_fopen=1
11+
--FILE--
12+
<?php
13+
14+
require __DIR__ . '/../../standard/tests/http/server.inc';
15+
16+
$responses = array(
17+
"data://text/plain,HTTP/1.0 200 Ok\r\nSome: Header\r\nSome: Header\r\n\r\nBody",
18+
);
19+
20+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
21+
22+
/* Note: the warning is bogus in this case as no data actually gets lost,
23+
* but this checks that stream casting works */
24+
$handle = fopen($uri, 'r');
25+
var_dump(posix_isatty($handle));
26+
var_dump(fread($handle, 20));
27+
fclose($handle);
28+
29+
http_server_kill($pid);
30+
?>
31+
--EXPECT--
32+
bool(false)
33+
string(4) "Body"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
posix_isatty(): uncastable user stream
3+
--EXTENSIONS--
4+
posix
5+
--FILE--
6+
<?php
7+
8+
require dirname(__DIR__, 3) . '/tests/output/DummyStreamWrapper.inc';
9+
stream_wrapper_register('custom', DummyStreamWrapper::class);
10+
11+
$fp = fopen("custom://myvar", "r+");
12+
var_dump(posix_isatty($fp));
13+
fclose($fp);
14+
15+
echo "Done";
16+
?>
17+
--EXPECTF--
18+
Warning: posix_isatty(): Could not use stream of type 'user-space' in %s on line %d
19+
bool(false)
20+
Done
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
posix_ttyname(): casting stream does not emit data loss and should not emit warnings
3+
--EXTENSIONS--
4+
posix
5+
--SKIPIF--
6+
<?php
7+
require __DIR__ . '/../../standard/tests/http/server.inc'; http_server_skipif();
8+
?>
9+
--INI--
10+
allow_url_fopen=1
11+
--FILE--
12+
<?php
13+
14+
require __DIR__ . '/../../standard/tests/http/server.inc';
15+
16+
$responses = array(
17+
"data://text/plain,HTTP/1.0 200 Ok\r\nSome: Header\r\nSome: Header\r\n\r\nBody",
18+
);
19+
20+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
21+
22+
/* Note: the warning is bogus in this case as no data actually gets lost,
23+
* but this checks that stream casting works */
24+
$handle = fopen($uri, 'r');
25+
var_dump(posix_ttyname($handle));
26+
var_dump(fread($handle, 20));
27+
fclose($handle);
28+
29+
http_server_kill($pid);
30+
?>
31+
--EXPECT--
32+
bool(false)
33+
string(4) "Body"
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
posix_ttyname(): uncastable user stream
3+
--EXTENSIONS--
4+
posix
5+
--FILE--
6+
<?php
7+
8+
require dirname(__DIR__, 3) . '/tests/output/DummyStreamWrapper.inc';
9+
stream_wrapper_register('custom', DummyStreamWrapper::class);
10+
11+
$fp = fopen("custom://myvar", "r+");
12+
var_dump(posix_ttyname($fp));
13+
fclose($fp);
14+
15+
echo "Done";
16+
?>
17+
--EXPECTF--
18+
Warning: posix_ttyname(): Could not use stream of type 'user-space' in %s on line %d
19+
bool(false)
20+
Done

ext/standard/streamsfuncs.c

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -74,14 +74,14 @@ PHP_FUNCTION(stream_socket_pair)
7474
close(pair[0]);
7575
close(pair[1]);
7676
php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
77-
RETURN_FALSE;
77+
RETURN_FALSE;
7878
}
7979
s2 = php_stream_sock_open_from_socket(pair[1], 0);
8080
if (s2 == NULL) {
8181
php_stream_free(s1, PHP_STREAM_FREE_CLOSE);
8282
close(pair[1]);
8383
php_error_docref(NULL, E_WARNING, "Failed to open stream from socketpair");
84-
RETURN_FALSE;
84+
RETURN_FALSE;
8585
}
8686

8787
array_init(return_value);
@@ -1645,10 +1645,14 @@ PHP_FUNCTION(stream_isatty)
16451645

16461646
php_stream_from_zval(stream, zsrc);
16471647

1648-
if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT) == SUCCESS) {
1649-
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT, (void*)&fileno, 0);
1650-
} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD) == SUCCESS) {
1651-
php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fileno, 0);
1648+
/* get the fd.
1649+
* NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag when casting.
1650+
* It is only used here so that the buffered data warning is not displayed.
1651+
*/
1652+
if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
1653+
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0);
1654+
} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
1655+
php_stream_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0);
16521656
} else {
16531657
RETURN_FALSE;
16541658
}
@@ -1686,13 +1690,15 @@ PHP_FUNCTION(sapi_windows_vt100_support)
16861690

16871691
php_stream_from_zval(stream, zsrc);
16881692

1689-
if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT) == SUCCESS) {
1690-
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT, (void*)&fileno, 0);
1691-
}
1692-
else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD) == SUCCESS) {
1693-
php_stream_cast(stream, PHP_STREAM_AS_FD, (void*)&fileno, 0);
1694-
}
1695-
else {
1693+
/* get the fd.
1694+
* NB: Most other code will NOT use the PHP_STREAM_CAST_INTERNAL flag when casting.
1695+
* It is only used here so that the buffered data warning is not displayed.
1696+
*/
1697+
if (php_stream_can_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
1698+
php_stream_cast(stream, PHP_STREAM_AS_FD_FOR_SELECT | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0);
1699+
} else if (php_stream_can_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL) == SUCCESS) {
1700+
php_stream_cast(stream, PHP_STREAM_AS_FD | PHP_STREAM_CAST_INTERNAL, (void*)&fileno, 0);
1701+
} else {
16961702
if (!enable_is_null) {
16971703
php_error_docref(
16981704
NULL,
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
--TEST--
2+
Casting a stream can lose data and needs to emit a warning
3+
--FILE--
4+
<?php
5+
6+
$tempnam = tempnam(sys_get_temp_dir(), 'test');
7+
8+
$stream = popen('echo 1; echo 2; rm ' . escapeshellarg($tempnam), 'r');
9+
10+
do {
11+
usleep(10);
12+
clearstatcache();
13+
} while (file_exists($tempnam));
14+
15+
// fills the read buffer with up to 8192 bytes
16+
fgets($stream);
17+
18+
// cast $stream and read fd until eof. Print each line that was read, prefixed with "proc open stdin:"
19+
$process = proc_open(
20+
'sed "s/^/proc open stdin:/"',
21+
[
22+
0 => $stream,
23+
1 => ['pipe', 'w'],
24+
],
25+
$pipes,
26+
);
27+
28+
var_dump(stream_get_contents($pipes[1]));
29+
30+
?>
31+
--EXPECTF--
32+
Warning: proc_open(): 2 bytes of buffered data lost during stream conversion! in %s on line %d
33+
string(0) ""

tests/output/DummyStreamWrapper.inc

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
class DummyStreamWrapper
4+
{
5+
/** @var resource|null */
6+
public $context;
7+
8+
/** @var resource|null */
9+
public $handle;
10+
11+
public function stream_cast(int $castAs)
12+
{
13+
return false;
14+
}
15+
16+
public function stream_close(): void { }
17+
18+
public function stream_open(string $path, string $mode, int $options = 0, ?string &$openedPath = null): bool
19+
{
20+
return true;
21+
}
22+
23+
public function stream_read(int $count)
24+
{
25+
return false;
26+
}
27+
28+
public function stream_seek(int $offset, int $whence = SEEK_SET): bool
29+
{
30+
var_dump('stream_seek!');
31+
return true;
32+
}
33+
34+
public function stream_set_option(int $option, int $arg1, ?int $arg2): bool
35+
{
36+
return false;
37+
}
38+
39+
public function stream_stat()
40+
{
41+
return false;
42+
}
43+
44+
public function stream_tell()
45+
{
46+
return 0;
47+
}
48+
49+
public function stream_truncate(int $newSize): bool
50+
{
51+
return true;
52+
}
53+
54+
public function stream_write(string $data) { }
55+
56+
57+
public function unlink(string $path): bool
58+
{
59+
return false;
60+
}
61+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
sapi_windows_vt100_support(): casting stream does not emit data loss and should not emit warnings
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY !== "Windows") {
6+
die("skip Only for Windows systems");
7+
}
8+
require __DIR__ . '/../../ext/standard/tests/http/server.inc'; http_server_skipif();
9+
?>
10+
--INI--
11+
allow_url_fopen=1
12+
--FILE--
13+
<?php
14+
15+
require __DIR__ . '/../../ext/standard/tests/http/server.inc';
16+
17+
$responses = array(
18+
"data://text/plain,HTTP/1.0 200 Ok\r\nSome: Header\r\nSome: Header\r\n\r\nBody",
19+
);
20+
21+
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
22+
23+
/* Note: the warning is bogus in this case as no data actually gets lost,
24+
* but this checks that stream casting works */
25+
$handle = fopen($uri, 'r');
26+
var_dump(sapi_windows_vt100_support($handle));
27+
var_dump(fread($handle, 20));
28+
fclose($handle);
29+
30+
http_server_kill($pid);
31+
?>
32+
--EXPECT--
33+
bool(false)
34+
string(4) "Body"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
--TEST--
2+
sapi_windows_vt100_support(): uncastable user stream
3+
--SKIPIF--
4+
<?php
5+
if (PHP_OS_FAMILY !== "Windows") {
6+
die("skip Only for Windows systems");
7+
}
8+
?>
9+
--FILE--
10+
<?php
11+
12+
require __DIR__ . '/DummyStreamWrapper.inc';
13+
stream_wrapper_register('custom', DummyStreamWrapper::class);
14+
15+
$fp = fopen("custom://myvar", "r+");
16+
try {
17+
var_dump(sapi_windows_vt100_support($fp));
18+
} catch (\TypeError $e) {
19+
echo $e->getMessage(), PHP_EOL;
20+
}
21+
fclose($fp);
22+
23+
echo "Done";
24+
?>
25+
--EXPECT--
26+
bool(false)
27+
Done

0 commit comments

Comments
 (0)