summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ext/standard/tests/file/bug53241.phpt18
-rwxr-xr-xmain/php_streams.h6
-rw-r--r--main/streams/cast.c50
-rw-r--r--main/streams/plain_wrapper.c4
4 files changed, 76 insertions, 2 deletions
diff --git a/ext/standard/tests/file/bug53241.phpt b/ext/standard/tests/file/bug53241.phpt
new file mode 100644
index 0000000000..e302e88140
--- /dev/null
+++ b/ext/standard/tests/file/bug53241.phpt
@@ -0,0 +1,18 @@
+--TEST--
+Bug #53241 (stream casting that relies on fdopen/fopencookie fails with 'xb' mode)
+--SKIPIF--
+<?php
+/* unfortunately no standard function does a cast to FILE*, so we need
+ * curl to test this */
+if (!extension_loaded("curl")) exit("skip curl extension not loaded");
+--FILE--
+<?php
+$fn = __DIR__ . "/test.tmp";
+@unlink($fn);
+$fh = fopen($fn, 'xb');
+$ch = curl_init('http://www.yahoo.com/');
+var_dump(curl_setopt($ch, CURLOPT_FILE, $fh));
+echo "Done.\n";
+--EXPECT--
+bool(true)
+Done.
diff --git a/main/php_streams.h b/main/php_streams.h
index 99a75473f4..2ac8151b53 100755
--- a/main/php_streams.h
+++ b/main/php_streams.h
@@ -462,6 +462,12 @@ END_EXTERN_C()
#define PHP_STREAM_CAST_MASK (PHP_STREAM_CAST_TRY_HARD | PHP_STREAM_CAST_RELEASE | PHP_STREAM_CAST_INTERNAL)
BEGIN_EXTERN_C()
PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err TSRMLS_DC);
+/* This functions transforms the first char to 'w' if it's not 'r', 'a' or 'w'
+ * and strips any subsequent chars except '+' and 'b'.
+ * Use this to sanitize stream->mode if you call e.g. fdopen, fopencookie or
+ * any other function that expects standard modes and you allow non-standard
+ * ones. result should be a char[5]. */
+PHPAPI void php_stream_rep_nonstand_mode(php_stream *stream, char *result);
END_EXTERN_C()
/* use this to check if a stream can be cast into another form */
#define php_stream_can_cast(stream, as) _php_stream_cast((stream), (as), NULL, 0 TSRMLS_CC)
diff --git a/main/streams/cast.c b/main/streams/cast.c
index e196a005e2..89bfa0ab46 100644
--- a/main/streams/cast.c
+++ b/main/streams/cast.c
@@ -144,6 +144,50 @@ static COOKIE_IO_FUNCTIONS_T stream_cookie_functions =
#endif
/* }}} */
+/* {{{ php_stream_rep_nonstand_mode
+ * Result should have at least size 5, e.g. to write wbx+\0 */
+PHPAPI void php_stream_rep_nonstand_mode(php_stream *stream, char *result)
+{
+ /* replace modes not supported by fdopen and fopencookie, but supported
+ * by PHP's fread(), so that their calls won't fail */
+ const char *cur_mode = stream->mode;
+ int has_plus = 0,
+ has_bin = 0,
+ i,
+ res_curs = 0;
+
+ if (cur_mode[0] == 'r' || cur_mode[0] == 'w' || cur_mode[0] == 'a') {
+ result[res_curs++] = cur_mode[0];
+ } else {
+ /* assume cur_mode[0] is 'c' or 'x'; substitute by 'w', which should not
+ * truncate anything in fdopen/fopencookie */
+ result[res_curs++] = 'w';
+
+ /* x is allowed (at least by glibc & compat), but not as the 1st mode
+ * as in PHP and in any case is (at best) ignored by fdopen and fopencookie */
+ }
+
+ /* assume current mode has at most length 4 (e.g. wbn+) */
+ for (i = 1; i < 4 && cur_mode[i] != '\0'; i++) {
+ if (cur_mode[i] == 'b') {
+ has_bin = 1;
+ } else if (cur_mode[i] == '+') {
+ has_plus = 1;
+ }
+ /* ignore 'n', 't' or other stuff */
+ }
+
+ if (has_bin) {
+ result[res_curs++] = 'b';
+ }
+ if (has_plus) {
+ result[res_curs++] = '+';
+ }
+
+ result[res_curs] = '\0';
+}
+/* }}} */
+
/* {{{ php_stream_cast */
PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show_err TSRMLS_DC)
{
@@ -187,7 +231,11 @@ PHPAPI int _php_stream_cast(php_stream *stream, int castas, void **ret, int show
goto exit_success;
}
- *(FILE**)ret = fopencookie(stream, stream->mode, PHP_STREAM_COOKIE_FUNCTIONS);
+ {
+ char fixed_mode[5];
+ php_stream_rep_nonstand_mode(stream, fixed_mode);
+ *(FILE**)ret = fopencookie(stream, fixed_mode, PHP_STREAM_COOKIE_FUNCTIONS);
+ }
if (*ret != NULL) {
off_t pos;
diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c
index 2fc0e8989a..bc5137c01f 100644
--- a/main/streams/plain_wrapper.c
+++ b/main/streams/plain_wrapper.c
@@ -490,7 +490,9 @@ static int php_stdiop_cast(php_stream *stream, int castas, void **ret TSRMLS_DC)
if (data->file == NULL) {
/* we were opened as a plain file descriptor, so we
* need fdopen now */
- data->file = fdopen(data->fd, stream->mode);
+ char fixed_mode[5];
+ php_stream_rep_nonstand_mode(stream, fixed_mode);
+ data->file = fdopen(data->fd, fixed_mode);
if (data->file == NULL) {
return FAILURE;
}