1 /* mod_deflate
2 *
3 *
4 * bug fix on Robert Jakabosky from alphatrade.com's lighttp 1.4.10 mod_deflate patch
5 *
6 * Bug fix and new features:
7 * 1) fix loop bug when content-length is bigger than work-block-size*k
8 *
9 * -------
10 *
11 * lighttpd-1.4.26.mod_deflate.patch from
12 * https://redmine.lighttpd.net/projects/1/wiki/Docs_ModDeflate
13 *
14 * -------
15 *
16 * Patch further modified in this incarnation.
17 *
18 * Note: this patch only handles completed responses
19 * (r->resp_body_finished)
20 * this patch does not currently handle streaming dynamic responses,
21 * and therefore also does not worry about Transfer-Encoding: chunked
22 * (or having separate con->output_queue for chunked-encoded output)
23 * (or using separate buffers per connection instead of p->tmp_buf)
24 * (or handling interactions with block buffering and write timeouts)
25 *
26 * Bug fix:
27 * - fixed major bug with compressing chunks with offset > 0
28 * x-ref:
29 * "Response breaking in mod_deflate"
30 * https://redmine.lighttpd.net/issues/986
31 * - fix broken (in some cases) chunk accounting in deflate_compress_response()
32 * - fix broken bzip2
33 * x-ref:
34 * "mod_deflate's bzip2 broken by default"
35 * https://redmine.lighttpd.net/issues/2035
36 * - fix mismatch with current chunk interfaces
37 * x-ref:
38 * "Weird things in chunk.c (functions only handling specific cases, unexpected behaviour)"
39 * https://redmine.lighttpd.net/issues/1510
40 *
41 * Behavior changes from prior patch:
42 * - deflate.mimetypes must now be configured to enable compression
43 * deflate.mimetypes = ( ) # compress nothing (disabled; default)
44 * deflate.mimetypes = ( "" ) # compress all mimetypes
45 * deflate.mimetypes = ( "text/" ) # compress text/... mimetypes
46 * x-ref:
47 * "mod_deflate enabled by default"
48 * https://redmine.lighttpd.net/issues/1394
49 * - deflate.enabled directive removed (see new behavior of deflate.mimetypes)
50 * - deflate.debug removed (was developer debug trace, not end-user debug)
51 * - deflate.bzip2 replaced with deflate.allowed-encodings (like mod_compress)
52 * x-ref:
53 * "mod_deflate should allow limiting of compression algorithm from the configuration file"
54 * https://redmine.lighttpd.net/issues/996
55 * "mod_compress disabling methods"
56 * https://redmine.lighttpd.net/issues/1773
57 * - deflate.nocompress-url removed since disabling compression for a URL
58 * can now easily be done by setting to a blank list either directive
59 * deflate.accept_encodings = () or deflate.mimetypes = () in a conditional
60 * block, e.g. $HTTP["url"] =~ "....." { deflate.mimetypes = ( ) }
61 * - deflate.sync-flush removed; controlled by r->conf.stream_response_body
62 * (though streaming compression not currently implemented in mod_deflate)
63 * - inactive directives in this patch
64 * (since r->resp_body_finished required)
65 * deflate.work-block-size
66 * deflate.output-buffer-size
67 * - remove weak file size check; SIGBUS is trapped, file that shrink will error
68 * x-ref:
69 * "mod_deflate: filesize check is too weak"
70 * https://redmine.lighttpd.net/issues/1512
71 * - change default deflate.min-compress-size from 0 to now be 256
72 * http://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
73 * Apache 2.4 mod_deflate minimum is 68 bytes
74 * Akamai recommends minimum 860 bytes
75 * Google recommends minimum be somewhere in range between 150 and 1024 bytes
76 * - deflate.max-compress-size new directive (in kb like compress.max_filesize)
77 * - deflate.mem-level removed (too many knobs for little benefit)
78 * - deflate.window-size removed (too many knobs for little benefit)
79 *
80 * Future:
81 * - config directives may be changed, renamed, or removed
82 * e.g. A set of reasonable defaults might be chosen
83 * instead of making them configurable.
84 * deflate.min-compress-size
85 * - might add deflate.mimetypes-exclude = ( ... ) for list of mimetypes
86 * to avoid compressing, even if a broader deflate.mimetypes matched,
87 * e.g. to compress all "text/" except "text/special".
88 *
89 * Implementation notes:
90 * - http_chunk_append_mem() used instead of http_chunk_append_buffer()
91 * so that p->tmp_buf can be large and re-used. This results in an extra copy
92 * of compressed data before data is sent to network, though if the compressed
93 * size is larger than 64k, it ends up being sent to a temporary file on
94 * disk without suffering an extra copy in memory, and without extra chunk
95 * create and destroy. If this is ever changed to give away buffers, then use
96 * a unique hctx->output buffer per hctx; do not reuse p->tmp_buf across
97 * multiple requests being handled in parallel.
98 */
99 #include "first.h"
100
101 #include <sys/types.h>
102 #include <sys/stat.h>
103 #include "sys-mmap.h"
104 #ifdef HAVE_MMAP
105 #include "sys-setjmp.h"
106 #endif
107 #include "sys-time.h"
108
109 #include <fcntl.h>
110 #include <stdlib.h>
111 #include <string.h>
112 #include <errno.h>
113 #include <unistd.h> /* getpid() read() unlink() write() */
114
115 #include "base.h"
116 #include "ck.h"
117 #include "fdevent.h"
118 #include "log.h"
119 #include "buffer.h"
120 #include "http_chunk.h"
121 #include "http_etag.h"
122 #include "http_header.h"
123 #include "response.h"
124 #include "stat_cache.h"
125
126 #include "plugin.h"
127
128 #if defined HAVE_ZLIB_H && defined HAVE_LIBZ
129 # define USE_ZLIB
130 # include <zlib.h>
131 #endif
132 #ifndef Z_DEFAULT_COMPRESSION
133 #define Z_DEFAULT_COMPRESSION -1
134 #endif
135 #ifndef MAX_WBITS
136 #define MAX_WBITS 15
137 #endif
138
139 #if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
140 # define USE_BZ2LIB
141 /* we don't need stdio interface */
142 # define BZ_NO_STDIO
143 # include <bzlib.h>
144 #endif
145
146 #if defined HAVE_BROTLI_ENCODE_H && defined HAVE_BROTLI
147 # define USE_BROTLI
148 # include <brotli/encode.h>
149 #endif
150
151 #if defined HAVE_ZSTD_H && defined HAVE_ZSTD
152 # define USE_ZSTD
153 # include <zstd.h>
154 #endif
155
156 #ifndef HAVE_LIBZ
157 #undef HAVE_LIBDEFLATE
158 #endif
159
160 /* request: accept-encoding */
161 #define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
162 #define HTTP_ACCEPT_ENCODING_GZIP BV(1)
163 #define HTTP_ACCEPT_ENCODING_DEFLATE BV(2)
164 #define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
165 #define HTTP_ACCEPT_ENCODING_BZIP2 BV(4)
166 #define HTTP_ACCEPT_ENCODING_X_GZIP BV(5)
167 #define HTTP_ACCEPT_ENCODING_X_BZIP2 BV(6)
168 #define HTTP_ACCEPT_ENCODING_BR BV(7)
169 #define HTTP_ACCEPT_ENCODING_ZSTD BV(8)
170
171 typedef struct {
172 struct {
173 int clevel; /*(compression level)*/
174 int windowBits;
175 int memLevel;
176 int strategy;
177 } gzip;
178 struct {
179 uint32_t quality; /*(compression level)*/
180 uint32_t window;
181 uint32_t mode;
182 } brotli;
183 struct {
184 int clevel; /*(compression level)*/
185 int strategy;
186 int windowLog;
187 } zstd;
188 struct {
189 int clevel; /*(compression level)*/
190 } bzip2;
191 } encparms;
192
193 typedef struct {
194 const array *mimetypes;
195 const buffer *cache_dir;
196 unsigned int max_compress_size;
197 unsigned short min_compress_size;
198 unsigned short output_buffer_size;
199 unsigned short work_block_size;
200 unsigned short sync_flush;
201 short compression_level;
202 uint16_t * allowed_encodings;
203 double max_loadavg;
204 const encparms *params;
205 } plugin_config;
206
207 typedef struct {
208 PLUGIN_DATA;
209 plugin_config defaults;
210 plugin_config conf;
211
212 buffer tmp_buf;
213 } plugin_data;
214
215 typedef struct {
216 union {
217 #ifdef USE_ZLIB
218 z_stream z;
219 #endif
220 #ifdef USE_BZ2LIB
221 bz_stream bz;
222 #endif
223 #ifdef USE_BROTLI
224 BrotliEncoderState *br;
225 #endif
226 #ifdef USE_ZSTD
227 ZSTD_CStream *cctx;
228 #endif
229 int dummy;
230 } u;
231 off_t bytes_in;
232 off_t bytes_out;
233 buffer *output;
234 plugin_data *plugin_data;
235 request_st *r;
236 int compression_type;
237 int cache_fd;
238 char *cache_fn;
239 chunkqueue in_queue;
240 } handler_ctx;
241
handler_ctx_init(void)242 static handler_ctx *handler_ctx_init(void) {
243 handler_ctx * const hctx = ck_calloc(1, sizeof(*hctx));
244 chunkqueue_init(&hctx->in_queue);
245 hctx->cache_fd = -1;
246 return hctx;
247 }
248
handler_ctx_free(handler_ctx * hctx)249 static void handler_ctx_free(handler_ctx *hctx) {
250 if (hctx->cache_fn) {
251 unlink(hctx->cache_fn);
252 free(hctx->cache_fn);
253 }
254 if (-1 != hctx->cache_fd)
255 close(hctx->cache_fd);
256 #if 0
257 if (hctx->output != &p->tmp_buf) {
258 buffer_free(hctx->output);
259 }
260 #endif
261 chunkqueue_reset(&hctx->in_queue);
262 free(hctx);
263 }
264
INIT_FUNC(mod_deflate_init)265 INIT_FUNC(mod_deflate_init) {
266 plugin_data * const p = ck_calloc(1, sizeof(plugin_data));
267 #ifdef USE_ZSTD
268 buffer_string_prepare_copy(&p->tmp_buf, ZSTD_CStreamOutSize());
269 #else
270 buffer_string_prepare_copy(&p->tmp_buf, 65536);
271 #endif
272 return p;
273 }
274
FREE_FUNC(mod_deflate_free)275 FREE_FUNC(mod_deflate_free) {
276 plugin_data *p = p_d;
277 free(p->tmp_buf.ptr);
278 if (NULL == p->cvlist) return;
279 /* (init i to 0 if global context; to 1 to skip empty global context) */
280 for (int i = !p->cvlist[0].v.u2[1], used = p->nconfig; i < used; ++i) {
281 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
282 for (; -1 != cpv->k_id; ++cpv) {
283 if (cpv->vtype != T_CONFIG_LOCAL || NULL == cpv->v.v) continue;
284 switch (cpv->k_id) {
285 case 1: /* deflate.allowed-encodings */
286 case 14:/* deflate.params */
287 free(cpv->v.v);
288 break;
289 default:
290 break;
291 }
292 }
293 }
294 }
295
296 #if defined(_WIN32) && !defined(__CYGWIN__)
297 #define mkdir(x,y) mkdir(x)
298 #endif
299
mkdir_for_file(char * fn)300 static int mkdir_for_file (char *fn) {
301 for (char *p = fn; (p = strchr(p + 1, '/')) != NULL; ) {
302 if (p[1] == '\0') return 0; /* ignore trailing slash */
303 *p = '\0';
304 int rc = mkdir(fn, 0700);
305 *p = '/';
306 if (0 != rc && errno != EEXIST) return -1;
307 }
308 return 0;
309 }
310
mkdir_recursive(char * dir)311 static int mkdir_recursive (char *dir) {
312 return 0 == mkdir_for_file(dir) && (0 == mkdir(dir,0700) || errno == EEXIST)
313 ? 0
314 : -1;
315 }
316
mod_deflate_cache_file_name(request_st * const r,const buffer * cache_dir,const buffer * const etag)317 static buffer * mod_deflate_cache_file_name(request_st * const r, const buffer *cache_dir, const buffer * const etag) {
318 /* XXX: future: for shorter paths into the cache, we could checksum path,
319 * and then shard it to avoid a huge single directory.
320 * Alternatively, could use &r->uri.path, minus any
321 * (matching) &r->pathinfo suffix, with result url-encoded
322 * Alternative, we could shard etag which is already our "checksum" */
323 #ifdef __COVERITY__ /* coverity misses etaglen already checked >= 2 earlier */
324 force_assert(buffer_clen(etag) >= 2);
325 #endif
326 buffer * const tb = r->tmp_buf;
327 buffer_copy_path_len2(tb, BUF_PTR_LEN(cache_dir),
328 BUF_PTR_LEN(&r->physical.path));
329 buffer_append_str2(tb, CONST_STR_LEN("-"), /*(strip surrounding '"')*/
330 etag->ptr+1, buffer_clen(etag)-2);
331 return tb;
332 }
333
mod_deflate_cache_file_open(handler_ctx * const hctx,const buffer * const fn)334 static void mod_deflate_cache_file_open (handler_ctx * const hctx, const buffer * const fn) {
335 /* race exists whereby up to # workers might attempt to compress same
336 * file at same time if requested at same time, but this is unlikely
337 * and resolves itself by atomic rename into place when done */
338 const uint32_t fnlen = buffer_clen(fn);
339 hctx->cache_fn = ck_malloc(fnlen+1+LI_ITOSTRING_LENGTH+1);
340 memcpy(hctx->cache_fn, fn->ptr, fnlen);
341 hctx->cache_fn[fnlen] = '.';
342 const size_t ilen =
343 li_itostrn(hctx->cache_fn+fnlen+1, LI_ITOSTRING_LENGTH, getpid());
344 hctx->cache_fn[fnlen+1+ilen] = '\0';
345 hctx->cache_fd = fdevent_open_cloexec(hctx->cache_fn, 1, O_RDWR|O_CREAT, 0600);
346 if (-1 == hctx->cache_fd) {
347 free(hctx->cache_fn);
348 hctx->cache_fn = NULL;
349 }
350 }
351
mod_deflate_cache_file_finish(request_st * const r,handler_ctx * const hctx,const buffer * const fn)352 static int mod_deflate_cache_file_finish (request_st * const r, handler_ctx * const hctx, const buffer * const fn) {
353 if (0 != fdevent_rename(hctx->cache_fn, fn->ptr))
354 return -1;
355 free(hctx->cache_fn);
356 hctx->cache_fn = NULL;
357 chunkqueue_reset(&r->write_queue);
358 int rc = http_chunk_append_file_fd(r, fn, hctx->cache_fd, hctx->bytes_out);
359 hctx->cache_fd = -1;
360 return rc;
361 }
362
mod_deflate_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)363 static void mod_deflate_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
364 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
365 case 0: /* deflate.mimetypes */
366 pconf->mimetypes = cpv->v.a;
367 break;
368 case 1: /* deflate.allowed-encodings */
369 if (cpv->vtype == T_CONFIG_LOCAL)
370 pconf->allowed_encodings = cpv->v.v;
371 break;
372 case 2: /* deflate.max-compress-size */
373 pconf->max_compress_size = cpv->v.u;
374 break;
375 case 3: /* deflate.min-compress-size */
376 pconf->min_compress_size = cpv->v.shrt;
377 break;
378 case 4: /* deflate.compression-level */
379 pconf->compression_level = (short)cpv->v.shrt;
380 break;
381 case 5: /* deflate.output-buffer-size */
382 pconf->output_buffer_size = cpv->v.shrt;
383 break;
384 case 6: /* deflate.work-block-size */
385 pconf->work_block_size = cpv->v.shrt;
386 break;
387 case 7: /* deflate.max-loadavg */
388 pconf->max_loadavg = cpv->v.d;
389 break;
390 case 8: /* deflate.cache-dir */
391 pconf->cache_dir = cpv->v.b;
392 break;
393 #if 0 /*(cpv->k_id remapped in mod_deflate_set_defaults())*/
394 case 9: /* compress.filetype */
395 pconf->mimetypes = cpv->v.a;
396 break;
397 case 10:/* compress.allowed-encodings */
398 if (cpv->vtype == T_CONFIG_LOCAL)
399 pconf->allowed_encodings = cpv->v.v;
400 break;
401 case 11:/* compress.cache-dir */
402 pconf->cache_dir = cpv->v.b;
403 break;
404 case 12:/* compress.max-filesize */
405 pconf->max_compress_size = cpv->v.u;
406 break;
407 case 13:/* compress.max-loadavg */
408 pconf->max_loadavg = cpv->v.d;
409 break;
410 #endif
411 case 14:/* deflate.params */
412 if (cpv->vtype == T_CONFIG_LOCAL)
413 pconf->params = cpv->v.v;
414 break;
415 default:/* should not happen */
416 return;
417 }
418 }
419
mod_deflate_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)420 static void mod_deflate_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
421 do {
422 mod_deflate_merge_config_cpv(pconf, cpv);
423 } while ((++cpv)->k_id != -1);
424 }
425
mod_deflate_patch_config(request_st * const r,plugin_data * const p)426 static void mod_deflate_patch_config(request_st * const r, plugin_data * const p) {
427 memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
428 for (int i = 1, used = p->nconfig; i < used; ++i) {
429 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
430 mod_deflate_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
431 }
432 }
433
mod_deflate_parse_params(const array * const a,log_error_st * const errh)434 static encparms * mod_deflate_parse_params(const array * const a, log_error_st * const errh) {
435 encparms * const params = ck_calloc(1, sizeof(encparms));
436
437 /* set defaults */
438 #ifdef USE_ZLIB
439 params->gzip.clevel = 0; /*(unset)*/
440 params->gzip.windowBits = MAX_WBITS;
441 params->gzip.memLevel = 8;
442 params->gzip.strategy = Z_DEFAULT_STRATEGY;
443 #endif
444 #ifdef USE_BROTLI
445 /* BROTLI_DEFAULT_QUALITY is 11 and can be *very* time-consuming */
446 params->brotli.quality = 5;
447 params->brotli.window = BROTLI_DEFAULT_WINDOW;
448 params->brotli.mode = BROTLI_MODE_GENERIC;
449 #endif
450 #ifdef USE_ZSTD
451 params->zstd.clevel = ZSTD_CLEVEL_DEFAULT;
452 params->zstd.strategy = 0; /*(use default strategy)*/
453 params->zstd.windowLog = 0;/*(use default windowLog)*/
454 #endif
455 #ifdef USE_BZ2LIB
456 params->bzip2.clevel = 0; /*(unset)*/
457 #endif
458
459 for (uint32_t i = 0; i < a->used; ++i) {
460 const data_unset * const du = a->data[i];
461 #if defined(USE_ZLIB) || defined(USE_BZ2LIB) || defined(USE_BROTLI) \
462 || defined(USE_ZSTD)
463 int32_t v = config_plugin_value_to_int32(du, -1);
464 #endif
465 #ifdef USE_BROTLI
466 if (buffer_eq_icase_slen(&du->key,
467 CONST_STR_LEN("BROTLI_PARAM_QUALITY"))) {
468 /*(future: could check for string and then look for and translate
469 * BROTLI_DEFAULT_QUALITY BROTLI_MIN_QUALITY BROTLI_MAX_QUALITY)*/
470 if (BROTLI_MIN_QUALITY <= v && v <= BROTLI_MAX_QUALITY)
471 params->brotli.quality = (uint32_t)v; /* 0 .. 11 */
472 else
473 log_error(errh, __FILE__, __LINE__,
474 "invalid value for BROTLI_PARAM_QUALITY");
475 continue;
476 }
477 if (buffer_eq_icase_slen(&du->key,
478 CONST_STR_LEN("BROTLI_PARAM_LGWIN"))) {
479 /*(future: could check for string and then look for and translate
480 * BROTLI_DEFAULT_WINDOW
481 * BROTLI_MIN_WINDOW_BITS BROTLI_MAX_WINDOW_BITS)*/
482 if (BROTLI_MIN_WINDOW_BITS <= v && v <= BROTLI_MAX_WINDOW_BITS)
483 params->brotli.window = (uint32_t)v; /* 10 .. 24 */
484 else
485 log_error(errh, __FILE__, __LINE__,
486 "invalid value for BROTLI_PARAM_LGWIN");
487 continue;
488 }
489 if (buffer_eq_icase_slen(&du->key,
490 CONST_STR_LEN("BROTLI_PARAM_MODE"))) {
491 /*(future: could check for string and then look for and translate
492 * BROTLI_MODE_GENERIC BROTLI_MODE_TEXT BROTLI_MODE_FONT)*/
493 if (0 <= v && v <= 2)
494 params->brotli.mode = (uint32_t)v; /* 0 .. 2 */
495 else
496 log_error(errh, __FILE__, __LINE__,
497 "invalid value for BROTLI_PARAM_MODE");
498 continue;
499 }
500 #endif
501 #ifdef USE_ZSTD
502 if (buffer_eq_icase_slen(&du->key,
503 CONST_STR_LEN("ZSTD_c_compressionLevel"))) {
504 params->zstd.clevel = v;
505 /*(not warning if number parse error. future: to detect, could
506 * use absurd default to config_plugin_value_to_int32 to detect)*/
507 continue;
508 }
509 #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
510 /*(XXX: (selected) experimental API params in zstd v1.4.0)*/
511 if (buffer_eq_icase_slen(&du->key,
512 CONST_STR_LEN("ZSTD_c_strategy"))) {
513 /*(future: could check for string and then look for and translate
514 * enum ZSTD_strategy ZSTD_STRATEGY_MIN ZSTD_STRATEGY_MAX)*/
515 #ifndef ZSTD_STRATEGY_MIN
516 #define ZSTD_STRATEGY_MIN 1
517 #endif
518 #ifndef ZSTD_STRATEGY_MAX
519 #define ZSTD_STRATEGY_MAX 9
520 #endif
521 if (ZSTD_STRATEGY_MIN <= v && v <= ZSTD_STRATEGY_MAX)
522 params->zstd.strategy = v; /* 1 .. 9 */
523 else
524 log_error(errh, __FILE__, __LINE__,
525 "invalid value for ZSTD_c_strategy");
526 continue;
527 }
528 if (buffer_eq_icase_slen(&du->key,
529 CONST_STR_LEN("ZSTD_c_windowLog"))) {
530 /*(future: could check for string and then look for and translate
531 * ZSTD_WINDOWLOG_MIN ZSTD_WINDOWLOG_MAX)*/
532 #ifndef ZSTD_WINDOWLOG_MIN
533 #define ZSTD_WINDOWLOG_MIN 10
534 #endif
535 #ifndef ZSTD_WINDOWLOG_MAX_32
536 #define ZSTD_WINDOWLOG_MAX_32 30
537 #endif
538 #ifndef ZSTD_WINDOWLOG_MAX_64
539 #define ZSTD_WINDOWLOG_MAX_64 31
540 #endif
541 #ifndef ZSTD_WINDOWLOG_MAX
542 #define ZSTD_WINDOWLOG_MAX \
543 (sizeof(size_t)==4 ? ZSTD_WINDOWLOG_MAX_32 : ZSTD_WINDOWLOG_MAX_64)
544 #endif
545 if (ZSTD_WINDOWLOG_MIN <= v && v <= ZSTD_WINDOWLOG_MAX)
546 params->zstd.windowLog = v;/* 10 .. 31 *//*(30 max for 32-bit)*/
547 else
548 log_error(errh, __FILE__, __LINE__,
549 "invalid value for ZSTD_c_windowLog");
550 continue;
551 }
552 #endif
553 #endif
554 #ifdef USE_ZLIB
555 if (buffer_eq_icase_slen(&du->key,
556 CONST_STR_LEN("gzip.level"))) {
557 if (1 <= v && v <= 9)
558 params->gzip.clevel = v; /* 1 .. 9 */
559 else
560 log_error(errh, __FILE__, __LINE__,
561 "invalid value for gzip.level");
562 continue;
563 }
564 if (buffer_eq_icase_slen(&du->key,
565 CONST_STR_LEN("gzip.windowBits"))) {
566 if (9 <= v && v <= 15)
567 params->gzip.windowBits = v; /* 9 .. 15 */
568 else
569 log_error(errh, __FILE__, __LINE__,
570 "invalid value for gzip.windowBits");
571 continue;
572 }
573 if (buffer_eq_icase_slen(&du->key,
574 CONST_STR_LEN("gzip.memLevel"))) {
575 if (1 <= v && v <= 9)
576 params->gzip.memLevel = v; /* 1 .. 9 */
577 else
578 log_error(errh, __FILE__, __LINE__,
579 "invalid value for gzip.memLevel");
580 continue;
581 }
582 if (buffer_eq_icase_slen(&du->key,
583 CONST_STR_LEN("gzip.strategy"))) {
584 /*(future: could check for string and then look for and translate
585 * Z_DEFAULT_STRATEGY Z_FILTERED Z_HUFFMAN_ONLY Z_RLE Z_FIXED)*/
586 if (0 <= v && v <= 4)
587 params->gzip.strategy = v; /* 0 .. 4 */
588 else
589 log_error(errh, __FILE__, __LINE__,
590 "invalid value for gzip.strategy");
591 continue;
592 }
593 #endif
594 #ifdef USE_BZ2LIB
595 if (buffer_eq_icase_slen(&du->key,
596 CONST_STR_LEN("bzip2.blockSize100k"))) {
597 if (1 <= v && v <= 9) /*(bzip2 blockSize100k param)*/
598 params->bzip2.clevel = v; /* 1 .. 9 */
599 else
600 log_error(errh, __FILE__, __LINE__,
601 "invalid value for bzip2.blockSize100k");
602 continue;
603 }
604 #endif
605 log_error(errh, __FILE__, __LINE__,
606 "unrecognized param: %s", du->key.ptr);
607 }
608
609 return params;
610 }
611
mod_deflate_encodings_to_flags(const array * encodings)612 static uint16_t * mod_deflate_encodings_to_flags(const array *encodings) {
613 if (encodings->used) {
614 uint16_t * const x = ck_calloc(encodings->used+1, sizeof(short));
615 int i = 0;
616 for (uint32_t j = 0; j < encodings->used; ++j) {
617 #if defined(USE_ZLIB) || defined(USE_BZ2LIB) || defined(USE_BROTLI) \
618 || defined(USE_ZSTD)
619 data_string *ds = (data_string *)encodings->data[j];
620 #endif
621 #ifdef USE_ZLIB /* "gzip", "x-gzip" */
622 if (NULL != strstr(ds->value.ptr, "gzip"))
623 x[i++] = HTTP_ACCEPT_ENCODING_GZIP
624 | HTTP_ACCEPT_ENCODING_X_GZIP;
625 if (NULL != strstr(ds->value.ptr, "deflate"))
626 x[i++] = HTTP_ACCEPT_ENCODING_DEFLATE;
627 /*
628 if (NULL != strstr(ds->value.ptr, "compress"))
629 x[i++] = HTTP_ACCEPT_ENCODING_COMPRESS;
630 */
631 #endif
632 #ifdef USE_BZ2LIB /* "bzip2", "x-bzip2" */
633 if (NULL != strstr(ds->value.ptr, "bzip2"))
634 x[i++] = HTTP_ACCEPT_ENCODING_BZIP2
635 | HTTP_ACCEPT_ENCODING_X_BZIP2;
636 #endif
637 #ifdef USE_BROTLI /* "br" (also accepts "brotli") */
638 if (NULL != strstr(ds->value.ptr, "br"))
639 x[i++] = HTTP_ACCEPT_ENCODING_BR;
640 #endif
641 #ifdef USE_ZSTD
642 if (NULL != strstr(ds->value.ptr, "zstd"))
643 x[i++] = HTTP_ACCEPT_ENCODING_ZSTD;
644 #endif
645 }
646 x[i] = 0; /* end of list */
647 return x;
648 }
649 else {
650 /* default encodings */
651 uint16_t * const x = ck_calloc(4+1, sizeof(short));
652 int i = 0;
653 #ifdef USE_ZSTD
654 x[i++] = HTTP_ACCEPT_ENCODING_ZSTD;
655 #endif
656 #ifdef USE_BROTLI
657 x[i++] = HTTP_ACCEPT_ENCODING_BR;
658 #endif
659 #ifdef USE_BZ2LIB
660 x[i++] = HTTP_ACCEPT_ENCODING_BZIP2
661 | HTTP_ACCEPT_ENCODING_X_BZIP2;
662 #endif
663 #ifdef USE_ZLIB
664 x[i++] = HTTP_ACCEPT_ENCODING_GZIP
665 | HTTP_ACCEPT_ENCODING_X_GZIP
666 | HTTP_ACCEPT_ENCODING_DEFLATE;
667 #endif
668 x[i] = 0; /* end of list */
669 return x;
670 }
671 }
672
SETDEFAULTS_FUNC(mod_deflate_set_defaults)673 SETDEFAULTS_FUNC(mod_deflate_set_defaults) {
674 static const config_plugin_keys_t cpk[] = {
675 { CONST_STR_LEN("deflate.mimetypes"),
676 T_CONFIG_ARRAY_VLIST,
677 T_CONFIG_SCOPE_CONNECTION }
678 ,{ CONST_STR_LEN("deflate.allowed-encodings"),
679 T_CONFIG_ARRAY_VLIST,
680 T_CONFIG_SCOPE_CONNECTION }
681 ,{ CONST_STR_LEN("deflate.max-compress-size"),
682 T_CONFIG_INT,
683 T_CONFIG_SCOPE_CONNECTION }
684 ,{ CONST_STR_LEN("deflate.min-compress-size"),
685 T_CONFIG_SHORT,
686 T_CONFIG_SCOPE_CONNECTION }
687 ,{ CONST_STR_LEN("deflate.compression-level"),
688 T_CONFIG_SHORT,
689 T_CONFIG_SCOPE_CONNECTION }
690 ,{ CONST_STR_LEN("deflate.output-buffer-size"),
691 T_CONFIG_SHORT,
692 T_CONFIG_SCOPE_CONNECTION }
693 ,{ CONST_STR_LEN("deflate.work-block-size"),
694 T_CONFIG_SHORT,
695 T_CONFIG_SCOPE_CONNECTION }
696 ,{ CONST_STR_LEN("deflate.max-loadavg"),
697 T_CONFIG_STRING,
698 T_CONFIG_SCOPE_CONNECTION }
699 ,{ CONST_STR_LEN("deflate.cache-dir"),
700 T_CONFIG_STRING,
701 T_CONFIG_SCOPE_CONNECTION }
702 ,{ CONST_STR_LEN("compress.filetype"),
703 T_CONFIG_ARRAY_VLIST,
704 T_CONFIG_SCOPE_CONNECTION }
705 ,{ CONST_STR_LEN("compress.allowed-encodings"),
706 T_CONFIG_ARRAY_VLIST,
707 T_CONFIG_SCOPE_CONNECTION }
708 ,{ CONST_STR_LEN("compress.cache-dir"),
709 T_CONFIG_STRING,
710 T_CONFIG_SCOPE_CONNECTION }
711 ,{ CONST_STR_LEN("compress.max-filesize"),
712 T_CONFIG_INT,
713 T_CONFIG_SCOPE_CONNECTION }
714 ,{ CONST_STR_LEN("compress.max-loadavg"),
715 T_CONFIG_STRING,
716 T_CONFIG_SCOPE_CONNECTION }
717 ,{ CONST_STR_LEN("deflate.params"),
718 T_CONFIG_ARRAY_KVANY,
719 T_CONFIG_SCOPE_CONNECTION }
720 ,{ NULL, 0,
721 T_CONFIG_UNSET,
722 T_CONFIG_SCOPE_UNSET }
723 };
724
725 plugin_data * const p = p_d;
726 if (!config_plugin_values_init(srv, p, cpk, "mod_deflate"))
727 return HANDLER_ERROR;
728
729 /* process and validate config directives
730 * (init i to 0 if global context; to 1 to skip empty global context) */
731 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
732 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
733 for (; -1 != cpv->k_id; ++cpv) {
734 switch (cpv->k_id) {
735 case 9: /* compress.filetype */
736 log_error(srv->errh, __FILE__, __LINE__,
737 "DEPRECATED: %s replaced with deflate.mimetypes",
738 cpk[cpv->k_id].k);
739 cpv->k_id = 0; /* deflate.mimetypes */
740 __attribute_fallthrough__
741 case 0: /* deflate.mimetypes */
742 /* mod_deflate matches mimetype as prefix of Content-Type
743 * so ignore '*' at end of mimetype for end-user flexibility
744 * in specifying trailing wildcard to grouping of mimetypes */
745 for (uint32_t m = 0; m < cpv->v.a->used; ++m) {
746 buffer *mimetype=&((data_string *)cpv->v.a->data[m])->value;
747 size_t len = buffer_clen(mimetype);
748 if (len > 2 && mimetype->ptr[len-1] == '*')
749 buffer_truncate(mimetype, len-1);
750 if (buffer_eq_slen(mimetype,
751 CONST_STR_LEN("application/javascript")))
752 buffer_copy_string_len(mimetype, "text/javascript", 15);
753 }
754 if (0 == cpv->v.a->used) cpv->v.a = NULL;
755 break;
756 case 10:/* compress.allowed-encodings */
757 log_error(srv->errh, __FILE__, __LINE__,
758 "DEPRECATED: %s replaced with deflate.allowed-encodings",
759 cpk[cpv->k_id].k);
760 cpv->k_id = 1; /* deflate.allowed-encodings */
761 __attribute_fallthrough__
762 case 1: /* deflate.allowed-encodings */
763 cpv->v.v = mod_deflate_encodings_to_flags(cpv->v.a);
764 cpv->vtype = T_CONFIG_LOCAL;
765 break;
766 case 12:/* compress.max-filesize */
767 log_error(srv->errh, __FILE__, __LINE__,
768 "DEPRECATED: %s replaced with deflate.max-compress-size",
769 cpk[cpv->k_id].k);
770 cpv->k_id = 2; /* deflate.max-compress-size */
771 __attribute_fallthrough__
772 case 2: /* deflate.max-compress-size */
773 case 3: /* deflate.min-compress-size */
774 break;
775 case 4: /* deflate.compression-level */
776 if ((cpv->v.shrt < 1 || cpv->v.shrt > 9)
777 && *(short *)&cpv->v.shrt != -1) {
778 log_error(srv->errh, __FILE__, __LINE__,
779 "compression-level must be between 1 and 9: %hu",
780 cpv->v.shrt);
781 return HANDLER_ERROR;
782 }
783 break;
784 case 5: /* deflate.output-buffer-size */
785 case 6: /* deflate.work-block-size */
786 break;
787 case 13:/* compress.max-loadavg */
788 log_error(srv->errh, __FILE__, __LINE__,
789 "DEPRECATED: %s replaced with deflate.max-loadavg",
790 cpk[cpv->k_id].k);
791 cpv->k_id = 7; /* deflate.max-loadavg */
792 __attribute_fallthrough__
793 case 7: /* deflate.max-loadavg */
794 cpv->v.d = (!buffer_is_blank(cpv->v.b))
795 ? strtod(cpv->v.b->ptr, NULL)
796 : 0.0;
797 break;
798 case 11:/* compress.cache-dir */
799 log_error(srv->errh, __FILE__, __LINE__,
800 "DEPRECATED: %s replaced with deflate.cache-dir",
801 cpk[cpv->k_id].k);
802 cpv->k_id = 8; /* deflate.cache-dir */
803 __attribute_fallthrough__
804 case 8: /* deflate.cache-dir */
805 if (!buffer_is_blank(cpv->v.b)) {
806 buffer *b;
807 *(const buffer **)&b = cpv->v.b;
808 const uint32_t len = buffer_clen(b);
809 if (len > 0 && '/' == b->ptr[len-1])
810 buffer_truncate(b, len-1); /*remove end slash*/
811 struct stat st;
812 if (0 != stat(b->ptr,&st) && 0 != mkdir_recursive(b->ptr)) {
813 log_perror(srv->errh, __FILE__, __LINE__,
814 "can't stat %s %s", cpk[cpv->k_id].k, b->ptr);
815 return HANDLER_ERROR;
816 }
817 }
818 else
819 cpv->v.b = NULL;
820 break;
821 #if 0 /*(handled further above)*/
822 case 9: /* compress.filetype */
823 case 10:/* compress.allowed-encodings */
824 case 11:/* compress.cache-dir */
825 case 12:/* compress.max-filesize */
826 case 13:/* compress.max-loadavg */
827 break;
828 #endif
829 case 14:/* deflate.params */
830 cpv->v.v = mod_deflate_parse_params(cpv->v.a, srv->errh);
831 cpv->vtype = T_CONFIG_LOCAL;
832 break;
833 default:/* should not happen */
834 break;
835 }
836 }
837 }
838
839 p->defaults.max_compress_size = 128*1024; /*(128 MB measured as num KB)*/
840 p->defaults.min_compress_size = 256;
841 p->defaults.compression_level = -1;
842 p->defaults.output_buffer_size = 0;
843 p->defaults.work_block_size = 2048;
844 p->defaults.max_loadavg = 0.0;
845 p->defaults.sync_flush = 0;
846
847 /* initialize p->defaults from global config context */
848 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
849 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
850 if (-1 != cpv->k_id)
851 mod_deflate_merge_config(&p->defaults, cpv);
852 }
853
854 return HANDLER_GO_ON;
855 }
856
857
858 #if defined(USE_ZLIB) || defined(USE_BZ2LIB) || defined(USE_BROTLI) \
859 || defined(USE_ZSTD)
mod_deflate_cache_file_append(handler_ctx * const hctx,const char * out,size_t len)860 static int mod_deflate_cache_file_append (handler_ctx * const hctx, const char *out, size_t len) {
861 ssize_t wr;
862 do {
863 wr = write(hctx->cache_fd, out, len);
864 } while (wr > 0 ? ((out += wr), (len -= (size_t)wr)) : errno == EINTR);
865 return (0 == len) ? 0 : -1;
866 }
867
stream_http_chunk_append_mem(handler_ctx * const hctx,const char * const out,size_t len)868 static int stream_http_chunk_append_mem(handler_ctx * const hctx, const char * const out, size_t len) {
869 if (0 == len) return 0;
870 return (-1 == hctx->cache_fd)
871 ? http_chunk_append_mem(hctx->r, out, len)
872 : mod_deflate_cache_file_append(hctx, out, len);
873 }
874 #endif
875
876
877 #ifdef USE_ZLIB
878
stream_deflate_init(handler_ctx * hctx)879 static int stream_deflate_init(handler_ctx *hctx) {
880 z_stream * const z = &hctx->u.z;
881 z->zalloc = Z_NULL;
882 z->zfree = Z_NULL;
883 z->opaque = Z_NULL;
884 z->total_in = 0;
885 z->total_out = 0;
886 z->next_out = (unsigned char *)hctx->output->ptr;
887 z->avail_out = hctx->output->size;
888
889 const plugin_data * const p = hctx->plugin_data;
890 const encparms * const params = p->conf.params;
891 const int clevel = (NULL != params)
892 ? params->gzip.clevel
893 : p->conf.compression_level;
894 const int wbits = (NULL != params)
895 ? params->gzip.windowBits
896 : MAX_WBITS;
897
898 if (Z_OK != deflateInit2(z,
899 clevel > 0 ? clevel : Z_DEFAULT_COMPRESSION,
900 Z_DEFLATED,
901 (hctx->compression_type == HTTP_ACCEPT_ENCODING_GZIP)
902 ? (wbits | 16) /*(0x10 flags gzip header, trailer)*/
903 : wbits,
904 params ? params->gzip.memLevel : 8,/*default memLevel*/
905 params ? params->gzip.strategy : Z_DEFAULT_STRATEGY)) {
906 return -1;
907 }
908
909 return 0;
910 }
911
stream_deflate_compress(handler_ctx * const hctx,const unsigned char * const start,off_t st_size)912 static int stream_deflate_compress(handler_ctx * const hctx, const unsigned char * const start, off_t st_size) {
913 z_stream * const z = &(hctx->u.z);
914 size_t len;
915
916 /*(unknown whether or not linked zlib was built with ZLIB_CONST defined)*/
917 *((const unsigned char **)&z->next_in) = start;
918 z->avail_in = st_size;
919 hctx->bytes_in += st_size;
920
921 /* compress data */
922 do {
923 if (Z_OK != deflate(z, Z_NO_FLUSH)) return -1;
924
925 if (z->avail_out == 0 || z->avail_in > 0) {
926 len = hctx->output->size - z->avail_out;
927 hctx->bytes_out += len;
928 if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
929 return -1;
930 z->next_out = (unsigned char *)hctx->output->ptr;
931 z->avail_out = hctx->output->size;
932 }
933 } while (z->avail_in > 0);
934
935 return 0;
936 }
937
stream_deflate_flush(handler_ctx * const hctx,int end)938 static int stream_deflate_flush(handler_ctx * const hctx, int end) {
939 z_stream * const z = &(hctx->u.z);
940 const plugin_data *p = hctx->plugin_data;
941 size_t len;
942 int rc = 0;
943 int done;
944
945 /* compress data */
946 do {
947 done = 1;
948 if (end) {
949 rc = deflate(z, Z_FINISH);
950 if (rc == Z_OK) {
951 done = 0;
952 } else if (rc != Z_STREAM_END) {
953 return -1;
954 }
955 } else {
956 if (p->conf.sync_flush) {
957 rc = deflate(z, Z_SYNC_FLUSH);
958 if (rc != Z_OK) return -1;
959 } else if (z->avail_in > 0) {
960 rc = deflate(z, Z_NO_FLUSH);
961 if (rc != Z_OK) return -1;
962 }
963 }
964
965 len = hctx->output->size - z->avail_out;
966 if (z->avail_out == 0 || (len > 0 && (end || p->conf.sync_flush))) {
967 hctx->bytes_out += len;
968 if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
969 return -1;
970 z->next_out = (unsigned char *)hctx->output->ptr;
971 z->avail_out = hctx->output->size;
972 }
973 } while (z->avail_in != 0 || !done);
974
975 return 0;
976 }
977
stream_deflate_end(handler_ctx * hctx)978 static int stream_deflate_end(handler_ctx *hctx) {
979 z_stream * const z = &(hctx->u.z);
980 int rc = deflateEnd(z);
981 if (Z_OK == rc || Z_DATA_ERROR == rc) return 0;
982
983 if (z->msg != NULL) {
984 log_error(hctx->r->conf.errh, __FILE__, __LINE__,
985 "deflateEnd error ret=%d, msg=%s", rc, z->msg);
986 } else {
987 log_error(hctx->r->conf.errh, __FILE__, __LINE__,
988 "deflateEnd error ret=%d", rc);
989 }
990 return -1;
991 }
992
993 #endif
994
995
996 #ifdef USE_BZ2LIB
997
stream_bzip2_init(handler_ctx * hctx)998 static int stream_bzip2_init(handler_ctx *hctx) {
999 bz_stream * const bz = &hctx->u.bz;
1000 bz->bzalloc = NULL;
1001 bz->bzfree = NULL;
1002 bz->opaque = NULL;
1003 bz->total_in_lo32 = 0;
1004 bz->total_in_hi32 = 0;
1005 bz->total_out_lo32 = 0;
1006 bz->total_out_hi32 = 0;
1007 bz->next_out = hctx->output->ptr;
1008 bz->avail_out = hctx->output->size;
1009
1010 const plugin_data * const p = hctx->plugin_data;
1011 const encparms * const params = p->conf.params;
1012 const int clevel = (NULL != params)
1013 ? params->bzip2.clevel
1014 : p->conf.compression_level;
1015
1016 if (BZ_OK != BZ2_bzCompressInit(bz,
1017 clevel > 0
1018 ? p->conf.compression_level
1019 : 9, /* blocksize = 900k */
1020 0, /* verbosity */
1021 0)) { /* workFactor: default */
1022 return -1;
1023 }
1024
1025 return 0;
1026 }
1027
stream_bzip2_compress(handler_ctx * const hctx,const unsigned char * const start,off_t st_size)1028 static int stream_bzip2_compress(handler_ctx * const hctx, const unsigned char * const start, off_t st_size) {
1029 bz_stream * const bz = &(hctx->u.bz);
1030 size_t len;
1031
1032 bz->next_in = (char *)start;
1033 bz->avail_in = st_size;
1034 hctx->bytes_in += st_size;
1035
1036 /* compress data */
1037 do {
1038 if (BZ_RUN_OK != BZ2_bzCompress(bz, BZ_RUN)) return -1;
1039
1040 if (bz->avail_out == 0 || bz->avail_in > 0) {
1041 len = hctx->output->size - bz->avail_out;
1042 hctx->bytes_out += len;
1043 if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
1044 return -1;
1045 bz->next_out = hctx->output->ptr;
1046 bz->avail_out = hctx->output->size;
1047 }
1048 } while (bz->avail_in > 0);
1049
1050 return 0;
1051 }
1052
stream_bzip2_flush(handler_ctx * const hctx,int end)1053 static int stream_bzip2_flush(handler_ctx * const hctx, int end) {
1054 bz_stream * const bz = &(hctx->u.bz);
1055 const plugin_data *p = hctx->plugin_data;
1056 size_t len;
1057 int rc;
1058 int done;
1059
1060 /* compress data */
1061 do {
1062 done = 1;
1063 if (end) {
1064 rc = BZ2_bzCompress(bz, BZ_FINISH);
1065 if (rc == BZ_FINISH_OK) {
1066 done = 0;
1067 } else if (rc != BZ_STREAM_END) {
1068 return -1;
1069 }
1070 } else if (bz->avail_in > 0) {
1071 /* p->conf.sync_flush not implemented here,
1072 * which would loop on BZ_FLUSH while BZ_FLUSH_OK
1073 * until BZ_RUN_OK returned */
1074 rc = BZ2_bzCompress(bz, BZ_RUN);
1075 if (rc != BZ_RUN_OK) {
1076 return -1;
1077 }
1078 }
1079
1080 len = hctx->output->size - bz->avail_out;
1081 if (bz->avail_out == 0 || (len > 0 && (end || p->conf.sync_flush))) {
1082 hctx->bytes_out += len;
1083 if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
1084 return -1;
1085 bz->next_out = hctx->output->ptr;
1086 bz->avail_out = hctx->output->size;
1087 }
1088 } while (bz->avail_in != 0 || !done);
1089
1090 return 0;
1091 }
1092
stream_bzip2_end(handler_ctx * hctx)1093 static int stream_bzip2_end(handler_ctx *hctx) {
1094 bz_stream * const bz = &(hctx->u.bz);
1095 int rc = BZ2_bzCompressEnd(bz);
1096 if (BZ_OK == rc || BZ_DATA_ERROR == rc) return 0;
1097
1098 log_error(hctx->r->conf.errh, __FILE__, __LINE__,
1099 "BZ2_bzCompressEnd error ret=%d", rc);
1100 return -1;
1101 }
1102
1103 #endif
1104
1105
1106 #ifdef USE_BROTLI
1107
stream_br_init(handler_ctx * hctx)1108 static int stream_br_init(handler_ctx *hctx) {
1109 BrotliEncoderState * const br = hctx->u.br =
1110 BrotliEncoderCreateInstance(NULL, NULL, NULL);
1111 if (NULL == br) return -1;
1112
1113 /*(note: we ignore any errors while tuning parameters here)*/
1114 const plugin_data * const p = hctx->plugin_data;
1115 const encparms * const params = p->conf.params;
1116 const uint32_t quality = (NULL != params)
1117 ? params->brotli.quality
1118 : (p->conf.compression_level >= 0) /* 0 .. 11 are valid values */
1119 ? (uint32_t)p->conf.compression_level
1120 : 5;
1121 /* BROTLI_DEFAULT_QUALITY is 11 and can be *very* time-consuming */
1122 if (quality != BROTLI_DEFAULT_QUALITY)
1123 BrotliEncoderSetParameter(br, BROTLI_PARAM_QUALITY, quality);
1124
1125 if (params && params->brotli.window != BROTLI_DEFAULT_WINDOW)
1126 BrotliEncoderSetParameter(br, BROTLI_PARAM_LGWIN,params->brotli.window);
1127
1128 const buffer *vb;
1129 if (params && params->brotli.mode != BROTLI_MODE_GENERIC)
1130 BrotliEncoderSetParameter(br, BROTLI_PARAM_MODE, params->brotli.mode);
1131 else if ((vb = http_header_response_get(hctx->r, HTTP_HEADER_CONTENT_TYPE,
1132 CONST_STR_LEN("Content-Type")))) {
1133 /* BROTLI_MODE_GENERIC vs BROTLI_MODE_TEXT or BROTLI_MODE_FONT */
1134 const uint32_t len = buffer_clen(vb);
1135 if (0 == strncmp(vb->ptr, "text/", sizeof("text/")-1)
1136 || (0 == strncmp(vb->ptr, "application/", sizeof("application/")-1)
1137 && (0 == strncmp(vb->ptr+12,"javascript",sizeof("javascript")-1)
1138 || 0 == strncmp(vb->ptr+12,"json", sizeof("json")-1)
1139 || 0 == strncmp(vb->ptr+12,"xml", sizeof("xml")-1)))
1140 || (len > 4
1141 && (0 == strncmp(vb->ptr+len-5, "+json", sizeof("+json")-1)
1142 || 0 == strncmp(vb->ptr+len-4, "+xml", sizeof("+xml")-1))))
1143 BrotliEncoderSetParameter(br, BROTLI_PARAM_MODE, BROTLI_MODE_TEXT);
1144 else if (0 == strncmp(vb->ptr, "font/", sizeof("font/")-1))
1145 BrotliEncoderSetParameter(br, BROTLI_PARAM_MODE, BROTLI_MODE_FONT);
1146 }
1147
1148 return 0;
1149 }
1150
stream_br_compress(handler_ctx * const hctx,const unsigned char * const start,off_t st_size)1151 static int stream_br_compress(handler_ctx * const hctx, const unsigned char * const start, off_t st_size) {
1152 const uint8_t *in = (uint8_t *)start;
1153 BrotliEncoderState * const br = hctx->u.br;
1154 hctx->bytes_in += st_size;
1155 while (st_size || BrotliEncoderHasMoreOutput(br)) {
1156 size_t insz = ((off_t)((~(uint32_t)0) >> 1) > st_size)
1157 ? (size_t)st_size
1158 : ((~(uint32_t)0) >> 1);
1159 size_t outsz = 0;
1160 BrotliEncoderCompressStream(br, BROTLI_OPERATION_PROCESS,
1161 &insz, &in, &outsz, NULL, NULL);
1162 const uint8_t *out = BrotliEncoderTakeOutput(br, &outsz);
1163 st_size -= (st_size - (off_t)insz);
1164 if (outsz) {
1165 hctx->bytes_out += (off_t)outsz;
1166 if (0 != stream_http_chunk_append_mem(hctx, (char *)out, outsz))
1167 return -1;
1168 }
1169 }
1170 return 0;
1171 }
1172
stream_br_flush(handler_ctx * const hctx,int end)1173 static int stream_br_flush(handler_ctx * const hctx, int end) {
1174 const int brmode = end ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_FLUSH;
1175 BrotliEncoderState * const br = hctx->u.br;
1176 do {
1177 size_t insz = 0;
1178 size_t outsz = 0;
1179 BrotliEncoderCompressStream(br, brmode,
1180 &insz, NULL, &outsz, NULL, NULL);
1181 const uint8_t *out = BrotliEncoderTakeOutput(br, &outsz);
1182 if (outsz) {
1183 hctx->bytes_out += (off_t)outsz;
1184 if (0 != stream_http_chunk_append_mem(hctx, (char *)out, outsz))
1185 return -1;
1186 }
1187 } while (BrotliEncoderHasMoreOutput(br));
1188 return 0;
1189 }
1190
stream_br_end(handler_ctx * hctx)1191 static int stream_br_end(handler_ctx *hctx) {
1192 BrotliEncoderState * const br = hctx->u.br;
1193 BrotliEncoderDestroyInstance(br);
1194 return 0;
1195 }
1196
1197 #endif
1198
1199
1200 #ifdef USE_ZSTD
1201
stream_zstd_init(handler_ctx * hctx)1202 static int stream_zstd_init(handler_ctx *hctx) {
1203 ZSTD_CStream * const cctx = hctx->u.cctx = ZSTD_createCStream();
1204 if (NULL == cctx) return -1;
1205 hctx->output->used = 0;
1206
1207 /*(note: we ignore any errors while tuning parameters here)*/
1208 const plugin_data * const p = hctx->plugin_data;
1209 const encparms * const params = p->conf.params;
1210 if (params) {
1211 if (params->zstd.clevel && params->zstd.clevel != ZSTD_CLEVEL_DEFAULT) {
1212 const int level = params->zstd.clevel;
1213 #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1214 ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, level);
1215 #else
1216 ZSTD_initCStream(cctx, level);
1217 #endif
1218 }
1219 #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1220 if (params->zstd.strategy)
1221 ZSTD_CCtx_setParameter(cctx, ZSTD_c_strategy,
1222 params->zstd.strategy);
1223 if (params->zstd.windowLog)
1224 ZSTD_CCtx_setParameter(cctx, ZSTD_c_windowLog,
1225 params->zstd.windowLog);
1226 #endif
1227 }
1228 else if (p->conf.compression_level >= 0) { /* -1 here is "unset" */
1229 int level = p->conf.compression_level;
1230 #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1231 ZSTD_CCtx_setParameter(cctx, ZSTD_c_strategy, level);
1232 #else
1233 ZSTD_initCStream(cctx, level);
1234 #endif
1235 }
1236 return 0;
1237 }
1238
stream_zstd_compress(handler_ctx * const hctx,const unsigned char * const start,off_t st_size)1239 static int stream_zstd_compress(handler_ctx * const hctx, const unsigned char * const start, off_t st_size) {
1240 ZSTD_CStream * const cctx = hctx->u.cctx;
1241 ZSTD_inBuffer zib = { start, (size_t)st_size, 0 };
1242 ZSTD_outBuffer zob = { hctx->output->ptr,
1243 hctx->output->size,
1244 hctx->output->used };
1245 hctx->output->used = 0;
1246 hctx->bytes_in += st_size;
1247 while (zib.pos < zib.size) {
1248 #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1249 const size_t rv = ZSTD_compressStream2(cctx,&zob,&zib,ZSTD_e_continue);
1250 #else
1251 const size_t rv = ZSTD_compressStream(cctx, &zob, &zib);
1252 #endif
1253 if (ZSTD_isError(rv)) return -1;
1254 if (zib.pos == zib.size) break; /* defer flush */
1255 hctx->bytes_out += (off_t)zob.pos;
1256 if (0 != stream_http_chunk_append_mem(hctx, zob.dst, zob.pos))
1257 return -1;
1258 zob.pos = 0;
1259 }
1260 hctx->output->used = (uint32_t)zob.pos;
1261 return 0;
1262 }
1263
stream_zstd_flush(handler_ctx * const hctx,int end)1264 static int stream_zstd_flush(handler_ctx * const hctx, int end) {
1265 ZSTD_CStream * const cctx = hctx->u.cctx;
1266 #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1267 const ZSTD_EndDirective endOp = end ? ZSTD_e_end : ZSTD_e_flush;
1268 ZSTD_inBuffer zib = { NULL, 0, 0 };
1269 #endif
1270 ZSTD_outBuffer zob = { hctx->output->ptr,
1271 hctx->output->size,
1272 hctx->output->used };
1273 size_t rv;
1274 do {
1275 #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1276 rv = ZSTD_compressStream2(cctx, &zob, &zib, endOp);
1277 #else
1278 rv = end
1279 ? ZSTD_endStream(cctx, &zob)
1280 : ZSTD_flushStream(cctx, &zob);
1281 #endif
1282 if (ZSTD_isError(rv)) return -1;
1283 hctx->bytes_out += (off_t)zob.pos;
1284 if (0 != stream_http_chunk_append_mem(hctx, zob.dst, zob.pos))
1285 return -1;
1286 zob.pos = 0;
1287 } while (0 != rv);
1288 return 0;
1289 }
1290
stream_zstd_end(handler_ctx * hctx)1291 static int stream_zstd_end(handler_ctx *hctx) {
1292 ZSTD_CStream * const cctx = hctx->u.cctx;
1293 ZSTD_freeCStream(cctx);
1294 return 0;
1295 }
1296
1297 #endif
1298
1299
mod_deflate_stream_init(handler_ctx * hctx)1300 static int mod_deflate_stream_init(handler_ctx *hctx) {
1301 switch(hctx->compression_type) {
1302 #ifdef USE_ZLIB
1303 case HTTP_ACCEPT_ENCODING_GZIP:
1304 case HTTP_ACCEPT_ENCODING_DEFLATE:
1305 return stream_deflate_init(hctx);
1306 #endif
1307 #ifdef USE_BZ2LIB
1308 case HTTP_ACCEPT_ENCODING_BZIP2:
1309 return stream_bzip2_init(hctx);
1310 #endif
1311 #ifdef USE_BROTLI
1312 case HTTP_ACCEPT_ENCODING_BR:
1313 return stream_br_init(hctx);
1314 #endif
1315 #ifdef USE_ZSTD
1316 case HTTP_ACCEPT_ENCODING_ZSTD:
1317 return stream_zstd_init(hctx);
1318 #endif
1319 default:
1320 return -1;
1321 }
1322 }
1323
mod_deflate_compress(handler_ctx * const hctx,const unsigned char * const start,off_t st_size)1324 static int mod_deflate_compress(handler_ctx * const hctx, const unsigned char * const start, off_t st_size) {
1325 if (0 == st_size) return 0;
1326 switch(hctx->compression_type) {
1327 #ifdef USE_ZLIB
1328 case HTTP_ACCEPT_ENCODING_GZIP:
1329 case HTTP_ACCEPT_ENCODING_DEFLATE:
1330 return stream_deflate_compress(hctx, start, st_size);
1331 #endif
1332 #ifdef USE_BZ2LIB
1333 case HTTP_ACCEPT_ENCODING_BZIP2:
1334 return stream_bzip2_compress(hctx, start, st_size);
1335 #endif
1336 #ifdef USE_BROTLI
1337 case HTTP_ACCEPT_ENCODING_BR:
1338 return stream_br_compress(hctx, start, st_size);
1339 #endif
1340 #ifdef USE_ZSTD
1341 case HTTP_ACCEPT_ENCODING_ZSTD:
1342 return stream_zstd_compress(hctx, start, st_size);
1343 #endif
1344 default:
1345 UNUSED(start);
1346 return -1;
1347 }
1348 }
1349
mod_deflate_stream_flush(handler_ctx * const hctx,int end)1350 static int mod_deflate_stream_flush(handler_ctx * const hctx, int end) {
1351 if (0 == hctx->bytes_in) return 0;
1352 switch(hctx->compression_type) {
1353 #ifdef USE_ZLIB
1354 case HTTP_ACCEPT_ENCODING_GZIP:
1355 case HTTP_ACCEPT_ENCODING_DEFLATE:
1356 return stream_deflate_flush(hctx, end);
1357 #endif
1358 #ifdef USE_BZ2LIB
1359 case HTTP_ACCEPT_ENCODING_BZIP2:
1360 return stream_bzip2_flush(hctx, end);
1361 #endif
1362 #ifdef USE_BROTLI
1363 case HTTP_ACCEPT_ENCODING_BR:
1364 return stream_br_flush(hctx, end);
1365 #endif
1366 #ifdef USE_ZSTD
1367 case HTTP_ACCEPT_ENCODING_ZSTD:
1368 return stream_zstd_flush(hctx, end);
1369 #endif
1370 default:
1371 UNUSED(end);
1372 return -1;
1373 }
1374 }
1375
mod_deflate_note_ratio(request_st * const r,const off_t bytes_out,const off_t bytes_in)1376 static void mod_deflate_note_ratio(request_st * const r, const off_t bytes_out, const off_t bytes_in) {
1377 /* store compression ratio in environment
1378 * for possible logging by mod_accesslog
1379 * (late in response handling, so not seen by most other modules) */
1380 /*(should be called only at end of successful response compression)*/
1381 if (0 == bytes_in) return;
1382 buffer_append_int(
1383 http_header_env_set_ptr(r, CONST_STR_LEN("ratio")),
1384 bytes_out * 100 / bytes_in);
1385 }
1386
mod_deflate_stream_end(handler_ctx * hctx)1387 static int mod_deflate_stream_end(handler_ctx *hctx) {
1388 switch(hctx->compression_type) {
1389 #ifdef USE_ZLIB
1390 case HTTP_ACCEPT_ENCODING_GZIP:
1391 case HTTP_ACCEPT_ENCODING_DEFLATE:
1392 return stream_deflate_end(hctx);
1393 #endif
1394 #ifdef USE_BZ2LIB
1395 case HTTP_ACCEPT_ENCODING_BZIP2:
1396 return stream_bzip2_end(hctx);
1397 #endif
1398 #ifdef USE_BROTLI
1399 case HTTP_ACCEPT_ENCODING_BR:
1400 return stream_br_end(hctx);
1401 #endif
1402 #ifdef USE_ZSTD
1403 case HTTP_ACCEPT_ENCODING_ZSTD:
1404 return stream_zstd_end(hctx);
1405 #endif
1406 default:
1407 return -1;
1408 }
1409 }
1410
deflate_compress_cleanup(request_st * const r,handler_ctx * const hctx)1411 static int deflate_compress_cleanup(request_st * const r, handler_ctx * const hctx) {
1412 int rc = mod_deflate_stream_end(hctx);
1413
1414 #if 1 /* unnecessary if deflate.min-compress-size is set to a reasonable value */
1415 if (0 == rc && hctx->bytes_in < hctx->bytes_out)
1416 log_error(r->conf.errh, __FILE__, __LINE__,
1417 "uri %s in=%lld smaller than out=%lld", r->target.ptr,
1418 (long long)hctx->bytes_in, (long long)hctx->bytes_out);
1419 #endif
1420
1421 handler_ctx_free(hctx);
1422 return rc;
1423 }
1424
1425
1426 #ifdef HAVE_LIBDEFLATE
1427 #include <libdeflate.h>
1428
1429 __attribute_cold__
1430 __attribute_noinline__
mod_deflate_using_libdeflate_err(handler_ctx * const hctx,buffer * const fn,const int fd)1431 static int mod_deflate_using_libdeflate_err (handler_ctx * const hctx, buffer * const fn, const int fd)
1432 {
1433 if (-1 == fd) {
1434 }
1435 else if (fd == hctx->cache_fd) {
1436 if (0 != ftruncate(fd, 0))
1437 log_perror(hctx->r->conf.errh, __FILE__, __LINE__, "ftruncate");
1438 if (0 != lseek(fd, 0, SEEK_SET))
1439 log_perror(hctx->r->conf.errh, __FILE__, __LINE__, "lseek");
1440 }
1441 else {
1442 if (0 != unlink(fn->ptr))
1443 log_perror(hctx->r->conf.errh, __FILE__, __LINE__, "unlink");
1444 close(fd);
1445 }
1446 buffer_clear(fn); /*(&p->tmp_buf)*/
1447 return 0;
1448 }
1449
1450
mod_deflate_using_libdeflate_sm(handler_ctx * const hctx,const plugin_data * const p)1451 static int mod_deflate_using_libdeflate_sm (handler_ctx * const hctx, const plugin_data * const p)
1452 {
1453 const encparms * const params = p->conf.params;
1454 const int clevel = (NULL != params)
1455 ? params->gzip.clevel
1456 : p->conf.compression_level;
1457 struct libdeflate_compressor * const compressor =
1458 libdeflate_alloc_compressor(clevel > 0 ? clevel : 6);
1459 /* Z_DEFAULT_COMPRESSION -1 not supported */
1460 if (NULL == compressor)
1461 return 0;
1462
1463 char * const in = hctx->r->write_queue.first->mem->ptr;
1464 const size_t in_nbytes = (size_t)hctx->bytes_in;
1465 buffer * const addrb = hctx->output; /*(&p->tmp_buf)*/
1466 size_t sz = buffer_string_space(addrb)+1;
1467 sz = (hctx->compression_type == HTTP_ACCEPT_ENCODING_GZIP)
1468 ? libdeflate_gzip_compress(compressor, in, in_nbytes, addrb->ptr, sz)
1469 : libdeflate_zlib_compress(compressor, in, in_nbytes, addrb->ptr, sz);
1470 libdeflate_free_compressor(compressor);
1471
1472 if (0 == sz) {
1473 buffer_clear(addrb);
1474 return 0;
1475 }
1476
1477 chunkqueue_reset(&hctx->r->write_queue);
1478 hctx->bytes_out = (off_t)sz;
1479 if (0 != stream_http_chunk_append_mem(hctx, addrb->ptr, sz))
1480 return mod_deflate_using_libdeflate_err(hctx, addrb, hctx->cache_fd);
1481
1482 buffer * const vb =
1483 http_header_response_set_ptr(hctx->r, HTTP_HEADER_CONTENT_LENGTH,
1484 CONST_STR_LEN("Content-Length"));
1485 buffer_append_int(vb, hctx->bytes_out);
1486 return 1;
1487 }
1488
1489
1490 #ifdef HAVE_MMAP
1491 #if defined(_LP64) || defined(__LP64__) || defined(_WIN64)
1492
1493 struct mod_deflate_setjmp_params {
1494 struct libdeflate_compressor *compressor;
1495 void *out;
1496 size_t outsz;
1497 };
1498
mod_deflate_using_libdeflate_setjmp_cb(void * dst,const void * src,off_t len)1499 static off_t mod_deflate_using_libdeflate_setjmp_cb (void *dst, const void *src, off_t len)
1500 {
1501 const struct mod_deflate_setjmp_params * const params = dst;
1502 const handler_ctx * const hctx = src;
1503 const chunk * const c = hctx->r->write_queue.first;
1504 const char *in = chunk_file_view_dptr(c->file.view, c->offset);
1505 return (off_t)((hctx->compression_type == HTTP_ACCEPT_ENCODING_GZIP)
1506 ? libdeflate_gzip_compress(params->compressor, in, (size_t)len,
1507 params->out, params->outsz)
1508 : libdeflate_zlib_compress(params->compressor, in, (size_t)len,
1509 params->out, params->outsz));
1510 }
1511
1512
mod_deflate_using_libdeflate(handler_ctx * const hctx,const plugin_data * const p)1513 static int mod_deflate_using_libdeflate (handler_ctx * const hctx, const plugin_data * const p)
1514 {
1515 buffer * const fn = hctx->output; /*(&p->tmp_buf)*/
1516 int fd = hctx->cache_fd;
1517 if (-1 == fd) {
1518 chunkqueue * const cq = &hctx->r->write_queue;
1519 if (cq->tempdirs && cq->tempdir_idx < cq->tempdirs->used) {
1520 data_string *ds =(data_string *)cq->tempdirs->data[cq->tempdir_idx];
1521 buffer_copy_string_len(fn, BUF_PTR_LEN(&ds->value));
1522 }
1523 else
1524 buffer_copy_string_len(fn, CONST_STR_LEN("/var/tmp"));
1525 buffer_append_path_len(fn, CONST_STR_LEN("lighttpd-XXXXXX"));
1526 fd = fdevent_mkostemp(fn->ptr, 0);
1527 if (-1 == fd) return 0;
1528 }
1529
1530 const size_t sz =
1531 libdeflate_zlib_compress_bound(NULL, (size_t)hctx->bytes_in);
1532 /*(XXX: consider trying posix_fallocate() first,
1533 * with fallback to ftrunctate() if EOPNOTSUPP)*/
1534 if (0 != ftruncate(fd, (off_t)sz)) {
1535 log_perror(hctx->r->conf.errh, __FILE__, __LINE__, "ftruncate");
1536 return mod_deflate_using_libdeflate_err(hctx, fn, fd);
1537 }
1538
1539 /*void *addr = mmap(NULL, sz, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);*/
1540 void * const addr = mmap(NULL, sz, PROT_WRITE, MAP_SHARED, fd, 0);
1541 if (MAP_FAILED == addr) {
1542 log_perror(hctx->r->conf.errh, __FILE__, __LINE__, "mmap");
1543 return mod_deflate_using_libdeflate_err(hctx, fn, fd);
1544 }
1545
1546 const encparms * const params = p->conf.params;
1547 const int clevel = (NULL != params)
1548 ? params->gzip.clevel
1549 : p->conf.compression_level;
1550 struct libdeflate_compressor * const compressor =
1551 libdeflate_alloc_compressor(clevel > 0 ? clevel : 6);
1552 /* Z_DEFAULT_COMPRESSION -1 not supported */
1553 if (NULL != compressor) {
1554 struct mod_deflate_setjmp_params outparams = { compressor, addr, sz };
1555 hctx->bytes_out =
1556 sys_setjmp_eval3(mod_deflate_using_libdeflate_setjmp_cb,
1557 &outparams, hctx, hctx->bytes_in);
1558 libdeflate_free_compressor(compressor);
1559 }
1560
1561 /*(XXX: we theoretically could assign mmap to FILE_CHUNK in output
1562 * r->write_queue, for potential use by TLS modules, or if not using
1563 * sendfile in network_write.c, but we do not (easily) know if either
1564 * are configured or in use for this request. Might consider heuristic:
1565 * (*srv->srvconf.network_backend->ptr == 'w') (writev or write), or
1566 * to check con->is_ssl_sock.) These files would be safe to pass to
1567 * TLS modules to read from mmap without catching SIGBUS since these files
1568 * are created by lighttpd, either in deflate cache or as temporary file */
1569 if (0 != munmap(addr, sz))
1570 log_perror(hctx->r->conf.errh, __FILE__, __LINE__, "munmap");
1571
1572 if (0 == hctx->bytes_out) {
1573 const chunk * const c = hctx->r->write_queue.first;
1574 log_error(hctx->r->conf.errh, __FILE__, __LINE__,
1575 "SIGBUS in mmap: %s %d", c->mem->ptr, c->file.fd);
1576 }
1577 else if (0 != ftruncate(fd, hctx->bytes_out))
1578 hctx->bytes_out = 0;
1579
1580 if (0 == hctx->bytes_out)
1581 return mod_deflate_using_libdeflate_err(hctx, fn, fd);
1582
1583 if (fd != hctx->cache_fd) {
1584 chunkqueue * const cq = &hctx->r->write_queue;
1585 chunkqueue_reset(cq);
1586 http_chunk_append_file_fd_range(hctx->r, fn, fd, 0, hctx->bytes_out);
1587 cq->last->file.is_temp = 1;
1588 }
1589
1590 buffer * const vb =
1591 http_header_response_set_ptr(hctx->r, HTTP_HEADER_CONTENT_LENGTH,
1592 CONST_STR_LEN("Content-Length"));
1593 buffer_append_int(vb, hctx->bytes_out);
1594 return 1;
1595 }
1596
1597 #endif /* defined(_LP64) || defined(__LP64__) || defined(_WIN64) */
1598 #endif /* HAVE_MMAP */
1599
1600 #endif /* HAVE_LIBDEFLATE */
1601
1602
mod_deflate_file_chunk_no_mmap(request_st * const r,handler_ctx * const hctx,const chunk * const c,off_t n)1603 static off_t mod_deflate_file_chunk_no_mmap(request_st * const r, handler_ctx * const hctx, const chunk * const c, off_t n)
1604 {
1605 const off_t insz = n;
1606 const size_t psz = (n < 2*1024*1024) ? (size_t)n : 2*1024*1024;
1607 char * const p = malloc(psz);
1608 if (NULL == p) {
1609 log_perror(r->conf.errh, __FILE__, __LINE__, "malloc");
1610 return -1;
1611 }
1612
1613 ssize_t rd = 0;
1614 for (n = 0; n < insz; n += rd) {
1615 rd = chunk_file_pread(c->file.fd, p, (size_t)psz, c->offset+n);
1616 if (__builtin_expect( (rd > 0), 1)) {
1617 if (0 == mod_deflate_compress(hctx, (unsigned char *)p, rd))
1618 continue;
1619 /*(else error trace printed upon return)*/
1620 }
1621 else if (-1 == rd)
1622 log_perror(r->conf.errh, __FILE__, __LINE__,
1623 "reading %s failed", c->mem->ptr);
1624 else /*(0 == rd)*/
1625 log_error(r->conf.errh, __FILE__, __LINE__,
1626 "file truncated %s", c->mem->ptr);
1627 n = -1;
1628 break;
1629 }
1630
1631 free(p);
1632 return n;
1633 }
1634
1635
1636 #ifdef ENABLE_MMAP
1637
mod_deflate_file_chunk_setjmp_cb(void * dst,const void * src,off_t len)1638 static off_t mod_deflate_file_chunk_setjmp_cb (void *dst, const void *src, off_t len)
1639 {
1640 return mod_deflate_compress(dst, (const unsigned char *)src, len);
1641 }
1642
1643
mod_deflate_file_chunk_mmap(request_st * const r,handler_ctx * const hctx,chunk * const c,off_t n)1644 static off_t mod_deflate_file_chunk_mmap(request_st * const r, handler_ctx * const hctx, chunk * const c, off_t n)
1645 {
1646 /* n is length of entire file since server blocks while compressing
1647 * (mod_deflate is not recommended for large files;
1648 * mod_deflate default upper limit is 128MB; deflate.max-compress-size) */
1649
1650 const chunk_file_view * const restrict cfv = (!c->file.is_temp)
1651 ? chunkqueue_chunk_file_view(c, n, r->conf.errh)
1652 : NULL;
1653 if (NULL == cfv)
1654 return mod_deflate_file_chunk_no_mmap(r, hctx, c, n);
1655
1656 const char * const p = chunk_file_view_dptr(cfv, c->offset);
1657 off_t len = chunk_file_view_dlen(cfv, c->offset);
1658 if (len > n) len = n;
1659 off_t rc = sys_setjmp_eval3(mod_deflate_file_chunk_setjmp_cb, hctx, p, len);
1660 if (__builtin_expect( (rc < 0), 0)) {
1661 if (errno == EFAULT)
1662 log_error(r->conf.errh, __FILE__, __LINE__,
1663 "SIGBUS in mmap: %s %d", c->mem->ptr, c->file.fd);
1664 else
1665 log_error(r->conf.errh, __FILE__, __LINE__, "compress failed.");
1666 len = -1; /*return -1;*/
1667 }
1668 return len;
1669 }
1670
1671 #endif
1672
1673
mod_deflate_file_chunk(request_st * const r,handler_ctx * const hctx,chunk * const c,off_t n)1674 static off_t mod_deflate_file_chunk(request_st * const r, handler_ctx * const hctx, chunk * const c, off_t n) {
1675 if (-1 == c->file.fd) { /* open the file if not already open */
1676 if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, r->conf.follow_symlink, O_RDONLY, 0))) {
1677 log_perror(r->conf.errh, __FILE__, __LINE__, "open failed %s", c->mem->ptr);
1678 return -1;
1679 }
1680 }
1681 #ifdef ENABLE_MMAP
1682 return mod_deflate_file_chunk_mmap(r, hctx, c, n);
1683 #else
1684 return mod_deflate_file_chunk_no_mmap(r, hctx, c, n);
1685 #endif
1686 }
1687
1688
deflate_compress_response(request_st * const r,handler_ctx * const hctx)1689 static handler_t deflate_compress_response(request_st * const r, handler_ctx * const hctx) {
1690 off_t len, max;
1691 int close_stream;
1692
1693 /* move all chunk from write_queue into our in_queue, then adjust
1694 * counters since r->write_queue is reused for compressed output */
1695 chunkqueue * const cq = &r->write_queue;
1696 len = chunkqueue_length(cq);
1697 chunkqueue_remove_finished_chunks(cq);
1698 chunkqueue_append_chunkqueue(&hctx->in_queue, cq);
1699 cq->bytes_in -= len;
1700 cq->bytes_out -= len;
1701
1702 max = chunkqueue_length(&hctx->in_queue);
1703 #if 0
1704 /* calculate max bytes to compress for this call */
1705 if (p->conf.sync_flush && max > (len = p->conf.work_block_size << 10)) {
1706 max = len;
1707 }
1708 #endif
1709
1710 /* Compress chunks from in_queue into chunks for write_queue */
1711 while (max) {
1712 chunk *c = hctx->in_queue.first;
1713
1714 switch(c->type) {
1715 case MEM_CHUNK:
1716 len = buffer_clen(c->mem) - c->offset;
1717 if (len > max) len = max;
1718 if (mod_deflate_compress(hctx, (unsigned char *)c->mem->ptr+c->offset, len) < 0) {
1719 log_error(r->conf.errh, __FILE__, __LINE__, "compress failed.");
1720 return HANDLER_ERROR;
1721 }
1722 break;
1723 case FILE_CHUNK:
1724 len = c->file.length - c->offset;
1725 if (len > max) len = max;
1726 if ((len = mod_deflate_file_chunk(r, hctx, c, len)) < 0) {
1727 log_error(r->conf.errh, __FILE__, __LINE__,
1728 "compress file chunk failed %s", c->mem->ptr);
1729 return HANDLER_ERROR;
1730 }
1731 break;
1732 default:
1733 log_error(r->conf.errh, __FILE__, __LINE__, "%d type not known", c->type);
1734 return HANDLER_ERROR;
1735 }
1736
1737 max -= len;
1738 chunkqueue_mark_written(&hctx->in_queue, len);
1739 }
1740
1741 /*(currently should always be true)*/
1742 /*(current implementation requires response be complete)*/
1743 close_stream = (r->resp_body_finished
1744 && chunkqueue_is_empty(&hctx->in_queue));
1745 if (mod_deflate_stream_flush(hctx, close_stream) < 0) {
1746 log_error(r->conf.errh, __FILE__, __LINE__, "flush error");
1747 return HANDLER_ERROR;
1748 }
1749
1750 return close_stream ? HANDLER_FINISHED : HANDLER_GO_ON;
1751 }
1752
1753
mod_deflate_choose_encoding(const char * value,plugin_data * p,const char ** label)1754 static int mod_deflate_choose_encoding (const char *value, plugin_data *p, const char **label) {
1755 /* get client side support encodings */
1756 int accept_encoding = 0;
1757 #if !defined(USE_ZLIB) && !defined(USE_BZ2LIB) && !defined(USE_BROTLI) \
1758 && !defined(USE_ZSTD)
1759 UNUSED(value);
1760 UNUSED(label);
1761 #else
1762 for (; *value; ++value) {
1763 const char *v;
1764 while (*value == ' ' || *value == ',') ++value;
1765 v = value;
1766 while (*value!=' ' && *value!=',' && *value!=';' && *value!='\0')
1767 ++value;
1768 switch (value - v) {
1769 case 2:
1770 #ifdef USE_BROTLI
1771 if (0 == memcmp(v, "br", 2))
1772 accept_encoding |= HTTP_ACCEPT_ENCODING_BR;
1773 #endif
1774 break;
1775 case 4:
1776 #ifdef USE_ZLIB
1777 if (0 == memcmp(v, "gzip", 4))
1778 accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP;
1779 #endif
1780 #ifdef USE_ZSTD
1781 #ifdef USE_ZLIB
1782 else
1783 #endif
1784 if (0 == memcmp(v, "zstd", 4))
1785 accept_encoding |= HTTP_ACCEPT_ENCODING_ZSTD;
1786 #endif
1787 break;
1788 case 5:
1789 #ifdef USE_BZ2LIB
1790 if (0 == memcmp(v, "bzip2", 5))
1791 accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2;
1792 #endif
1793 break;
1794 case 6:
1795 #ifdef USE_ZLIB
1796 if (0 == memcmp(v, "x-gzip", 6))
1797 accept_encoding |= HTTP_ACCEPT_ENCODING_X_GZIP;
1798 #endif
1799 break;
1800 case 7:
1801 #ifdef USE_ZLIB
1802 if (0 == memcmp(v, "deflate", 7))
1803 accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE;
1804 #endif
1805 #ifdef USE_BZ2LIB
1806 if (0 == memcmp(v, "x-bzip2", 7))
1807 accept_encoding |= HTTP_ACCEPT_ENCODING_X_BZIP2;
1808 #endif
1809 break;
1810 #if 0
1811 case 8:
1812 if (0 == memcmp(v, "identity", 8))
1813 accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY;
1814 else if (0 == memcmp(v, "compress", 8))
1815 accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS;
1816 break;
1817 #endif
1818 default:
1819 break;
1820 }
1821 if (*value == ';') {
1822 while (*value != ',' && *value != '\0') ++value;
1823 }
1824 if (*value == '\0') break;
1825 }
1826 #endif
1827
1828 /* select best matching encoding */
1829 const uint16_t *x = p->conf.allowed_encodings;
1830 if (NULL == x) return 0;
1831 while (*x && !(*x & accept_encoding)) ++x;
1832 accept_encoding &= *x;
1833 #ifdef USE_ZSTD
1834 if (accept_encoding & HTTP_ACCEPT_ENCODING_ZSTD) {
1835 *label = "zstd";
1836 return HTTP_ACCEPT_ENCODING_ZSTD;
1837 } else
1838 #endif
1839 #ifdef USE_BROTLI
1840 if (accept_encoding & HTTP_ACCEPT_ENCODING_BR) {
1841 *label = "br";
1842 return HTTP_ACCEPT_ENCODING_BR;
1843 } else
1844 #endif
1845 #ifdef USE_BZ2LIB
1846 if (accept_encoding & HTTP_ACCEPT_ENCODING_BZIP2) {
1847 *label = "bzip2";
1848 return HTTP_ACCEPT_ENCODING_BZIP2;
1849 } else if (accept_encoding & HTTP_ACCEPT_ENCODING_X_BZIP2) {
1850 *label = "x-bzip2";
1851 return HTTP_ACCEPT_ENCODING_BZIP2;
1852 } else
1853 #endif
1854 #ifdef USE_ZLIB
1855 if (accept_encoding & HTTP_ACCEPT_ENCODING_GZIP) {
1856 *label = "gzip";
1857 return HTTP_ACCEPT_ENCODING_GZIP;
1858 } else if (accept_encoding & HTTP_ACCEPT_ENCODING_X_GZIP) {
1859 *label = "x-gzip";
1860 return HTTP_ACCEPT_ENCODING_GZIP;
1861 } else if (accept_encoding & HTTP_ACCEPT_ENCODING_DEFLATE) {
1862 *label = "deflate";
1863 return HTTP_ACCEPT_ENCODING_DEFLATE;
1864 } else
1865 #endif
1866 if (0 == accept_encoding) {
1867 return 0;
1868 } else {
1869 return 0;
1870 }
1871 }
1872
REQUEST_FUNC(mod_deflate_handle_response_start)1873 REQUEST_FUNC(mod_deflate_handle_response_start) {
1874 plugin_data *p = p_d;
1875 const buffer *vbro;
1876 buffer *vb;
1877 handler_ctx *hctx;
1878 const char *label;
1879 off_t len;
1880 uint32_t etaglen;
1881 int compression_type;
1882 handler_t rc;
1883 int had_vary = 0;
1884
1885 /*(current implementation requires response be complete)*/
1886 if (!r->resp_body_finished) return HANDLER_GO_ON;
1887 if (r->http_method == HTTP_METHOD_HEAD) return HANDLER_GO_ON;
1888 if (light_btst(r->resp_htags, HTTP_HEADER_TRANSFER_ENCODING))
1889 return HANDLER_GO_ON;
1890 if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_ENCODING))
1891 return HANDLER_GO_ON;
1892
1893 /* disable compression for some http status types. */
1894 switch(r->http_status) {
1895 case 100:
1896 case 101:
1897 case 204:
1898 case 205:
1899 case 304:
1900 /* disable compression as we have no response entity */
1901 return HANDLER_GO_ON;
1902 default:
1903 break;
1904 }
1905
1906 mod_deflate_patch_config(r, p);
1907
1908 /* check if deflate configured for any mimetypes */
1909 if (NULL == p->conf.mimetypes) return HANDLER_GO_ON;
1910
1911 /* check if size of response is below min-compress-size or exceeds max*/
1912 /* (r->resp_body_finished checked at top of routine) */
1913 len = chunkqueue_length(&r->write_queue);
1914 if (len <= (off_t)p->conf.min_compress_size) return HANDLER_GO_ON;
1915 if (p->conf.max_compress_size /*(max_compress_size in KB)*/
1916 && len > ((off_t)p->conf.max_compress_size << 10)) {
1917 return HANDLER_GO_ON;
1918 }
1919
1920 /* Check Accept-Encoding for supported encoding. */
1921 vbro = http_header_request_get(r, HTTP_HEADER_ACCEPT_ENCODING, CONST_STR_LEN("Accept-Encoding"));
1922 if (NULL == vbro) return HANDLER_GO_ON;
1923
1924 /* find matching encodings */
1925 compression_type = mod_deflate_choose_encoding(vbro->ptr, p, &label);
1926 if (!compression_type) return HANDLER_GO_ON;
1927
1928 /* Check mimetype in response header "Content-Type" */
1929 if (NULL != (vbro = http_header_response_get(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type")))) {
1930 if (NULL == array_match_value_prefix(p->conf.mimetypes, vbro)) return HANDLER_GO_ON;
1931 } else {
1932 /* If no Content-Type set, compress only if first p->conf.mimetypes value is "" */
1933 data_string *mimetype = (data_string *)p->conf.mimetypes->data[0];
1934 if (!buffer_is_blank(&mimetype->value)) return HANDLER_GO_ON;
1935 }
1936
1937 /* Vary: Accept-Encoding (response might change according to request Accept-Encoding) */
1938 if (NULL != (vb = http_header_response_get(r, HTTP_HEADER_VARY, CONST_STR_LEN("Vary")))) {
1939 had_vary = 1;
1940 if (!http_header_str_contains_token(BUF_PTR_LEN(vb),
1941 CONST_STR_LEN("Accept-Encoding")))
1942 buffer_append_string_len(vb, CONST_STR_LEN(",Accept-Encoding"));
1943 } else {
1944 http_header_response_append(r, HTTP_HEADER_VARY,
1945 CONST_STR_LEN("Vary"),
1946 CONST_STR_LEN("Accept-Encoding"));
1947 }
1948
1949 /* check ETag as is done in http_response_handle_cachable()
1950 * (slightly imperfect (close enough?) match of ETag "000000" to "000000-gzip") */
1951 vb = http_header_response_get(r, HTTP_HEADER_ETAG, CONST_STR_LEN("ETag"));
1952 etaglen = vb ? buffer_clen(vb) : 0;
1953 if (etaglen && light_btst(r->rqst_htags, HTTP_HEADER_IF_NONE_MATCH)) {
1954 const buffer *if_none_match = http_header_request_get(r, HTTP_HEADER_IF_NONE_MATCH, CONST_STR_LEN("If-None-Match"));
1955 if ( r->http_status < 300 /*(want 2xx only)*/
1956 && NULL != if_none_match
1957 && 0 == strncmp(if_none_match->ptr, vb->ptr, etaglen-1)
1958 && if_none_match->ptr[etaglen-1] == '-'
1959 && 0 == strncmp(if_none_match->ptr+etaglen, label, strlen(label))) {
1960
1961 if (http_method_get_head_query(r->http_method)) {
1962 /* modify ETag response header in-place to remove '"' and append '-label"' */
1963 vb->ptr[etaglen-1] = '-'; /*(overwrite end '"')*/
1964 buffer_append_string(vb, label);
1965 buffer_append_char(vb, '"');
1966 r->http_status = 304;
1967 } else {
1968 r->http_status = 412;
1969 }
1970
1971 /* response_start hook occurs after error docs have been handled.
1972 * For now, send back empty response body.
1973 * In the future, might extract the error doc code so that it
1974 * might be run again if response_start hooks return with
1975 * changed http_status and r->handler_module = NULL */
1976 /* clear content length even if 304 since compressed length unknown */
1977 http_response_body_clear(r, 0);
1978
1979 r->resp_body_finished = 1;
1980 r->handler_module = NULL;
1981 return HANDLER_GO_ON;
1982 }
1983 }
1984
1985 if (0.0 < p->conf.max_loadavg && p->conf.max_loadavg < r->con->srv->loadavg[0]) {
1986 return HANDLER_GO_ON;
1987 }
1988
1989 /* update ETag, if ETag response header is set */
1990 if (etaglen) {
1991 /* modify ETag response header in-place to remove '"' and append '-label"' */
1992 vb->ptr[etaglen-1] = '-'; /*(overwrite end '"')*/
1993 buffer_append_string(vb, label);
1994 buffer_append_char(vb, '"');
1995 }
1996
1997 /* set Content-Encoding to show selected compression type */
1998 http_header_response_set(r, HTTP_HEADER_CONTENT_ENCODING, CONST_STR_LEN("Content-Encoding"), label, strlen(label));
1999
2000 /* clear Content-Length and r->write_queue if HTTP HEAD request
2001 * (alternatively, could return original Content-Length with HEAD
2002 * request if ETag not modified and Content-Encoding not added)
2003 * (see top of this func where short-circuit is done on HTTP HEAD) */
2004 if (HTTP_METHOD_HEAD == r->http_method) {
2005 /* ensure that uncompressed Content-Length is not sent in HEAD response */
2006 http_response_body_clear(r, 0);
2007 r->resp_body_finished = 1;
2008 return HANDLER_GO_ON;
2009 }
2010
2011 /* restrict items eligible for cache of compressed responses
2012 * (This module does not aim to be a full caching proxy)
2013 * response must be complete (not streaming response)
2014 * must not have prior Vary response header (before Accept-Encoding added)
2015 * must have ETag
2016 * must be file
2017 * must be single FILE_CHUNK in chunkqueue
2018 * must not be chunkqueue temporary file
2019 * must be whole file, not partial content
2020 * must not be HTTP status 206 Partial Content
2021 * must not have Cache-Control 'private' or 'no-store'
2022 * Note: small files (< 32k (see http_chunk.c)) will have been read into
2023 * memory (if streaming HTTP/1.1 chunked response) and will end up
2024 * getting stream-compressed rather than cached on disk as compressed
2025 * file
2026 */
2027 buffer *tb = NULL;
2028 if (p->conf.cache_dir
2029 && !had_vary
2030 && etaglen > 2
2031 && r->resp_body_finished
2032 && r->write_queue.first == r->write_queue.last
2033 && r->write_queue.first->type == FILE_CHUNK
2034 && r->write_queue.first->offset == 0
2035 && !r->write_queue.first->file.is_temp
2036 && r->http_status != 206
2037 && (!(vbro = http_header_response_get(r, HTTP_HEADER_CACHE_CONTROL,
2038 CONST_STR_LEN("Cache-Control")))
2039 || (!http_header_str_contains_token(BUF_PTR_LEN(vbro),
2040 CONST_STR_LEN("private"))
2041 && !http_header_str_contains_token(BUF_PTR_LEN(vbro),
2042 CONST_STR_LEN("no-store"))))
2043 ) {
2044 tb = mod_deflate_cache_file_name(r, p->conf.cache_dir, vb);
2045 /*(checked earlier and skipped if Transfer-Encoding had been set)*/
2046 stat_cache_entry *sce = stat_cache_get_entry_open(tb, 1);
2047 if (NULL != sce) {
2048 chunkqueue_reset(&r->write_queue);
2049 if (sce->fd < 0 || 0 != http_chunk_append_file_ref(r, sce))
2050 return HANDLER_ERROR;
2051 if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LENGTH))
2052 http_header_response_unset(r, HTTP_HEADER_CONTENT_LENGTH,
2053 CONST_STR_LEN("Content-Length"));
2054 mod_deflate_note_ratio(r, sce->st.st_size, len);
2055 return HANDLER_GO_ON;
2056 }
2057 /* sanity check that response was whole file;
2058 * (racy since using stat_cache, but cache file only if match) */
2059 sce = stat_cache_get_entry(r->write_queue.first->mem);
2060 if (NULL == sce || sce->st.st_size != len)
2061 tb = NULL;
2062 else if (0 != mkdir_for_file(tb->ptr))
2063 tb = NULL;
2064 }
2065
2066 /* enable compression */
2067 p->conf.sync_flush =
2068 ((r->conf.stream_response_body
2069 & (FDEVENT_STREAM_RESPONSE | FDEVENT_STREAM_RESPONSE_BUFMIN))
2070 && 0 == p->conf.output_buffer_size);
2071 hctx = handler_ctx_init();
2072 hctx->plugin_data = p;
2073 hctx->compression_type = compression_type;
2074 hctx->r = r;
2075 /* setup output buffer */
2076 buffer_clear(&p->tmp_buf);
2077 hctx->output = &p->tmp_buf;
2078 /* open cache file if caching compressed file */
2079 if (tb) mod_deflate_cache_file_open(hctx, tb);
2080
2081 #ifdef HAVE_LIBDEFLATE
2082 chunk * const c = r->write_queue.first; /*(invalid after compression)*/
2083 #ifdef HAVE_MMAP
2084 #if defined(_LP64) || defined(__LP64__) || defined(_WIN64)
2085 /* optimization to compress single file in one-shot to writeable mmap */
2086 /*(future: might extend to other compression types)*/
2087 /*(chunkqueue_chunk_file_view() current min size for mmap is 128k)*/
2088 if (len > 131072 /* XXX: TBD what should min size be for optimization?*/
2089 && (hctx->compression_type == HTTP_ACCEPT_ENCODING_GZIP
2090 || hctx->compression_type == HTTP_ACCEPT_ENCODING_DEFLATE)
2091 && c == r->write_queue.last
2092 && c->type == FILE_CHUNK
2093 && chunkqueue_chunk_file_view(c, len, r->conf.errh)
2094 && chunk_file_view_dlen(c->file.view, c->offset) >= len) { /*(cfv)*/
2095 rc = HANDLER_GO_ON;
2096 hctx->bytes_in = len;
2097 if (mod_deflate_using_libdeflate(hctx, p)) {
2098 if (NULL == tb || 0 == mod_deflate_cache_file_finish(r, hctx, tb))
2099 mod_deflate_note_ratio(r, hctx->bytes_out, hctx->bytes_in);
2100 else
2101 rc = HANDLER_ERROR;
2102 handler_ctx_free(hctx);
2103 return rc;
2104 }
2105 hctx->bytes_in = hctx->bytes_out = 0;
2106 }
2107 else
2108 #endif
2109 #endif
2110 if (len <= 65536 /*(p->tmp_buf is at least 64k)*/
2111 && (hctx->compression_type == HTTP_ACCEPT_ENCODING_GZIP
2112 || hctx->compression_type == HTTP_ACCEPT_ENCODING_DEFLATE)
2113 && c == r->write_queue.last
2114 && c->type == MEM_CHUNK) {
2115 /*(skip if FILE_CHUNK; not worth mmap/munmap overhead on small file)*/
2116 rc = HANDLER_GO_ON;
2117 hctx->bytes_in = len;
2118 if (mod_deflate_using_libdeflate_sm(hctx, p)) {
2119 if (NULL == tb || 0 == mod_deflate_cache_file_finish(r, hctx, tb))
2120 mod_deflate_note_ratio(r, hctx->bytes_out, hctx->bytes_in);
2121 else
2122 rc = HANDLER_ERROR;
2123 handler_ctx_free(hctx);
2124 return rc;
2125 }
2126 hctx->bytes_in = hctx->bytes_out = 0;
2127 }
2128 #endif /* HAVE_LIBDEFLATE */
2129
2130 if (0 != mod_deflate_stream_init(hctx)) {
2131 /*(should not happen unless ENOMEM)*/
2132 handler_ctx_free(hctx);
2133 log_error(r->conf.errh, __FILE__, __LINE__,
2134 "Failed to initialize compression %s", label);
2135 /* restore prior Etag and unset Content-Encoding */
2136 if (etaglen) {
2137 vb->ptr[etaglen-1] = '"'; /*(overwrite '-')*/
2138 buffer_truncate(vb, etaglen);
2139 }
2140 http_header_response_unset(r, HTTP_HEADER_CONTENT_ENCODING, CONST_STR_LEN("Content-Encoding"));
2141 return HANDLER_GO_ON;
2142 }
2143
2144 #ifdef USE_BROTLI
2145 if (r->resp_body_finished
2146 && (hctx->compression_type & HTTP_ACCEPT_ENCODING_BR)
2147 && (len >> 1) < (off_t)((~(uint32_t)0u) >> 1))
2148 BrotliEncoderSetParameter(hctx->u.br, BROTLI_PARAM_SIZE_HINT, (uint32_t)len);
2149 #endif
2150
2151 if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LENGTH)) {
2152 http_header_response_unset(r, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length"));
2153 }
2154 r->plugin_ctx[p->id] = hctx;
2155
2156 rc = deflate_compress_response(r, hctx);
2157 if (HANDLER_GO_ON == rc) return HANDLER_GO_ON;
2158 if (HANDLER_FINISHED == rc) {
2159 #ifdef __COVERITY__
2160 /* coverity misses if hctx->cache_fd is not -1, then tb is not NULL */
2161 force_assert(-1 == hctx->cache_fd || NULL != tb);
2162 #endif
2163 if (-1 == hctx->cache_fd
2164 || 0 == mod_deflate_cache_file_finish(r, hctx, tb)) {
2165 mod_deflate_note_ratio(r, hctx->bytes_out, hctx->bytes_in);
2166 rc = HANDLER_GO_ON;
2167 }
2168 else
2169 rc = HANDLER_ERROR;
2170 }
2171 r->plugin_ctx[p->id] = NULL;
2172 if (deflate_compress_cleanup(r, hctx) < 0) return HANDLER_ERROR;
2173 return rc;
2174 }
2175
mod_deflate_cleanup(request_st * const r,void * p_d)2176 static handler_t mod_deflate_cleanup(request_st * const r, void *p_d) {
2177 plugin_data *p = p_d;
2178 handler_ctx *hctx = r->plugin_ctx[p->id];
2179
2180 if (NULL != hctx) {
2181 r->plugin_ctx[p->id] = NULL;
2182 deflate_compress_cleanup(r, hctx);
2183 }
2184
2185 return HANDLER_GO_ON;
2186 }
2187
2188
2189 __attribute_cold__
2190 int mod_deflate_plugin_init(plugin *p);
mod_deflate_plugin_init(plugin * p)2191 int mod_deflate_plugin_init(plugin *p) {
2192 p->version = LIGHTTPD_VERSION_ID;
2193 p->name = "deflate";
2194
2195 p->init = mod_deflate_init;
2196 p->cleanup = mod_deflate_free;
2197 p->set_defaults = mod_deflate_set_defaults;
2198 p->handle_request_reset = mod_deflate_cleanup;
2199 p->handle_response_start = mod_deflate_handle_response_start;
2200
2201 return 0;
2202 }
2203