xref: /lighttpd1.4/src/mod_deflate.c (revision 3392e8fb)
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 
105 #include <fcntl.h>
106 #include <stdlib.h>
107 #include <string.h>
108 #include <errno.h>
109 #include <time.h>
110 #include <unistd.h>     /* getpid() read() unlink() write() */
111 
112 #include "base.h"
113 #include "fdevent.h"
114 #include "log.h"
115 #include "buffer.h"
116 #include "http_chunk.h"
117 #include "http_etag.h"
118 #include "http_header.h"
119 #include "response.h"
120 #include "stat_cache.h"
121 
122 #include "plugin.h"
123 
124 #if defined HAVE_ZLIB_H && defined HAVE_LIBZ
125 # define USE_ZLIB
126 # include <zlib.h>
127 #endif
128 #ifndef Z_DEFAULT_COMPRESSION
129 #define Z_DEFAULT_COMPRESSION -1
130 #endif
131 #ifndef MAX_WBITS
132 #define MAX_WBITS 15
133 #endif
134 
135 #if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
136 # define USE_BZ2LIB
137 /* we don't need stdio interface */
138 # define BZ_NO_STDIO
139 # include <bzlib.h>
140 #endif
141 
142 #if defined HAVE_BROTLI_ENCODE_H && defined HAVE_BROTLI
143 # define USE_BROTLI
144 # include <brotli/encode.h>
145 #endif
146 
147 #if defined HAVE_ZSTD_H && defined HAVE_ZSTD
148 # define USE_ZSTD
149 # include <zstd.h>
150 #endif
151 
152 #if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP && defined ENABLE_MMAP
153 #define USE_MMAP
154 
155 #include "sys-mmap.h"
156 #include <setjmp.h>
157 #include <signal.h>
158 
159 static volatile int sigbus_jmp_valid;
160 static sigjmp_buf sigbus_jmp;
161 
162 static void sigbus_handler(int sig) {
163 	UNUSED(sig);
164 	if (sigbus_jmp_valid) siglongjmp(sigbus_jmp, 1);
165 	log_failed_assert(__FILE__, __LINE__, "SIGBUS");
166 }
167 #endif
168 
169 /* request: accept-encoding */
170 #define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
171 #define HTTP_ACCEPT_ENCODING_GZIP     BV(1)
172 #define HTTP_ACCEPT_ENCODING_DEFLATE  BV(2)
173 #define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
174 #define HTTP_ACCEPT_ENCODING_BZIP2    BV(4)
175 #define HTTP_ACCEPT_ENCODING_X_GZIP   BV(5)
176 #define HTTP_ACCEPT_ENCODING_X_BZIP2  BV(6)
177 #define HTTP_ACCEPT_ENCODING_BR       BV(7)
178 #define HTTP_ACCEPT_ENCODING_ZSTD     BV(8)
179 
180 typedef struct {
181 	const array	*mimetypes;
182 	const buffer    *cache_dir;
183 	unsigned int	max_compress_size;
184 	unsigned short	min_compress_size;
185 	unsigned short	output_buffer_size;
186 	unsigned short	work_block_size;
187 	unsigned short	sync_flush;
188 	short		compression_level;
189 	short		allowed_encodings;
190 	double		max_loadavg;
191 } plugin_config;
192 
193 typedef struct {
194     PLUGIN_DATA;
195     plugin_config defaults;
196     plugin_config conf;
197 
198     buffer tmp_buf;
199 } plugin_data;
200 
201 typedef struct {
202 	union {
203 	      #ifdef USE_ZLIB
204 		z_stream z;
205 	      #endif
206 	      #ifdef USE_BZ2LIB
207 		bz_stream bz;
208 	      #endif
209 	      #ifdef USE_BROTLI
210 		BrotliEncoderState *br;
211 	      #endif
212 	      #ifdef USE_ZSTD
213 		ZSTD_CStream *cctx;
214 	      #endif
215 		int dummy;
216 	} u;
217 	off_t bytes_in;
218 	off_t bytes_out;
219 	buffer *output;
220 	plugin_data *plugin_data;
221 	request_st *r;
222 	int compression_type;
223 	int cache_fd;
224 	char *cache_fn;
225 	chunkqueue in_queue;
226 } handler_ctx;
227 
228 static handler_ctx *handler_ctx_init() {
229 	handler_ctx *hctx;
230 
231 	hctx = calloc(1, sizeof(*hctx));
232 	chunkqueue_init(&hctx->in_queue);
233 	hctx->cache_fd = -1;
234 
235 	return hctx;
236 }
237 
238 static void handler_ctx_free(handler_ctx *hctx) {
239 	if (hctx->cache_fn) {
240 		unlink(hctx->cache_fn);
241 		free(hctx->cache_fn);
242 	}
243 	if (-1 != hctx->cache_fd)
244 		close(hctx->cache_fd);
245       #if 0
246 	if (hctx->output != &p->tmp_buf) {
247 		buffer_free(hctx->output);
248 	}
249       #endif
250 	chunkqueue_reset(&hctx->in_queue);
251 	free(hctx);
252 }
253 
254 INIT_FUNC(mod_deflate_init) {
255     plugin_data * const p = calloc(1, sizeof(plugin_data));
256   #ifdef USE_ZSTD
257     buffer_string_prepare_copy(&p->tmp_buf, ZSTD_CStreamOutSize());
258   #else
259     buffer_string_prepare_copy(&p->tmp_buf, 65536);
260   #endif
261     return p;
262 }
263 
264 FREE_FUNC(mod_deflate_free) {
265     plugin_data *p = p_d;
266     free(p->tmp_buf.ptr);
267 }
268 
269 #if defined(_WIN32) && !defined(__CYGWIN__)
270 #define mkdir(x,y) mkdir(x)
271 #endif
272 
273 static int mkdir_for_file (char *fn) {
274     for (char *p = fn; (p = strchr(p + 1, '/')) != NULL; ) {
275         if (p[1] == '\0') return 0; /* ignore trailing slash */
276         *p = '\0';
277         int rc = mkdir(fn, 0700);
278         *p = '/';
279         if (0 != rc && errno != EEXIST) return -1;
280     }
281     return 0;
282 }
283 
284 static int mkdir_recursive (char *dir) {
285     return 0 == mkdir_for_file(dir) && (0 == mkdir(dir,0700) || errno == EEXIST)
286       ? 0
287       : -1;
288 }
289 
290 static buffer * mod_deflate_cache_file_name(request_st * const r, const buffer *cache_dir, const buffer * const etag) {
291     /* XXX: future: for shorter paths into the cache, we could checksum path,
292      *      and then shard it to avoid a huge single directory.
293      *      Alternatively, could use &r->uri.path, minus any
294      *      (matching) &r->pathinfo suffix, with result url-encoded
295      *      Alternative, we could shard etag which is already our "checksum" */
296     buffer * const tb = r->tmp_buf;
297     buffer_copy_path_len2(tb, CONST_BUF_LEN(cache_dir),
298                               CONST_BUF_LEN(&r->physical.path));
299     buffer_append_str2(tb, CONST_STR_LEN("-"), /*(strip surrounding '"')*/
300                            etag->ptr+1, buffer_string_length(etag)-2);
301     return tb;
302 }
303 
304 static void mod_deflate_cache_file_open (handler_ctx * const hctx, const buffer * const fn) {
305     /* race exists whereby up to # workers might attempt to compress same
306      * file at same time if requested at same time, but this is unlikely
307      * and resolves itself by atomic rename into place when done */
308     const uint32_t fnlen = buffer_string_length(fn);
309     hctx->cache_fn = malloc(fnlen+1+LI_ITOSTRING_LENGTH+1);
310     force_assert(hctx->cache_fn);
311     memcpy(hctx->cache_fn, fn->ptr, fnlen);
312     hctx->cache_fn[fnlen] = '.';
313     const size_t ilen =
314       li_itostrn(hctx->cache_fn+fnlen+1, LI_ITOSTRING_LENGTH, getpid());
315     hctx->cache_fn[fnlen+1+ilen] = '\0';
316     hctx->cache_fd = fdevent_open_cloexec(hctx->cache_fn, 1, O_RDWR|O_CREAT, 0600);
317     if (-1 == hctx->cache_fd) {
318         free(hctx->cache_fn);
319         hctx->cache_fn = NULL;
320     }
321 }
322 
323 static int mod_deflate_cache_file_finish (request_st * const r, handler_ctx * const hctx, const buffer * const fn) {
324     if (0 != fdevent_rename(hctx->cache_fn, fn->ptr))
325         return -1;
326     free(hctx->cache_fn);
327     hctx->cache_fn = NULL;
328     chunkqueue_reset(&r->write_queue);
329     int rc = http_chunk_append_file_fd(r, fn, hctx->cache_fd, hctx->bytes_out);
330     hctx->cache_fd = -1;
331     return rc;
332 }
333 
334 static void mod_deflate_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
335     switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
336       case 0: /* deflate.mimetypes */
337         pconf->mimetypes = cpv->v.a;
338         break;
339       case 1: /* deflate.allowed-encodings */
340         pconf->allowed_encodings = (short)cpv->v.shrt;
341         break;
342       case 2: /* deflate.max-compress-size */
343         pconf->max_compress_size = cpv->v.u;
344         break;
345       case 3: /* deflate.min-compress-size */
346         pconf->min_compress_size = cpv->v.shrt;
347         break;
348       case 4: /* deflate.compression-level */
349         pconf->compression_level = (short)cpv->v.shrt;
350         break;
351       case 5: /* deflate.output-buffer-size */
352         pconf->output_buffer_size = cpv->v.shrt;
353         break;
354       case 6: /* deflate.work-block-size */
355         pconf->work_block_size = cpv->v.shrt;
356         break;
357       case 7: /* deflate.max-loadavg */
358         pconf->max_loadavg = cpv->v.d;
359         break;
360       case 8: /* deflate.cache-dir */
361         pconf->cache_dir = cpv->v.b;
362         break;
363       case 9: /* compress.filetype */
364         pconf->mimetypes = cpv->v.a;
365         break;
366       case 10:/* compress.allowed-encodings */
367         pconf->allowed_encodings = (short)cpv->v.shrt;
368         break;
369       case 11:/* compress.cache-dir */
370         pconf->cache_dir = cpv->v.b;
371         break;
372       case 12:/* compress.max-filesize */
373         pconf->max_compress_size = cpv->v.u;
374         break;
375       case 13:/* compress.max-loadavg */
376         pconf->max_loadavg = cpv->v.d;
377         break;
378       default:/* should not happen */
379         return;
380     }
381 }
382 
383 static void mod_deflate_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
384     do {
385         mod_deflate_merge_config_cpv(pconf, cpv);
386     } while ((++cpv)->k_id != -1);
387 }
388 
389 static void mod_deflate_patch_config(request_st * const r, plugin_data * const p) {
390     memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
391     for (int i = 1, used = p->nconfig; i < used; ++i) {
392         if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
393             mod_deflate_merge_config(&p->conf, p->cvlist + p->cvlist[i].v.u2[0]);
394     }
395 }
396 
397 static short mod_deflate_encodings_to_flags(const array *encodings) {
398     short allowed_encodings = 0;
399     if (encodings->used) {
400         for (uint32_t j = 0; j < encodings->used; ++j) {
401           #if defined(USE_ZLIB) || defined(USE_BZ2LIB) || defined(USE_BROTLI) \
402            || defined(USE_ZSTD)
403             data_string *ds = (data_string *)encodings->data[j];
404           #endif
405           #ifdef USE_ZLIB
406             if (NULL != strstr(ds->value.ptr, "gzip"))
407                 allowed_encodings |= HTTP_ACCEPT_ENCODING_GZIP
408                                   |  HTTP_ACCEPT_ENCODING_X_GZIP;
409             if (NULL != strstr(ds->value.ptr, "x-gzip"))
410                 allowed_encodings |= HTTP_ACCEPT_ENCODING_X_GZIP;
411             if (NULL != strstr(ds->value.ptr, "deflate"))
412                 allowed_encodings |= HTTP_ACCEPT_ENCODING_DEFLATE;
413             /*
414             if (NULL != strstr(ds->value.ptr, "compress"))
415                 allowed_encodings |= HTTP_ACCEPT_ENCODING_COMPRESS;
416             */
417           #endif
418           #ifdef USE_BZ2LIB
419             if (NULL != strstr(ds->value.ptr, "bzip2"))
420                 allowed_encodings |= HTTP_ACCEPT_ENCODING_BZIP2
421                                   |  HTTP_ACCEPT_ENCODING_X_BZIP2;
422             if (NULL != strstr(ds->value.ptr, "x-bzip2"))
423                 allowed_encodings |= HTTP_ACCEPT_ENCODING_X_BZIP2;
424           #endif
425           #ifdef USE_BROTLI
426             if (NULL != strstr(ds->value.ptr, "br"))
427                 allowed_encodings |= HTTP_ACCEPT_ENCODING_BR;
428           #endif
429           #ifdef USE_ZSTD
430             if (NULL != strstr(ds->value.ptr, "zstd"))
431                 allowed_encodings |= HTTP_ACCEPT_ENCODING_ZSTD;
432           #endif
433         }
434     }
435     else {
436         /* default encodings */
437       #ifdef USE_ZLIB
438         allowed_encodings |= HTTP_ACCEPT_ENCODING_GZIP
439                           |  HTTP_ACCEPT_ENCODING_X_GZIP
440                           |  HTTP_ACCEPT_ENCODING_DEFLATE;
441       #endif
442       #ifdef USE_BZ2LIB
443         allowed_encodings |= HTTP_ACCEPT_ENCODING_BZIP2
444                           |  HTTP_ACCEPT_ENCODING_X_BZIP2;
445       #endif
446       #ifdef USE_BROTLI
447         allowed_encodings |= HTTP_ACCEPT_ENCODING_BR;
448       #endif
449       #ifdef USE_ZSTD
450         allowed_encodings |= HTTP_ACCEPT_ENCODING_ZSTD;
451       #endif
452     }
453     return allowed_encodings;
454 }
455 
456 SETDEFAULTS_FUNC(mod_deflate_set_defaults) {
457     static const config_plugin_keys_t cpk[] = {
458       { CONST_STR_LEN("deflate.mimetypes"),
459         T_CONFIG_ARRAY_VLIST,
460         T_CONFIG_SCOPE_CONNECTION }
461      ,{ CONST_STR_LEN("deflate.allowed-encodings"),
462         T_CONFIG_ARRAY_VLIST,
463         T_CONFIG_SCOPE_CONNECTION }
464      ,{ CONST_STR_LEN("deflate.max-compress-size"),
465         T_CONFIG_INT,
466         T_CONFIG_SCOPE_CONNECTION }
467      ,{ CONST_STR_LEN("deflate.min-compress-size"),
468         T_CONFIG_SHORT,
469         T_CONFIG_SCOPE_CONNECTION }
470      ,{ CONST_STR_LEN("deflate.compression-level"),
471         T_CONFIG_SHORT,
472         T_CONFIG_SCOPE_CONNECTION }
473      ,{ CONST_STR_LEN("deflate.output-buffer-size"),
474         T_CONFIG_SHORT,
475         T_CONFIG_SCOPE_CONNECTION }
476      ,{ CONST_STR_LEN("deflate.work-block-size"),
477         T_CONFIG_SHORT,
478         T_CONFIG_SCOPE_CONNECTION }
479      ,{ CONST_STR_LEN("deflate.max-loadavg"),
480         T_CONFIG_STRING,
481         T_CONFIG_SCOPE_CONNECTION }
482      ,{ CONST_STR_LEN("deflate.cache-dir"),
483         T_CONFIG_STRING,
484         T_CONFIG_SCOPE_CONNECTION }
485      ,{ CONST_STR_LEN("compress.filetype"),
486         T_CONFIG_ARRAY_VLIST,
487         T_CONFIG_SCOPE_CONNECTION }
488      ,{ CONST_STR_LEN("compress.allowed-encodings"),
489         T_CONFIG_ARRAY_VLIST,
490         T_CONFIG_SCOPE_CONNECTION }
491      ,{ CONST_STR_LEN("compress.cache-dir"),
492         T_CONFIG_STRING,
493         T_CONFIG_SCOPE_CONNECTION }
494      ,{ CONST_STR_LEN("compress.max-filesize"),
495         T_CONFIG_INT,
496         T_CONFIG_SCOPE_CONNECTION }
497      ,{ CONST_STR_LEN("compress.max-loadavg"),
498         T_CONFIG_STRING,
499         T_CONFIG_SCOPE_CONNECTION }
500      ,{ NULL, 0,
501         T_CONFIG_UNSET,
502         T_CONFIG_SCOPE_UNSET }
503     };
504 
505     plugin_data * const p = p_d;
506     if (!config_plugin_values_init(srv, p, cpk, "mod_deflate"))
507         return HANDLER_ERROR;
508 
509     /* process and validate config directives
510      * (init i to 0 if global context; to 1 to skip empty global context) */
511     for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
512         config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
513         for (; -1 != cpv->k_id; ++cpv) {
514             switch (cpv->k_id) {
515               case 0: /* deflate.mimetypes */
516                 /* mod_deflate matches mimetype as prefix of Content-Type
517                  * so ignore '*' at end of mimetype for end-user flexibility
518                  * in specifying trailing wildcard to grouping of mimetypes */
519                 for (uint32_t m = 0; m < cpv->v.a->used; ++m) {
520                     buffer *mimetype=&((data_string *)cpv->v.a->data[m])->value;
521                     size_t len = buffer_string_length(mimetype);
522                     if (len > 2 && mimetype->ptr[len-1] == '*')
523                         buffer_string_set_length(mimetype, len-1);
524                 }
525                 if (0 == cpv->v.a->used) cpv->v.a = NULL;
526                 break;
527               case 1: /* deflate.allowed-encodings */
528                 cpv->v.shrt = (unsigned short)
529                   mod_deflate_encodings_to_flags(cpv->v.a);
530                 cpv->vtype = T_CONFIG_SHORT;
531                 break;
532               case 2: /* deflate.max-compress-size */
533               case 3: /* deflate.min-compress-size */
534                 break;
535               case 4: /* deflate.compression-level */
536                 if ((cpv->v.shrt < 1 || cpv->v.shrt > 9)
537                     && *(short *)&cpv->v.shrt != -1) {
538                     log_error(srv->errh, __FILE__, __LINE__,
539                       "compression-level must be between 1 and 9: %hu",
540                       cpv->v.shrt);
541                     return HANDLER_ERROR;
542                 }
543                 break;
544               case 5: /* deflate.output-buffer-size */
545               case 6: /* deflate.work-block-size */
546                 break;
547               case 7: /* deflate.max-loadavg */
548                 cpv->v.d = (!buffer_string_is_empty(cpv->v.b))
549                   ? strtod(cpv->v.b->ptr, NULL)
550                   : 0.0;
551                 break;
552               case 8: /* deflate.cache-dir */
553                 if (!buffer_string_is_empty(cpv->v.b)) {
554                     buffer *b;
555                     *(const buffer **)&b = cpv->v.b;
556                     const uint32_t len = buffer_string_length(b);
557                     if (len > 0 && '/' == b->ptr[len-1])
558                         buffer_string_set_length(b, len-1); /*remove end slash*/
559                     struct stat st;
560                     if (0 != stat(b->ptr,&st) && 0 != mkdir_recursive(b->ptr)) {
561                         log_perror(srv->errh, __FILE__, __LINE__,
562                           "can't stat %s %s", cpk[cpv->k_id].k, b->ptr);
563                         return HANDLER_ERROR;
564                     }
565                 }
566                 break;
567               case 9: /* compress.filetype */
568                 log_error(srv->errh, __FILE__, __LINE__,
569                   "DEPRECATED: %s replaced with deflate.mimetypes",
570                   cpk[cpv->k_id].k);
571                 break;
572               case 10:/* compress.allowed-encodings */
573                 log_error(srv->errh, __FILE__, __LINE__,
574                   "DEPRECATED: %s replaced with deflate.allowed-encodings",
575                   cpk[cpv->k_id].k);
576                 cpv->v.shrt = (unsigned short)
577                   mod_deflate_encodings_to_flags(cpv->v.a);
578                 cpv->vtype = T_CONFIG_SHORT;
579                 break;
580               case 11:/* compress.cache-dir */
581                 log_error(srv->errh, __FILE__, __LINE__,
582                   "DEPRECATED: %s replaced with deflate.cache-dir",
583                   cpk[cpv->k_id].k);
584                 if (!buffer_string_is_empty(cpv->v.b)) {
585                     buffer *b;
586                     *(const buffer **)&b = cpv->v.b;
587                     const uint32_t len = buffer_string_length(b);
588                     if (len > 0 && '/' == b->ptr[len-1])
589                         buffer_string_set_length(b, len-1); /*remove end slash*/
590                     struct stat st;
591                     if (0 != stat(b->ptr,&st) && 0 != mkdir_recursive(b->ptr)) {
592                         log_perror(srv->errh, __FILE__, __LINE__,
593                           "can't stat %s %s", cpk[cpv->k_id].k, b->ptr);
594                         return HANDLER_ERROR;
595                     }
596                 }
597                 break;
598               case 12:/* compress.max-filesize */
599                 log_error(srv->errh, __FILE__, __LINE__,
600                   "DEPRECATED: %s replaced with deflate.max-compress-size",
601                   cpk[cpv->k_id].k);
602                 break;
603               case 13:/* compress.max-loadavg */
604                 log_error(srv->errh, __FILE__, __LINE__,
605                   "DEPRECATED: %s replaced with deflate.max-loadavg",
606                   cpk[cpv->k_id].k);
607                 cpv->v.d = (!buffer_string_is_empty(cpv->v.b))
608                   ? strtod(cpv->v.b->ptr, NULL)
609                   : 0.0;
610                 break;
611               default:/* should not happen */
612                 break;
613             }
614         }
615     }
616 
617     p->defaults.allowed_encodings = 0;
618     p->defaults.max_compress_size = 128*1024; /*(128 MB measured as num KB)*/
619     p->defaults.min_compress_size = 256;
620     p->defaults.compression_level = -1;
621     p->defaults.output_buffer_size = 0;
622     p->defaults.work_block_size = 2048;
623     p->defaults.max_loadavg = 0.0;
624     p->defaults.sync_flush = 0;
625 
626     /* initialize p->defaults from global config context */
627     if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
628         const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
629         if (-1 != cpv->k_id)
630             mod_deflate_merge_config(&p->defaults, cpv);
631     }
632 
633     return HANDLER_GO_ON;
634 }
635 
636 
637 #if defined(USE_ZLIB) || defined(USE_BZ2LIB) || defined(USE_BROTLI) \
638  || defined(USE_ZSTD)
639 static int mod_deflate_cache_file_append (handler_ctx * const hctx, const char *out, size_t len) {
640     ssize_t wr;
641     do {
642         wr = write(hctx->cache_fd, out, len);
643     } while (wr > 0 ? ((out += wr), (len -= (size_t)wr)) : errno == EINTR);
644     return (0 == len) ? 0 : -1;
645 }
646 
647 static int stream_http_chunk_append_mem(handler_ctx * const hctx, const char * const out, size_t len) {
648     if (0 == len) return 0;
649     return (-1 == hctx->cache_fd)
650       ? http_chunk_append_mem(hctx->r, out, len)
651       : mod_deflate_cache_file_append(hctx, out, len);
652 }
653 #endif
654 
655 
656 #ifdef USE_ZLIB
657 
658 static int stream_deflate_init(handler_ctx *hctx) {
659 	z_stream * const z = &hctx->u.z;
660 	const plugin_data * const p = hctx->plugin_data;
661 	z->zalloc = Z_NULL;
662 	z->zfree = Z_NULL;
663 	z->opaque = Z_NULL;
664 	z->total_in = 0;
665 	z->total_out = 0;
666 	z->next_out = (unsigned char *)hctx->output->ptr;
667 	z->avail_out = hctx->output->size;
668 
669 	if (Z_OK != deflateInit2(z,
670 				 p->conf.compression_level > 0
671 				  ? p->conf.compression_level
672 				  : Z_DEFAULT_COMPRESSION,
673 				 Z_DEFLATED,
674 				 (hctx->compression_type == HTTP_ACCEPT_ENCODING_GZIP)
675 				  ? (MAX_WBITS | 16) /*(0x10 flags gzip header, trailer)*/
676 				  : -MAX_WBITS,      /*(negate to suppress zlib header)*/
677 				 8, /* default memLevel */
678 				 Z_DEFAULT_STRATEGY)) {
679 		return -1;
680 	}
681 
682 	return 0;
683 }
684 
685 static int stream_deflate_compress(handler_ctx * const hctx, unsigned char * const start, off_t st_size) {
686 	z_stream * const z = &(hctx->u.z);
687 	size_t len;
688 
689 	z->next_in = start;
690 	z->avail_in = st_size;
691 	hctx->bytes_in += st_size;
692 
693 	/* compress data */
694 	do {
695 		if (Z_OK != deflate(z, Z_NO_FLUSH)) return -1;
696 
697 		if (z->avail_out == 0 || z->avail_in > 0) {
698 			len = hctx->output->size - z->avail_out;
699 			hctx->bytes_out += len;
700 			if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
701 				return -1;
702 			z->next_out = (unsigned char *)hctx->output->ptr;
703 			z->avail_out = hctx->output->size;
704 		}
705 	} while (z->avail_in > 0);
706 
707 	return 0;
708 }
709 
710 static int stream_deflate_flush(handler_ctx * const hctx, int end) {
711 	z_stream * const z = &(hctx->u.z);
712 	const plugin_data *p = hctx->plugin_data;
713 	size_t len;
714 	int rc = 0;
715 	int done;
716 
717 	/* compress data */
718 	do {
719 		done = 1;
720 		if (end) {
721 			rc = deflate(z, Z_FINISH);
722 			if (rc == Z_OK) {
723 				done = 0;
724 			} else if (rc != Z_STREAM_END) {
725 				return -1;
726 			}
727 		} else {
728 			if (p->conf.sync_flush) {
729 				rc = deflate(z, Z_SYNC_FLUSH);
730 				if (rc != Z_OK) return -1;
731 			} else if (z->avail_in > 0) {
732 				rc = deflate(z, Z_NO_FLUSH);
733 				if (rc != Z_OK) return -1;
734 			}
735 		}
736 
737 		len = hctx->output->size - z->avail_out;
738 		if (z->avail_out == 0 || (len > 0 && (end || p->conf.sync_flush))) {
739 			hctx->bytes_out += len;
740 			if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
741 				return -1;
742 			z->next_out = (unsigned char *)hctx->output->ptr;
743 			z->avail_out = hctx->output->size;
744 		}
745 	} while (z->avail_in != 0 || !done);
746 
747 	return 0;
748 }
749 
750 static int stream_deflate_end(handler_ctx *hctx) {
751 	z_stream * const z = &(hctx->u.z);
752 	int rc = deflateEnd(z);
753 	if (Z_OK == rc || Z_DATA_ERROR == rc) return 0;
754 
755 	if (z->msg != NULL) {
756 		log_error(hctx->r->conf.errh, __FILE__, __LINE__,
757 		  "deflateEnd error ret=%d, msg=%s", rc, z->msg);
758 	} else {
759 		log_error(hctx->r->conf.errh, __FILE__, __LINE__,
760 		  "deflateEnd error ret=%d", rc);
761 	}
762 	return -1;
763 }
764 
765 #endif
766 
767 
768 #ifdef USE_BZ2LIB
769 
770 static int stream_bzip2_init(handler_ctx *hctx) {
771 	bz_stream * const bz = &hctx->u.bz;
772 	const plugin_data * const p = hctx->plugin_data;
773 	bz->bzalloc = NULL;
774 	bz->bzfree = NULL;
775 	bz->opaque = NULL;
776 	bz->total_in_lo32 = 0;
777 	bz->total_in_hi32 = 0;
778 	bz->total_out_lo32 = 0;
779 	bz->total_out_hi32 = 0;
780 	bz->next_out = hctx->output->ptr;
781 	bz->avail_out = hctx->output->size;
782 
783 	if (BZ_OK != BZ2_bzCompressInit(bz,
784 					p->conf.compression_level > 0
785 					 ? p->conf.compression_level
786 					 : 9, /* blocksize = 900k */
787 					0,    /* verbosity */
788 					0)) { /* workFactor: default */
789 		return -1;
790 	}
791 
792 	return 0;
793 }
794 
795 static int stream_bzip2_compress(handler_ctx * const hctx, unsigned char * const start, off_t st_size) {
796 	bz_stream * const bz = &(hctx->u.bz);
797 	size_t len;
798 
799 	bz->next_in = (char *)start;
800 	bz->avail_in = st_size;
801 	hctx->bytes_in += st_size;
802 
803 	/* compress data */
804 	do {
805 		if (BZ_RUN_OK != BZ2_bzCompress(bz, BZ_RUN)) return -1;
806 
807 		if (bz->avail_out == 0 || bz->avail_in > 0) {
808 			len = hctx->output->size - bz->avail_out;
809 			hctx->bytes_out += len;
810 			if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
811 				return -1;
812 			bz->next_out = hctx->output->ptr;
813 			bz->avail_out = hctx->output->size;
814 		}
815 	} while (bz->avail_in > 0);
816 
817 	return 0;
818 }
819 
820 static int stream_bzip2_flush(handler_ctx * const hctx, int end) {
821 	bz_stream * const bz = &(hctx->u.bz);
822 	const plugin_data *p = hctx->plugin_data;
823 	size_t len;
824 	int rc;
825 	int done;
826 
827 	/* compress data */
828 	do {
829 		done = 1;
830 		if (end) {
831 			rc = BZ2_bzCompress(bz, BZ_FINISH);
832 			if (rc == BZ_FINISH_OK) {
833 				done = 0;
834 			} else if (rc != BZ_STREAM_END) {
835 				return -1;
836 			}
837 		} else if (bz->avail_in > 0) {
838 			/* p->conf.sync_flush not implemented here,
839 			 * which would loop on BZ_FLUSH while BZ_FLUSH_OK
840 			 * until BZ_RUN_OK returned */
841 			rc = BZ2_bzCompress(bz, BZ_RUN);
842 			if (rc != BZ_RUN_OK) {
843 				return -1;
844 			}
845 		}
846 
847 		len = hctx->output->size - bz->avail_out;
848 		if (bz->avail_out == 0 || (len > 0 && (end || p->conf.sync_flush))) {
849 			hctx->bytes_out += len;
850 			if (0 != stream_http_chunk_append_mem(hctx, hctx->output->ptr, len))
851 				return -1;
852 			bz->next_out = hctx->output->ptr;
853 			bz->avail_out = hctx->output->size;
854 		}
855 	} while (bz->avail_in != 0 || !done);
856 
857 	return 0;
858 }
859 
860 static int stream_bzip2_end(handler_ctx *hctx) {
861 	bz_stream * const bz = &(hctx->u.bz);
862 	int rc = BZ2_bzCompressEnd(bz);
863 	if (BZ_OK == rc || BZ_DATA_ERROR == rc) return 0;
864 
865 	log_error(hctx->r->conf.errh, __FILE__, __LINE__,
866 	  "BZ2_bzCompressEnd error ret=%d", rc);
867 	return -1;
868 }
869 
870 #endif
871 
872 
873 #ifdef USE_BROTLI
874 
875 static int stream_br_init(handler_ctx *hctx) {
876     BrotliEncoderState * const br = hctx->u.br =
877       BrotliEncoderCreateInstance(NULL, NULL, NULL);
878     if (NULL == br) return -1;
879 
880     /* future: consider allowing tunables by encoder algorithm,
881      * (i.e. not generic "compression_level") */
882     /*(note: we ignore any errors while tuning parameters here)*/
883     const plugin_data * const p = hctx->plugin_data;
884     if (p->conf.compression_level >= 0) /* 0 .. 11 are valid values */
885         BrotliEncoderSetParameter(br, BROTLI_PARAM_QUALITY,
886                                   p->conf.compression_level);
887 
888     /* XXX: is this worth checking?
889      * BROTLI_MODE_GENERIC vs BROTLI_MODE_TEXT or BROTLI_MODE_FONT */
890     const buffer *vb =
891       http_header_response_get(hctx->r, HTTP_HEADER_CONTENT_TYPE,
892                                CONST_STR_LEN("Content-Type"));
893     if (NULL != vb) {
894         if (0 == strncmp(vb->ptr, "text/", sizeof("text/")-1)
895             || (0 == strncmp(vb->ptr, "application/", sizeof("application/")-1)
896                 && (0 == strncmp(vb->ptr+12,"javascript",sizeof("javascript")-1)
897                  || 0 == strncmp(vb->ptr+12,"ld+json",   sizeof("ld+json")-1)
898                  || 0 == strncmp(vb->ptr+12,"json",      sizeof("json")-1)
899                  || 0 == strncmp(vb->ptr+12,"xhtml+xml", sizeof("xhtml+xml")-1)
900                  || 0 == strncmp(vb->ptr+12,"xml",       sizeof("xml")-1)))
901             || 0 == strncmp(vb->ptr, "image/svg+xml", sizeof("image/svg+xml")-1))
902             BrotliEncoderSetParameter(br, BROTLI_PARAM_MODE, BROTLI_MODE_TEXT);
903         else if (0 == strncmp(vb->ptr, "font/", sizeof("font/")-1))
904             BrotliEncoderSetParameter(br, BROTLI_PARAM_MODE, BROTLI_MODE_FONT);
905     }
906 
907     return 0;
908 }
909 
910 static int stream_br_compress(handler_ctx * const hctx, unsigned char * const start, off_t st_size) {
911     const uint8_t *in = (uint8_t *)start;
912     BrotliEncoderState * const br = hctx->u.br;
913     hctx->bytes_in += st_size;
914     while (st_size || BrotliEncoderHasMoreOutput(br)) {
915         size_t insz = ((off_t)((~(uint32_t)0) >> 1) > st_size)
916           ? (size_t)st_size
917           : ((~(uint32_t)0) >> 1);
918         size_t outsz = 0;
919         BrotliEncoderCompressStream(br, BROTLI_OPERATION_PROCESS,
920                                     &insz, &in, &outsz, NULL, NULL);
921         const uint8_t *out = BrotliEncoderTakeOutput(br, &outsz);
922         st_size -= (st_size - (off_t)insz);
923         if (outsz) {
924             hctx->bytes_out += (off_t)outsz;
925             if (0 != stream_http_chunk_append_mem(hctx, (char *)out, outsz))
926                 return -1;
927         }
928     }
929     return 0;
930 }
931 
932 static int stream_br_flush(handler_ctx * const hctx, int end) {
933     const int brmode = end ? BROTLI_OPERATION_FINISH : BROTLI_OPERATION_FLUSH;
934     BrotliEncoderState * const br = hctx->u.br;
935     do {
936         size_t insz = 0;
937         size_t outsz = 0;
938         BrotliEncoderCompressStream(br, brmode,
939                                     &insz, NULL, &outsz, NULL, NULL);
940         const uint8_t *out = BrotliEncoderTakeOutput(br, &outsz);
941         if (outsz) {
942             hctx->bytes_out += (off_t)outsz;
943             if (0 != stream_http_chunk_append_mem(hctx, (char *)out, outsz))
944                 return -1;
945         }
946     } while (BrotliEncoderHasMoreOutput(br));
947     return 0;
948 }
949 
950 static int stream_br_end(handler_ctx *hctx) {
951     BrotliEncoderState * const br = hctx->u.br;
952     BrotliEncoderDestroyInstance(br);
953     return 0;
954 }
955 
956 #endif
957 
958 
959 #ifdef USE_ZSTD
960 
961 static int stream_zstd_init(handler_ctx *hctx) {
962     ZSTD_CStream * const cctx = hctx->u.cctx = ZSTD_createCStream();
963     if (NULL == cctx) return -1;
964     hctx->output->used = 0;
965 
966     /* future: consider allowing tunables by encoder algorithm,
967      * (i.e. not generic "compression_level" across all compression algos) */
968     /*(note: we ignore any errors while tuning parameters here)*/
969     const plugin_data * const p = hctx->plugin_data;
970     if (p->conf.compression_level >= 0) { /* -1 is lighttpd default for "unset" */
971         int level = p->conf.compression_level;
972       #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
973         ZSTD_CCtx_setParameter(cctx, ZSTD_c_compressionLevel, level);
974       #else
975         ZSTD_initCStream(cctx, level);
976       #endif
977     }
978     return 0;
979 }
980 
981 static int stream_zstd_compress(handler_ctx * const hctx, unsigned char * const start, off_t st_size) {
982     ZSTD_CStream * const cctx = hctx->u.cctx;
983     ZSTD_inBuffer zib = { start, (size_t)st_size, 0 };
984     ZSTD_outBuffer zob = { hctx->output->ptr,
985                            hctx->output->size,
986                            hctx->output->used };
987     hctx->output->used = 0;
988     hctx->bytes_in += st_size;
989     while (zib.pos < zib.size) {
990       #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
991         const size_t rv = ZSTD_compressStream2(cctx,&zob,&zib,ZSTD_e_continue);
992       #else
993         const size_t rv = ZSTD_compressStream(cctx, &zob, &zib);
994       #endif
995         if (ZSTD_isError(rv)) return -1;
996         if (zib.pos == zib.size) break; /* defer flush */
997         hctx->bytes_out += (off_t)zob.pos;
998         if (0 != stream_http_chunk_append_mem(hctx, zob.dst, zob.pos))
999             return -1;
1000         zob.pos = 0;
1001     }
1002     hctx->output->used = (uint32_t)zob.pos;
1003     return 0;
1004 }
1005 
1006 static int stream_zstd_flush(handler_ctx * const hctx, int end) {
1007     ZSTD_CStream * const cctx = hctx->u.cctx;
1008   #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1009     const ZSTD_EndDirective endOp = end ? ZSTD_e_end : ZSTD_e_flush;
1010     ZSTD_inBuffer zib = { NULL, 0, 0 };
1011   #endif
1012     ZSTD_outBuffer zob = { hctx->output->ptr,
1013                            hctx->output->size,
1014                            hctx->output->used };
1015     size_t rv;
1016     do {
1017       #if ZSTD_VERSION_NUMBER >= 10000+400+0 /* v1.4.0 */
1018         rv = ZSTD_compressStream2(cctx, &zob, &zib, endOp);
1019       #else
1020         rv = end
1021            ? ZSTD_endStream(cctx, &zob)
1022            : ZSTD_flushStream(cctx, &zob);
1023       #endif
1024         if (ZSTD_isError(rv)) return -1;
1025         hctx->bytes_out += (off_t)zob.pos;
1026         if (0 != stream_http_chunk_append_mem(hctx, zob.dst, zob.pos))
1027             return -1;
1028         zob.pos = 0;
1029     } while (0 != rv);
1030     return 0;
1031 }
1032 
1033 static int stream_zstd_end(handler_ctx *hctx) {
1034     ZSTD_CStream * const cctx = hctx->u.cctx;
1035     ZSTD_freeCStream(cctx);
1036     return 0;
1037 }
1038 
1039 #endif
1040 
1041 
1042 static int mod_deflate_stream_init(handler_ctx *hctx) {
1043 	switch(hctx->compression_type) {
1044 #ifdef USE_ZLIB
1045 	case HTTP_ACCEPT_ENCODING_GZIP:
1046 	case HTTP_ACCEPT_ENCODING_DEFLATE:
1047 		return stream_deflate_init(hctx);
1048 #endif
1049 #ifdef USE_BZ2LIB
1050 	case HTTP_ACCEPT_ENCODING_BZIP2:
1051 		return stream_bzip2_init(hctx);
1052 #endif
1053 #ifdef USE_BROTLI
1054 	case HTTP_ACCEPT_ENCODING_BR:
1055 		return stream_br_init(hctx);
1056 #endif
1057 #ifdef USE_ZSTD
1058 	case HTTP_ACCEPT_ENCODING_ZSTD:
1059 		return stream_zstd_init(hctx);
1060 #endif
1061 	default:
1062 		return -1;
1063 	}
1064 }
1065 
1066 static int mod_deflate_compress(handler_ctx * const hctx, unsigned char * const start, off_t st_size) {
1067 	if (0 == st_size) return 0;
1068 	switch(hctx->compression_type) {
1069 #ifdef USE_ZLIB
1070 	case HTTP_ACCEPT_ENCODING_GZIP:
1071 	case HTTP_ACCEPT_ENCODING_DEFLATE:
1072 		return stream_deflate_compress(hctx, start, st_size);
1073 #endif
1074 #ifdef USE_BZ2LIB
1075 	case HTTP_ACCEPT_ENCODING_BZIP2:
1076 		return stream_bzip2_compress(hctx, start, st_size);
1077 #endif
1078 #ifdef USE_BROTLI
1079 	case HTTP_ACCEPT_ENCODING_BR:
1080 		return stream_br_compress(hctx, start, st_size);
1081 #endif
1082 #ifdef USE_ZSTD
1083 	case HTTP_ACCEPT_ENCODING_ZSTD:
1084 		return stream_zstd_compress(hctx, start, st_size);
1085 #endif
1086 	default:
1087 		UNUSED(start);
1088 		return -1;
1089 	}
1090 }
1091 
1092 static int mod_deflate_stream_flush(handler_ctx * const hctx, int end) {
1093 	if (0 == hctx->bytes_in) return 0;
1094 	switch(hctx->compression_type) {
1095 #ifdef USE_ZLIB
1096 	case HTTP_ACCEPT_ENCODING_GZIP:
1097 	case HTTP_ACCEPT_ENCODING_DEFLATE:
1098 		return stream_deflate_flush(hctx, end);
1099 #endif
1100 #ifdef USE_BZ2LIB
1101 	case HTTP_ACCEPT_ENCODING_BZIP2:
1102 		return stream_bzip2_flush(hctx, end);
1103 #endif
1104 #ifdef USE_BROTLI
1105 	case HTTP_ACCEPT_ENCODING_BR:
1106 		return stream_br_flush(hctx, end);
1107 #endif
1108 #ifdef USE_ZSTD
1109 	case HTTP_ACCEPT_ENCODING_ZSTD:
1110 		return stream_zstd_flush(hctx, end);
1111 #endif
1112 	default:
1113 		UNUSED(end);
1114 		return -1;
1115 	}
1116 }
1117 
1118 static void mod_deflate_note_ratio(request_st * const r, const off_t bytes_out, const off_t bytes_in) {
1119     /* store compression ratio in environment
1120      * for possible logging by mod_accesslog
1121      * (late in response handling, so not seen by most other modules) */
1122     /*(should be called only at end of successful response compression)*/
1123     if (0 == bytes_in) return;
1124     buffer_append_int(
1125       http_header_env_set_ptr(r, CONST_STR_LEN("ratio")),
1126       bytes_out * 100 / bytes_in);
1127 }
1128 
1129 static int mod_deflate_stream_end(handler_ctx *hctx) {
1130 	switch(hctx->compression_type) {
1131 #ifdef USE_ZLIB
1132 	case HTTP_ACCEPT_ENCODING_GZIP:
1133 	case HTTP_ACCEPT_ENCODING_DEFLATE:
1134 		return stream_deflate_end(hctx);
1135 #endif
1136 #ifdef USE_BZ2LIB
1137 	case HTTP_ACCEPT_ENCODING_BZIP2:
1138 		return stream_bzip2_end(hctx);
1139 #endif
1140 #ifdef USE_BROTLI
1141 	case HTTP_ACCEPT_ENCODING_BR:
1142 		return stream_br_end(hctx);
1143 #endif
1144 #ifdef USE_ZSTD
1145 	case HTTP_ACCEPT_ENCODING_ZSTD:
1146 		return stream_zstd_end(hctx);
1147 #endif
1148 	default:
1149 		return -1;
1150 	}
1151 }
1152 
1153 static int deflate_compress_cleanup(request_st * const r, handler_ctx * const hctx) {
1154 	int rc = mod_deflate_stream_end(hctx);
1155 
1156       #if 1 /* unnecessary if deflate.min-compress-size is set to a reasonable value */
1157 	if (0 == rc && hctx->bytes_in < hctx->bytes_out)
1158 		log_error(r->conf.errh, __FILE__, __LINE__,
1159 		  "uri %s in=%lld smaller than out=%lld", r->target.ptr,
1160 		  (long long)hctx->bytes_in, (long long)hctx->bytes_out);
1161       #endif
1162 
1163 	handler_ctx_free(hctx);
1164 	return rc;
1165 }
1166 
1167 
1168 static int mod_deflate_file_chunk(request_st * const r, handler_ctx * const hctx, chunk * const c, off_t st_size) {
1169 	off_t abs_offset;
1170 	off_t toSend = -1;
1171 	char *start;
1172 #ifdef USE_MMAP
1173 	/* use large chunks since server blocks while compressing, anyway
1174 	 * (mod_deflate is not recommended for large files) */
1175 	const off_t mmap_chunk_size = 16 * 1024 * 1024; /* must be power-of-2 */
1176 	off_t we_want_to_send = st_size;
1177 	volatile int mapped = 0;/* quiet warning: might be clobbered by 'longjmp' */
1178 #else
1179 	start = NULL;
1180 #endif
1181 
1182 	if (-1 == c->file.fd) {  /* open the file if not already open */
1183 		if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, r->conf.follow_symlink, O_RDONLY, 0))) {
1184 			log_perror(r->conf.errh, __FILE__, __LINE__, "open failed %s", c->mem->ptr);
1185 			return -1;
1186 		}
1187 	}
1188 
1189 	abs_offset = c->offset;
1190 
1191 #ifdef USE_MMAP
1192 	/* mmap the buffer
1193 	 * - first mmap
1194 	 * - new mmap as the we are at the end of the last one */
1195 	if (c->file.mmap.start == MAP_FAILED ||
1196 	    abs_offset == (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
1197 
1198 		/* Optimizations for the future:
1199 		 *
1200 		 * (comment is outdated since server blocks compressing file and then munmap()s file)
1201 		 *
1202 		 * adaptive mem-mapping
1203 		 *   the problem:
1204 		 *     we mmap() the whole file. If someone has a lot of large files and 32bit
1205 		 *     machine the virtual address area will be exhausted and we will have a failing
1206 		 *     mmap() call.
1207 		 *   solution:
1208 		 *     only mmap 16M in one chunk and move the window as soon as we have finished
1209 		 *     the first 8M
1210 		 *
1211 		 * read-ahead buffering
1212 		 *   the problem:
1213 		 *     sending out several large files in parallel trashes the read-ahead of the
1214 		 *     kernel leading to long wait-for-seek times.
1215 		 *   solutions: (increasing complexity)
1216 		 *     1. use madvise
1217 		 *     2. use a internal read-ahead buffer in the chunk-structure
1218 		 *     3. use non-blocking IO for file-transfers
1219 		 *   */
1220 
1221 		/* all mmap()ed areas are mmap_chunk_size except the last which might be smaller */
1222 		off_t to_mmap;
1223 
1224 		/* this is a remap, move the mmap-offset */
1225 		if (c->file.mmap.start != MAP_FAILED) {
1226 			munmap(c->file.mmap.start, c->file.mmap.length);
1227 			c->file.mmap.offset += mmap_chunk_size;
1228 		} else {
1229 			/* in case the range-offset is after the first mmap()ed area we skip the area */
1230 			c->file.mmap.offset = c->offset & ~(mmap_chunk_size-1);
1231 		}
1232 
1233 		to_mmap = c->file.length - c->file.mmap.offset;
1234 		if (to_mmap > mmap_chunk_size) to_mmap = mmap_chunk_size;
1235 		/* we have more to send than we can mmap() at once */
1236 		if (we_want_to_send > to_mmap) we_want_to_send = to_mmap;
1237 
1238 		if (MAP_FAILED == (c->file.mmap.start = mmap(0, (size_t)to_mmap, PROT_READ, MAP_SHARED, c->file.fd, c->file.mmap.offset))
1239 		    && (errno != EINVAL || MAP_FAILED == (c->file.mmap.start = mmap(0, (size_t)to_mmap, PROT_READ, MAP_PRIVATE, c->file.fd, c->file.mmap.offset)))) {
1240 			log_perror(r->conf.errh, __FILE__, __LINE__,
1241 			  "mmap failed %s %d", c->mem->ptr, c->file.fd);
1242 
1243 			return -1;
1244 		}
1245 
1246 		c->file.mmap.length = to_mmap;
1247 #ifdef HAVE_MADVISE
1248 		/* don't advise files <= 64Kb */
1249 		if (c->file.mmap.length > 65536 &&
1250 		    0 != madvise(c->file.mmap.start, c->file.mmap.length, MADV_WILLNEED)) {
1251 			log_perror(r->conf.errh, __FILE__, __LINE__,
1252 			  "madvise failed %s %d", c->mem->ptr, c->file.fd);
1253 		}
1254 #endif
1255 
1256 		/* chunk_reset() or chunk_free() will cleanup for us */
1257 	}
1258 
1259 	/* to_send = abs_mmap_end - abs_offset */
1260 	toSend = (c->file.mmap.offset + c->file.mmap.length) - abs_offset;
1261 	if (toSend > we_want_to_send) toSend = we_want_to_send;
1262 
1263 	if (toSend < 0) {
1264 		log_error(r->conf.errh, __FILE__, __LINE__,
1265 		  "toSend is negative: %lld %lld %lld %lld",
1266 		  (long long)toSend,
1267 		  (long long)c->file.mmap.length,
1268 		  (long long)abs_offset,
1269 		  (long long)c->file.mmap.offset);
1270 		force_assert(toSend < 0);
1271 	}
1272 
1273 	start = c->file.mmap.start;
1274 	mapped = 1;
1275 #endif
1276 
1277 	if (MAP_FAILED == c->file.mmap.start) {
1278 		toSend = st_size;
1279 		if (toSend > 2*1024*1024) toSend = 2*1024*1024;
1280 		if (NULL == (start = malloc((size_t)toSend)) || -1 == lseek(c->file.fd, abs_offset, SEEK_SET) || toSend != read(c->file.fd, start, (size_t)toSend)) {
1281 			log_perror(r->conf.errh, __FILE__, __LINE__, "reading %s failed", c->mem->ptr);
1282 
1283 			free(start);
1284 			return -1;
1285 		}
1286 		abs_offset = 0;
1287 	}
1288 
1289 #ifdef USE_MMAP
1290 	if (mapped) {
1291 		signal(SIGBUS, sigbus_handler);
1292 		sigbus_jmp_valid = 1;
1293 		if (0 != sigsetjmp(sigbus_jmp, 1)) {
1294 			sigbus_jmp_valid = 0;
1295 
1296 			log_error(r->conf.errh, __FILE__, __LINE__,
1297 			  "SIGBUS in mmap: %s %d", c->mem->ptr, c->file.fd);
1298 			return -1;
1299 		}
1300 	}
1301 #endif
1302 
1303 	if (mod_deflate_compress(hctx,
1304 				(unsigned char *)start + (abs_offset - c->file.mmap.offset), toSend) < 0) {
1305 		log_error(r->conf.errh, __FILE__, __LINE__, "compress failed.");
1306 		toSend = -1;
1307 	}
1308 
1309 #ifdef USE_MMAP
1310 	if (mapped)
1311 		sigbus_jmp_valid = 0;
1312 	else
1313 #endif
1314 		free(start);
1315 
1316 	return toSend;
1317 }
1318 
1319 
1320 static handler_t deflate_compress_response(request_st * const r, handler_ctx * const hctx) {
1321 	off_t len, max;
1322 	int close_stream;
1323 
1324 	/* move all chunk from write_queue into our in_queue, then adjust
1325 	 * counters since r->write_queue is reused for compressed output */
1326 	chunkqueue * const cq = &r->write_queue;
1327 	len = chunkqueue_length(cq);
1328 	chunkqueue_remove_finished_chunks(cq);
1329 	chunkqueue_append_chunkqueue(&hctx->in_queue, cq);
1330 	cq->bytes_in  -= len;
1331 	cq->bytes_out -= len;
1332 
1333 	max = chunkqueue_length(&hctx->in_queue);
1334       #if 0
1335 	/* calculate max bytes to compress for this call */
1336 	if (p->conf.sync_flush && max > (len = p->conf.work_block_size << 10)) {
1337 		max = len;
1338 	}
1339       #endif
1340 
1341 	/* Compress chunks from in_queue into chunks for write_queue */
1342 	while (max) {
1343 		chunk *c = hctx->in_queue.first;
1344 
1345 		switch(c->type) {
1346 		case MEM_CHUNK:
1347 			len = buffer_string_length(c->mem) - c->offset;
1348 			if (len > max) len = max;
1349 			if (mod_deflate_compress(hctx, (unsigned char *)c->mem->ptr+c->offset, len) < 0) {
1350 				log_error(r->conf.errh, __FILE__, __LINE__, "compress failed.");
1351 				return HANDLER_ERROR;
1352 			}
1353 			break;
1354 		case FILE_CHUNK:
1355 			len = c->file.length - c->offset;
1356 			if (len > max) len = max;
1357 			if ((len = mod_deflate_file_chunk(r, hctx, c, len)) < 0) {
1358 				log_error(r->conf.errh, __FILE__, __LINE__, "compress file chunk failed.");
1359 				return HANDLER_ERROR;
1360 			}
1361 			break;
1362 		default:
1363 			log_error(r->conf.errh, __FILE__, __LINE__, "%d type not known", c->type);
1364 			return HANDLER_ERROR;
1365 		}
1366 
1367 		max -= len;
1368 		chunkqueue_mark_written(&hctx->in_queue, len);
1369 	}
1370 
1371 	/*(currently should always be true)*/
1372 	/*(current implementation requires response be complete)*/
1373 	close_stream = (r->resp_body_finished
1374                         && chunkqueue_is_empty(&hctx->in_queue));
1375 	if (mod_deflate_stream_flush(hctx, close_stream) < 0) {
1376 		log_error(r->conf.errh, __FILE__, __LINE__, "flush error");
1377 		return HANDLER_ERROR;
1378 	}
1379 
1380 	return close_stream ? HANDLER_FINISHED : HANDLER_GO_ON;
1381 }
1382 
1383 
1384 static int mod_deflate_choose_encoding (const char *value, plugin_data *p, const char **label) {
1385 	/* get client side support encodings */
1386 	int accept_encoding = 0;
1387       #if !defined(USE_ZLIB) && !defined(USE_BZ2LIB) && !defined(USE_BROTLI) \
1388        && !defined(USE_ZSTD)
1389 	UNUSED(value);
1390 	UNUSED(label);
1391       #else
1392         for (; *value; ++value) {
1393             const char *v;
1394             while (*value == ' ' || *value == ',') ++value;
1395             v = value;
1396             while (*value!=' ' && *value!=',' && *value!=';' && *value!='\0')
1397                 ++value;
1398             switch (value - v) {
1399               case 2:
1400                #ifdef USE_BROTLI
1401                 if (0 == memcmp(v, "br", 2))
1402                     accept_encoding |= HTTP_ACCEPT_ENCODING_BR;
1403                #endif
1404                 break;
1405               case 4:
1406                #ifdef USE_ZLIB
1407                 if (0 == memcmp(v, "gzip", 4))
1408                     accept_encoding |= HTTP_ACCEPT_ENCODING_GZIP;
1409                #endif
1410                #ifdef USE_ZSTD
1411                 #ifdef USE_ZLIB
1412                 else
1413                 #endif
1414                 if (0 == memcmp(v, "zstd", 4))
1415                     accept_encoding |= HTTP_ACCEPT_ENCODING_ZSTD;
1416                #endif
1417                 break;
1418               case 5:
1419                #ifdef USE_BZ2LIB
1420                 if (0 == memcmp(v, "bzip2", 5))
1421                     accept_encoding |= HTTP_ACCEPT_ENCODING_BZIP2;
1422                #endif
1423                 break;
1424               case 6:
1425                #ifdef USE_ZLIB
1426                 if (0 == memcmp(v, "x-gzip", 6))
1427                     accept_encoding |= HTTP_ACCEPT_ENCODING_X_GZIP;
1428                #endif
1429                 break;
1430               case 7:
1431                #ifdef USE_ZLIB
1432                 if (0 == memcmp(v, "deflate", 7))
1433                     accept_encoding |= HTTP_ACCEPT_ENCODING_DEFLATE;
1434                #endif
1435                #ifdef USE_BZ2LIB
1436                 if (0 == memcmp(v, "x-bzip2", 7))
1437                     accept_encoding |= HTTP_ACCEPT_ENCODING_X_BZIP2;
1438                #endif
1439                 break;
1440              #if 0
1441               case 8:
1442                 if (0 == memcmp(v, "identity", 8))
1443                     accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY;
1444                 else if (0 == memcmp(v, "compress", 8))
1445                     accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS;
1446                 break;
1447              #endif
1448               default:
1449                 break;
1450             }
1451             if (*value == ';') {
1452                 while (*value != ',' && *value != '\0') ++value;
1453             }
1454             if (*value == '\0') break;
1455         }
1456       #endif
1457 
1458 	/* mask to limit to allowed_encodings */
1459 	accept_encoding &= p->conf.allowed_encodings;
1460 
1461 	/* select best matching encoding */
1462 #ifdef USE_ZSTD
1463 	if (accept_encoding & HTTP_ACCEPT_ENCODING_ZSTD) {
1464 		*label = "zstd";
1465 		return HTTP_ACCEPT_ENCODING_ZSTD;
1466 	} else
1467 #endif
1468 #ifdef USE_BROTLI
1469 	if (accept_encoding & HTTP_ACCEPT_ENCODING_BR) {
1470 		*label = "br";
1471 		return HTTP_ACCEPT_ENCODING_BR;
1472 	} else
1473 #endif
1474 #ifdef USE_BZ2LIB
1475 	if (accept_encoding & HTTP_ACCEPT_ENCODING_BZIP2) {
1476 		*label = "bzip2";
1477 		return HTTP_ACCEPT_ENCODING_BZIP2;
1478 	} else if (accept_encoding & HTTP_ACCEPT_ENCODING_X_BZIP2) {
1479 		*label = "x-bzip2";
1480 		return HTTP_ACCEPT_ENCODING_BZIP2;
1481 	} else
1482 #endif
1483 #ifdef USE_ZLIB
1484 	if (accept_encoding & HTTP_ACCEPT_ENCODING_GZIP) {
1485 		*label = "gzip";
1486 		return HTTP_ACCEPT_ENCODING_GZIP;
1487 	} else if (accept_encoding & HTTP_ACCEPT_ENCODING_X_GZIP) {
1488 		*label = "x-gzip";
1489 		return HTTP_ACCEPT_ENCODING_GZIP;
1490 	} else if (accept_encoding & HTTP_ACCEPT_ENCODING_DEFLATE) {
1491 		*label = "deflate";
1492 		return HTTP_ACCEPT_ENCODING_DEFLATE;
1493 	} else
1494 #endif
1495 	if (0 == accept_encoding) {
1496 		return 0;
1497 	} else {
1498 		return 0;
1499 	}
1500 }
1501 
1502 REQUEST_FUNC(mod_deflate_handle_response_start) {
1503 	plugin_data *p = p_d;
1504 	const buffer *vbro;
1505 	buffer *vb;
1506 	handler_ctx *hctx;
1507 	const char *label;
1508 	off_t len;
1509 	uint32_t etaglen;
1510 	int compression_type;
1511 	handler_t rc;
1512 	int had_vary = 0;
1513 
1514 	/*(current implementation requires response be complete)*/
1515 	if (!r->resp_body_finished) return HANDLER_GO_ON;
1516 	if (r->http_method == HTTP_METHOD_HEAD) return HANDLER_GO_ON;
1517 	if (light_btst(r->resp_htags, HTTP_HEADER_TRANSFER_ENCODING))
1518 		return HANDLER_GO_ON;
1519 	if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_ENCODING))
1520 		return HANDLER_GO_ON;
1521 
1522 	/* disable compression for some http status types. */
1523 	switch(r->http_status) {
1524 	case 100:
1525 	case 101:
1526 	case 204:
1527 	case 205:
1528 	case 304:
1529 		/* disable compression as we have no response entity */
1530 		return HANDLER_GO_ON;
1531 	default:
1532 		break;
1533 	}
1534 
1535 	mod_deflate_patch_config(r, p);
1536 
1537 	/* check if deflate configured for any mimetypes */
1538 	if (NULL == p->conf.mimetypes) return HANDLER_GO_ON;
1539 
1540 	/* check if size of response is below min-compress-size or exceeds max*/
1541 	/* (r->resp_body_finished checked at top of routine) */
1542 	len = chunkqueue_length(&r->write_queue);
1543 	if (len <= (off_t)p->conf.min_compress_size) return HANDLER_GO_ON;
1544 	if (p->conf.max_compress_size /*(max_compress_size in KB)*/
1545 	    && len > ((off_t)p->conf.max_compress_size << 10)) {
1546 		return HANDLER_GO_ON;
1547 	}
1548 
1549 	/* Check Accept-Encoding for supported encoding. */
1550 	vbro = http_header_request_get(r, HTTP_HEADER_ACCEPT_ENCODING, CONST_STR_LEN("Accept-Encoding"));
1551 	if (NULL == vbro) return HANDLER_GO_ON;
1552 
1553 	/* find matching encodings */
1554 	compression_type = mod_deflate_choose_encoding(vbro->ptr, p, &label);
1555 	if (!compression_type) return HANDLER_GO_ON;
1556 
1557 	/* Check mimetype in response header "Content-Type" */
1558 	if (NULL != (vbro = http_header_response_get(r, HTTP_HEADER_CONTENT_TYPE, CONST_STR_LEN("Content-Type")))) {
1559 		if (NULL == array_match_value_prefix(p->conf.mimetypes, vbro)) return HANDLER_GO_ON;
1560 	} else {
1561 		/* If no Content-Type set, compress only if first p->conf.mimetypes value is "" */
1562 		data_string *mimetype = (data_string *)p->conf.mimetypes->data[0];
1563 		if (!buffer_string_is_empty(&mimetype->value)) return HANDLER_GO_ON;
1564 	}
1565 
1566 	/* Vary: Accept-Encoding (response might change according to request Accept-Encoding) */
1567 	if (NULL != (vb = http_header_response_get(r, HTTP_HEADER_VARY, CONST_STR_LEN("Vary")))) {
1568 		had_vary = 1;
1569 		if (NULL == strstr(vb->ptr, "Accept-Encoding")) {
1570 			buffer_append_string_len(vb, CONST_STR_LEN(",Accept-Encoding"));
1571 		}
1572 	} else {
1573 		http_header_response_append(r, HTTP_HEADER_VARY,
1574 					    CONST_STR_LEN("Vary"),
1575 					    CONST_STR_LEN("Accept-Encoding"));
1576 	}
1577 
1578 	/* check ETag as is done in http_response_handle_cachable()
1579 	 * (slightly imperfect (close enough?) match of ETag "000000" to "000000-gzip") */
1580 	vb = http_header_response_get(r, HTTP_HEADER_ETAG, CONST_STR_LEN("ETag"));
1581 	etaglen = (NULL != vb) ? buffer_string_length(vb) : 0;
1582 	if (NULL != vb && light_btst(r->rqst_htags, HTTP_HEADER_IF_NONE_MATCH)) {
1583 		const buffer *if_none_match = http_header_response_get(r, HTTP_HEADER_IF_NONE_MATCH, CONST_STR_LEN("If-None-Match"));
1584 		if (etaglen
1585 		    && r->http_status < 300 /*(want 2xx only)*/
1586 		    && NULL != if_none_match
1587 		    && 0 == strncmp(if_none_match->ptr, vb->ptr, etaglen-1)
1588 		    && if_none_match->ptr[etaglen-1] == '-'
1589 		    && 0 == strncmp(if_none_match->ptr+etaglen, label, strlen(label))) {
1590 
1591 			if (http_method_get_or_head(r->http_method)) {
1592 				/* modify ETag response header in-place to remove '"' and append '-label"' */
1593 				vb->ptr[etaglen-1] = '-'; /*(overwrite end '"')*/
1594 				buffer_append_string(vb, label);
1595 				buffer_append_string_len(vb, CONST_STR_LEN("\""));
1596 				/*buffer_copy_buffer(&r->physical.etag, vb);*//*(keep in sync?)*/
1597 				r->http_status = 304;
1598 			} else {
1599 				r->http_status = 412;
1600 			}
1601 
1602 			/* response_start hook occurs after error docs have been handled.
1603 			 * For now, send back empty response body.
1604 			 * In the future, might extract the error doc code so that it
1605 			 * might be run again if response_start hooks return with
1606 			 * changed http_status and r->handler_module = NULL */
1607 			/* clear content length even if 304 since compressed length unknown */
1608 			http_response_body_clear(r, 0);
1609 
1610 			r->resp_body_finished = 1;
1611 			r->handler_module = NULL;
1612 			return HANDLER_GO_ON;
1613 		}
1614 	}
1615 
1616 	if (0.0 < p->conf.max_loadavg && p->conf.max_loadavg < r->con->srv->loadavg[0]) {
1617 		return HANDLER_GO_ON;
1618 	}
1619 
1620 	/* update ETag, if ETag response header is set */
1621 	if (etaglen) {
1622 		/* modify ETag response header in-place to remove '"' and append '-label"' */
1623 		vb->ptr[etaglen-1] = '-'; /*(overwrite end '"')*/
1624 		buffer_append_string(vb, label);
1625 		buffer_append_string_len(vb, CONST_STR_LEN("\""));
1626 		/*buffer_copy_buffer(&r->physical.etag, vb);*//*(keep in sync?)*/
1627 	}
1628 
1629 	/* set Content-Encoding to show selected compression type */
1630 	http_header_response_set(r, HTTP_HEADER_CONTENT_ENCODING, CONST_STR_LEN("Content-Encoding"), label, strlen(label));
1631 
1632 	/* clear Content-Length and r->write_queue if HTTP HEAD request
1633 	 * (alternatively, could return original Content-Length with HEAD
1634 	 *  request if ETag not modified and Content-Encoding not added) */
1635 	if (HTTP_METHOD_HEAD == r->http_method) {
1636 		/* ensure that uncompressed Content-Length is not sent in HEAD response */
1637 		http_response_body_clear(r, 0);
1638 		return HANDLER_GO_ON;
1639 	}
1640 
1641 	/* restrict items eligible for cache of compressed responses
1642 	 * (This module does not aim to be a full caching proxy)
1643 	 * response must be complete (not streaming response)
1644 	 * must not have prior Vary response header (before Accept-Encoding added)
1645 	 * must have ETag
1646 	 * must be file
1647 	 * must be single FILE_CHUNK in chunkqueue
1648 	 * must not be chunkqueue temporary file
1649 	 * must be whole file, not partial content
1650 	 * must not be HTTP status 206 Partial Content
1651 	 * Note: small files (< 32k (see http_chunk.c)) will have been read into
1652 	 *       memory (if streaming HTTP/1.1 chunked response) and will end up
1653 	 *       getting stream-compressed rather than cached on disk as compressed
1654 	 *       file
1655 	 */
1656 	buffer *tb = NULL;
1657 	if (!buffer_is_empty(p->conf.cache_dir)
1658 	    && !had_vary
1659 	    && etaglen > 2
1660 	    && r->resp_body_finished
1661 	    && r->write_queue.first == r->write_queue.last
1662 	    && r->write_queue.first->type == FILE_CHUNK
1663 	    && r->write_queue.first->offset == 0
1664 	    && !r->write_queue.first->file.is_temp
1665 	    && r->http_status != 206) {
1666 		tb = mod_deflate_cache_file_name(r, p->conf.cache_dir, vb);
1667 		/*(checked earlier and skipped if Transfer-Encoding had been set)*/
1668 		stat_cache_entry *sce = stat_cache_get_entry_open(tb, 1);
1669 		if (NULL != sce) {
1670 			chunkqueue_reset(&r->write_queue);
1671 			if (sce->fd < 0 || 0 != http_chunk_append_file_ref(r, sce))
1672 				return HANDLER_ERROR;
1673 			if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LENGTH))
1674 				http_header_response_unset(r, HTTP_HEADER_CONTENT_LENGTH,
1675 				                           CONST_STR_LEN("Content-Length"));
1676 			mod_deflate_note_ratio(r, sce->st.st_size, len);
1677 			return HANDLER_GO_ON;
1678 		}
1679 		/* sanity check that response was whole file;
1680 		 * (racy since using stat_cache, but cache file only if match) */
1681 		sce = stat_cache_get_entry(r->write_queue.first->mem);
1682 		if (NULL == sce || sce->st.st_size != len)
1683 			tb = NULL;
1684 		else if (0 != mkdir_for_file(tb->ptr))
1685 			tb = NULL;
1686 	}
1687 
1688 	/* enable compression */
1689 	p->conf.sync_flush =
1690 	  ((r->conf.stream_response_body
1691 	    & (FDEVENT_STREAM_RESPONSE | FDEVENT_STREAM_RESPONSE_BUFMIN))
1692 	   && 0 == p->conf.output_buffer_size);
1693 	hctx = handler_ctx_init();
1694 	hctx->plugin_data = p;
1695 	hctx->compression_type = compression_type;
1696 	hctx->r = r;
1697 	/* setup output buffer */
1698 	buffer_clear(&p->tmp_buf);
1699 	hctx->output = &p->tmp_buf;
1700 	/* open cache file if caching compressed file */
1701 	if (tb) mod_deflate_cache_file_open(hctx, tb);
1702 	if (0 != mod_deflate_stream_init(hctx)) {
1703 		/*(should not happen unless ENOMEM)*/
1704 		handler_ctx_free(hctx);
1705 		log_error(r->conf.errh, __FILE__, __LINE__,
1706 		  "Failed to initialize compression %s", label);
1707 		/* restore prior Etag and unset Content-Encoding */
1708 		if (etaglen) {
1709 			vb->ptr[etaglen-1] = '"'; /*(overwrite '-')*/
1710 			buffer_string_set_length(vb, etaglen);
1711 		}
1712 		http_header_response_unset(r, HTTP_HEADER_CONTENT_ENCODING, CONST_STR_LEN("Content-Encoding"));
1713 		return HANDLER_GO_ON;
1714 	}
1715 
1716   #ifdef USE_BROTLI
1717 	if (r->resp_body_finished
1718 	    && (hctx->compression_type & HTTP_ACCEPT_ENCODING_BR)
1719 	    && (len >> 1) < (off_t)((~(uint32_t)0u) >> 1))
1720 		BrotliEncoderSetParameter(hctx->u.br, BROTLI_PARAM_SIZE_HINT, (uint32_t)len);
1721   #endif
1722 
1723 	if (light_btst(r->resp_htags, HTTP_HEADER_CONTENT_LENGTH)) {
1724 		http_header_response_unset(r, HTTP_HEADER_CONTENT_LENGTH, CONST_STR_LEN("Content-Length"));
1725 	}
1726 	r->plugin_ctx[p->id] = hctx;
1727 
1728 	rc = deflate_compress_response(r, hctx);
1729 	if (HANDLER_GO_ON == rc) return HANDLER_GO_ON;
1730 	if (HANDLER_FINISHED == rc) {
1731 	  #ifdef __COVERITY__
1732 		/* coverity misses if hctx->cache_fd is not -1, then tb is not NULL */
1733 		force_assert(-1 == hctx->cache_fd || NULL != tb);
1734 	  #endif
1735 		if (-1 == hctx->cache_fd
1736 		    || 0 == mod_deflate_cache_file_finish(r, hctx, tb)) {
1737 			mod_deflate_note_ratio(r, hctx->bytes_out, hctx->bytes_in);
1738 			rc = HANDLER_GO_ON;
1739 		}
1740 		else
1741 			rc = HANDLER_ERROR;
1742 	}
1743 	r->plugin_ctx[p->id] = NULL;
1744 	if (deflate_compress_cleanup(r, hctx) < 0) return HANDLER_ERROR;
1745 	return rc;
1746 }
1747 
1748 static handler_t mod_deflate_cleanup(request_st * const r, void *p_d) {
1749 	plugin_data *p = p_d;
1750 	handler_ctx *hctx = r->plugin_ctx[p->id];
1751 
1752 	if (NULL != hctx) {
1753 		r->plugin_ctx[p->id] = NULL;
1754 		deflate_compress_cleanup(r, hctx);
1755 	}
1756 
1757 	return HANDLER_GO_ON;
1758 }
1759 
1760 int mod_deflate_plugin_init(plugin *p);
1761 int mod_deflate_plugin_init(plugin *p) {
1762 	p->version     = LIGHTTPD_VERSION_ID;
1763 	p->name        = "deflate";
1764 
1765 	p->init		= mod_deflate_init;
1766 	p->cleanup	= mod_deflate_free;
1767 	p->set_defaults	= mod_deflate_set_defaults;
1768 	p->handle_request_reset = mod_deflate_cleanup;
1769 	p->handle_response_start	= mod_deflate_handle_response_start;
1770 
1771 	return 0;
1772 }
1773