+ *decoded_len = 0;
+ *decoded = ecalloc(1, encoded_len);
+
+ while ((encoded + encoded_len - e_ptr) > 0) {
+ ulong chunk_len = 0, rest;
+
+ chunk_len = strtoul(e_ptr, &n_ptr, 16);
+
+ /* we could not read in chunk size */
+ if (n_ptr == e_ptr) {
+ /*
+ * if this is the first turn and there doesn't seem to be a chunk
+ * size at the begining of the body, do not fail on apparently
+ * not encoded data and return a copy
+ */
+ if (e_ptr == encoded) {
+ http_error(HE_NOTICE, HTTP_E_ENCODING, "Data does not seem to be chunked encoded");
+ memcpy(*decoded, encoded, encoded_len);
+ *decoded_len = encoded_len;
+ return encoded + encoded_len;
+ } else {
+ efree(*decoded);
+ http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Expected chunk size at pos %tu of %zu but got trash", n_ptr - encoded, encoded_len);
+ return NULL;
+ }
+ }
+
+ /* reached the end */
+ if (!chunk_len) {
+ /* move over '0' chunked encoding terminator */
+ while (*e_ptr == '0') ++e_ptr;
+ break;
+ }
+
+ /* there should be CRLF after the chunk size, but we'll ignore SP+ too */
+ if (*n_ptr && !eol_match(&n_ptr, &eol_len)) {
+ if (eol_len == 2) {
+ http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Expected CRLF at pos %tu of %zu but got 0x%02X 0x%02X", n_ptr - encoded, encoded_len, *n_ptr, *(n_ptr + 1));
+ } else {
+ http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Expected LF at pos %tu of %zu but got 0x%02X", n_ptr - encoded, encoded_len, *n_ptr);
+ }
+ }
+ n_ptr += eol_len;
+
+ /* chunk size pretends more data than we actually got, so it's probably a truncated message */
+ if (chunk_len > (rest = encoded + encoded_len - n_ptr)) {
+ http_error_ex(HE_WARNING, HTTP_E_ENCODING, "Truncated message: chunk size %lu exceeds remaining data size %lu at pos %tu of %zu", chunk_len, rest, n_ptr - encoded, encoded_len);
+ chunk_len = rest;
+ }
+
+ /* copy the chunk */
+ memcpy(*decoded + *decoded_len, n_ptr, chunk_len);
+ *decoded_len += chunk_len;
+
+ if (chunk_len == rest) {
+ e_ptr = n_ptr + chunk_len;
+ break;
+ } else {
+ /* advance to next chunk */
+ e_ptr = n_ptr + chunk_len + eol_len;
+ }
+ }
+
+ return e_ptr;
+}
+/* }}} */
+
+/* {{{ int http_encoding_response_start(size_t) */
+PHP_HTTP_API int _http_encoding_response_start(size_t content_length TSRMLS_DC)
+{
+ if ( php_ob_handler_used("ob_gzhandler" TSRMLS_CC) ||
+ php_ob_handler_used("zlib output compression" TSRMLS_CC)) {
+ HTTP_G(send).deflate.encoding = 0;
+ } else {
+ if (!HTTP_G(send).deflate.encoding) {
+ /* emit a content-length header */
+ if (content_length) {
+ char cl_header_str[128];
+ size_t cl_header_len;
+ cl_header_len = snprintf(cl_header_str, lenof(cl_header_str), "Content-Length: %zu", content_length);
+ http_send_header_string_ex(cl_header_str, cl_header_len, 1);
+ }
+ } else {
+#ifndef HTTP_HAVE_ZLIB
+ HTTP_G(send).deflate.encoding = 0;
+ php_start_ob_buffer_named("ob_gzhandler", 0, 0 TSRMLS_CC);
+#else
+ HashTable *selected;
+ zval zsupported;
+
+ INIT_PZVAL(&zsupported);
+ array_init(&zsupported);
+ add_next_index_stringl(&zsupported, "gzip", lenof("gzip"), 1);
+ add_next_index_stringl(&zsupported, "x-gzip", lenof("x-gzip"), 1);
+ add_next_index_stringl(&zsupported, "deflate", lenof("deflate"), 1);
+
+ HTTP_G(send).deflate.encoding = 0;
+
+ if ((selected = http_negotiate_encoding(&zsupported))) {
+ STATUS hs = FAILURE;
+ char *encoding = NULL;
+ ulong idx;
+
+ if (HASH_KEY_IS_STRING == zend_hash_get_current_key(selected, &encoding, &idx, 0) && encoding) {
+ if (!strcmp(encoding, "gzip") || !strcmp(encoding, "x-gzip")) {
+ if (SUCCESS == (hs = http_send_header_string("Content-Encoding: gzip"))) {
+ HTTP_G(send).deflate.encoding = HTTP_ENCODING_GZIP;
+ }
+ } else if (!strcmp(encoding, "deflate")) {
+ if (SUCCESS == (hs = http_send_header_string("Content-Encoding: deflate"))) {
+ HTTP_G(send).deflate.encoding = HTTP_ENCODING_DEFLATE;
+ }
+ }
+ if (SUCCESS == hs) {
+ http_send_header_string("Vary: Accept-Encoding");
+ }
+ }
+
+ zend_hash_destroy(selected);
+ FREE_HASHTABLE(selected);
+ }
+
+ zval_dtor(&zsupported);
+ return HTTP_G(send).deflate.encoding;
+#endif
+ }
+ }
+ return 0;
+}
+/* }}} */
+
+#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; \
+ }