From 9d4113f62a7a8fe2fe3879b94a3712d11cec8726 Mon Sep 17 00:00:00 2001 From: Michael Wallner Date: Mon, 10 Oct 2005 14:12:05 +0000 Subject: [PATCH 1/1] - add own zlib layer --- config.m4 | 28 ++++- config.w32 | 9 +- http_encoding_api.c | 271 ++++++++++++++++++++++++++++++++++++++++ http_functions.c | 1 + http_message_api.c | 39 ++++++ package2.xml | 4 + php_http_encoding_api.h | 49 ++++++++ tests/request_gzip.phpt | 45 +++++++ 8 files changed, 443 insertions(+), 3 deletions(-) create mode 100644 http_encoding_api.c create mode 100644 php_http_encoding_api.h create mode 100644 tests/request_gzip.phpt diff --git a/config.m4 b/config.m4 index ec5b519..7412b63 100644 --- a/config.m4 +++ b/config.m4 @@ -12,6 +12,9 @@ PHP_ARG_WITH([http-mhash-etags], [whether to enable mhash ETag generator], PHP_ARG_WITH([http-magic-mime], [whether to enable response content type guessing], [ --with-http-magic-mime[=MAGICDIR] With magic mime response content type guessing]) +PHP_ARG_WITH([http-zlib-compression], [whether to enable support for gzencoded/deflated message bodies]) +[ --with-http-zlib-compression[=ZLIBDIR] + With zlib gzdecode and inflate support]) if test "$PHP_HTTP" != "no"; then @@ -26,6 +29,26 @@ dnl ------- AC_MSG_RESULT(not found in default path) fi +dnl ---- +dnl ZLIB +dnl ---- + AC_MSG_CHECKING([for zlib.h]) + ZLIB_DIR= + for i int "$PHP_HTTP_ZLIB_COMPRESSION" /user/local /usr /opt; do + if test -r "$i/include/zlib.h"; then + ZLIB_DIR=$i + break; + fi + done + if test -z "$ZLIB_DIR"; then + AC_MSG_RESULT([not found]) + AC_MSG_WARNING([zlib support not enabled; zlib.h not found]) + else + PHP_ADD_INCLUDE($ZLIB_DIR/include) + PHP_ADD_LIBRARY_WITH_PATH(libz, $ZLIB_DIR/$PHP_LIBDIR, HTTP_SHARED_LIBADD) + AC_DEFINE([HTTP_HAVE_ZLIB], [1], [Have zlib support]) + fi + dnl ---- dnl CURL dnl ---- @@ -146,14 +169,15 @@ dnl ---- http_response_object.c http_exception_object.c http_requestpool_object.c \ http_api.c http_cache_api.c http_request_api.c http_date_api.c \ http_headers_api.c http_message_api.c http_send_api.c http_url_api.c \ - http_info_api.c http_request_method_api.c" + http_info_api.c http_request_method_api.c http_encoding_api.c" PHP_NEW_EXTENSION([http], $PHP_HTTP_SOURCES, [$ext_shared]) PHP_ADD_BUILD_DIR($ext_builddir/phpstr, 1) PHP_SUBST([HTTP_SHARED_LIBADD]) HTTP_HEADER_FILES="php_http_std_defs.h php_http.h php_http_api.h php_http_cache_api.h \ php_http_date_api.h php_http_headers_api.h php_http_info_api.h php_http_message_api.h \ - php_http_request_api.h php_http_request_method_api.h php_http_send_api.h php_http_url_api.h" + php_http_request_api.h php_http_request_method_api.h php_http_send_api.h php_http_url_api.h \ + php_http_encodig_api.h" PHP_SUBST([HTTP_HEADER_FILES]) ifdef([PHP_INSTALL_HEADERS], diff --git a/config.w32 b/config.w32 index 8822ef4..6c0fa81 100644 --- a/config.w32 +++ b/config.w32 @@ -12,7 +12,7 @@ if (PHP_HTTP != "no") { "http_api.c http_cache_api.c http_request_pool_api.c "+ "http_request_api.c http_date_api.c http_headers_api.c "+ "http_message_api.c http_send_api.c http_url_api.c "+ - "http_info_api.c http_request_method_api.c", + "http_info_api.c http_request_method_api.c http_encoding_api.c", null, "/I\"" + configure_module_dirname + "/phpstr\""); ADD_SOURCES(configure_module_dirname + "/phpstr", "phpstr.c", "http"); @@ -21,6 +21,13 @@ if (PHP_HTTP != "no") { if (PHP_DEBUG != "no") { ADD_FLAG("CFLAGS_HTTP", "/W3"); } + + if (CHECK_HEADER_ADD_INCLUDE('zlib.h', 'CFLAGS_HTTP') && + CHECK_LIB('zlib.lib')) { + AC_DEFINE('HTTP_HAVE_ZLIB', 1, "Have zlib library"); + } else { + WARNING("zlib encoding functions not enabled; libraries and headers not found"); + } MHASH_LIB = PHP_DEBUG != "no" ? "libmhash-staticd.lib":"libmhash-static.lib"; if (CHECK_HEADER_ADD_INCLUDE('mhash.h', 'CFLAGS_HTTP') && diff --git a/http_encoding_api.c b/http_encoding_api.c new file mode 100644 index 0000000..67c27a1 --- /dev/null +++ b/http_encoding_api.c @@ -0,0 +1,271 @@ +/* + +----------------------------------------------------------------------+ + | PECL :: http | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, that | + | is bundled with this package in the file LICENSE, and is available | + | through the world-wide-web at http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Copyright (c) 2004-2005 Michael Wallner | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#include "php.h" + +#include "php_http_encoding_api.h" +#include "php_http.h" +#include "php_http_api.h" + +#ifdef HTTP_HAVE_ZLIB +#include + +#define HTTP_GZMAXTRY 10 +#define HTTP_GZBUFLEN(l) (l + (l / 1000) + 16 + 1) + +ZEND_EXTERN_MODULE_GLOBALS(http); + +static const char http_gzencode_header[] = { + (const char) 0x1f, + (const char) 0x8b, + (const char) Z_DEFLATED, + 0, 0, 0, 0, 0, 0, + (const char) 0x03 +}; + +inline void http_init_gzencode_buffer(z_stream *Z, const char *data, size_t data_len, char **buf_ptr) +{ + Z->zalloc = Z_NULL; + Z->zfree = Z_NULL; + Z->opaque = Z_NULL; + + Z->next_in = (Bytef *) data; + Z->avail_in = data_len; + Z->avail_out = HTTP_GZBUFLEN(data_len) - 1; + + *buf_ptr = emalloc(Z->avail_out + sizeof(http_gzencode_header)); + memcpy(*buf_ptr, http_gzencode_header, sizeof(http_gzencode_header)); + + Z->next_out = *buf_ptr + sizeof(http_gzencode_header); +} + +inline void http_init_deflate_buffer(z_stream *Z, const char *data, size_t data_len, char **buf_ptr) +{ + Z->zalloc = Z_NULL; + Z->zfree = Z_NULL; + Z->opaque = Z_NULL; + + Z->data_type = Z_ASCII; + Z->next_in = (Bytef *) data; + Z->avail_in = data_len; + Z->avail_out = HTTP_GZBUFLEN(data_len) - 1; + Z->next_out = emalloc(Z->avail_out); + + *buf_ptr = Z->next_out; +} + +inline void http_init_inflate_buffer(z_stream *Z, const char *data, size_t data_len, char **buf_ptr, size_t *buf_len, int iteration) +{ + Z->zalloc = Z_NULL; + Z->zfree = Z_NULL; + + if (!iteration) { + *buf_len = data_len * 2; + *buf_ptr = emalloc(*buf_len + 1); + } else { + *buf_len <<= 2; + *buf_ptr = erealloc(*buf_ptr, *buf_len + 1); + } + + Z->next_in = (Bytef *) data; + Z->avail_in = data_len; + Z->avail_out = *buf_len; + Z->next_out = *buf_ptr; +} + +inline size_t http_finish_buffer(size_t buf_len, char **buf_ptr) +{ + (*buf_ptr)[buf_len] = '\0'; + return buf_len; +} + +inline size_t http_finish_gzencode_buffer(z_stream *Z, const char *data, size_t data_len, char **buf_ptr) +{ + unsigned long crc; + char *trailer; + + crc = crc32(0L, Z_NULL, 0); + crc = crc32(crc, (const Bytef *) data, data_len); + + trailer = *buf_ptr + sizeof(http_gzencode_header) + Z->total_out; + + /* write crc & stream.total_in in LSB order */ + trailer[0] = (char) crc & 0xFF; + trailer[1] = (char) (crc >> 8) & 0xFF; + trailer[2] = (char) (crc >> 16) & 0xFF; + trailer[3] = (char) (crc >> 24) & 0xFF; + trailer[4] = (char) (Z->total_in) & 0xFF; + trailer[5] = (char) (Z->total_in >> 8) & 0xFF; + trailer[6] = (char) (Z->total_in >> 16) & 0xFF; + trailer[7] = (char) (Z->total_in >> 24) & 0xFF; + + return http_finish_buffer(Z->total_out + sizeof(http_gzencode_header) + 8, buf_ptr); +} + + +PHP_HTTP_API STATUS _http_encoding_gzencode(int level, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC) +{ + z_stream Z; + STATUS status = Z_OK; + + http_init_gzencode_buffer(&Z, data, data_len, encoded); + + if ( (Z_OK == (status = deflateInit2(&Z, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY))) && + (Z_STREAM_END == (status = deflate(&Z, Z_FINISH))) && + (Z_OK == (status = deflateEnd(&Z)))) { + *encoded_len = http_finish_gzencode_buffer(&Z, data, data_len, encoded); + return SUCCESS; + } + + efree(*encoded); + http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Could not gzencode data: %s", zError(status)); + return FAILURE; +} + +PHP_HTTP_API STATUS _http_encoding_deflate(int level, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC) +{ + z_stream Z; + STATUS status = Z_OK; + + http_init_deflate_buffer(&Z, data, data_len, encoded); + + if ( (Z_OK == (status = deflateInit2(&Z, level, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY))) && + (Z_STREAM_END == (status = deflate(&Z, Z_FINISH))) && + (Z_OK == (status = deflateEnd(&Z)))) { + *encoded_len = http_finish_buffer(Z.total_out, encoded); + return SUCCESS; + } + + efree(encoded); + http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Could not deflate data: %s", zError(status)); + return FAILURE; +} + +PHP_HTTP_API STATUS _http_encoding_compress(int level, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC) +{ + STATUS status; + + *encoded = emalloc(*encoded_len = HTTP_GZBUFLEN(data_len)); + + if (Z_OK == (status = compress2(*encoded, encoded_len, data, data_len, level))) { + http_finish_buffer(*encoded_len, encoded); + return SUCCESS; + } + + efree(encoded); + http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Could not compress data: %s", zError(status)); + return FAILURE; +} + +PHP_HTTP_API STATUS _http_encoding_gzdecode(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC) +{ + const char *encoded = data + sizeof(http_gzencode_header); + size_t encoded_len; + + if (data_len <= sizeof(http_gzencode_header) + 8) { + http_error(HE_WARNING, HTTP_E_ENCODING, "Could not gzdecode data: too short data length"); + } else { + encoded_len = data_len - sizeof(http_gzencode_header) - 8; + + if (SUCCESS == http_encoding_inflate(encoded, encoded_len, decoded, decoded_len)) { + unsigned long len = 0, cmp = 0, crc = crc32(0L, Z_NULL, 0); + + crc = crc32(crc, *decoded, *decoded_len); + + cmp = (unsigned) (data[data_len-8]); + cmp += (unsigned) (data[data_len-7] << 8); + cmp += (unsigned) (data[data_len-6] << 16); + cmp += (unsigned) (data[data_len-5] << 24); + len = (unsigned) (data[data_len-4]); + len += (unsigned) (data[data_len-4] << 8); + len += (unsigned) (data[data_len-4] << 16); + len += (unsigned) (data[data_len-4] << 24); + + if (cmp != crc) { + http_error(HE_NOTICE, HTTP_E_ENCODING, "Could not verify data integrity: CRC check failed"); + } + if (len != *decoded_len) { + http_error(HE_NOTICE, HTTP_E_ENCODING, "Could not verify data integrity: data length check failed"); + } + + return SUCCESS; + } + } + return FAILURE; +} + +PHP_HTTP_API STATUS _http_encoding_inflate(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC) +{ + int max = 0; + STATUS status; + z_stream Z; + + do { + http_init_inflate_buffer(&Z, data, data_len, decoded, decoded_len, max++); + if (Z_OK == (status = inflateInit2(&Z, -MAX_WBITS))) { + if (Z_STREAM_END == (status = inflate(&Z, Z_FINISH))) { + if (Z_OK == (status = inflateEnd(&Z))) { + *decoded_len = http_finish_buffer(Z.total_out, decoded); + return SUCCESS; + } + } + } + } while (max < HTTP_GZMAXTRY); + + http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Could not inflate data: %s", zError(status)); + return FAILURE; +} + +PHP_HTTP_API STATUS _http_encoding_uncompress(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC) +{ + int max = 0; + STATUS status; + size_t want = data_len * 2; + + *decoded = emalloc(want + 1); + if (Z_BUF_ERROR == (status = uncompress(*decoded, &want, data, data_len))) do { + /* this is a lot faster with large data than gzuncompress(), + but could be a problem with a low memory limit */ + want <<= 2; + *decoded = erealloc(*decoded, want + 1); + status = uncompress(*decoded, &want, data, data_len); + } while (++max < HTTP_GZMAXTRY && status == Z_BUF_ERROR); + + if (Z_OK == status) { + *decoded_len = http_finish_buffer(want, decoded); + return SUCCESS; + } + + efree(*decoded); + http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Could not uncompress data: %s", zError(status)); + return FAILURE; +} + +#endif /* HTTP_HAVE_ZLIB */ + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/http_functions.c b/http_functions.c index 631de14..ae08b4f 100644 --- a/http_functions.c +++ b/http_functions.c @@ -42,6 +42,7 @@ #include "php_http_message_api.h" #include "php_http_send_api.h" #include "php_http_url_api.h" +#include "php_http_encoding_api.h" #include "phpstr/phpstr.h" diff --git a/http_message_api.c b/http_message_api.c index 20fc791..89f67e0 100644 --- a/http_message_api.c +++ b/http_message_api.c @@ -28,6 +28,7 @@ #include "php_http_send_api.h" #include "php_http_request_api.h" #include "php_http_url_api.h" +#include "php_http_encoding_api.h" #include "phpstr/phpstr.h" @@ -166,6 +167,7 @@ PHP_HTTP_API http_message *_http_message_parse_ex(http_message *msg, const char ZVAL_STRINGL(len, tmp, tmp_len, 0); zend_hash_del(&msg->hdrs, "Transfer-Encoding", sizeof("Transfer-Encoding")); + zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length")); zend_hash_add(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &len, sizeof(zval *), NULL); phpstr_from_string_ex(PHPSTR(msg), decoded, decoded_len); @@ -213,6 +215,43 @@ PHP_HTTP_API http_message *_http_message_parse_ex(http_message *msg, const char } else { continue_at = body; } + +#ifdef HTTP_HAVE_ZLIB + /* check for compressed data */ + if (c = http_message_header(msg, "Content-Encoding")) { + char *decoded = NULL; + size_t decoded_len = 0; + + if (!strcasecmp(Z_STRVAL_P(c), "gzip")) { + http_encoding_gzdecode(PHPSTR_VAL(msg), PHPSTR_LEN(msg), &decoded, &decoded_len); + } else + if (!strcasecmp(Z_STRVAL_P(c), "deflate")) { + http_encoding_inflate(PHPSTR_VAL(msg), PHPSTR_LEN(msg), &decoded, &decoded_len); + } else + if (!strcasecmp(Z_STRVAL_P(c), "compress")) { + http_encoding_uncompress(PHPSTR_VAL(msg), PHPSTR_LEN(msg), &decoded, &decoded_len); + } + + if (decoded && decoded_len) { + zval *len; + char *tmp; + int tmp_len; + + tmp_len = (int) spprintf(&tmp, 0, "%lu", (ulong) decoded_len); + MAKE_STD_ZVAL(len); + ZVAL_STRINGL(len, tmp, tmp_len, 0); + + zend_hash_del(&msg->hdrs, "Content-Encoding", sizeof("Content-Encoding")); + zend_hash_del(&msg->hdrs, "Content-Length", sizeof("Content-Length")); + zend_hash_add(&msg->hdrs, "Content-Length", sizeof("Content-Length"), (void *) &len, sizeof(zval *), NULL); + + phpstr_dtor(PHPSTR(msg)); + PHPSTR(msg)->data = decoded; + PHPSTR(msg)->used = decoded_len; + PHPSTR(msg)->free = 1; + } + } +#endif /* check for following messages */ if (continue_at) { diff --git a/package2.xml b/package2.xml index 0417d51..efeadf3 100644 --- a/package2.xml +++ b/package2.xml @@ -40,11 +40,15 @@ PHP License diff --git a/php_http_encoding_api.h b/php_http_encoding_api.h new file mode 100644 index 0000000..f559ee9 --- /dev/null +++ b/php_http_encoding_api.h @@ -0,0 +1,49 @@ +/* + +----------------------------------------------------------------------+ + | PECL :: http | + +----------------------------------------------------------------------+ + | This source file is subject to version 3.0 of the PHP license, that | + | is bundled with this package in the file LICENSE, and is available | + | through the world-wide-web at http://www.php.net/license/3_0.txt. | + | If you did not receive a copy of the PHP license and are unable to | + | obtain it through the world-wide-web, please send a note to | + | license@php.net so we can mail you a copy immediately. | + +----------------------------------------------------------------------+ + | Copyright (c) 2004-2005 Michael Wallner | + +----------------------------------------------------------------------+ +*/ + +/* $Id$ */ + +#ifndef PHP_HTTP_ENCODING_API_H +#define PHP_HTTP_ENCODING_API_H + +#ifdef HTTP_HAVE_ZLIB + +#include "php_http_std_defs.h" + +#define http_encoding_gzencode(l, d, dl, r, rl) _http_encoding_gzencode((l), (d), (dl), (r), (rl) TSRMLS_CC) +PHP_HTTP_API STATUS _http_encoding_gzencode(int level, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC); +#define http_encoding_gzdecode(d, dl, r, rl) _http_encoding_gzdecode((d), (dl), (r), (rl) TSRMLS_CC) +PHP_HTTP_API STATUS _http_encoding_gzdecode(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC); +#define http_encoding_deflate(l, d, dl, r, rl) _http_encoding_deflate((l), (d), (dl), (r), (rl) TSRMLS_CC) +PHP_HTTP_API STATUS _http_encoding_deflate(int level, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC); +#define http_encoding_inflate(d, dl, r, rl) _http_encoding_inflate((d), (dl), (r), (rl) TSRMLS_CC) +PHP_HTTP_API STATUS _http_encoding_inflate(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC); +#define http_encoding_compress(l, d, dl, r, rl) _http_encoding_compress((l), (d), (dl), (r), (rl) TSRMLS_CC) +PHP_HTTP_API STATUS _http_encoding_compress(int level, const char *data, size_t data_len, char **encoded, size_t *encoded_len TSRMLS_DC); +#define http_encoding_uncompress(d, dl, r, rl) _http_encoding_uncompress((d), (dl), (r), (rl) TSRMLS_CC) +PHP_HTTP_API STATUS _http_encoding_uncompress(const char *data, size_t data_len, char **decoded, size_t *decoded_len TSRMLS_DC); + +#endif +#endif + +/* + * Local variables: + * tab-width: 4 + * c-basic-offset: 4 + * End: + * vim600: noet sw=4 ts=4 fdm=marker + * vim<600: noet sw=4 ts=4 + */ + diff --git a/tests/request_gzip.phpt b/tests/request_gzip.phpt new file mode 100644 index 0000000..7a9f027 --- /dev/null +++ b/tests/request_gzip.phpt @@ -0,0 +1,45 @@ +--TEST-- +GZIP request +--SKIPIF-- + +--FILE-- + true)))); + +echo "Done\n"; +--EXPECTF-- +%sTEST +object(stdClass)#%d (%d) { + ["type"]=> + int(2) + ["httpVersion"]=> + float(1.1) + ["responseCode"]=> + int(200) + ["responseStatus"]=> + string(2) "OK" + ["headers"]=> + array(6) { + %s + ["Vary"]=> + string(15) "Accept-Encoding" + ["Content-Type"]=> + string(9) "text/html" + ["Content-Length"]=> + string(2) "27" + } + ["body"]=> + string(27) "Array +( + [gzip] => 1 +) + +" + ["parentMessage"]=> + NULL +}Done -- 2.30.2