Skip to content

Commit f3d56e7

Browse files
committed
Handle non-seekable streams
1 parent ddacfb2 commit f3d56e7

File tree

2 files changed

+43
-15
lines changed

2 files changed

+43
-15
lines changed

ext/standard/http_fopen_wrapper.c

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,7 @@ static bool php_stream_unwrap_content(php_stream_context *context, zend_string *
362362
} else if (Z_TYPE_P(content) == IS_RESOURCE) {
363363
*stream_out = php_stream_from_zval_no_verify_no_error(content);
364364
if (*stream_out) {
365+
/* Note: at this point we don't know whether the stream is seekable, we can only know for sure by trying. */
365366
return true;
366367
} else {
367368
const char *space;
@@ -374,21 +375,29 @@ static bool php_stream_unwrap_content(php_stream_context *context, zend_string *
374375
return false;
375376
}
376377

377-
static bool php_stream_append_content_length(smart_str *req_buf, zend_string *content_str, php_stream *content_stream)
378+
static bool php_stream_append_content_length(smart_str *req_buf, zend_string **content_str, php_stream **content_stream, bool *content_str_tmp)
378379
{
379380
smart_str_appends(req_buf, "Content-Length: ");
380-
if (content_str) {
381-
smart_str_append_unsigned(req_buf, ZSTR_LEN(content_str));
381+
if (*content_str) {
382+
smart_str_append_unsigned(req_buf, ZSTR_LEN(*content_str));
382383
} else {
383-
zend_off_t current_position = php_stream_tell(content_stream);
384-
if (php_stream_seek(content_stream, 0, SEEK_END) < 0) {
385-
return false;
386-
}
387-
zend_off_t end_position = php_stream_tell(content_stream);
388-
if (php_stream_seek(content_stream, current_position, SEEK_SET) < 0) {
389-
return false;
384+
zend_off_t current_position = php_stream_tell(*content_stream);
385+
if (php_stream_seek(*content_stream, 0, SEEK_END) < 0) {
386+
*content_str = php_stream_copy_to_mem(*content_stream, PHP_STREAM_COPY_ALL, false);
387+
*content_stream = NULL;
388+
if (*content_str) {
389+
*content_str_tmp = true;
390+
smart_str_append_unsigned(req_buf, ZSTR_LEN(*content_str));
391+
} else {
392+
return false;
393+
}
394+
} else {
395+
zend_off_t end_position = php_stream_tell(*content_stream);
396+
if (php_stream_seek(*content_stream, current_position, SEEK_SET) < 0) {
397+
return false;
398+
}
399+
smart_str_append_unsigned(req_buf, end_position - current_position);
390400
}
391-
smart_str_append_unsigned(req_buf, end_position - current_position);
392401
}
393402
smart_str_appends(req_buf, "\r\n");
394403
return true;
@@ -876,6 +885,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
876885

877886
zend_string *content_str = NULL;
878887
php_stream *content_stream = NULL;
888+
bool content_str_tmp = false;
879889

880890
if (user_headers) {
881891
/* A bit weird, but some servers require that Content-Length be sent prior to Content-Type for POST
@@ -887,7 +897,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
887897
!(have_header & HTTP_HEADER_CONTENT_LENGTH) &&
888898
php_stream_unwrap_content(context, &content_str, &content_stream)
889899
) {
890-
if (!php_stream_append_content_length(&req_buf, content_str, content_stream)) {
900+
if (!php_stream_append_content_length(&req_buf, &content_str, &content_stream, &content_str_tmp)) {
891901
php_stream_close(stream);
892902
stream = NULL;
893903
efree(user_headers);
@@ -903,6 +913,9 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
903913

904914
/* php_stream_unwrap_content() may throw a TypeError for non-stream resources */
905915
if (UNEXPECTED(EG(exception))) {
916+
if (content_str_tmp) {
917+
zend_string_efree(content_str);
918+
}
906919
php_stream_close(stream);
907920
stream = NULL;
908921
goto out;
@@ -913,7 +926,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
913926
if ((header_init || redirect_keep_method) && context &&
914927
(content_str || content_stream || php_stream_unwrap_content(context, &content_str, &content_stream))) {
915928
if (!(have_header & HTTP_HEADER_CONTENT_LENGTH)) {
916-
if (!php_stream_append_content_length(&req_buf, content_str, content_stream)) {
929+
if (!php_stream_append_content_length(&req_buf, &content_str, &content_stream, &content_str_tmp)) {
917930
php_stream_close(stream);
918931
stream = NULL;
919932
php_stream_wrapper_log_error(wrapper, options, "Unable to determine length of \"content\" stream!");
@@ -928,6 +941,10 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
928941
if (content_str) {
929942
smart_str_append(&req_buf, content_str);
930943
php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
944+
945+
if (content_str_tmp) {
946+
zend_string_efree(content_str);
947+
}
931948
} else {
932949
php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
933950

ext/standard/tests/http/gh19249_custom_stream.phpt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,22 @@ server
88
<?php
99
class MyStream {
1010
public $context;
11+
private $counter = 0;
1112

1213
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool {
1314
return true;
1415
}
1516

1617
public function stream_read(int $count): string|false {
17-
return false;
18+
$this->counter++;
19+
if ($this->stream_eof()) {
20+
return false;
21+
}
22+
return "test";
23+
}
24+
25+
public function stream_eof(): bool {
26+
return $this->counter == 2;
1827
}
1928
}
2029

@@ -43,4 +52,6 @@ echo file_get_contents("http://" . PHP_CLI_SERVER_ADDRESS . "/", false, stream_c
4352
--EXPECTF--
4453
Warning: file_get_contents(): Stream does not support seeking in %s on line %d
4554

46-
Warning: file_get_contents(%s): Failed to open stream: Unable to determine length of "content" stream! in %s on line %d
55+
Warning: file_get_contents(): MyStream::stream_stat is not implemented! in %s on line %d
56+
string(1) "4"
57+
test

0 commit comments

Comments
 (0)