From: Michael Wallner Date: Fri, 21 Apr 2006 10:38:59 +0000 (+0000) Subject: - use a more iterative approach in inflate code (instead of a retry-style) X-Git-Tag: RELEASE_1_0_0~41 X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=commitdiff_plain;h=e37040ebf8a470c77c7ae3498ee582ca20db259c - use a more iterative approach in inflate code (instead of a retry-style) - revert forcing flush on stream filters; s'been a bug in php-src - add flush flags for encoding streams - add ctor to HttpInflateStream --- diff --git a/KnownIssues.txt b/KnownIssues.txt index e4ce6c8..62ba032 100644 --- a/KnownIssues.txt +++ b/KnownIssues.txt @@ -20,9 +20,8 @@ to not check for zlib header bytes. This is not preventable AFAICS. generate any compressed data (gzip, deflate AKA zlib and raw deflate); just use the flag for the data format you want to generate: HTTP_DEFLATE_TYPE_GZIP, HTTP_DEFLATE_TYPE_ZLIB or HTTP_DEFLATE_TYPE_RAW. - Using a deflate stream filter on a stream you read from, will -not work as expected, because the filter does not know when to finish, -thus the trailing bytes (GZIP and ZLIB, not RAW) won't ever be generated. + Using an encoding stream filter on a stream you read from, will +not work as expected in a PHP version lower than 5.1.3. Internals: - there's a memleak with sizeof(zval) for each thrown exception, diff --git a/http_deflatestream_object.c b/http_deflatestream_object.c index 4b9e582..161578f 100644 --- a/http_deflatestream_object.c +++ b/http_deflatestream_object.c @@ -71,6 +71,9 @@ PHP_MINIT_FUNCTION(http_deflatestream_object) DCL_CONST(long, "STRATEGY_HUFF", HTTP_DEFLATE_STRATEGY_HUFF); DCL_CONST(long, "STRATEGY_RLE", HTTP_DEFLATE_STRATEGY_RLE); DCL_CONST(long, "STRATEGY_FIXED", HTTP_DEFLATE_STRATEGY_FIXED); + DCL_CONST(long, "FLUSH_NONE", HTTP_ENCODING_STREAM_FLUSH_NONE); + DCL_CONST(long, "FLUSH_SYNC", HTTP_ENCODING_STREAM_FLUSH_SYNC); + DCL_CONST(long, "FLUSH_FULL", HTTP_ENCODING_STREAM_FLUSH_FULL); #endif return SUCCESS; @@ -148,7 +151,7 @@ PHP_METHOD(HttpDeflateStream, __construct) getObject(http_deflatestream_object, obj); if (!obj->stream) { - obj->stream = http_encoding_deflate_stream_init(NULL, flags); + obj->stream = http_encoding_deflate_stream_init(NULL, flags & 0x0fffffff); } else { http_error_ex(HE_WARNING, HTTP_E_ENCODING, "HttpDeflateStream cannot be initialized twice"); } diff --git a/http_encoding_api.c b/http_encoding_api.c index bf6fd5c..08a41a8 100644 --- a/http_encoding_api.c +++ b/http_encoding_api.c @@ -35,6 +35,11 @@ PHP_MINIT_FUNCTION(http_encoding) HTTP_LONG_CONSTANT("HTTP_DEFLATE_STRATEGY_HUFF", HTTP_DEFLATE_STRATEGY_HUFF); HTTP_LONG_CONSTANT("HTTP_DEFLATE_STRATEGY_RLE", HTTP_DEFLATE_STRATEGY_RLE); HTTP_LONG_CONSTANT("HTTP_DEFLATE_STRATEGY_FIXED", HTTP_DEFLATE_STRATEGY_FIXED); + + HTTP_LONG_CONSTANT("HTTP_ENCODING_STREAM_FLUSH_NONE", HTTP_ENCODING_STREAM_FLUSH_NONE); + HTTP_LONG_CONSTANT("HTTP_ENCODING_STREAM_FLUSH_SYNC", HTTP_ENCODING_STREAM_FLUSH_SYNC); + HTTP_LONG_CONSTANT("HTTP_ENCODING_STREAM_FLUSH_FULL", HTTP_ENCODING_STREAM_FLUSH_FULL); + return SUCCESS; } @@ -218,65 +223,51 @@ PHP_HTTP_API int _http_encoding_response_start(size_t content_length TSRMLS_DC) #ifdef HTTP_HAVE_ZLIB -/* {{{ */ -#define HTTP_DEFLATE_LEVEL_SET(flags, level) \ - switch (flags & 0xf) \ - { \ - default: \ - if ((flags & 0xf) < 10) { \ - level = flags & 0xf; \ - break; \ - } \ - case HTTP_DEFLATE_LEVEL_DEF: \ - level = Z_DEFAULT_COMPRESSION; \ - break; \ - } +/* {{{ inline int http_inflate_rounds */ +static inline int http_inflate_rounds(z_stream *Z, int flush, char **buf, size_t *len) +{ + int status = 0, round = 0; + phpstr buffer; -#define HTTP_DEFLATE_WBITS_SET(flags, wbits) \ - switch (flags & 0xf0) \ - { \ - case HTTP_DEFLATE_TYPE_GZIP: \ - wbits = HTTP_WINDOW_BITS_GZIP; \ - break; \ - case HTTP_DEFLATE_TYPE_RAW: \ - wbits = HTTP_WINDOW_BITS_RAW; \ - break; \ - default: \ - wbits = HTTP_WINDOW_BITS_ZLIB; \ - break; \ - } - -#define HTTP_INFLATE_WBITS_SET(flags, wbits) \ - if (flags & HTTP_INFLATE_TYPE_RAW) { \ - wbits = HTTP_WINDOW_BITS_RAW; \ - } else { \ - wbits = HTTP_WINDOW_BITS_ANY; \ - } - -#define HTTP_DEFLATE_STRATEGY_SET(flags, strategy) \ - switch (flags & 0xf00) \ - { \ - case HTTP_DEFLATE_STRATEGY_FILT: \ - strategy = Z_FILTERED; \ - break; \ - case HTTP_DEFLATE_STRATEGY_HUFF: \ - strategy = Z_HUFFMAN_ONLY; \ - break; \ - case HTTP_DEFLATE_STRATEGY_RLE: \ - strategy = Z_RLE; \ - break; \ - case HTTP_DEFLATE_STRATEGY_FIXED: \ - strategy = Z_FIXED; \ - break; \ - default: \ - strategy = Z_DEFAULT_STRATEGY; \ - break; \ + *buf = NULL; + *len = 0; + + phpstr_init_ex(&buffer, Z->avail_in, PHPSTR_INIT_PREALLOC); + + do { + if (phpstr_resize_ex(&buffer, buffer.size, 0, 1) == (size_t) -1) { + status = Z_MEM_ERROR; + } else { + do { + Z->avail_out = buffer.free; + Z->next_out = (Bytef *) buffer.data + buffer.used; +#if 0 + fprintf(stderr, "PRIOR: size=%lu, avail=%lu, used=%lu, (%d/%d)\n", buffer.size, Z->avail_out, buffer.used, status, round); +#endif + status = inflate(Z, flush); + + buffer.used += buffer.free - Z->avail_out; + buffer.free = Z->avail_out; +#if 0 + fprintf(stderr, "AFTER: size=%lu, avail=%lu, used=%lu, (%d/%d)\n", buffer.size, Z->avail_out, buffer.used, status, round); +#endif + } while (Z_OK == status && Z->avail_in); + + HTTP_INFLATE_BUFFER_SIZE_ALIGN(buffer.size); + } + } while (Z_BUF_ERROR == status && ++round < HTTP_INFLATE_ROUNDS); + + if (status == Z_OK || status == Z_STREAM_END) { + phpstr_shrink(&buffer); + phpstr_fix(&buffer); + *buf = buffer.data; + *len = buffer.used; + } else { + phpstr_dtor(&buffer); } - -#define HTTP_WINDOW_BITS_ZLIB 0x0000000f -#define HTTP_WINDOW_BITS_GZIP 0x0000001f -#define HTTP_WINDOW_BITS_ANY 0x0000002f -#define HTTP_WINDOW_BITS_RAW -0x000000f + + return status; +} /* }}} */ /* {{{ STATUS http_encoding_deflate(int, char *, size_t, char **, size_t *) */ @@ -325,50 +316,34 @@ PHP_HTTP_API STATUS _http_encoding_deflate(int flags, const char *data, size_t d /* {{{ STATUS http_encoding_inflate(char *, size_t, char **, size_t) */ PHP_HTTP_API STATUS _http_encoding_inflate(const char *data, size_t data_len, char **decoded, size_t *decoded_len ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC TSRMLS_DC) { - int status, round = 0, wbits = HTTP_WINDOW_BITS_ANY; z_stream Z; - phpstr buffer; + int status, wbits = HTTP_WINDOW_BITS_ANY; memset(&Z, 0, sizeof(z_stream)); - *decoded = NULL; - *decoded_len = 0; - - phpstr_init_ex(&buffer, data_len << 2, PHPSTR_INIT_PREALLOC); - buffer.size = data_len; -retry_inflate: +retry_raw_inflate: status = inflateInit2(&Z, wbits); if (Z_OK == status) { Z.next_in = (Bytef *) data; Z.avail_in = data_len; - do { - if (phpstr_resize_ex(&buffer, data_len << 2, 0, 1) == (size_t) -1) { - status = Z_MEM_ERROR; - } else do { - Z.avail_out = (buffer.free -= Z.total_out - buffer.used); - Z.next_out = (Bytef *) buffer.data + (buffer.used = Z.total_out); - status = inflate(&Z, Z_NO_FLUSH); - } while (Z_OK == status); - } while (Z_BUF_ERROR == status && ++round < HTTP_INFLATE_ROUNDS); - - if (Z_DATA_ERROR == status && HTTP_WINDOW_BITS_ANY == wbits) { - /* raw deflated data? */ - inflateEnd(&Z); - wbits = HTTP_WINDOW_BITS_RAW; - goto retry_inflate; + switch (status = http_inflate_rounds(&Z, Z_NO_FLUSH, decoded, decoded_len)) + { + case Z_OK: + case Z_STREAM_END: + return SUCCESS; + break; + + case Z_DATA_ERROR: + /* raw deflated data? */ + if (HTTP_WINDOW_BITS_ANY == wbits) { + inflateEnd(&Z); + wbits = HTTP_WINDOW_BITS_RAW; + goto retry_raw_inflate; + } + break; } - inflateEnd(&Z); - - if (Z_STREAM_END == status) { - *decoded_len = Z.total_out; - *decoded = erealloc_rel(buffer.data, *decoded_len + 1); - (*decoded)[*decoded_len] = '\0'; - return SUCCESS; - } else { - phpstr_dtor(&buffer); - } } http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Could not inflate data: %s", zError(status)); @@ -462,7 +437,11 @@ PHP_HTTP_API STATUS _http_encoding_deflate_stream_update(http_encoding_stream *s case Z_OK: case Z_STREAM_END: /* cut processed chunk off the buffer */ - phpstr_cut(PHPSTR(s->stream.opaque), 0, PHPSTR_LEN(s->stream.opaque) - s->stream.avail_in); + if (s->stream.avail_in) { + phpstr_cut(PHPSTR(s->stream.opaque), 0, PHPSTR_LEN(s->stream.opaque) - s->stream.avail_in); + } else { + phpstr_reset(PHPSTR(s->stream.opaque)); + } /* size buffer down to actual size */ *encoded_len -= s->stream.avail_out; @@ -482,55 +461,39 @@ PHP_HTTP_API STATUS _http_encoding_deflate_stream_update(http_encoding_stream *s /* {{{ STATUS http_encoding_inflate_stream_update(http_encoding_stream *, char *, size_t, char **, size_t *) */ PHP_HTTP_API STATUS _http_encoding_inflate_stream_update(http_encoding_stream *s, const char *data, size_t data_len, char **decoded, size_t *decoded_len ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC TSRMLS_DC) { - int status, round = 0; + int status; /* append input to buffer */ phpstr_append(PHPSTR(s->stream.opaque), data, data_len); - /* for realloc() */ - *decoded = NULL; - *decoded_len = data_len << 1; - - /* inflate */ - do { - *decoded_len <<= 1; - *decoded = erealloc_rel(*decoded, *decoded_len); - retry_raw_inflate: - s->stream.next_in = (Bytef *) PHPSTR_VAL(s->stream.opaque); - s->stream.avail_in = PHPSTR_LEN(s->stream.opaque); + s->stream.next_in = (Bytef *) PHPSTR_VAL(s->stream.opaque); + s->stream.avail_in = PHPSTR_LEN(s->stream.opaque); - s->stream.next_out = (Bytef *) *decoded; - s->stream.avail_out = *decoded_len; - - switch (status = inflate(&s->stream, HTTP_ENCODING_STREAM_FLUSH_FLAG(s->flags))) - { - case Z_OK: - case Z_STREAM_END: - /* cut off */ + switch (status = http_inflate_rounds(&s->stream, HTTP_ENCODING_STREAM_FLUSH_FLAG(s->flags), decoded, decoded_len)) + { + case Z_OK: + case Z_STREAM_END: + /* cut off */ + if (s->stream.avail_in) { phpstr_cut(PHPSTR(s->stream.opaque), 0, PHPSTR_LEN(s->stream.opaque) - s->stream.avail_in); - - /* size down */ - *decoded_len -= s->stream.avail_out; - *decoded = erealloc_rel(*decoded, *decoded_len + 1); - (*decoded)[*decoded_len] = '\0'; - return SUCCESS; - break; - - case Z_DATA_ERROR: - /* raw deflated data ? */ - if (!(s->flags & HTTP_INFLATE_TYPE_RAW) && !s->stream.total_out) { - inflateEnd(&s->stream); - s->flags |= HTTP_INFLATE_TYPE_RAW; - inflateInit2(&s->stream, HTTP_WINDOW_BITS_RAW); - goto retry_raw_inflate; - } - break; - } - } while (Z_BUF_ERROR == status && ++round < HTTP_INFLATE_ROUNDS); + } else { + phpstr_reset(PHPSTR(s->stream.opaque)); + } + return SUCCESS; + break; + + case Z_DATA_ERROR: + /* raw deflated data ? */ + if (!(s->flags & HTTP_INFLATE_TYPE_RAW) && !s->stream.total_out) { + inflateEnd(&s->stream); + s->flags |= HTTP_INFLATE_TYPE_RAW; + inflateInit2(&s->stream, HTTP_WINDOW_BITS_RAW); + goto retry_raw_inflate; + } + break; + } - STR_SET(*decoded, NULL); - *decoded_len = 0; http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Failed to update inflate stream: %s", zError(status)); return FAILURE; } diff --git a/http_filter_api.c b/http_filter_api.c index 8d138b9..19260ee 100644 --- a/http_filter_api.c +++ b/http_filter_api.c @@ -477,7 +477,7 @@ static php_stream_filter *http_filter_create(const char *name, zval *params, int } else if (!strcasecmp(name, "http.inflate")) { - int flags = HTTP_ENCODING_STREAM_FLUSH_SYNC | (p ? HTTP_ENCODING_STREAM_PERSISTENT : 0); + int flags = p ? HTTP_ENCODING_STREAM_PERSISTENT : 0; HTTP_FILTER_BUFFER(inflate) *b = NULL; if ((b = http_encoding_inflate_stream_init(NULL, flags))) { @@ -488,7 +488,7 @@ static php_stream_filter *http_filter_create(const char *name, zval *params, int } else if (!strcasecmp(name, "http.deflate")) { - int flags = HTTP_ENCODING_STREAM_FLUSH_SYNC | (p ? HTTP_ENCODING_STREAM_PERSISTENT : 0); + int flags = p ? HTTP_ENCODING_STREAM_PERSISTENT : 0; HTTP_FILTER_BUFFER(deflate) *b = NULL; if (params) { @@ -504,7 +504,7 @@ static php_stream_filter *http_filter_create(const char *name, zval *params, int zval *orig = *tmp; convert_to_long_ex(tmp); - flags |= (Z_LVAL_PP(tmp) & 0x00ffffff); + flags |= (Z_LVAL_PP(tmp) & 0x0fffffff); if (orig != *tmp) zval_ptr_dtor(tmp); } } diff --git a/http_inflatestream_object.c b/http_inflatestream_object.c index c46a4f1..851980e 100644 --- a/http_inflatestream_object.c +++ b/http_inflatestream_object.c @@ -26,6 +26,10 @@ #define HTTP_EMPTY_ARGS(method) HTTP_EMPTY_ARGS_EX(HttpInflateStream, method, 0) #define HTTP_INFLATE_ME(method, visibility) PHP_ME(HttpInflateStream, method, HTTP_ARGS(HttpInflateStream, method), visibility) +HTTP_BEGIN_ARGS(__construct, 0) + HTTP_ARG_VAL(flags, 0) +HTTP_END_ARGS; + HTTP_BEGIN_ARGS(update, 1) HTTP_ARG_VAL(data, 0) HTTP_END_ARGS; @@ -41,6 +45,7 @@ HTTP_END_ARGS; #define OBJ_PROP_CE http_inflatestream_object_ce zend_class_entry *http_inflatestream_object_ce; zend_function_entry http_inflatestream_object_fe[] = { + HTTP_INFLATE_ME(__construct, ZEND_ACC_PUBLIC|ZEND_ACC_CTOR) HTTP_INFLATE_ME(update, ZEND_ACC_PUBLIC) HTTP_INFLATE_ME(flush, ZEND_ACC_PUBLIC) HTTP_INFLATE_ME(finish, ZEND_ACC_PUBLIC) @@ -53,6 +58,13 @@ PHP_MINIT_FUNCTION(http_inflatestream_object) { HTTP_REGISTER_CLASS_EX(HttpInflateStream, http_inflatestream_object, NULL, 0); http_inflatestream_object_handlers.clone_obj = _http_inflatestream_object_clone_obj; + +#ifndef WONKY + DCL_CONST(long, "FLUSH_NONE", HTTP_ENCODING_STREAM_FLUSH_NONE); + DCL_CONST(long, "FLUSH_SYNC", HTTP_ENCODING_STREAM_FLUSH_SYNC); + DCL_CONST(long, "FLUSH_FULL", HTTP_ENCODING_STREAM_FLUSH_FULL); +#endif + return SUCCESS; } @@ -113,6 +125,30 @@ void _http_inflatestream_object_free(zend_object *object TSRMLS_DC) efree(o); } +/* {{{ proto void HttpInflateStream::__construct([int flags = 0]) + * + * Creates a new HttpInflateStream object instance. + * + * Accepts an optional int parameter specifying how to initialize the inflate stream. + */ +PHP_METHOD(HttpInflateStream, __construct) +{ + long flags = 0; + + SET_EH_THROW_HTTP(); + if (SUCCESS == zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "|l", &flags)) { + getObject(http_inflatestream_object, obj); + + if (!obj->stream) { + obj->stream = http_encoding_inflate_stream_init(NULL, flags & 0x0fffffff); + } else { + http_error_ex(HE_WARNING, HTTP_E_ENCODING, "HttpInflateStream cannot be initialized twice"); + } + } + SET_EH_NORMAL(); +} +/* }}} */ + /* {{{ proto string HttpInflateStream::update(string data) * * Passes more data through the inflate stream. diff --git a/package2.xml b/package2.xml index a5abfda..9dbcddc 100644 --- a/package2.xml +++ b/package2.xml @@ -48,7 +48,6 @@ HttpResponse diff --git a/php_http_encoding_api.h b/php_http_encoding_api.h index 88b4295..918c013 100644 --- a/php_http_encoding_api.h +++ b/php_http_encoding_api.h @@ -37,6 +37,10 @@ typedef enum _http_encoding_type_t { #define HTTP_DEFLATE_BUFFER_SIZE_GUESS(S) \ (((size_t) ((double) S * (double) 1.015)) + 10 + 8 + 4 + 1) +#define HTTP_INFLATE_BUFFER_SIZE_GUESS(S) \ + (((S) + 1) << 3) +#define HTTP_INFLATE_BUFFER_SIZE_ALIGN(S) \ + ((S) += (S) >> (3)) #define HTTP_DEFLATE_BUFFER_SIZE 0x8000 #define HTTP_INFLATE_BUFFER_SIZE 0x1000 @@ -53,6 +57,65 @@ typedef enum _http_encoding_type_t { #define HTTP_DEFLATE_STRATEGY_RLE 0x00000300 #define HTTP_DEFLATE_STRATEGY_FIXED 0x00000400 +#define HTTP_DEFLATE_LEVEL_SET(flags, level) \ + switch (flags & 0xf) \ + { \ + default: \ + if ((flags & 0xf) < 10) { \ + level = flags & 0xf; \ + break; \ + } \ + case HTTP_DEFLATE_LEVEL_DEF: \ + level = Z_DEFAULT_COMPRESSION; \ + break; \ + } + +#define HTTP_DEFLATE_WBITS_SET(flags, wbits) \ + switch (flags & 0xf0) \ + { \ + case HTTP_DEFLATE_TYPE_GZIP: \ + wbits = HTTP_WINDOW_BITS_GZIP; \ + break; \ + case HTTP_DEFLATE_TYPE_RAW: \ + wbits = HTTP_WINDOW_BITS_RAW; \ + break; \ + default: \ + wbits = HTTP_WINDOW_BITS_ZLIB; \ + break; \ + } + +#define HTTP_INFLATE_WBITS_SET(flags, wbits) \ + if (flags & HTTP_INFLATE_TYPE_RAW) { \ + wbits = HTTP_WINDOW_BITS_RAW; \ +} else { \ + wbits = HTTP_WINDOW_BITS_ANY; \ +} + +#define HTTP_DEFLATE_STRATEGY_SET(flags, strategy) \ + switch (flags & 0xf00) \ + { \ + case HTTP_DEFLATE_STRATEGY_FILT: \ + strategy = Z_FILTERED; \ + break; \ + case HTTP_DEFLATE_STRATEGY_HUFF: \ + strategy = Z_HUFFMAN_ONLY; \ + break; \ + case HTTP_DEFLATE_STRATEGY_RLE: \ + strategy = Z_RLE; \ + break; \ + case HTTP_DEFLATE_STRATEGY_FIXED: \ + strategy = Z_FIXED; \ + break; \ + default: \ + strategy = Z_DEFAULT_STRATEGY; \ + break; \ + } + +#define HTTP_WINDOW_BITS_ZLIB 0x0000000f +#define HTTP_WINDOW_BITS_GZIP 0x0000001f +#define HTTP_WINDOW_BITS_ANY 0x0000002f +#define HTTP_WINDOW_BITS_RAW -0x000000f + #ifndef Z_FIXED /* Z_FIXED does not exist prior 1.2.2.2 */ # define Z_FIXED 0 @@ -66,10 +129,9 @@ typedef enum _http_encoding_type_t { #define HTTP_ENCODING_STREAM_FLUSH_SYNC 0x00100000 #define HTTP_ENCODING_STREAM_FLUSH_FULL 0x00200000 -#define HTTP_ENCODING_STREAM_FLUSH_FLAG(f) ( \ - (f) & HTTP_ENCODING_STREAM_FLUSH_FULL ? Z_FULL_FLUSH : \ - (f) & HTTP_ENCODING_STREAM_FLUSH_SYNC ? Z_SYNC_FLUSH : \ - Z_NO_FLUSH) +#define HTTP_ENCODING_STREAM_FLUSH_FLAG(f) \ + (((f) & HTTP_ENCODING_STREAM_FLUSH_FULL) ? Z_FULL_FLUSH : \ + (((f) & HTTP_ENCODING_STREAM_FLUSH_SYNC) ? Z_SYNC_FLUSH : Z_NO_FLUSH)) #define HTTP_ENCODING_STREAM_PERSISTENT 0x01000000 diff --git a/php_http_inflatestream_object.h b/php_http_inflatestream_object.h index f7f2dc6..bc08afe 100644 --- a/php_http_inflatestream_object.h +++ b/php_http_inflatestream_object.h @@ -35,6 +35,7 @@ extern zend_object_value _http_inflatestream_object_clone_obj(zval *object TSRML #define http_inflatestream_object_free(o) _http_inflatestream_object_free((o) TSRMLS_CC) extern void _http_inflatestream_object_free(zend_object *object TSRMLS_DC); +PHP_METHOD(HttpInflateStream, __construct); PHP_METHOD(HttpInflateStream, update); PHP_METHOD(HttpInflateStream, flush); PHP_METHOD(HttpInflateStream, finish);