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 <[email protected]> |
+----------------------------------------------------------------------+
*/
-/* $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