|
|
|
@ -16,6 +16,7 @@ |
|
|
|
| Jim Winstead <jimw@php.net> | |
|
|
|
| Hartmut Holzgraefe <hholzgra@php.net> | |
|
|
|
| Wez Furlong <wez@thebrainroom.com> | |
|
|
|
| Sara Golemon <pollita@php.net> | |
|
|
|
+----------------------------------------------------------------------+ |
|
|
|
*/ |
|
|
|
/* $Id$ */ |
|
|
|
@ -85,9 +86,182 @@ |
|
|
|
#define HTTP_HEADER_HOST 2 |
|
|
|
#define HTTP_HEADER_AUTH 4 |
|
|
|
#define HTTP_HEADER_FROM 8 |
|
|
|
#define HTTP_HEADER_CONTENT_LENGTH 16 |
|
|
|
#define HTTP_HEADER_CONTENT_LENGTH 16 |
|
|
|
#define HTTP_HEADER_TYPE 32 |
|
|
|
|
|
|
|
/* 8 hexits plus \r\n\0 */ |
|
|
|
#define HTTP_CHUNK_SIZE_MAXLEN 11 |
|
|
|
#define HTTP_CHUNKED_ENCODING_BUFFER_LEN (HTTP_CHUNK_SIZE_MAXLEN + 1) |
|
|
|
|
|
|
|
typedef struct _php_http_chunked_encoding_data { |
|
|
|
int is_persistent; |
|
|
|
size_t chunk_remaining; |
|
|
|
char chunksize_buffer[HTTP_CHUNKED_ENCODING_BUFFER_LEN]; |
|
|
|
char *chunksize_buffer_pos; |
|
|
|
} php_http_chunked_encoding_data; |
|
|
|
|
|
|
|
static php_stream_filter_status_t php_http_chunked_encoding_filter( |
|
|
|
php_stream *stream, |
|
|
|
php_stream_filter *thisfilter, |
|
|
|
php_stream_bucket_brigade *buckets_in, |
|
|
|
php_stream_bucket_brigade *buckets_out, |
|
|
|
size_t *bytes_consumed, |
|
|
|
int flags |
|
|
|
TSRMLS_DC) |
|
|
|
{ |
|
|
|
php_stream_bucket *bucket; |
|
|
|
php_http_chunked_encoding_data *data = (php_http_chunked_encoding_data*)thisfilter->abstract; |
|
|
|
size_t consumed = 0; |
|
|
|
char *buf; |
|
|
|
size_t buflen; |
|
|
|
|
|
|
|
while (buckets_in->head) { |
|
|
|
char *e = NULL; |
|
|
|
size_t chunk_remaining; |
|
|
|
|
|
|
|
bucket = buckets_in->head; |
|
|
|
php_stream_bucket_unlink(bucket TSRMLS_CC); |
|
|
|
|
|
|
|
buf = bucket->buf; |
|
|
|
buflen = bucket->buflen; |
|
|
|
|
|
|
|
continue_bucket: |
|
|
|
|
|
|
|
if (data->chunk_remaining > 0) { |
|
|
|
if ((data->chunk_remaining > buflen) && (bucket->buf == buf)) { |
|
|
|
/* This bucket is smaller than our remaining chunksize, |
|
|
|
Pass it on unmolested */ |
|
|
|
consumed += buflen; |
|
|
|
data->chunk_remaining -= buflen; |
|
|
|
php_stream_bucket_append(buckets_out, bucket TSRMLS_CC); |
|
|
|
|
|
|
|
/* Next bucket please */ |
|
|
|
continue; |
|
|
|
} else if (data->chunk_remaining > buflen) { |
|
|
|
php_stream_bucket *newbucket; |
|
|
|
char *newbuf; |
|
|
|
|
|
|
|
/* Previously split bucket can be used en toto */ |
|
|
|
consumed += buflen; |
|
|
|
data->chunk_remaining -= buflen; |
|
|
|
|
|
|
|
newbuf = estrndup(buf, buflen); |
|
|
|
newbucket = php_stream_bucket_new(stream, newbuf, buflen, 1, stream->is_persistent TSRMLS_CC); |
|
|
|
php_stream_bucket_append(buckets_out, newbucket TSRMLS_CC); |
|
|
|
php_stream_bucket_delref(bucket TSRMLS_CC); |
|
|
|
/* Next bucket please */ |
|
|
|
continue; |
|
|
|
} else { |
|
|
|
php_stream_bucket *newbucket; |
|
|
|
char *newbuf; |
|
|
|
|
|
|
|
/* Consume enough of this bucket to satisfy the current chunk */ |
|
|
|
newbuf = pemalloc(data->chunk_remaining, stream->is_persistent); |
|
|
|
memcpy(newbuf, buf, data->chunk_remaining); |
|
|
|
|
|
|
|
newbucket = php_stream_bucket_new(stream, newbuf, data->chunk_remaining, 1, stream->is_persistent TSRMLS_CC); |
|
|
|
php_stream_bucket_append(buckets_out, newbucket TSRMLS_CC); |
|
|
|
buf += data->chunk_remaining; |
|
|
|
buflen -= data->chunk_remaining; |
|
|
|
consumed += data->chunk_remaining; |
|
|
|
data->chunk_remaining = 0; |
|
|
|
/* Fall Through */ |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
while (buflen > 0 && (*buf == '\r' || *buf == '\n')) { |
|
|
|
buf++; |
|
|
|
buflen--; |
|
|
|
} |
|
|
|
|
|
|
|
if (buflen <= 0) { |
|
|
|
php_stream_bucket_delref(bucket TSRMLS_CC); |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if ((data->chunksize_buffer_pos - data->chunksize_buffer) < HTTP_CHUNK_SIZE_MAXLEN) { |
|
|
|
/* Copy buf into chunksize_buffer */ |
|
|
|
if ((HTTP_CHUNKED_ENCODING_BUFFER_LEN - (data->chunksize_buffer_pos - data->chunksize_buffer)) > buflen) { |
|
|
|
/* Possible split chunklen */ |
|
|
|
memcpy(data->chunksize_buffer_pos, buf, buflen); |
|
|
|
memset(data->chunksize_buffer_pos + buflen, 0, HTTP_CHUNK_SIZE_MAXLEN - buflen - (data->chunksize_buffer_pos - data->chunksize_buffer)); |
|
|
|
chunk_remaining = strtoul(data->chunksize_buffer, &e, 16); |
|
|
|
if (*e != '\r') { |
|
|
|
if (!((*e >= '0' && *e <= '9') || |
|
|
|
(*e >= 'a' && *e <= 'f') || |
|
|
|
(*e >= 'A' && *e <= 'F'))) { |
|
|
|
/* Unexpected character */ |
|
|
|
return PSFS_ERR_FATAL; |
|
|
|
} |
|
|
|
|
|
|
|
/* Not enough data to know the chunksize yet */ |
|
|
|
php_stream_bucket_delref(bucket TSRMLS_CC); |
|
|
|
continue; |
|
|
|
} |
|
|
|
} else { |
|
|
|
memcpy(data->chunksize_buffer_pos, buf, HTTP_CHUNK_SIZE_MAXLEN - (data->chunksize_buffer_pos - data->chunksize_buffer)); |
|
|
|
chunk_remaining = strtoul(data->chunksize_buffer, &e, 16); |
|
|
|
if (*e != '\r') { |
|
|
|
/* Invalid chunksize */ |
|
|
|
return PSFS_ERR_FATAL; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
data->chunk_remaining = chunk_remaining; |
|
|
|
/* Skip past the chunksize itself */ |
|
|
|
buf += (e - data->chunksize_buffer) + sizeof("\r\n") - 1; |
|
|
|
buflen -= (e - data->chunksize_buffer) + sizeof("\r\n") - 1; |
|
|
|
consumed += (e - data->chunksize_buffer) + sizeof("\r\n") - 1; |
|
|
|
memset(data->chunksize_buffer, 0, sizeof(data->chunksize_buffer)); |
|
|
|
data->chunksize_buffer_pos = data->chunksize_buffer; |
|
|
|
|
|
|
|
if (buflen > 0) { |
|
|
|
goto continue_bucket; |
|
|
|
} |
|
|
|
php_stream_bucket_delref(bucket TSRMLS_CC); |
|
|
|
} |
|
|
|
|
|
|
|
if (bytes_consumed) { |
|
|
|
*bytes_consumed = consumed; |
|
|
|
} |
|
|
|
|
|
|
|
if (consumed > 0) { |
|
|
|
return PSFS_PASS_ON; |
|
|
|
} |
|
|
|
|
|
|
|
return PSFS_FEED_ME; |
|
|
|
} |
|
|
|
|
|
|
|
static void php_http_chunked_encoding_dtor(php_stream_filter *thisfilter TSRMLS_DC) |
|
|
|
{ |
|
|
|
if (thisfilter && thisfilter->abstract) { |
|
|
|
php_http_chunked_encoding_data *data = (php_http_chunked_encoding_data*)thisfilter->abstract; |
|
|
|
|
|
|
|
pefree(data, data->is_persistent); |
|
|
|
thisfilter->abstract = NULL; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
static php_stream_filter_ops php_http_chunked_encoding_filter_ops = { |
|
|
|
php_http_chunked_encoding_filter, |
|
|
|
php_http_chunked_encoding_dtor, |
|
|
|
"http.chunked.decode" |
|
|
|
}; |
|
|
|
|
|
|
|
static php_stream_filter *php_http_chunked_encoding_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC) |
|
|
|
{ |
|
|
|
php_http_chunked_encoding_data *data; |
|
|
|
|
|
|
|
data = pemalloc(sizeof(php_http_chunked_encoding_data), persistent); |
|
|
|
data->chunk_remaining = 0; |
|
|
|
data->is_persistent = persistent; |
|
|
|
memset(data->chunksize_buffer, 0, sizeof(data->chunksize_buffer)); |
|
|
|
data->chunksize_buffer_pos = data->chunksize_buffer; |
|
|
|
|
|
|
|
return php_stream_filter_alloc(&php_http_chunked_encoding_filter_ops, data, persistent); |
|
|
|
} |
|
|
|
|
|
|
|
php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, char *mode, int options, char **opened_path, php_stream_context *context, int redirect_max, int header_init STREAMS_DC TSRMLS_DC) |
|
|
|
{ |
|
|
|
php_stream *stream = NULL; |
|
|
|
@ -108,6 +282,9 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, |
|
|
|
int eol_detect = 0; |
|
|
|
char *transport_string, *errstr = NULL; |
|
|
|
int transport_len, have_header = 0, request_fulluri = 0; |
|
|
|
char *protocol_version = NULL; |
|
|
|
int protocol_version_len = 3; /* Default: "1.0" */ |
|
|
|
int autofilter_chunked_encoding = 0; |
|
|
|
|
|
|
|
if (redirect_max < 1) { |
|
|
|
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Circular redirect, aborting."); |
|
|
|
@ -198,8 +375,16 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (context && |
|
|
|
php_stream_context_get_option(context, "http", "protocol_version", &tmpzval) == SUCCESS) { |
|
|
|
SEPARATE_ZVAL(tmpzval); |
|
|
|
convert_to_double_ex(tmpzval); |
|
|
|
protocol_version_len = spprintf(&protocol_version, 0, "%.1f", Z_DVAL_PP(tmpzval)); |
|
|
|
zval_ptr_dtor(tmpzval); |
|
|
|
} |
|
|
|
|
|
|
|
if (!scratch) { |
|
|
|
scratch_len = strlen(path) + 32; |
|
|
|
scratch_len = strlen(path) + 29 + protocol_version_len; |
|
|
|
scratch = emalloc(scratch_len); |
|
|
|
strcpy(scratch, "GET "); |
|
|
|
} |
|
|
|
@ -236,7 +421,16 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, |
|
|
|
} |
|
|
|
|
|
|
|
/* protocol version we are speaking */ |
|
|
|
strlcat(scratch, " HTTP/1.0\r\n", scratch_len); |
|
|
|
if (protocol_version) { |
|
|
|
strlcat(scratch, " HTTP/", scratch_len); |
|
|
|
strlcat(scratch, protocol_version, scratch_len); |
|
|
|
strlcat(scratch, "\r\n", scratch_len); |
|
|
|
efree(protocol_version); |
|
|
|
protocol_version = NULL; |
|
|
|
} else { |
|
|
|
strlcat(scratch, " HTTP/1.0\r\n", scratch_len); |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/* send it */ |
|
|
|
php_stream_write(stream, scratch, strlen(scratch)); |
|
|
|
@ -471,6 +665,9 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, |
|
|
|
} else if (!strncasecmp(http_header_line, "Content-Length: ", 16)) { |
|
|
|
file_size = atoi(http_header_line + 16); |
|
|
|
php_stream_notify_file_size(context, file_size, http_header_line, 0); |
|
|
|
} else if ((strncasecmp(http_header_line, "Transfer-Encoding: ", sizeof("Transfer-Encoding: ") - 1) == 0) && |
|
|
|
(strstr(http_header_line + sizeof("Transfer-Encoding: ") - 1, "chunked") != NULL)) { |
|
|
|
autofilter_chunked_encoding = 1; |
|
|
|
} |
|
|
|
|
|
|
|
if (http_header_line[0] == '\0') { |
|
|
|
@ -560,10 +757,18 @@ php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, char *path, |
|
|
|
} |
|
|
|
} |
|
|
|
out: |
|
|
|
if (http_header_line) |
|
|
|
if (protocol_version) { |
|
|
|
efree(protocol_version); |
|
|
|
} |
|
|
|
|
|
|
|
if (http_header_line) { |
|
|
|
efree(http_header_line); |
|
|
|
if (scratch) |
|
|
|
} |
|
|
|
|
|
|
|
if (scratch) { |
|
|
|
efree(scratch); |
|
|
|
} |
|
|
|
|
|
|
|
php_url_free(resource); |
|
|
|
|
|
|
|
if (stream) { |
|
|
|
@ -582,6 +787,19 @@ out: |
|
|
|
/* as far as streams are concerned, we are now at the start of |
|
|
|
* the stream */ |
|
|
|
stream->position = 0; |
|
|
|
|
|
|
|
if (autofilter_chunked_encoding) { |
|
|
|
php_stream_filter *filter; |
|
|
|
|
|
|
|
filter = php_http_chunked_encoding_filter_create("http.chunked.decode", NULL, stream->is_persistent TSRMLS_CC); |
|
|
|
if (!filter) { |
|
|
|
/* Never actually happens */ |
|
|
|
php_stream_wrapper_log_error(wrapper, options TSRMLS_CC, "Unable to apply chunked content decoding"); |
|
|
|
} else { |
|
|
|
php_stream_filter_append(&stream->readfilters, filter); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return stream; |
|
|
|
|