dmitry Thu Apr 16 10:34:15 2009 UTC Modified files: /php-src/ext/standard filters.c http_fopen_wrapper.c /php-src/ext/standard/tests/filters chunked_001.phpt Log: - Added "dechunk" filter which can decode HTTP responces with chunked transfer-encoding. HTTP streams use this filter automatically in case "Transfer-Encoding: chunked" header presents in responce. It's possible to disable this behaviour using "http"=>array("auto_decode"=>0) in stream context - Fixed bug #47021 (SoapClient stumbles over WSDL delivered with "Transfer-Encoding: chunked")
http://cvs.php.net/viewvc.cgi/php-src/ext/standard/filters.c?r1=1.63&r2=1.64&diff_format=u Index: php-src/ext/standard/filters.c diff -u php-src/ext/standard/filters.c:1.63 php-src/ext/standard/filters.c:1.64 --- php-src/ext/standard/filters.c:1.63 Tue Mar 10 23:39:39 2009 +++ php-src/ext/standard/filters.c Thu Apr 16 10:34:15 2009 @@ -20,7 +20,7 @@ +----------------------------------------------------------------------+ */ -/* $Id: filters.c,v 1.63 2009/03/10 23:39:39 helly Exp $ */ +/* $Id: filters.c,v 1.64 2009/04/16 10:34:15 dmitry Exp $ */ #include "php.h" #include "php_globals.h" @@ -1978,6 +1978,225 @@ /* }}} */ +/* {{{ chunked filter implementation */ +typedef enum _php_chunked_filter_state { + CHUNK_SIZE_START, + CHUNK_SIZE, + CHUNK_SIZE_EXT_START, + CHUNK_SIZE_EXT, + CHUNK_SIZE_CR, + CHUNK_SIZE_LF, + CHUNK_BODY, + CHUNK_BODY_CR, + CHUNK_BODY_LF, + CHUNK_TRAILER, + CHUNK_ERROR +} php_chunked_filter_state; + +typedef struct _php_chunked_filter_data { + php_chunked_filter_state state; + int chunk_size; + int persistent; +} php_chunked_filter_data; + +static int php_dechunk(char *buf, int len, php_chunked_filter_data *data) +{ + char *p = buf; + char *end = p + len; + char *out = buf; + int out_len = 0; + + while (p < end) { + switch (data->state) { + case CHUNK_SIZE_START: + data->chunk_size = 0; + case CHUNK_SIZE: + while (p < end) { + if (*p >= '0' && *p <= '9') { + data->chunk_size = (data->chunk_size * 16) + (*p - '0'); + } else if (*p >= 'A' && *p <= 'F') { + data->chunk_size = (data->chunk_size * 16) + (*p - 'A' + 10); + } else if (*p >= 'a' && *p <= 'f') { + data->chunk_size = (data->chunk_size * 16) + (*p - 'a' + 10); + } else if (data->state == CHUNK_SIZE_START) { + data->state = CHUNK_ERROR; + break; + } else { + data->state = CHUNK_SIZE_EXT_START; + break; + } + data->state = CHUNK_SIZE; + p++; + } + if (data->state == CHUNK_ERROR) { + continue; + } else if (p == end) { + return out_len; + } + case CHUNK_SIZE_EXT_START: + if (*p == ';'|| *p == '\r' || *p == '\n') { + data->state = CHUNK_SIZE_EXT; + } else { + data->state = CHUNK_ERROR; + continue; + } + case CHUNK_SIZE_EXT: + /* skip extension */ + while (p < end && *p != '\r' && *p != '\n') { + p++; + } + if (p == end) { + return out_len; + } + case CHUNK_SIZE_CR: + if (*p == '\r') { + p++; + if (p == end) { + data->state = CHUNK_SIZE_LF; + return out_len; + } + } + case CHUNK_SIZE_LF: + if (*p == '\n') { + p++; + if (data->chunk_size == 0) { + /* last chunk */ + data->state = CHUNK_TRAILER; + continue; + } else if (p == end) { + data->state = CHUNK_BODY; + return out_len; + } + } else { + data->state = CHUNK_ERROR; + continue; + } + case CHUNK_BODY: + if (end - p >= data->chunk_size) { + if (p != out) { + memmove(out, p, data->chunk_size); + } + out += data->chunk_size; + out_len += data->chunk_size; + p += data->chunk_size; + if (p == end) { + data->state = CHUNK_BODY_CR; + return out_len; + } + } else { + if (p != out) { + memmove(out, p, end - p); + } + data->chunk_size -= end - p; + out_len += end - p; + return out_len; + } + case CHUNK_BODY_CR: + if (*p == '\r') { + p++; + if (p == end) { + data->state = CHUNK_BODY_LF; + return out_len; + } + } + case CHUNK_BODY_LF: + if (*p == '\n') { + p++; + data->state = CHUNK_SIZE_START; + continue; + } else { + data->state = CHUNK_ERROR; + continue; + } + case CHUNK_TRAILER: + /* ignore trailer */ + p = end; + continue; + case CHUNK_ERROR: + if (p != out) { + memmove(out, p, end - p); + } + out_len += end - p; + return out_len; + } + } + return out_len; +} + +static php_stream_filter_status_t php_chunked_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; + size_t consumed = 0; + php_chunked_filter_data *data = (php_chunked_filter_data *) thisfilter->abstract; + + while (buckets_in->head) { + if (buckets_in->head->buf_type == IS_UNICODE) { + /* dechuk not allowed for unicode data */ + return PSFS_ERR_FATAL; + } + bucket = php_stream_bucket_make_writeable(buckets_in->head TSRMLS_CC); + consumed += bucket->buflen; + bucket->buflen = php_dechunk(bucket->buf.s, bucket->buflen, data); + php_stream_bucket_append(buckets_out, bucket TSRMLS_CC); + } + + if (bytes_consumed) { + *bytes_consumed = consumed; + } + + return PSFS_PASS_ON; +} + +static void php_chunked_dtor(php_stream_filter *thisfilter TSRMLS_DC) +{ + if (thisfilter && thisfilter->abstract) { + php_chunked_filter_data *data = (php_chunked_filter_data *) thisfilter->abstract; + pefree(data, data->persistent); + } +} + +static php_stream_filter_ops chunked_filter_ops = { + php_chunked_filter, + php_chunked_dtor, + "dechunk", + PSFO_FLAG_ACCEPTS_STRING | PSFO_FLAG_OUTPUTS_STRING +}; + +static php_stream_filter *chunked_filter_create(const char *filtername, zval *filterparams, int persistent TSRMLS_DC) +{ + php_stream_filter_ops *fops = NULL; + php_chunked_filter_data *data; + + if (strcasecmp(filtername, "dechunk")) { + return NULL; + } + + /* Create this filter */ + data = (php_chunked_filter_data *)pecalloc(1, sizeof(php_chunked_filter_data), persistent); + if (!data) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Failed allocating %zd bytes", sizeof(php_chunked_filter_data)); + return NULL; + } + data->state = CHUNK_SIZE_START; + data->chunk_size = 0; + data->persistent = persistent; + fops = &chunked_filter_ops; + + return php_stream_filter_alloc(fops, data, persistent); +} + +static php_stream_filter_factory chunked_filter_factory = { + chunked_filter_create +}; +/* }}} */ + static const struct { php_stream_filter_ops *ops; php_stream_filter_factory *factory; @@ -1988,6 +2207,7 @@ { &strfilter_strip_tags_ops, &strfilter_strip_tags_factory }, { &strfilter_convert_ops, &strfilter_convert_factory }, { &consumed_filter_ops, &consumed_filter_factory }, + { &chunked_filter_ops, &chunked_filter_factory }, /* additional filters to go here */ { NULL, NULL } }; http://cvs.php.net/viewvc.cgi/php-src/ext/standard/http_fopen_wrapper.c?r1=1.140&r2=1.141&diff_format=u Index: php-src/ext/standard/http_fopen_wrapper.c diff -u php-src/ext/standard/http_fopen_wrapper.c:1.140 php-src/ext/standard/http_fopen_wrapper.c:1.141 --- php-src/ext/standard/http_fopen_wrapper.c:1.140 Thu Mar 26 20:02:28 2009 +++ php-src/ext/standard/http_fopen_wrapper.c Thu Apr 16 10:34:15 2009 @@ -19,7 +19,7 @@ | Sara Golemon <poll...@php.net> | +----------------------------------------------------------------------+ */ -/* $Id: http_fopen_wrapper.c,v 1.140 2009/03/26 20:02:28 felipe Exp $ */ +/* $Id: http_fopen_wrapper.c,v 1.141 2009/04/16 10:34:15 dmitry Exp $ */ #include "php.h" #include "php_globals.h" @@ -154,6 +154,7 @@ char *user_headers = NULL; int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0); int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0); + php_stream_filter *transfer_encoding = NULL; tmp_line[0] = '\0'; @@ -645,6 +646,25 @@ } 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: chunked", sizeof("Transfer-Encoding: chunked"))) { + + /* create filter to decode response body */ + if (!(options & STREAM_ONLY_GET_HEADERS)) { + long decode = 1; + + if (context && php_stream_context_get_option(context, "http", "auto_decode", &tmpzval) == SUCCESS) { + SEPARATE_ZVAL(tmpzval); + convert_to_boolean(*tmpzval); + decode = Z_LVAL_PP(tmpzval); + } + if (decode) { + transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream) TSRMLS_CC); + if (transfer_encoding) { + /* don't store transfer-encodeing header */ + continue; + } + } + } } if (http_header_line[0] == '\0') { @@ -793,6 +813,11 @@ * the stream */ stream->position = 0; + if (transfer_encoding) { + php_stream_filter_append(&stream->readfilters, transfer_encoding); + } + } else if (transfer_encoding) { + php_stream_filter_free(transfer_encoding TSRMLS_CC); } if (charset) { http://cvs.php.net/viewvc.cgi/php-src/ext/standard/tests/filters/chunked_001.phpt?r1=1.1&r2=1.2&diff_format=u Index: php-src/ext/standard/tests/filters/chunked_001.phpt diff -u /dev/null php-src/ext/standard/tests/filters/chunked_001.phpt:1.2 --- /dev/null Thu Apr 16 10:34:15 2009 +++ php-src/ext/standard/tests/filters/chunked_001.phpt Thu Apr 16 10:34:15 2009 @@ -0,0 +1,33 @@ +--TEST-- +Chunked encoding +--SKIPIF-- +<?php +$filters = stream_get_filters(); +if(! in_array( "dechunk", $filters )) die( "chunked filter not available." ); +?> +--FILE-- +<?php +$streams = array( + b"data://text/plain,0\r\n", + b"data://text/plain,2\r\nte\r\n2\r\nst\r\n0\r\n", + b"data://text/plain,2\nte\n2\nst\n0\n", + b"data://text/plain,2;a=1\nte\n2;a=2;b=3\r\nst\n0\n", + b"data://text/plain,2\nte\n2\nst\n0\na=b\r\nc=d\n\r\n", + b"data://text/plain,1f\n0123456789abcdef0123456789abcde\n1\nf\n0\n", + b"data://text/plain,1E\n0123456789abcdef0123456789abcd\n2\nef\n0\n", +); +foreach ($streams as $name) { + $fp = fopen($name, "rb"); + stream_filter_append($fp, "dechunk", STREAM_FILTER_READ); + var_dump(stream_get_contents($fp)); + fclose($fp); +} +?> +--EXPECT-- +string(0) "" +string(4) "test" +string(4) "test" +string(4) "test" +string(4) "test" +string(32) "0123456789abcdef0123456789abcdef" +string(32) "0123456789abcdef0123456789abcdef"
-- PHP CVS Mailing List (http://www.php.net/) To unsubscribe, visit: http://www.php.net/unsub.php