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

Reply via email to