mirror of
https://github.com/php/php-src.git
synced 2025-08-15 21:48:51 +02:00
Fix GHSA-ghsa-v8xr-gpvj-cx9g: http header folding
This adds HTTP header folding support for HTTP wrapper response headers. Reviewed-by: Tim Düsterhus <tim@tideways-gmbh.com>
This commit is contained in:
parent
f209eb448e
commit
d20b4c97a9
7 changed files with 484 additions and 134 deletions
|
@ -115,6 +115,171 @@ static bool check_has_header(const char *headers, const char *header) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
typedef struct _php_stream_http_response_header_info {
|
||||||
|
php_stream_filter *transfer_encoding;
|
||||||
|
size_t file_size;
|
||||||
|
bool follow_location;
|
||||||
|
char location[HTTP_HEADER_BLOCK_SIZE];
|
||||||
|
} php_stream_http_response_header_info;
|
||||||
|
|
||||||
|
static void php_stream_http_response_header_info_init(
|
||||||
|
php_stream_http_response_header_info *header_info)
|
||||||
|
{
|
||||||
|
header_info->transfer_encoding = NULL;
|
||||||
|
header_info->file_size = 0;
|
||||||
|
header_info->follow_location = 1;
|
||||||
|
header_info->location[0] = '\0';
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Trim white spaces from response header line and update its length */
|
||||||
|
static bool php_stream_http_response_header_trim(char *http_header_line,
|
||||||
|
size_t *http_header_line_length)
|
||||||
|
{
|
||||||
|
char *http_header_line_end = http_header_line + *http_header_line_length - 1;
|
||||||
|
while (http_header_line_end >= http_header_line &&
|
||||||
|
(*http_header_line_end == '\n' || *http_header_line_end == '\r')) {
|
||||||
|
http_header_line_end--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* The primary definition of an HTTP header in RFC 7230 states:
|
||||||
|
* > Each header field consists of a case-insensitive field name followed
|
||||||
|
* > by a colon (":"), optional leading whitespace, the field value, and
|
||||||
|
* > optional trailing whitespace. */
|
||||||
|
|
||||||
|
/* Strip trailing whitespace */
|
||||||
|
bool space_trim = (*http_header_line_end == ' ' || *http_header_line_end == '\t');
|
||||||
|
if (space_trim) {
|
||||||
|
do {
|
||||||
|
http_header_line_end--;
|
||||||
|
} while (http_header_line_end >= http_header_line &&
|
||||||
|
(*http_header_line_end == ' ' || *http_header_line_end == '\t'));
|
||||||
|
}
|
||||||
|
http_header_line_end++;
|
||||||
|
*http_header_line_end = '\0';
|
||||||
|
*http_header_line_length = http_header_line_end - http_header_line;
|
||||||
|
|
||||||
|
return space_trim;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process folding headers of the current line and if there are none, parse last full response
|
||||||
|
* header line. It returns NULL if the last header is finished, otherwise it returns updated
|
||||||
|
* last header line. */
|
||||||
|
static zend_string *php_stream_http_response_headers_parse(php_stream *stream,
|
||||||
|
php_stream_context *context, int options, zend_string *last_header_line_str,
|
||||||
|
char *header_line, size_t *header_line_length, int response_code,
|
||||||
|
zval *response_header, php_stream_http_response_header_info *header_info)
|
||||||
|
{
|
||||||
|
char *last_header_line = ZSTR_VAL(last_header_line_str);
|
||||||
|
size_t last_header_line_length = ZSTR_LEN(last_header_line_str);
|
||||||
|
char *last_header_line_end = ZSTR_VAL(last_header_line_str) + ZSTR_LEN(last_header_line_str) - 1;
|
||||||
|
|
||||||
|
/* Process non empty header line. */
|
||||||
|
if (header_line && (*header_line != '\n' && *header_line != '\r')) {
|
||||||
|
/* Removing trailing white spaces. */
|
||||||
|
if (php_stream_http_response_header_trim(header_line, header_line_length) &&
|
||||||
|
*header_line_length == 0) {
|
||||||
|
/* Only spaces so treat as an empty folding header. */
|
||||||
|
return last_header_line_str;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Process folding headers if starting with a space or a tab. */
|
||||||
|
if (header_line && (*header_line == ' ' || *header_line == '\t')) {
|
||||||
|
char *http_folded_header_line = header_line;
|
||||||
|
size_t http_folded_header_line_length = *header_line_length;
|
||||||
|
/* Remove the leading white spaces. */
|
||||||
|
while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') {
|
||||||
|
http_folded_header_line++;
|
||||||
|
http_folded_header_line_length--;
|
||||||
|
}
|
||||||
|
/* It has to have some characters because it would get returned after the call
|
||||||
|
* php_stream_http_response_header_trim above. */
|
||||||
|
ZEND_ASSERT(http_folded_header_line_length > 0);
|
||||||
|
/* Concatenate last header line, space and current header line. */
|
||||||
|
zend_string *extended_header_str = zend_string_concat3(
|
||||||
|
last_header_line, last_header_line_length,
|
||||||
|
" ", 1,
|
||||||
|
http_folded_header_line, http_folded_header_line_length);
|
||||||
|
zend_string_efree(last_header_line_str);
|
||||||
|
last_header_line_str = extended_header_str;
|
||||||
|
/* Return new header line. */
|
||||||
|
return last_header_line_str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Find header separator position. */
|
||||||
|
char *last_header_value = memchr(last_header_line, ':', last_header_line_length);
|
||||||
|
if (last_header_value) {
|
||||||
|
last_header_value++; /* Skip ':'. */
|
||||||
|
|
||||||
|
/* Strip leading whitespace. */
|
||||||
|
while (last_header_value < last_header_line_end
|
||||||
|
&& (*last_header_value == ' ' || *last_header_value == '\t')) {
|
||||||
|
last_header_value++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
/* There is no colon. Set the value to the end of the header line, which is effectively
|
||||||
|
* an empty string. */
|
||||||
|
last_header_value = last_header_line_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool store_header = true;
|
||||||
|
zval *tmpzval = NULL;
|
||||||
|
|
||||||
|
if (!strncasecmp(last_header_line, "Location:", sizeof("Location:")-1)) {
|
||||||
|
/* Check if the location should be followed. */
|
||||||
|
if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
|
||||||
|
header_info->follow_location = zval_is_true(tmpzval);
|
||||||
|
} else if (!((response_code >= 300 && response_code < 304)
|
||||||
|
|| 307 == response_code || 308 == response_code)) {
|
||||||
|
/* The redirection should not be automatic if follow_location is not set and
|
||||||
|
* response_code not in (300, 301, 302, 303 and 307)
|
||||||
|
* see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
|
||||||
|
* RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
|
||||||
|
header_info->follow_location = 0;
|
||||||
|
}
|
||||||
|
strlcpy(header_info->location, last_header_value, sizeof(header_info->location));
|
||||||
|
} else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
|
||||||
|
php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0);
|
||||||
|
} else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
|
||||||
|
header_info->file_size = atoi(last_header_value);
|
||||||
|
php_stream_notify_file_size(context, header_info->file_size, last_header_line, 0);
|
||||||
|
} else if (
|
||||||
|
!strncasecmp(last_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
|
||||||
|
&& !strncasecmp(last_header_value, "Chunked", sizeof("Chunked")-1)
|
||||||
|
) {
|
||||||
|
/* Create filter to decode response body. */
|
||||||
|
if (!(options & STREAM_ONLY_GET_HEADERS)) {
|
||||||
|
zend_long decode = 1;
|
||||||
|
|
||||||
|
if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
|
||||||
|
decode = zend_is_true(tmpzval);
|
||||||
|
}
|
||||||
|
if (decode) {
|
||||||
|
if (header_info->transfer_encoding != NULL) {
|
||||||
|
/* Prevent a memory leak in case there are more transfer-encoding headers. */
|
||||||
|
php_stream_filter_free(header_info->transfer_encoding);
|
||||||
|
}
|
||||||
|
header_info->transfer_encoding = php_stream_filter_create(
|
||||||
|
"dechunk", NULL, php_stream_is_persistent(stream));
|
||||||
|
if (header_info->transfer_encoding != NULL) {
|
||||||
|
/* Do not store transfer-encoding header. */
|
||||||
|
store_header = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (store_header) {
|
||||||
|
zval http_header;
|
||||||
|
ZVAL_NEW_STR(&http_header, last_header_line_str);
|
||||||
|
zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
|
||||||
|
} else {
|
||||||
|
zend_string_efree(last_header_line_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
|
static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
|
||||||
const char *path, const char *mode, int options, zend_string **opened_path,
|
const char *path, const char *mode, int options, zend_string **opened_path,
|
||||||
php_stream_context *context, int redirect_max, int flags,
|
php_stream_context *context, int redirect_max, int flags,
|
||||||
|
@ -127,11 +292,12 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
|
||||||
zend_string *tmp = NULL;
|
zend_string *tmp = NULL;
|
||||||
char *ua_str = NULL;
|
char *ua_str = NULL;
|
||||||
zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name;
|
zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name;
|
||||||
char location[HTTP_HEADER_BLOCK_SIZE];
|
|
||||||
int reqok = 0;
|
int reqok = 0;
|
||||||
char *http_header_line = NULL;
|
char *http_header_line = NULL;
|
||||||
|
zend_string *last_header_line_str = NULL;
|
||||||
|
php_stream_http_response_header_info header_info;
|
||||||
char tmp_line[128];
|
char tmp_line[128];
|
||||||
size_t chunk_size = 0, file_size = 0;
|
size_t chunk_size = 0;
|
||||||
int eol_detect = 0;
|
int eol_detect = 0;
|
||||||
zend_string *transport_string;
|
zend_string *transport_string;
|
||||||
zend_string *errstr = NULL;
|
zend_string *errstr = NULL;
|
||||||
|
@ -142,8 +308,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper,
|
||||||
int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
|
int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0);
|
||||||
int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
|
int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0);
|
||||||
int redirect_keep_method = ((flags & HTTP_WRAPPER_KEEP_METHOD) != 0);
|
int redirect_keep_method = ((flags & HTTP_WRAPPER_KEEP_METHOD) != 0);
|
||||||
bool follow_location = 1;
|
|
||||||
php_stream_filter *transfer_encoding = NULL;
|
|
||||||
int response_code;
|
int response_code;
|
||||||
smart_str req_buf = {0};
|
smart_str req_buf = {0};
|
||||||
bool custom_request_method;
|
bool custom_request_method;
|
||||||
|
@ -653,8 +817,6 @@ finish:
|
||||||
/* send it */
|
/* send it */
|
||||||
php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
|
php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s));
|
||||||
|
|
||||||
location[0] = '\0';
|
|
||||||
|
|
||||||
if (Z_ISUNDEF_P(response_header)) {
|
if (Z_ISUNDEF_P(response_header)) {
|
||||||
array_init(response_header);
|
array_init(response_header);
|
||||||
}
|
}
|
||||||
|
@ -736,130 +898,101 @@ finish:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* read past HTTP headers */
|
php_stream_http_response_header_info_init(&header_info);
|
||||||
|
|
||||||
|
/* read past HTTP headers */
|
||||||
while (!php_stream_eof(stream)) {
|
while (!php_stream_eof(stream)) {
|
||||||
size_t http_header_line_length;
|
size_t http_header_line_length;
|
||||||
|
|
||||||
if (http_header_line != NULL) {
|
if (http_header_line != NULL) {
|
||||||
efree(http_header_line);
|
efree(http_header_line);
|
||||||
}
|
}
|
||||||
if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length)) && *http_header_line != '\n' && *http_header_line != '\r') {
|
if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length))) {
|
||||||
char *e = http_header_line + http_header_line_length - 1;
|
bool last_line;
|
||||||
char *http_header_value;
|
if (*http_header_line == '\r') {
|
||||||
|
if (http_header_line[1] != '\n') {
|
||||||
while (e >= http_header_line && (*e == '\n' || *e == '\r')) {
|
php_stream_close(stream);
|
||||||
e--;
|
stream = NULL;
|
||||||
}
|
php_stream_wrapper_log_error(wrapper, options,
|
||||||
|
"HTTP invalid header name (cannot start with CR character)!");
|
||||||
/* The primary definition of an HTTP header in RFC 7230 states:
|
goto out;
|
||||||
* > Each header field consists of a case-insensitive field name followed
|
|
||||||
* > by a colon (":"), optional leading whitespace, the field value, and
|
|
||||||
* > optional trailing whitespace. */
|
|
||||||
|
|
||||||
/* Strip trailing whitespace */
|
|
||||||
while (e >= http_header_line && (*e == ' ' || *e == '\t')) {
|
|
||||||
e--;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Terminate header line */
|
|
||||||
e++;
|
|
||||||
*e = '\0';
|
|
||||||
http_header_line_length = e - http_header_line;
|
|
||||||
|
|
||||||
http_header_value = memchr(http_header_line, ':', http_header_line_length);
|
|
||||||
if (http_header_value) {
|
|
||||||
http_header_value++; /* Skip ':' */
|
|
||||||
|
|
||||||
/* Strip leading whitespace */
|
|
||||||
while (http_header_value < e
|
|
||||||
&& (*http_header_value == ' ' || *http_header_value == '\t')) {
|
|
||||||
http_header_value++;
|
|
||||||
}
|
}
|
||||||
|
last_line = true;
|
||||||
|
} else if (*http_header_line == '\n') {
|
||||||
|
last_line = true;
|
||||||
} else {
|
} else {
|
||||||
/* There is no colon. Set the value to the end of the header line, which is
|
last_line = false;
|
||||||
* effectively an empty string. */
|
|
||||||
http_header_value = e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) {
|
if (last_header_line_str != NULL) {
|
||||||
if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) {
|
/* Parse last header line. */
|
||||||
follow_location = zval_is_true(tmpzval);
|
last_header_line_str = php_stream_http_response_headers_parse(stream, context,
|
||||||
} else if (!((response_code >= 300 && response_code < 304)
|
options, last_header_line_str, http_header_line, &http_header_line_length,
|
||||||
|| 307 == response_code || 308 == response_code)) {
|
response_code, response_header, &header_info);
|
||||||
/* we shouldn't redirect automatically
|
if (last_header_line_str != NULL) {
|
||||||
if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307)
|
/* Folding header present so continue. */
|
||||||
see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1
|
continue;
|
||||||
RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */
|
|
||||||
follow_location = 0;
|
|
||||||
}
|
}
|
||||||
strlcpy(location, http_header_value, sizeof(location));
|
} else if (!last_line) {
|
||||||
} else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) {
|
/* The first line cannot start with spaces. */
|
||||||
php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0);
|
if (*http_header_line == ' ' || *http_header_line == '\t') {
|
||||||
} else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) {
|
php_stream_close(stream);
|
||||||
file_size = atoi(http_header_value);
|
stream = NULL;
|
||||||
php_stream_notify_file_size(context, file_size, http_header_line, 0);
|
php_stream_wrapper_log_error(wrapper, options,
|
||||||
} else if (
|
"HTTP invalid response format (folding header at the start)!");
|
||||||
!strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1)
|
goto out;
|
||||||
&& !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1)
|
|
||||||
) {
|
|
||||||
|
|
||||||
/* create filter to decode response body */
|
|
||||||
if (!(options & STREAM_ONLY_GET_HEADERS)) {
|
|
||||||
zend_long decode = 1;
|
|
||||||
|
|
||||||
if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) {
|
|
||||||
decode = zend_is_true(tmpzval);
|
|
||||||
}
|
|
||||||
if (decode) {
|
|
||||||
transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream));
|
|
||||||
if (transfer_encoding) {
|
|
||||||
/* don't store transfer-encodeing header */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
/* Trim the first line if it is not the last line. */
|
||||||
|
php_stream_http_response_header_trim(http_header_line, &http_header_line_length);
|
||||||
}
|
}
|
||||||
|
if (last_line) {
|
||||||
{
|
/* For the last line the last header line must be NULL. */
|
||||||
zval http_header;
|
ZEND_ASSERT(last_header_line_str == NULL);
|
||||||
ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length);
|
break;
|
||||||
zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header);
|
|
||||||
}
|
}
|
||||||
|
/* Save current line as the last line so it gets parsed in the next round. */
|
||||||
|
last_header_line_str = zend_string_init(http_header_line, http_header_line_length, 0);
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!reqok || (location[0] != '\0' && follow_location)) {
|
/* If the stream was closed early, we still want to process the last line to keep BC. */
|
||||||
if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
|
if (last_header_line_str != NULL) {
|
||||||
|
php_stream_http_response_headers_parse(stream, context, options, last_header_line_str,
|
||||||
|
NULL, NULL, response_code, response_header, &header_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!reqok || (header_info.location[0] != '\0' && header_info.follow_location)) {
|
||||||
|
if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) {
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location[0] != '\0')
|
if (header_info.location[0] != '\0')
|
||||||
php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0);
|
php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0);
|
||||||
|
|
||||||
php_stream_close(stream);
|
php_stream_close(stream);
|
||||||
stream = NULL;
|
stream = NULL;
|
||||||
|
|
||||||
if (transfer_encoding) {
|
if (header_info.transfer_encoding) {
|
||||||
php_stream_filter_free(transfer_encoding);
|
php_stream_filter_free(header_info.transfer_encoding);
|
||||||
transfer_encoding = NULL;
|
header_info.transfer_encoding = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location[0] != '\0') {
|
if (header_info.location[0] != '\0') {
|
||||||
|
|
||||||
char new_path[HTTP_HEADER_BLOCK_SIZE];
|
char new_path[HTTP_HEADER_BLOCK_SIZE];
|
||||||
char loc_path[HTTP_HEADER_BLOCK_SIZE];
|
char loc_path[HTTP_HEADER_BLOCK_SIZE];
|
||||||
|
|
||||||
*new_path='\0';
|
*new_path='\0';
|
||||||
if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) &&
|
if (strlen(header_info.location) < 8 ||
|
||||||
strncasecmp(location, "https://", sizeof("https://")-1) &&
|
(strncasecmp(header_info.location, "http://", sizeof("http://")-1) &&
|
||||||
strncasecmp(location, "ftp://", sizeof("ftp://")-1) &&
|
strncasecmp(header_info.location, "https://", sizeof("https://")-1) &&
|
||||||
strncasecmp(location, "ftps://", sizeof("ftps://")-1)))
|
strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) &&
|
||||||
|
strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1)))
|
||||||
{
|
{
|
||||||
if (*location != '/') {
|
if (*header_info.location != '/') {
|
||||||
if (*(location+1) != '\0' && resource->path) {
|
if (*(header_info.location+1) != '\0' && resource->path) {
|
||||||
char *s = strrchr(ZSTR_VAL(resource->path), '/');
|
char *s = strrchr(ZSTR_VAL(resource->path), '/');
|
||||||
if (!s) {
|
if (!s) {
|
||||||
s = ZSTR_VAL(resource->path);
|
s = ZSTR_VAL(resource->path);
|
||||||
|
@ -875,15 +1008,17 @@ finish:
|
||||||
if (resource->path &&
|
if (resource->path &&
|
||||||
ZSTR_VAL(resource->path)[0] == '/' &&
|
ZSTR_VAL(resource->path)[0] == '/' &&
|
||||||
ZSTR_VAL(resource->path)[1] == '\0') {
|
ZSTR_VAL(resource->path)[1] == '\0') {
|
||||||
snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", ZSTR_VAL(resource->path), location);
|
snprintf(loc_path, sizeof(loc_path) - 1, "%s%s",
|
||||||
|
ZSTR_VAL(resource->path), header_info.location);
|
||||||
} else {
|
} else {
|
||||||
snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", ZSTR_VAL(resource->path), location);
|
snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s",
|
||||||
|
ZSTR_VAL(resource->path), header_info.location);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location);
|
snprintf(loc_path, sizeof(loc_path) - 1, "/%s", header_info.location);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strlcpy(loc_path, location, sizeof(loc_path));
|
strlcpy(loc_path, header_info.location, sizeof(loc_path));
|
||||||
}
|
}
|
||||||
if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
|
if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) {
|
||||||
snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
|
snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path);
|
||||||
|
@ -891,7 +1026,7 @@ finish:
|
||||||
snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
|
snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
strlcpy(new_path, location, sizeof(new_path));
|
strlcpy(new_path, header_info.location, sizeof(new_path));
|
||||||
}
|
}
|
||||||
|
|
||||||
php_url_free(resource);
|
php_url_free(resource);
|
||||||
|
@ -951,7 +1086,7 @@ out:
|
||||||
if (header_init) {
|
if (header_init) {
|
||||||
ZVAL_COPY(&stream->wrapperdata, response_header);
|
ZVAL_COPY(&stream->wrapperdata, response_header);
|
||||||
}
|
}
|
||||||
php_stream_notify_progress_init(context, 0, file_size);
|
php_stream_notify_progress_init(context, 0, header_info.file_size);
|
||||||
|
|
||||||
/* Restore original chunk size now that we're done with headers */
|
/* Restore original chunk size now that we're done with headers */
|
||||||
if (options & STREAM_WILL_CAST)
|
if (options & STREAM_WILL_CAST)
|
||||||
|
@ -967,8 +1102,8 @@ out:
|
||||||
/* restore mode */
|
/* restore mode */
|
||||||
strlcpy(stream->mode, mode, sizeof(stream->mode));
|
strlcpy(stream->mode, mode, sizeof(stream->mode));
|
||||||
|
|
||||||
if (transfer_encoding) {
|
if (header_info.transfer_encoding) {
|
||||||
php_stream_filter_append(&stream->readfilters, transfer_encoding);
|
php_stream_filter_append(&stream->readfilters, header_info.transfer_encoding);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* It's possible that the server already sent in more data than just the headers.
|
/* It's possible that the server already sent in more data than just the headers.
|
||||||
|
|
49
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
Normal file
49
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
--TEST--
|
||||||
|
GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (single)
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
$serverCode = <<<'CODE'
|
||||||
|
$ctxt = stream_context_create([
|
||||||
|
"socket" => [
|
||||||
|
"tcp_nodelay" => true
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = stream_socket_server(
|
||||||
|
"tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
|
||||||
|
phpt_notify_server_start($server);
|
||||||
|
|
||||||
|
$conn = stream_socket_accept($server);
|
||||||
|
|
||||||
|
phpt_notify(message:"server-accepted");
|
||||||
|
|
||||||
|
fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n charset=utf-8\r\n\r\nbody\r\n");
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
$clientCode = <<<'CODE'
|
||||||
|
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
|
||||||
|
switch($notification_code) {
|
||||||
|
case STREAM_NOTIFY_MIME_TYPE_IS:
|
||||||
|
echo "Found the mime-type: ", $message, PHP_EOL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ctx = stream_context_create();
|
||||||
|
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
|
||||||
|
var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
|
||||||
|
var_dump($http_response_header);
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
|
||||||
|
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
Found the mime-type: text/html; charset=utf-8
|
||||||
|
string(4) "body"
|
||||||
|
array(2) {
|
||||||
|
[0]=>
|
||||||
|
string(15) "HTTP/1.0 200 Ok"
|
||||||
|
[1]=>
|
||||||
|
string(38) "Content-Type: text/html; charset=utf-8"
|
||||||
|
}
|
51
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
Normal file
51
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
--TEST--
|
||||||
|
GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (multiple)
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
$serverCode = <<<'CODE'
|
||||||
|
$ctxt = stream_context_create([
|
||||||
|
"socket" => [
|
||||||
|
"tcp_nodelay" => true
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = stream_socket_server(
|
||||||
|
"tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
|
||||||
|
phpt_notify_server_start($server);
|
||||||
|
|
||||||
|
$conn = stream_socket_accept($server);
|
||||||
|
|
||||||
|
phpt_notify(message:"server-accepted");
|
||||||
|
|
||||||
|
fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\nCustom-Header: somevalue;\r\n param1=value1; \r\n param2=value2\r\n\r\nbody\r\n");
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
$clientCode = <<<'CODE'
|
||||||
|
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
|
||||||
|
switch($notification_code) {
|
||||||
|
case STREAM_NOTIFY_MIME_TYPE_IS:
|
||||||
|
echo "Found the mime-type: ", $message, PHP_EOL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ctx = stream_context_create();
|
||||||
|
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
|
||||||
|
var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
|
||||||
|
var_dump($http_response_header);
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
|
||||||
|
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
Found the mime-type: text/html;
|
||||||
|
string(4) "body"
|
||||||
|
array(3) {
|
||||||
|
[0]=>
|
||||||
|
string(15) "HTTP/1.0 200 Ok"
|
||||||
|
[1]=>
|
||||||
|
string(24) "Content-Type: text/html;"
|
||||||
|
[2]=>
|
||||||
|
string(54) "Custom-Header: somevalue; param1=value1; param2=value2"
|
||||||
|
}
|
49
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
Normal file
49
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
--TEST--
|
||||||
|
GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (empty)
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
$serverCode = <<<'CODE'
|
||||||
|
$ctxt = stream_context_create([
|
||||||
|
"socket" => [
|
||||||
|
"tcp_nodelay" => true
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = stream_socket_server(
|
||||||
|
"tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
|
||||||
|
phpt_notify_server_start($server);
|
||||||
|
|
||||||
|
$conn = stream_socket_accept($server);
|
||||||
|
|
||||||
|
phpt_notify(message:"server-accepted");
|
||||||
|
|
||||||
|
fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
$clientCode = <<<'CODE'
|
||||||
|
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
|
||||||
|
switch($notification_code) {
|
||||||
|
case STREAM_NOTIFY_MIME_TYPE_IS:
|
||||||
|
echo "Found the mime-type: ", $message, PHP_EOL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ctx = stream_context_create();
|
||||||
|
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
|
||||||
|
var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx)));
|
||||||
|
var_dump($http_response_header);
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
|
||||||
|
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
Found the mime-type: text/html; charset=utf-8
|
||||||
|
string(4) "body"
|
||||||
|
array(2) {
|
||||||
|
[0]=>
|
||||||
|
string(15) "HTTP/1.0 200 Ok"
|
||||||
|
[1]=>
|
||||||
|
string(38) "Content-Type: text/html; charset=utf-8"
|
||||||
|
}
|
48
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
Normal file
48
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
--TEST--
|
||||||
|
GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (first line)
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
$serverCode = <<<'CODE'
|
||||||
|
$ctxt = stream_context_create([
|
||||||
|
"socket" => [
|
||||||
|
"tcp_nodelay" => true
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = stream_socket_server(
|
||||||
|
"tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
|
||||||
|
phpt_notify_server_start($server);
|
||||||
|
|
||||||
|
$conn = stream_socket_accept($server);
|
||||||
|
|
||||||
|
phpt_notify(message:"server-accepted");
|
||||||
|
|
||||||
|
fwrite($conn, "HTTP/1.0 200 Ok\r\n Content-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n");
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
$clientCode = <<<'CODE'
|
||||||
|
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
|
||||||
|
switch($notification_code) {
|
||||||
|
case STREAM_NOTIFY_MIME_TYPE_IS:
|
||||||
|
echo "Found the mime-type: ", $message, PHP_EOL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ctx = stream_context_create();
|
||||||
|
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
|
||||||
|
var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
|
||||||
|
var_dump($http_response_header);
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
|
||||||
|
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
|
||||||
|
Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (folding header at the start)! in %s
|
||||||
|
bool(false)
|
||||||
|
array(1) {
|
||||||
|
[0]=>
|
||||||
|
string(15) "HTTP/1.0 200 Ok"
|
||||||
|
}
|
48
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
Normal file
48
ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
--TEST--
|
||||||
|
GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (CR before header name)
|
||||||
|
--FILE--
|
||||||
|
<?php
|
||||||
|
$serverCode = <<<'CODE'
|
||||||
|
$ctxt = stream_context_create([
|
||||||
|
"socket" => [
|
||||||
|
"tcp_nodelay" => true
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$server = stream_socket_server(
|
||||||
|
"tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt);
|
||||||
|
phpt_notify_server_start($server);
|
||||||
|
|
||||||
|
$conn = stream_socket_accept($server);
|
||||||
|
|
||||||
|
phpt_notify(message:"server-accepted");
|
||||||
|
|
||||||
|
fwrite($conn, "HTTP/1.0 200 Ok\r\n\rIgnored: ignored\r\n\r\nbody\r\n");
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
$clientCode = <<<'CODE'
|
||||||
|
function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) {
|
||||||
|
switch($notification_code) {
|
||||||
|
case STREAM_NOTIFY_MIME_TYPE_IS:
|
||||||
|
echo "Found the mime-type: ", $message, PHP_EOL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$ctx = stream_context_create();
|
||||||
|
stream_context_set_params($ctx, array("notification" => "stream_notification_callback"));
|
||||||
|
var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx));
|
||||||
|
var_dump($http_response_header);
|
||||||
|
CODE;
|
||||||
|
|
||||||
|
include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__);
|
||||||
|
ServerClientTestCase::getInstance()->run($clientCode, $serverCode);
|
||||||
|
?>
|
||||||
|
--EXPECTF--
|
||||||
|
|
||||||
|
Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid header name (cannot start with CR character)! in %s
|
||||||
|
bool(false)
|
||||||
|
array(1) {
|
||||||
|
[0]=>
|
||||||
|
string(15) "HTTP/1.0 200 Ok"
|
||||||
|
}
|
|
@ -1,30 +0,0 @@
|
||||||
--TEST--
|
|
||||||
$http_reponse_header (whitespace-only "header")
|
|
||||||
--SKIPIF--
|
|
||||||
<?php require 'server.inc'; http_server_skipif(); ?>
|
|
||||||
--INI--
|
|
||||||
allow_url_fopen=1
|
|
||||||
--FILE--
|
|
||||||
<?php
|
|
||||||
require 'server.inc';
|
|
||||||
|
|
||||||
$responses = array(
|
|
||||||
"data://text/plain,HTTP/1.0 200 Ok\r\n \r\n\r\nBody",
|
|
||||||
);
|
|
||||||
|
|
||||||
['pid' => $pid, 'uri' => $uri] = http_server($responses, $output);
|
|
||||||
|
|
||||||
$f = file_get_contents($uri);
|
|
||||||
var_dump($f);
|
|
||||||
var_dump($http_response_header);
|
|
||||||
|
|
||||||
http_server_kill($pid);
|
|
||||||
|
|
||||||
--EXPECT--
|
|
||||||
string(4) "Body"
|
|
||||||
array(2) {
|
|
||||||
[0]=>
|
|
||||||
string(15) "HTTP/1.0 200 Ok"
|
|
||||||
[1]=>
|
|
||||||
string(0) ""
|
|
||||||
}
|
|
Loading…
Add table
Add a link
Reference in a new issue