X-Git-Url: https://git.m6w6.name/?p=m6w6%2Fext-http;a=blobdiff_plain;f=http_send_api.c;h=f6b3d73467bd78933ffe88ef5afc3766342b83d0;hp=6dfc677f993b5be0eef58657c291506311b986c8;hb=451ed82bb79fd9bb89baadebd783497cd8823072;hpb=a60610ff0f84cdf46d8b39c9e64eefd701c1b565 diff --git a/http_send_api.c b/http_send_api.c index 6dfc677..f6b3d73 100644 --- a/http_send_api.c +++ b/http_send_api.c @@ -32,6 +32,7 @@ #include "php_http_headers_api.h" #include "php_http_date_api.h" #include "php_http_cache_api.h" +#include "php_http_encoding_api.h" ZEND_EXTERN_MODULE_GLOBALS(http); @@ -73,11 +74,30 @@ static inline void _http_sleep(TSRMLS_D) } } /* }}} */ +#ifdef HTTP_HAVE_ZLIB +# define HTTP_CHUNK_ENCODE(data, size, dogzip) \ + if (dogzip) { \ + char *encoded = NULL; \ + size_t encoded_len = 0; \ + \ + if (SUCCESS != http_encode(dogzip, 1, data, size, &encoded, &encoded_len)) { \ + return FAILURE; \ + } \ + \ + data = encoded; \ + size = encoded_len; \ + } +#else +# define HTTP_CHUNK_ENCODE(data, size, dogzip) +#endif -#define HTTP_CHUNK_AVAIL(len) ((len -= HTTP_G(send).buffer_size) >= 0) -#define HTTP_CHUNK_WRITE(data, l, dofree, dosleep) \ +#define HTTP_CHUNK_AVAIL(len, cs) ((len -= cs) >= 0) +#define HTTP_CHUNK_WRITE(d, l, dofree, dosleep, dogzip) \ { \ long size = (long) l; \ + char *data = (char *) d; \ + \ + HTTP_CHUNK_ENCODE(data, size, dogzip); \ \ if ((1 > size) || (size - PHPWRITE(data, size))) { \ if (dofree) { \ @@ -97,6 +117,8 @@ static inline void _http_sleep(TSRMLS_D) static STATUS _http_send_chunk(const void *data, size_t begin, size_t end, http_send_mode mode TSRMLS_DC) { long len = end - begin; + size_t chunk_size = HTTP_G(send).buffer_size; + http_encoding_type gzip = HTTP_G(send).gzip_encoding; switch (mode) { @@ -111,13 +133,13 @@ static STATUS _http_send_chunk(const void *data, size_t begin, size_t end, http_ buf = emalloc(HTTP_G(send).buffer_size); - while (HTTP_CHUNK_AVAIL(len)) { - HTTP_CHUNK_WRITE(buf, php_stream_read(s, buf, HTTP_G(send).buffer_size), 1, 1); + while (HTTP_CHUNK_AVAIL(len, chunk_size)) { + HTTP_CHUNK_WRITE(buf, php_stream_read(s, buf, chunk_size), 1, 1, gzip); } /* read & write left over */ if (len) { - HTTP_CHUNK_WRITE(buf, php_stream_read(s, buf, HTTP_G(send).buffer_size + len), 1, 0); + HTTP_CHUNK_WRITE(buf, php_stream_read(s, buf, chunk_size + len), 1, 0, gzip); } efree(buf); @@ -128,14 +150,14 @@ static STATUS _http_send_chunk(const void *data, size_t begin, size_t end, http_ { char *s = (char *) data + begin; - while (HTTP_CHUNK_AVAIL(len)) { - HTTP_CHUNK_WRITE(s, HTTP_G(send).buffer_size, 0, 1); - s += HTTP_G(send).buffer_size; + while (HTTP_CHUNK_AVAIL(len, chunk_size)) { + HTTP_CHUNK_WRITE(s, chunk_size, 0, 1, gzip); + s += chunk_size; } /* write left over */ if (len) { - HTTP_CHUNK_WRITE(s, HTTP_G(send).buffer_size + len, 0, 0); + HTTP_CHUNK_WRITE(s, chunk_size + len, 0, 0, gzip); } return SUCCESS; @@ -156,18 +178,22 @@ PHP_HTTP_API STATUS _http_send_header_ex(const char *name, size_t name_len, cons char *header = emalloc(header_len + 1); header[header_len] = '\0'; - snprintf(header, header_len, "%s: %s", name, value); - ret = http_send_header_string_ex(header, replace); - efree(header); + header_len = snprintf(header, header_len, "%s: %s", name, value); + ret = http_send_header_string_ex(header, header_len, replace); + if (sent_header) { + *sent_header = header; + } else { + efree(header); + } return ret; } /* }}} */ /* {{{ STATUS http_send_status_header(int, char *) */ -PHP_HTTP_API STATUS _http_send_status_header_ex(int status, const char *header, zend_bool replace TSRMLS_DC) +PHP_HTTP_API STATUS _http_send_status_header_ex(int status, const char *header, size_t header_len, zend_bool replace TSRMLS_DC) { STATUS ret; - sapi_header_line h = {(char *) header, header ? strlen(header) : 0, status}; + sapi_header_line h = {(char *) header, header_len, status}; if (SUCCESS != (ret = sapi_header_op(replace ? SAPI_HEADER_REPLACE : SAPI_HEADER_ADD, &h TSRMLS_CC))) { http_error_ex(HE_WARNING, HTTP_E_HEADER, "Could not send header: %s (%d)", header, status); } @@ -185,7 +211,7 @@ PHP_HTTP_API STATUS _http_send_last_modified_ex(time_t t, char **sent_header TSR return FAILURE; } - ret = http_send_header_ex("Last-Modified", lenof("Last-Modifed"), date, strlen(date), 1, sent_header); + ret = http_send_header_ex("Last-Modified", lenof("Last-Modified"), date, strlen(date), 1, sent_header); efree(date); /* remember */ @@ -207,12 +233,10 @@ PHP_HTTP_API STATUS _http_send_etag_ex(const char *etag, size_t etag_len, char * } /* remember */ - STR_FREE(HTTP_G(send).unquoted_etag); - HTTP_G(send).unquoted_etag = estrdup(etag); + STR_SET(HTTP_G(send).unquoted_etag, estrndup(etag, etag_len)); - etag_header = ecalloc(1, sizeof("ETag: \"\"") + etag_len); - sprintf(etag_header, "ETag: \"%s\"", etag); - status = http_send_header_string(etag_header); + etag_len = spprintf(&etag_header, 0, "ETag: \"%s\"", etag); + status = http_send_header_string_ex(etag_header, etag_len, 1); if (sent_header) { *sent_header = etag_header; @@ -227,10 +251,7 @@ PHP_HTTP_API STATUS _http_send_etag_ex(const char *etag, size_t etag_len, char * /* {{{ STATUS http_send_content_type(char *, size_t) */ PHP_HTTP_API STATUS _http_send_content_type(const char *content_type, size_t ct_len TSRMLS_DC) { - if (!strchr(content_type, '/')) { - http_error_ex(HE_WARNING, HTTP_E_INVALID_PARAM, "Content-Type '%s' doesn't seem to consist of a primary and a secondary part", content_type); - return FAILURE; - } + HTTP_CHECK_CONTENT_TYPE(content_type, return FAILURE); /* remember for multiple ranges */ STR_FREE(HTTP_G(send).content_type); @@ -289,6 +310,7 @@ PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, size_ /* multi range */ else { + size_t preface_len; char bound[23] = {0}, preface[1024] = {0}, multi_header[68] = "Content-Type: multipart/byteranges; boundary="; @@ -307,7 +329,7 @@ PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, size_ break; } - snprintf(preface, 1023, + preface_len = snprintf(preface, 1023, HTTP_CRLF "%s" HTTP_CRLF "Content-Type: %s" HTTP_CRLF "Content-Range: bytes %ld-%ld/%lu" @@ -321,7 +343,7 @@ PHP_HTTP_API STATUS _http_send_ranges(HashTable *ranges, const void *data, size_ (ulong) size ); - PHPWRITE(preface, strlen(preface)); + PHPWRITE(preface, preface_len); http_send_chunk(data, Z_LVAL_PP(zbegin), Z_LVAL_PP(zend) + 1, mode); } @@ -340,7 +362,7 @@ PHP_HTTP_API STATUS _http_send_ex(const void *data_ptr, size_t data_size, http_s { HashTable ranges; http_range_status range_status; - int cache_etag = 0; + int cache_etag = 0, external_gzip_handlers = 0; if (!data_ptr) { return FAILURE; @@ -350,11 +372,12 @@ PHP_HTTP_API STATUS _http_send_ex(const void *data_ptr, size_t data_size, http_s } /* stop on-the-fly etag generation */ - if (cache_etag = HTTP_G(etag).started) { - /* interrupt ob_etaghandler */ - HTTP_G(etag).started = 0; - } + cache_etag = http_interrupt_ob_etaghandler(); + if ( php_ob_handler_used("ob_gzhandler" TSRMLS_CC) || + php_ob_handler_used("zlib output compression" TSRMLS_CC)) { + external_gzip_handlers = 1; + } /* enable partial dl and resume */ http_send_header_string("Accept-Ranges: bytes"); @@ -382,13 +405,11 @@ PHP_HTTP_API STATUS _http_send_ex(const void *data_ptr, size_t data_size, http_s /* send 304 Not Modified if etag matches - DON'T return on ETag generation failure */ if (!no_cache && cache_etag) { char *etag = NULL; - - if (!(etag = http_etag(data_ptr, data_size, data_mode))) { - http_error(HE_NOTICE, HTTP_E_RUNTIME, "Failed to generate ETag for data source"); - } else { + + if (etag = http_etag(data_ptr, data_size, data_mode)) { char *sent_header = NULL; - http_send_etag_ex(etag, 32, &sent_header); + http_send_etag_ex(etag, strlen(etag), &sent_header); if (http_match_etag("HTTP_IF_NONE_MATCH", etag)) { return http_exit_ex(304, sent_header, NULL, 0); } else { @@ -405,13 +426,62 @@ PHP_HTTP_API STATUS _http_send_ex(const void *data_ptr, size_t data_size, http_s return http_exit_ex(304, sent_header, NULL, 0); } - /* emit a content-length header */ - if (!php_ob_handler_used("ob_gzhandler" TSRMLS_CC)) { + if (external_gzip_handlers) { +#ifdef HTTP_HAVE_ZLIB + if (HTTP_G(send).gzip_encoding) { + HTTP_G(send).gzip_encoding = 0; + } + } else if (HTTP_G(send).gzip_encoding) { + HashTable *selected; + zval zsupported; + + INIT_PZVAL(&zsupported); + array_init(&zsupported); + add_next_index_stringl(&zsupported, "gzip", lenof("gzip"), 1); + add_next_index_stringl(&zsupported, "deflate", lenof("deflate"), 1); + add_next_index_stringl(&zsupported, "compress", lenof("compress"), 1); + + if (selected = http_negotiate_encoding(&zsupported)) { + char *encoding = NULL; + ulong idx; + + if (HASH_KEY_IS_STRING == zend_hash_get_current_key(selected, &encoding, &idx, 0) && encoding) { + STATUS hs = FAILURE; + + if (!strcmp(encoding, "gzip")) { + if (SUCCESS == (hs = http_send_header_string("Content-Encoding: gzip"))) { + HTTP_G(send).gzip_encoding = HTTP_ENCODING_GZIP; + } + } else if (!strcmp(encoding, "deflate")) { + if (SUCCESS == (hs = http_send_header_string("Content-Encoding: deflate"))) { + HTTP_G(send).gzip_encoding = HTTP_ENCODING_DEFLATE; + } + } else if (!strcmp(encoding, "compress")) { + if (SUCCESS == (hs = http_send_header_string("Content-Encoding: compress"))) { + HTTP_G(send).gzip_encoding = HTTP_ENCODING_COMPRESS; + } + } + if (SUCCESS == hs) { + http_send_header_string("Vary: Accept-Encoding"); + } else { + HTTP_G(send).gzip_encoding = 0; + } + } + + zend_hash_destroy(selected); + FREE_HASHTABLE(selected); + } + + zval_dtor(&zsupported); +#endif + } else { + /* emit a content-length header */ char *cl; spprintf(&cl, 0, "Content-Length: %lu", (unsigned long) data_size); http_send_header_string(cl); efree(cl); } + /* send full entity */ return http_send_chunk(data_ptr, 0, data_size, data_mode); }