+#include "php_http_encoding_api.h"
+#include "php_http_send_api.h"
+#include "php_http_headers_api.h"
+
+/* {{{ */
+#ifdef HTTP_HAVE_ZLIB
+PHP_MINIT_FUNCTION(http_encoding)
+{
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_LEVEL_DEF", HTTP_DEFLATE_LEVEL_DEF);
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_LEVEL_MIN", HTTP_DEFLATE_LEVEL_MIN);
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_LEVEL_MAX", HTTP_DEFLATE_LEVEL_MAX);
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_TYPE_ZLIB", HTTP_DEFLATE_TYPE_ZLIB);
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_TYPE_GZIP", HTTP_DEFLATE_TYPE_GZIP);
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_TYPE_RAW", HTTP_DEFLATE_TYPE_RAW);
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_STRATEGY_DEF", HTTP_DEFLATE_STRATEGY_DEF);
+ HTTP_LONG_CONSTANT("HTTP_DEFLATE_STRATEGY_FILT", HTTP_DEFLATE_STRATEGY_FILT);
+ 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);
+ return SUCCESS;
+}
+
+PHP_RINIT_FUNCTION(http_encoding)
+{
+ if (HTTP_G->send.inflate.start_auto) {
+ php_ob_set_internal_handler(_http_ob_inflatehandler, HTTP_INFLATE_BUFFER_SIZE, "http inflate", 0 TSRMLS_CC);
+ }
+ if (HTTP_G->send.deflate.start_auto) {
+ php_ob_set_internal_handler(_http_ob_deflatehandler, HTTP_DEFLATE_BUFFER_SIZE, "http deflate", 0 TSRMLS_CC);
+ }
+ return SUCCESS;
+}
+
+PHP_RSHUTDOWN_FUNCTION(http_encoding)
+{
+ if (HTTP_G->send.deflate.stream) {
+ http_encoding_deflate_stream_free((http_encoding_stream **) &HTTP_G->send.deflate.stream);
+ }
+ if (HTTP_G->send.inflate.stream) {
+ http_encoding_inflate_stream_free((http_encoding_stream **) &HTTP_G->send.inflate.stream);
+ }
+ return SUCCESS;
+}
+#endif
+/* }}} */
+
+/* {{{ eol_match(char **, int *) */
+static inline int eol_match(char **line, int *eol_len)
+{
+ char *ptr = *line;
+
+ while (' ' == *ptr) ++ptr;
+
+ if (ptr == http_locate_eol(*line, eol_len)) {
+ *line = ptr;
+ return 1;
+ } else {
+ return 0;
+ }
+}
+/* }}} */
+
+/* {{{ char *http_encoding_dechunk(char *, size_t, char **, size_t *) */
+PHP_HTTP_API const char *_http_encoding_dechunk(const char *encoded, size_t encoded_len, char **decoded, size_t *decoded_len TSRMLS_DC)
+{
+ int eol_len = 0;
+ char *n_ptr = NULL;
+ const char *e_ptr = encoded;
+
+ *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;
+}
+/* }}} */