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