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