this patch implements gzip content encoding. It is used when "gzip" string is present in a Accept-Encoding header of a request and the decision to use chunked encoding in a response was already made. Adds zlib dependency.
Signed-off-by: Andrej Krpic <[email protected]> --- CMakeLists.txt | 7 +++++++ client.c | 16 ++++++++++++++-- gzip.c | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ main.c | 16 +++++++++++++++- uhttpd.h | 17 +++++++++++++++++ utils.c | 13 ++++++++++++- 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 gzip.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c285dc..1dd24b8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,6 +10,7 @@ ADD_DEFINITIONS(-D_FILE_OFFSET_BITS=64 -Os -Wall -Werror -Wmissing-declarations OPTION(TLS_SUPPORT "TLS support" ON) OPTION(LUA_SUPPORT "Lua support" ON) OPTION(UBUS_SUPPORT "ubus support" ON) +OPTION(GZIP_SUPPORT "gzip encoding support" ON) IF(APPLE) INCLUDE_DIRECTORIES(/opt/local/include) @@ -27,6 +28,12 @@ IF(TLS_SUPPORT) ADD_DEFINITIONS(-DHAVE_TLS) ENDIF() +IF(GZIP_SUPPORT) + SET(SOURCES ${SOURCES} gzip.c) + SET(LIBS ${LIBS} "z") + ADD_DEFINITIONS(-DHAVE_ZLIB) +ENDIF() + CHECK_FUNCTION_EXISTS(getspnam HAVE_SHADOW) IF(HAVE_SHADOW) ADD_DEFINITIONS(-DHAVE_SHADOW) diff --git a/client.c b/client.c index 8569b21..96eecd6 100644 --- a/client.c +++ b/client.c @@ -61,6 +61,7 @@ void uh_http_header(struct client *cl, int code, const char *summary) { struct http_request *r = &cl->request; const char *enc = "Transfer-Encoding: chunked\r\n"; + const char *cenc = "Content-Encoding: gzip\r\n"; const char *conn; cl->http_code = code; @@ -70,14 +71,19 @@ void uh_http_header(struct client *cl, int code, const char *summary) else chunked_init(cl); + if (!cl->request.respond_gzipped || !uh_gzip_init(cl)) { + cl->request.respond_gzipped = false; + cenc = ""; + } + if (r->connection_close) conn = "Connection: close"; else conn = "Connection: Keep-Alive"; - ustream_printf(cl->us, "%s %03i %s\r\n%s\r\n%s", + ustream_printf(cl->us, "%s %03i %s\r\n%s\r\n%s%s", http_versions[cl->request.version], - code, summary, conn, enc); + code, summary, conn, enc, cenc); if (!r->connection_close) ustream_printf(cl->us, "Keep-Alive: timeout=%d\r\n", conf.http_keepalive); @@ -279,6 +285,7 @@ static bool tls_redirect_check(struct client *cl) *ptr = 0; cl->request.respond_chunked = false; + cl->request.respond_gzipped = false; cl->request.connection_close = true; uh_http_header(cl, 307, "Temporary Redirect"); @@ -359,6 +366,11 @@ static void client_parse_header(struct client *cl, char *data) uh_header_error(cl, 400, "Bad Request"); return; } +#ifdef HAVE_ZLIB + } else if (!strcmp(data, "accept-encoding") && strstr(val, "gzip")) { + if (!conf.no_gzip_encoding && r->respond_chunked) + r->respond_gzipped = true; +#endif } else if (!strcmp(data, "transfer-encoding")) { if (!strcmp(val, "chunked")) r->transfer_chunked = true; diff --git a/gzip.c b/gzip.c new file mode 100644 index 0000000..c4c5969 --- /dev/null +++ b/gzip.c @@ -0,0 +1,49 @@ +#include <zlib.h> +#include <string.h> +#include "uhttpd.h" + +static int ustream_gzip_write(struct ustream *s, const char *buf, int len, bool more) +{ + struct client *cl = container_of(s, struct client, gzip); + z_stream *zs = &cl->zstream; + bool flush = !more && len == 0 && buf && *buf == 0; + + char *gzbuf; + int gzlen, ret; + + zs->avail_in = len; + zs->next_in = (z_const Bytef *)buf; + + gzlen = len > 0 ? len : 1024; + gzbuf = calloc(gzlen, sizeof(char)); + if (!gzbuf) + return -1; +more: + zs->avail_out = gzlen; + zs->next_out = (Bytef *)gzbuf; + + ret = deflate(zs, flush ? Z_FINISH : Z_SYNC_FLUSH); + + ustream_write(&cl->chunked, gzbuf, gzlen - zs->avail_out, more); + + if (flush && ret == Z_OK) + goto more; + else if (ret == Z_STREAM_END) + ret = deflateEnd(zs); + + free(gzbuf); + return len; +} + +bool uh_gzip_init(struct client *cl) { + ustream_init_defaults(&cl->gzip); + cl->gzip.write = &ustream_gzip_write; + + cl->zstream.zalloc = Z_NULL; + cl->zstream.zfree = Z_NULL; + cl->zstream.opaque = Z_NULL; + + return deflateInit2(&cl->zstream, Z_DEFAULT_COMPRESSION, + Z_DEFLATED, 16 | MAX_WBITS, + 8, Z_DEFAULT_STRATEGY) == Z_OK; +} diff --git a/main.c b/main.c index ed47486..a1138dc 100644 --- a/main.c +++ b/main.c @@ -162,6 +162,9 @@ static int usage(const char *name) " -d string URL decode given string\n" " -r string Specify basic auth realm\n" " -m string MD5 crypt given string\n" +#ifdef HAVE_ZLIB + " -Z Disable gzip content encoding.\n" +#endif "\n", name ); return 1; @@ -177,6 +180,7 @@ static void init_defaults_pre(void) conf.realm = "Protected Area"; conf.cgi_prefix = "/cgi-bin"; conf.cgi_path = "/sbin:/usr/sbin:/bin:/usr/bin"; + conf.no_gzip_encoding = 0; } static void init_defaults_post(void) @@ -228,7 +232,7 @@ int main(int argc, char **argv) init_defaults_pre(); signal(SIGPIPE, SIG_IGN); - while ((ch = getopt(argc, argv, "afqSDRXC:K:E:I:p:s:h:c:l:L:d:r:m:n:N:x:i:t:k:T:A:u:U:")) != -1) { + while ((ch = getopt(argc, argv, "afqSDRXC:K:E:I:p:s:h:c:l:L:d:r:m:n:N:x:i:t:k:T:A:u:U:Z")) != -1) { switch(ch) { #ifdef HAVE_TLS case 'C': @@ -422,6 +426,16 @@ int main(int argc, char **argv) "ignoring -%c\n", ch); break; #endif +#if HAVE_ZLIB + case 'Z': + conf.no_gzip_encoding = 1; + break; +#else + case 'Z': + fprintf(stderr, "uhttpd: zlib support not compiled, " + "ignoring -%c\n", ch); + break +#endif default: return usage(argv[0]); } diff --git a/uhttpd.h b/uhttpd.h index dd41c25..2d0a0a7 100644 --- a/uhttpd.h +++ b/uhttpd.h @@ -36,6 +36,9 @@ #ifdef HAVE_TLS #include <libubox/ustream-ssl.h> #endif +#ifdef HAVE_ZLIB +#include <zlib.h> +#endif #include "utils.h" @@ -70,6 +73,9 @@ struct config { int script_timeout; int ubus_noauth; int ubus_cors; +#if HAVE_ZLIB + int no_gzip_encoding; +#endif }; struct auth_realm { @@ -113,6 +119,7 @@ struct http_request { bool expect_cont; bool connection_close; bool respond_chunked; + bool respond_gzipped; uint8_t transfer_chunked; const struct auth_realm *realm; }; @@ -245,6 +252,10 @@ struct client { enum client_state state; bool tls; +#ifdef HAVE_ZLIB + struct ustream gzip; + z_stream zstream; +#endif int http_code; struct http_request request; @@ -311,6 +322,12 @@ bool uh_create_process(struct client *cl, struct path_info *pi, char *url, int uh_plugin_init(const char *name); void uh_plugin_post_init(void); +#if HAVE_ZLIB +bool uh_gzip_init(struct client *cl); +#else +bool uh_gzip_init(struct client *cl) { return false; } +#endif + static inline void uh_client_ref(struct client *cl) { cl->refcount++; diff --git a/utils.c b/utils.c index 572beb9..49d7b9b 100644 --- a/utils.c +++ b/utils.c @@ -44,6 +44,10 @@ void uh_chunk_write(struct client *cl, const void *data, int len) if (!cl->request.respond_chunked) ustream_write(cl->us, data, len, true); +#if HAVE_ZLIB + else if (cl->request.respond_gzipped) + ustream_write(&cl->gzip, data, len, true); +#endif else ustream_write(&cl->chunked, data, len, true); } @@ -56,6 +60,10 @@ void uh_chunk_vprintf(struct client *cl, const char *format, va_list arg) uloop_timeout_set(&cl->timeout, conf.network_timeout * 1000); if (!cl->request.respond_chunked) ustream_vprintf(cl->us, format, arg); +#if HAVE_ZLIB + else if (cl->request.respond_gzipped) + ustream_vprintf(&cl->gzip, format, arg); +#endif else ustream_vprintf(&cl->chunked, format, arg); } @@ -76,7 +84,10 @@ void uh_chunk_eof(struct client *cl) if (cl->state == CLIENT_STATE_CLEANUP) return; - +#if HAVE_ZLIB + if (cl->request.respond_gzipped) + ustream_write(&cl->gzip, "", 0, false); +#endif ustream_printf(cl->us, "0\r\n\r\n"); } -- 2.4.6 _______________________________________________ openwrt-devel mailing list [email protected] https://lists.openwrt.org/cgi-bin/mailman/listinfo/openwrt-devel
