1 /**
2  *
3  * Response body compression callback function for Ulfius Framework
4  *
5  * Copyright 2020 Nicolas Mora <mail@babelouest.org>
6  *
7  * Version 20201213
8  *
9  * Compress the response body using `deflate` or `gzip` depending on the request header `Accept-Encoding` and the callback configuration.
10  * The rest of the response, status, headers, cookies won't change.
11  * After compressing response body, the response header Content-Encoding will be set accordingly.
12  *
13  * The MIT License (MIT)
14  *
15  * Permission is hereby granted, free of charge, to any person obtaining a copy
16  * of this software and associated documentation files (the "Software"), to deal
17  * in the Software without restriction, including without limitation the rights
18  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
19  * copies of the Software, and to permit persons to whom the Software is
20  * furnished to do so, subject to the following conditions:
21  *
22  * The above copyright notice and this permission notice shall be included in all
23  * copies or substantial portions of the Software.
24  *
25  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
26  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
27  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
28  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
29  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
30  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
31  * SOFTWARE.
32  *
33  */
34 
35 #include <zlib.h>
36 #include <string.h>
37 #include <ulfius.h>
38 
39 #include "http_compression_callback.h"
40 
41 #define U_COMPRESS_NONE 0
42 #define U_COMPRESS_GZIP 1
43 #define U_COMPRESS_DEFL 2
44 
45 #define U_ACCEPT_HEADER  "Accept-Encoding"
46 #define U_CONTENT_HEADER "Content-Encoding"
47 
48 #define U_ACCEPT_GZIP    "gzip"
49 #define U_ACCEPT_DEFLATE "deflate"
50 
51 #define U_GZIP_WINDOW_BITS 15
52 #define U_GZIP_ENCODING    16
53 
54 #define CHUNK 0x4000
55 
u_zalloc(void * q,unsigned n,unsigned m)56 static void * u_zalloc(void * q, unsigned n, unsigned m) {
57   (void)q;
58   return o_malloc((size_t) n * m);
59 }
60 
u_zfree(void * q,void * p)61 static void u_zfree(void *q, void *p) {
62   (void)q;
63   o_free(p);
64 }
65 
callback_http_compression(const struct _u_request * request,struct _u_response * response,void * user_data)66 int callback_http_compression (const struct _u_request * request, struct _u_response * response, void * user_data) {
67   struct _http_compression_config * config = (struct _http_compression_config *)user_data;
68   char ** accept_list = NULL;
69   int ret = U_CALLBACK_IGNORE, compress_mode = U_COMPRESS_NONE, res;
70   z_stream defstream;
71   char * data_zip = NULL;
72   size_t data_zip_len = 0;
73 
74   if (response->binary_body_length && u_map_has_key_case(request->map_header, U_ACCEPT_HEADER)) {
75     if (split_string(u_map_get_case(request->map_header, U_ACCEPT_HEADER), ",", &accept_list)) {
76       if ((config == NULL || config->allow_gzip) && string_array_has_trimmed_value((const char **)accept_list, U_ACCEPT_GZIP)) {
77         compress_mode = U_COMPRESS_GZIP;
78       } else if ((config == NULL || config->allow_deflate) && string_array_has_trimmed_value((const char **)accept_list, U_ACCEPT_DEFLATE)) {
79         compress_mode = U_COMPRESS_DEFL;
80       }
81 
82       if (compress_mode != U_COMPRESS_NONE) {
83         defstream.zalloc = u_zalloc;
84         defstream.zfree = u_zfree;
85         defstream.opaque = Z_NULL;
86         defstream.avail_in = (uInt)response->binary_body_length;
87         defstream.next_in = (Bytef *)response->binary_body;
88 
89         if (compress_mode == U_COMPRESS_GZIP) {
90           if (deflateInit2(&defstream,
91                            Z_BEST_COMPRESSION,
92                            Z_DEFLATED,
93                            U_GZIP_WINDOW_BITS | U_GZIP_ENCODING,
94                            8,
95                            Z_DEFAULT_STRATEGY) != Z_OK) {
96             y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error deflateInit (gzip)");
97             ret = U_CALLBACK_ERROR;
98           }
99         } else {
100           if (deflateInit(&defstream, Z_BEST_COMPRESSION) != Z_OK) {
101             y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error deflateInit (deflate)");
102             ret = U_CALLBACK_ERROR;
103           }
104         }
105         if (ret == U_CALLBACK_IGNORE) {
106           do {
107             if ((data_zip = o_realloc(data_zip, data_zip_len+_U_C_BLOCK_SIZE)) != NULL) {
108               defstream.avail_out = _U_C_BLOCK_SIZE;
109               defstream.next_out = ((Bytef *)data_zip)+data_zip_len;
110               switch ((res = deflate(&defstream, Z_FINISH))) {
111                 case Z_OK:
112                 case Z_STREAM_END:
113                 case Z_BUF_ERROR:
114                   break;
115                 default:
116                   y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error deflate %d", res);
117                   ret = U_CALLBACK_ERROR;
118                   break;
119               }
120               data_zip_len += _U_C_BLOCK_SIZE - defstream.avail_out;
121             } else {
122               y_log_message(Y_LOG_LEVEL_ERROR, "callback_http_compression - Error allocating resources for data_zip");
123               ret = U_CALLBACK_ERROR;
124             }
125           } while (U_CALLBACK_IGNORE == ret && defstream.avail_out == 0);
126 
127           if (ret == U_CALLBACK_IGNORE) {
128             ulfius_set_binary_body_response(response, response->status, (const char *)data_zip, defstream.total_out);
129             u_map_put(response->map_header, U_CONTENT_HEADER, compress_mode==U_COMPRESS_GZIP?U_ACCEPT_GZIP:U_ACCEPT_DEFLATE);
130           }
131           deflateEnd(&defstream);
132           o_free(data_zip);
133         }
134       }
135     }
136     free_string_array(accept_list);
137   }
138 
139   return ret;
140 }
141