4 * bug fix on Robert Jakabosky from alphatrade.com's lighttp 1.4.10 mod_deflate patch
6 * Bug fix and new features:
7 * 1) fix loop bug when content-length is bigger than work-block-size*k
11 * lighttpd-1.4.26.mod_deflate.patch from
12 * https://redmine.lighttpd.net/projects/1/wiki/Docs_ModDeflate
16 * Patch further modified in this incarnation.
18 * Note: this patch only handles completed responses (con->file_finished);
19 * this patch does not currently handle streaming dynamic responses,
20 * and therefore also does not worry about Transfer-Encoding: chunked
21 * (or having separate con->output_queue for chunked-encoded output)
22 * (or using separate buffers per connection instead of p->tmp_buf)
23 * (or handling interactions with block buffering and write timeouts)
26 * - fixed major bug with compressing chunks with offset > 0
28 * "Response breaking in mod_deflate"
29 * https://redmine.lighttpd.net/issues/986
30 * - fix broken (in some cases) chunk accounting in deflate_compress_response()
33 * "mod_deflate's bzip2 broken by default"
34 * https://redmine.lighttpd.net/issues/2035
35 * - fix mismatch with current chunk interfaces
37 * "Weird things in chunk.c (functions only handling specific cases, unexpected behaviour)"
38 * https://redmine.lighttpd.net/issues/1510
40 * Behavior changes from prior patch:
41 * - deflate.mimetypes must now be configured to enable compression
42 * deflate.mimetypes = ( ) # compress nothing (disabled; default)
43 * deflate.mimetypes = ( "" ) # compress all mimetypes
44 * deflate.mimetypes = ( "text/" ) # compress text/... mimetypes
46 * "mod_deflate enabled by default"
47 * https://redmine.lighttpd.net/issues/1394
48 * - deflate.enabled directive removed (see new behavior of deflate.mimetypes)
49 * - deflate.debug removed (was developer debug trace, not end-user debug)
50 * - deflate.bzip2 replaced with deflate.allowed-encodings (like mod_compress)
52 * "mod_deflate should allow limiting of compression algorithm from the configuration file"
53 * https://redmine.lighttpd.net/issues/996
54 * "mod_compress disabling methods"
55 * https://redmine.lighttpd.net/issues/1773
56 * - deflate.nocompress-url removed since disabling compression for a URL
57 * can now easily be done by setting to a blank list either directive
58 * deflate.accept_encodings = () or deflate.mimetypes = () in a conditional
59 * block, e.g. $HTTP["url"] =~ "....." { deflate.mimetypes = ( ) }
60 * - deflate.sync-flush removed; controlled by con->conf.stream_response_body
61 * (though streaming compression not currently implemented in mod_deflate)
62 * - inactive directives in this patch (since con->file_finished required)
63 * deflate.work-block-size
64 * deflate.output-buffer-size
65 * - remove weak file size check; SIGBUS is trapped, file that shrink will error
67 * "mod_deflate: filesize check is too weak"
68 * https://redmine.lighttpd.net/issues/1512
69 * - change default deflate.min-compress-size from 0 to now be 256
70 * http://webmasters.stackexchange.com/questions/31750/what-is-recommended-minimum-object-size-for-gzip-performance-benefits
71 * Apache 2.4 mod_deflate minimum is 68 bytes
72 * Akamai recommends minimum 860 bytes
73 * Google recommends minimum be somewhere in range between 150 and 1024 bytes
74 * - deflate.max-compress-size new directive (in kb like compress.max_filesize)
75 * - deflate.mem-level removed (too many knobs for little benefit)
76 * - deflate.window-size removed (too many knobs for little benefit)
79 * - config directives may be changed, renamed, or removed
80 * e.g. A set of reasonable defaults might be chosen
81 * instead of making them configurable.
82 * deflate.min-compress-size
83 * - might add deflate.mimetypes-exclude = ( ... ) for list of mimetypes
84 * to avoid compressing, even if a broader deflate.mimetypes matched,
85 * e.g. to compress all "text/" except "text/special".
86 * - mod_compress and mod_deflate might merge overlapping feature sets
87 * (mod_compress.cache-dir does not yet have an equivalent in mod_deflate)
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.
101 #include <sys/types.h>
102 #include <sys/stat.h>
103 #include "sys-mmap.h"
115 #include "http_chunk.h"
116 #include "response.h"
120 #if defined HAVE_ZLIB_H && defined HAVE_LIBZ
124 #ifndef Z_DEFAULT_COMPRESSION
125 #define Z_DEFAULT_COMPRESSION -1
131 #if defined HAVE_BZLIB_H && defined HAVE_LIBBZ2
133 /* we don't need stdio interface */
138 #if defined HAVE_SYS_MMAN_H && defined HAVE_MMAP && defined ENABLE_MMAP
141 #include "sys-mmap.h"
145 static volatile int sigbus_jmp_valid
;
146 static sigjmp_buf sigbus_jmp
;
148 static void sigbus_handler(int sig
) {
150 if (sigbus_jmp_valid
) siglongjmp(sigbus_jmp
, 1);
151 log_failed_assert(__FILE__
, __LINE__
, "SIGBUS");
155 /* request: accept-encoding */
156 #define HTTP_ACCEPT_ENCODING_IDENTITY BV(0)
157 #define HTTP_ACCEPT_ENCODING_GZIP BV(1)
158 #define HTTP_ACCEPT_ENCODING_DEFLATE BV(2)
159 #define HTTP_ACCEPT_ENCODING_COMPRESS BV(3)
160 #define HTTP_ACCEPT_ENCODING_BZIP2 BV(4)
161 #define HTTP_ACCEPT_ENCODING_X_GZIP BV(5)
162 #define HTTP_ACCEPT_ENCODING_X_BZIP2 BV(6)
165 #define MByte * 1024 KByte
166 #define GByte * 1024 MByte
170 int allowed_encodings
;
171 unsigned int max_compress_size
;
172 unsigned short min_compress_size
;
173 unsigned short output_buffer_size
;
174 unsigned short work_block_size
;
175 unsigned short sync_flush
;
176 short compression_level
;
185 plugin_config
**config_storage
;
201 chunkqueue
*in_queue
;
203 plugin_data
*plugin_data
;
204 int compression_type
;
207 static handler_ctx
*handler_ctx_init() {
210 hctx
= calloc(1, sizeof(*hctx
));
211 hctx
->in_queue
= chunkqueue_init();
216 static void handler_ctx_free(handler_ctx
*hctx
) {
218 if (hctx
->output
!= p
->tmp_buf
) {
219 buffer_free(hctx
->output
);
222 chunkqueue_free(hctx
->in_queue
);
226 INIT_FUNC(mod_deflate_init
) {
229 p
= calloc(1, sizeof(*p
));
231 p
->encodings
= array_init();
232 p
->tmp_buf
= buffer_init();
233 buffer_string_prepare_copy(p
->tmp_buf
, 64 KByte
);
238 FREE_FUNC(mod_deflate_free
) {
239 plugin_data
*p
= p_d
;
243 if (!p
) return HANDLER_GO_ON
;
245 if (p
->config_storage
) {
247 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
248 plugin_config
*s
= p
->config_storage
[i
];
252 array_free(s
->mimetypes
);
255 free(p
->config_storage
);
258 buffer_free(p
->tmp_buf
);
259 array_free(p
->encodings
);
263 return HANDLER_GO_ON
;
266 SETDEFAULTS_FUNC(mod_deflate_setdefaults
) {
267 plugin_data
*p
= p_d
;
270 config_values_t cv
[] = {
271 { "deflate.mimetypes", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
},
272 { "deflate.allowed-encodings", NULL
, T_CONFIG_ARRAY
, T_CONFIG_SCOPE_CONNECTION
},
273 { "deflate.max-compress-size", NULL
, T_CONFIG_INT
, T_CONFIG_SCOPE_CONNECTION
},
274 { "deflate.min-compress-size", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
},
275 { "deflate.compression-level", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
},
276 { "deflate.output-buffer-size", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
},
277 { "deflate.work-block-size", NULL
, T_CONFIG_SHORT
, T_CONFIG_SCOPE_CONNECTION
},
278 { "deflate.max-loadavg", NULL
, T_CONFIG_STRING
,T_CONFIG_SCOPE_CONNECTION
},
279 { NULL
, NULL
, T_CONFIG_UNSET
, T_CONFIG_SCOPE_UNSET
}
282 p
->config_storage
= calloc(1, srv
->config_context
->used
* sizeof(plugin_config
*));
284 for (i
= 0; i
< srv
->config_context
->used
; i
++) {
287 s
= calloc(1, sizeof(plugin_config
));
288 s
->mimetypes
= array_init();
289 s
->allowed_encodings
= 0;
290 s
->max_compress_size
= 128*1024; /*(128 MB measured as num KB)*/
291 s
->min_compress_size
= 256;
292 s
->output_buffer_size
= 0;
293 s
->work_block_size
= 2048;
295 s
->compression_level
= -1;
296 s
->max_loadavg
= 0.0;
298 array_reset(p
->encodings
); /* temp array for allowed encodings list */
300 cv
[0].destination
= s
->mimetypes
;
301 cv
[1].destination
= p
->encodings
;
302 cv
[2].destination
= &(s
->max_compress_size
);
303 cv
[3].destination
= &(s
->min_compress_size
);
304 cv
[4].destination
= &(s
->compression_level
);
305 cv
[5].destination
= &(s
->output_buffer_size
);
306 cv
[6].destination
= &(s
->work_block_size
);
307 cv
[7].destination
= p
->tmp_buf
;
308 buffer_string_set_length(p
->tmp_buf
, 0);
310 p
->config_storage
[i
] = s
;
312 if (0 != config_insert_values_global(srv
, ((data_config
*)srv
->config_context
->data
[i
])->value
, cv
, i
== 0 ? T_CONFIG_SCOPE_SERVER
: T_CONFIG_SCOPE_CONNECTION
)) {
313 return HANDLER_ERROR
;
316 if ((s
->compression_level
< 1 || s
->compression_level
> 9) &&
317 s
->compression_level
!= -1) {
318 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
319 "compression-level must be between 1 and 9:", s
->compression_level
);
320 return HANDLER_ERROR
;
323 if (!buffer_string_is_empty(p
->tmp_buf
)) {
324 s
->max_loadavg
= strtod(p
->tmp_buf
->ptr
, NULL
);
327 if (p
->encodings
->used
) {
329 for (j
= 0; j
< p
->encodings
->used
; j
++) {
330 #if defined(USE_ZLIB) || defined(USE_BZ2LIB)
331 data_string
*ds
= (data_string
*)p
->encodings
->data
[j
];
334 if (NULL
!= strstr(ds
->value
->ptr
, "gzip"))
335 s
->allowed_encodings
|= HTTP_ACCEPT_ENCODING_GZIP
| HTTP_ACCEPT_ENCODING_X_GZIP
;
336 if (NULL
!= strstr(ds
->value
->ptr
, "x-gzip"))
337 s
->allowed_encodings
|= HTTP_ACCEPT_ENCODING_X_GZIP
;
338 if (NULL
!= strstr(ds
->value
->ptr
, "deflate"))
339 s
->allowed_encodings
|= HTTP_ACCEPT_ENCODING_DEFLATE
;
341 if (NULL != strstr(ds->value->ptr, "compress"))
342 s->allowed_encodings |= HTTP_ACCEPT_ENCODING_COMPRESS;
346 if (NULL
!= strstr(ds
->value
->ptr
, "bzip2"))
347 s
->allowed_encodings
|= HTTP_ACCEPT_ENCODING_BZIP2
| HTTP_ACCEPT_ENCODING_X_BZIP2
;
348 if (NULL
!= strstr(ds
->value
->ptr
, "x-bzip2"))
349 s
->allowed_encodings
|= HTTP_ACCEPT_ENCODING_X_BZIP2
;
353 /* default encodings */
355 s
->allowed_encodings
|= HTTP_ACCEPT_ENCODING_GZIP
356 | HTTP_ACCEPT_ENCODING_X_GZIP
357 | HTTP_ACCEPT_ENCODING_DEFLATE
;
360 s
->allowed_encodings
|= HTTP_ACCEPT_ENCODING_BZIP2
361 | HTTP_ACCEPT_ENCODING_X_BZIP2
;
365 /* mod_deflate matches mimetype as prefix of Content-Type
366 * so ignore '*' at end of mimetype for end-user flexibility
367 * in specifying trailing wildcard to grouping of mimetypes */
368 for (size_t m
= 0; m
< s
->mimetypes
->used
; ++m
) {
369 buffer
*mimetype
= ((data_string
*)s
->mimetypes
->data
[m
])->value
;
370 size_t len
= buffer_string_length(mimetype
);
371 if (len
> 2 && mimetype
->ptr
[len
-1] == '*') {
372 buffer_string_set_length(mimetype
, len
-1);
377 return HANDLER_GO_ON
;
382 #if defined(USE_ZLIB) || defined(USE_BZ2LIB)
383 static int stream_http_chunk_append_mem(server
*srv
, connection
*con
, handler_ctx
*hctx
, size_t len
) {
384 /* future: might also write stream to hctx temporary file in compressed file cache */
385 return http_chunk_append_mem(srv
, con
, hctx
->output
->ptr
, len
);
392 static int stream_deflate_init(handler_ctx
*hctx
) {
393 z_stream
* const z
= &hctx
->u
.z
;
394 const plugin_data
* const p
= hctx
->plugin_data
;
400 z
->next_out
= (unsigned char *)hctx
->output
->ptr
;
401 z
->avail_out
= hctx
->output
->size
;
403 if (Z_OK
!= deflateInit2(z
,
404 p
->conf
.compression_level
> 0
405 ? p
->conf
.compression_level
406 : Z_DEFAULT_COMPRESSION
,
408 (hctx
->compression_type
== HTTP_ACCEPT_ENCODING_GZIP
)
409 ? (MAX_WBITS
| 16) /*(0x10 flags gzip header, trailer)*/
410 : -MAX_WBITS
, /*(negate to suppress zlib header)*/
411 8, /* default memLevel */
412 Z_DEFAULT_STRATEGY
)) {
419 static int stream_deflate_compress(server
*srv
, connection
*con
, handler_ctx
*hctx
, unsigned char *start
, off_t st_size
) {
420 z_stream
* const z
= &(hctx
->u
.z
);
424 z
->avail_in
= st_size
;
425 hctx
->bytes_in
+= st_size
;
429 if (Z_OK
!= deflate(z
, Z_NO_FLUSH
)) return -1;
431 if (z
->avail_out
== 0 || z
->avail_in
> 0) {
432 len
= hctx
->output
->size
- z
->avail_out
;
433 hctx
->bytes_out
+= len
;
434 stream_http_chunk_append_mem(srv
, con
, hctx
, len
);
435 z
->next_out
= (unsigned char *)hctx
->output
->ptr
;
436 z
->avail_out
= hctx
->output
->size
;
438 } while (z
->avail_in
> 0);
443 static int stream_deflate_flush(server
*srv
, connection
*con
, handler_ctx
*hctx
, int end
) {
444 z_stream
* const z
= &(hctx
->u
.z
);
445 const plugin_data
*p
= hctx
->plugin_data
;
454 rc
= deflate(z
, Z_FINISH
);
457 } else if (rc
!= Z_STREAM_END
) {
461 if (p
->conf
.sync_flush
) {
462 rc
= deflate(z
, Z_SYNC_FLUSH
);
463 if (rc
!= Z_OK
) return -1;
464 } else if (z
->avail_in
> 0) {
465 rc
= deflate(z
, Z_NO_FLUSH
);
466 if (rc
!= Z_OK
) return -1;
470 len
= hctx
->output
->size
- z
->avail_out
;
471 if (z
->avail_out
== 0 || (len
> 0 && (end
|| p
->conf
.sync_flush
))) {
472 hctx
->bytes_out
+= len
;
473 stream_http_chunk_append_mem(srv
, con
, hctx
, len
);
474 z
->next_out
= (unsigned char *)hctx
->output
->ptr
;
475 z
->avail_out
= hctx
->output
->size
;
477 } while (z
->avail_in
!= 0 || !done
);
482 static int stream_deflate_end(server
*srv
, handler_ctx
*hctx
) {
483 z_stream
* const z
= &(hctx
->u
.z
);
484 int rc
= deflateEnd(z
);
485 if (Z_OK
== rc
|| Z_DATA_ERROR
== rc
) return 0;
487 if (z
->msg
!= NULL
) {
488 log_error_write(srv
, __FILE__
, __LINE__
, "sdss",
489 "deflateEnd error ret=", rc
, ", msg=", z
->msg
);
491 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
492 "deflateEnd error ret=", rc
);
502 static int stream_bzip2_init(handler_ctx
*hctx
) {
503 bz_stream
* const bz
= &hctx
->u
.bz
;
504 const plugin_data
* const p
= hctx
->plugin_data
;
508 bz
->total_in_lo32
= 0;
509 bz
->total_in_hi32
= 0;
510 bz
->total_out_lo32
= 0;
511 bz
->total_out_hi32
= 0;
512 bz
->next_out
= hctx
->output
->ptr
;
513 bz
->avail_out
= hctx
->output
->size
;
515 if (BZ_OK
!= BZ2_bzCompressInit(bz
,
516 p
->conf
.compression_level
> 0
517 ? p
->conf
.compression_level
518 : 9, /* blocksize = 900k */
520 0)) { /* workFactor: default */
527 static int stream_bzip2_compress(server
*srv
, connection
*con
, handler_ctx
*hctx
, unsigned char *start
, off_t st_size
) {
528 bz_stream
* const bz
= &(hctx
->u
.bz
);
531 bz
->next_in
= (char *)start
;
532 bz
->avail_in
= st_size
;
533 hctx
->bytes_in
+= st_size
;
537 if (BZ_RUN_OK
!= BZ2_bzCompress(bz
, BZ_RUN
)) return -1;
539 if (bz
->avail_out
== 0 || bz
->avail_in
> 0) {
540 len
= hctx
->output
->size
- bz
->avail_out
;
541 hctx
->bytes_out
+= len
;
542 stream_http_chunk_append_mem(srv
, con
, hctx
, len
);
543 bz
->next_out
= hctx
->output
->ptr
;
544 bz
->avail_out
= hctx
->output
->size
;
546 } while (bz
->avail_in
> 0);
551 static int stream_bzip2_flush(server
*srv
, connection
*con
, handler_ctx
*hctx
, int end
) {
552 bz_stream
* const bz
= &(hctx
->u
.bz
);
553 const plugin_data
*p
= hctx
->plugin_data
;
562 rc
= BZ2_bzCompress(bz
, BZ_FINISH
);
563 if (rc
== BZ_FINISH_OK
) {
565 } else if (rc
!= BZ_STREAM_END
) {
568 } else if (bz
->avail_in
> 0) {
569 /* p->conf.sync_flush not implemented here,
570 * which would loop on BZ_FLUSH while BZ_FLUSH_OK
571 * until BZ_RUN_OK returned */
572 rc
= BZ2_bzCompress(bz
, BZ_RUN
);
573 if (rc
!= BZ_RUN_OK
) {
578 len
= hctx
->output
->size
- bz
->avail_out
;
579 if (bz
->avail_out
== 0 || (len
> 0 && (end
|| p
->conf
.sync_flush
))) {
580 hctx
->bytes_out
+= len
;
581 stream_http_chunk_append_mem(srv
, con
, hctx
, len
);
582 bz
->next_out
= hctx
->output
->ptr
;
583 bz
->avail_out
= hctx
->output
->size
;
585 } while (bz
->avail_in
!= 0 || !done
);
590 static int stream_bzip2_end(server
*srv
, handler_ctx
*hctx
) {
591 bz_stream
* const bz
= &(hctx
->u
.bz
);
592 int rc
= BZ2_bzCompressEnd(bz
);
593 if (BZ_OK
== rc
|| BZ_DATA_ERROR
== rc
) return 0;
595 log_error_write(srv
, __FILE__
, __LINE__
, "sd",
596 "BZ2_bzCompressEnd error ret=", rc
);
603 static int mod_deflate_stream_init(handler_ctx
*hctx
) {
604 switch(hctx
->compression_type
) {
606 case HTTP_ACCEPT_ENCODING_GZIP
:
607 case HTTP_ACCEPT_ENCODING_DEFLATE
:
608 return stream_deflate_init(hctx
);
611 case HTTP_ACCEPT_ENCODING_BZIP2
:
612 return stream_bzip2_init(hctx
);
619 static int mod_deflate_compress(server
*srv
, connection
*con
, handler_ctx
*hctx
, unsigned char *start
, off_t st_size
) {
620 if (0 == st_size
) return 0;
621 switch(hctx
->compression_type
) {
623 case HTTP_ACCEPT_ENCODING_GZIP
:
624 case HTTP_ACCEPT_ENCODING_DEFLATE
:
625 return stream_deflate_compress(srv
, con
, hctx
, start
, st_size
);
628 case HTTP_ACCEPT_ENCODING_BZIP2
:
629 return stream_bzip2_compress(srv
, con
, hctx
, start
, st_size
);
639 static int mod_deflate_stream_flush(server
*srv
, connection
*con
, handler_ctx
*hctx
, int end
) {
640 if (0 == hctx
->bytes_in
) return 0;
641 switch(hctx
->compression_type
) {
643 case HTTP_ACCEPT_ENCODING_GZIP
:
644 case HTTP_ACCEPT_ENCODING_DEFLATE
:
645 return stream_deflate_flush(srv
, con
, hctx
, end
);
648 case HTTP_ACCEPT_ENCODING_BZIP2
:
649 return stream_bzip2_flush(srv
, con
, hctx
, end
);
659 static void mod_deflate_note_ratio(server
*srv
, connection
*con
, handler_ctx
*hctx
) {
660 /* store compression ratio in con->environment
661 * for possible logging by mod_accesslog
662 * (late in response handling, so not seen by most other modules) */
663 /*(should be called only at end of successful response compression)*/
664 char ratio
[LI_ITOSTRING_LENGTH
];
665 if (0 == hctx
->bytes_in
) return;
666 li_itostrn(ratio
, sizeof(ratio
), hctx
->bytes_out
* 100 / hctx
->bytes_in
);
667 array_set_key_value(con
->environment
,
668 CONST_STR_LEN("ratio"),
669 ratio
, strlen(ratio
));
673 static int mod_deflate_stream_end(server
*srv
, handler_ctx
*hctx
) {
674 switch(hctx
->compression_type
) {
676 case HTTP_ACCEPT_ENCODING_GZIP
:
677 case HTTP_ACCEPT_ENCODING_DEFLATE
:
678 return stream_deflate_end(srv
, hctx
);
681 case HTTP_ACCEPT_ENCODING_BZIP2
:
682 return stream_bzip2_end(srv
, hctx
);
690 static void deflate_compress_cleanup(server
*srv
, connection
*con
, handler_ctx
*hctx
) {
691 const plugin_data
*p
= hctx
->plugin_data
;
692 con
->plugin_ctx
[p
->id
] = NULL
;
694 if (0 != mod_deflate_stream_end(srv
, hctx
)) {
695 log_error_write(srv
, __FILE__
, __LINE__
, "s", "error closing stream");
698 #if 1 /* unnecessary if deflate.min-compress-size is set to a reasonable value */
699 if (hctx
->bytes_in
< hctx
->bytes_out
) {
700 log_error_write(srv
, __FILE__
, __LINE__
, "sbsdsd",
701 "uri ", con
->uri
.path_raw
, " in=", hctx
->bytes_in
, " smaller than out=", hctx
->bytes_out
);
705 handler_ctx_free(hctx
);
709 static int mod_deflate_file_chunk(server
*srv
, connection
*con
, handler_ctx
*hctx
, chunk
*c
, off_t st_size
) {
714 off_t we_want_to_mmap
= 2 MByte
;
715 off_t we_want_to_send
= st_size
;
716 volatile int mapped
= 0;/* quiet warning: might be clobbered by 'longjmp' */
721 if (-1 == c
->file
.fd
) { /* open the file if not already open */
722 if (-1 == (c
->file
.fd
= fdevent_open_cloexec(c
->file
.name
->ptr
, O_RDONLY
, 0))) {
723 log_error_write(srv
, __FILE__
, __LINE__
, "sbs", "open failed for:", c
->file
.name
, strerror(errno
));
729 abs_offset
= c
->file
.start
+ c
->offset
;
734 * - new mmap as the we are at the end of the last one */
735 if (c
->file
.mmap
.start
== MAP_FAILED
||
736 abs_offset
== (off_t
)(c
->file
.mmap
.offset
+ c
->file
.mmap
.length
)) {
738 /* Optimizations for the future:
740 * adaptive mem-mapping
742 * we mmap() the whole file. If someone has alot large files and 32bit
743 * machine the virtual address area will be unrun and we will have a failing
746 * only mmap 16M in one chunk and move the window as soon as we have finished
749 * read-ahead buffering
751 * sending out several large files in parallel trashes the read-ahead of the
752 * kernel leading to long wait-for-seek times.
753 * solutions: (increasing complexity)
755 * 2. use a internal read-ahead buffer in the chunk-structure
756 * 3. use non-blocking IO for file-transfers
759 /* all mmap()ed areas are 512kb expect the last which might be smaller */
762 /* this is a remap, move the mmap-offset */
763 if (c
->file
.mmap
.start
!= MAP_FAILED
) {
764 munmap(c
->file
.mmap
.start
, c
->file
.mmap
.length
);
765 c
->file
.mmap
.offset
+= we_want_to_mmap
;
767 /* in case the range-offset is after the first mmap()ed area we skip the area */
768 c
->file
.mmap
.offset
= 0;
770 while (c
->file
.mmap
.offset
+ we_want_to_mmap
< c
->file
.start
) {
771 c
->file
.mmap
.offset
+= we_want_to_mmap
;
775 /* length is rel, c->offset too, assume there is no limit at the mmap-boundaries */
776 to_mmap
= (c
->file
.start
+ c
->file
.length
) - c
->file
.mmap
.offset
;
777 if (to_mmap
> we_want_to_mmap
) to_mmap
= we_want_to_mmap
;
778 /* we have more to send than we can mmap() at once */
779 if (we_want_to_send
> to_mmap
) we_want_to_send
= to_mmap
;
781 if (MAP_FAILED
== (c
->file
.mmap
.start
= mmap(0, (size_t)to_mmap
, PROT_READ
, MAP_SHARED
, c
->file
.fd
, c
->file
.mmap
.offset
))) {
782 /* close it here, otherwise we'd have to set FD_CLOEXEC */
784 log_error_write(srv
, __FILE__
, __LINE__
, "ssbd", "mmap failed:",
785 strerror(errno
), c
->file
.name
, c
->file
.fd
);
790 c
->file
.mmap
.length
= to_mmap
;
792 /* don't advise files < 64Kb */
793 if (c
->file
.mmap
.length
> (64 KByte
) &&
794 0 != madvise(c
->file
.mmap
.start
, c
->file
.mmap
.length
, MADV_WILLNEED
)) {
795 log_error_write(srv
, __FILE__
, __LINE__
, "ssbd", "madvise failed:",
796 strerror(errno
), c
->file
.name
, c
->file
.fd
);
800 /* chunk_reset() or chunk_free() will cleanup for us */
803 /* to_send = abs_mmap_end - abs_offset */
804 toSend
= (c
->file
.mmap
.offset
+ c
->file
.mmap
.length
) - abs_offset
;
805 if (toSend
> we_want_to_send
) toSend
= we_want_to_send
;
808 log_error_write(srv
, __FILE__
, __LINE__
, "soooo",
809 "toSend is negative:",
813 c
->file
.mmap
.offset
);
814 force_assert(toSend
< 0);
817 start
= c
->file
.mmap
.start
;
821 if (MAP_FAILED
== c
->file
.mmap
.start
) {
823 if (toSend
> 2 MByte
) toSend
= 2 MByte
;
824 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
)) {
825 log_error_write(srv
, __FILE__
, __LINE__
, "sbss", "reading", c
->file
.name
, "failed:", strerror(errno
));
834 signal(SIGBUS
, sigbus_handler
);
835 sigbus_jmp_valid
= 1;
836 if (0 != sigsetjmp(sigbus_jmp
, 1)) {
837 sigbus_jmp_valid
= 0;
839 log_error_write(srv
, __FILE__
, __LINE__
, "sbd", "SIGBUS in mmap:",
840 c
->file
.name
, c
->file
.fd
);
846 if (mod_deflate_compress(srv
, con
, hctx
,
847 (unsigned char *)start
+ (abs_offset
- c
->file
.mmap
.offset
), toSend
) < 0) {
848 log_error_write(srv
, __FILE__
, __LINE__
, "s",
855 sigbus_jmp_valid
= 0;
864 static handler_t
deflate_compress_response(server
*srv
, connection
*con
, handler_ctx
*hctx
) {
868 /* move all chunk from write_queue into our in_queue, then adjust
869 * counters since con->write_queue is reused for compressed output */
870 len
= chunkqueue_length(con
->write_queue
);
871 chunkqueue_remove_finished_chunks(con
->write_queue
);
872 chunkqueue_append_chunkqueue(hctx
->in_queue
, con
->write_queue
);
873 con
->write_queue
->bytes_in
-= len
;
874 con
->write_queue
->bytes_out
-= len
;
876 max
= chunkqueue_length(hctx
->in_queue
);
878 /* calculate max bytes to compress for this call */
879 if (p
->conf
.sync_flush
&& max
> (len
= p
->conf
.work_block_size
<< 10)) {
884 /* Compress chunks from in_queue into chunks for write_queue */
886 chunk
*c
= hctx
->in_queue
->first
;
890 len
= buffer_string_length(c
->mem
) - c
->offset
;
891 if (len
> max
) len
= max
;
892 if (mod_deflate_compress(srv
, con
, hctx
, (unsigned char *)c
->mem
->ptr
+c
->offset
, len
) < 0) {
893 log_error_write(srv
, __FILE__
, __LINE__
, "s",
895 return HANDLER_ERROR
;
899 len
= c
->file
.length
- c
->offset
;
900 if (len
> max
) len
= max
;
901 if ((len
= mod_deflate_file_chunk(srv
, con
, hctx
, c
, len
)) < 0) {
902 log_error_write(srv
, __FILE__
, __LINE__
, "s",
903 "compress file chunk failed.");
904 return HANDLER_ERROR
;
908 log_error_write(srv
, __FILE__
, __LINE__
, "ds", c
, "type not known");
909 return HANDLER_ERROR
;
913 chunkqueue_mark_written(hctx
->in_queue
, len
);
916 /*(currently should always be true)*/
917 /*(current implementation requires response be complete)*/
918 close_stream
= (con
->file_finished
&& chunkqueue_is_empty(hctx
->in_queue
));
919 if (mod_deflate_stream_flush(srv
, con
, hctx
, close_stream
) < 0) {
920 log_error_write(srv
, __FILE__
, __LINE__
, "s", "flush error");
921 return HANDLER_ERROR
;
924 return close_stream
? HANDLER_FINISHED
: HANDLER_GO_ON
;
930 static int mod_deflate_patch_connection(server
*srv
, connection
*con
, plugin_data
*p
) {
932 plugin_config
*s
= p
->config_storage
[0];
935 PATCH(allowed_encodings
);
936 PATCH(max_compress_size
);
937 PATCH(min_compress_size
);
938 PATCH(compression_level
);
939 PATCH(output_buffer_size
);
940 PATCH(work_block_size
);
943 /* skip the first, the global context */
944 for (i
= 1; i
< srv
->config_context
->used
; i
++) {
945 data_config
*dc
= (data_config
*)srv
->config_context
->data
[i
];
946 s
= p
->config_storage
[i
];
948 /* condition didn't match */
949 if (!config_check_cond(srv
, con
, dc
)) continue;
952 for (j
= 0; j
< dc
->value
->used
; j
++) {
953 data_unset
*du
= dc
->value
->data
[j
];
955 if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.mimetypes"))) {
957 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.allowed-encodings"))) {
958 PATCH(allowed_encodings
);
959 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.max-compress-size"))) {
960 PATCH(max_compress_size
);
961 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.min-compress-size"))) {
962 PATCH(min_compress_size
);
963 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.compression-level"))) {
964 PATCH(compression_level
);
965 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.output-buffer-size"))) {
966 PATCH(output_buffer_size
);
967 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.work-block-size"))) {
968 PATCH(work_block_size
);
969 } else if (buffer_is_equal_string(du
->key
, CONST_STR_LEN("deflate.max-loadavg"))) {
979 static int mod_deflate_choose_encoding (const char *value
, plugin_data
*p
, const char **label
) {
980 /* get client side support encodings */
981 int accept_encoding
= 0;
982 #if !defined(USE_ZLIB) && !defined(USE_BZ2LIB)
986 if (NULL
!= strstr(value
, "gzip")) accept_encoding
|= HTTP_ACCEPT_ENCODING_GZIP
;
987 else if (NULL
!= strstr(value
, "x-gzip")) accept_encoding
|= HTTP_ACCEPT_ENCODING_X_GZIP
;
988 if (NULL
!= strstr(value
, "deflate")) accept_encoding
|= HTTP_ACCEPT_ENCODING_DEFLATE
;
990 /* if (NULL != strstr(value, "compress")) accept_encoding |= HTTP_ACCEPT_ENCODING_COMPRESS; */
992 if (p
->conf
.allowed_encodings
& (HTTP_ACCEPT_ENCODING_BZIP2
| HTTP_ACCEPT_ENCODING_X_BZIP2
)) {
993 if (NULL
!= strstr(value
, "bzip2")) accept_encoding
|= HTTP_ACCEPT_ENCODING_BZIP2
;
994 else if (NULL
!= strstr(value
, "x-bzip2")) accept_encoding
|= HTTP_ACCEPT_ENCODING_X_BZIP2
;
997 /* if (NULL != strstr(value, "identity")) accept_encoding |= HTTP_ACCEPT_ENCODING_IDENTITY; */
999 /* mask to limit to allowed_encodings */
1000 accept_encoding
&= p
->conf
.allowed_encodings
;
1002 /* select best matching encoding */
1004 if (accept_encoding
& HTTP_ACCEPT_ENCODING_BZIP2
) {
1006 return HTTP_ACCEPT_ENCODING_BZIP2
;
1007 } else if (accept_encoding
& HTTP_ACCEPT_ENCODING_X_BZIP2
) {
1009 return HTTP_ACCEPT_ENCODING_BZIP2
;
1012 if (accept_encoding
& HTTP_ACCEPT_ENCODING_GZIP
) {
1014 return HTTP_ACCEPT_ENCODING_GZIP
;
1015 } else if (accept_encoding
& HTTP_ACCEPT_ENCODING_X_GZIP
) {
1017 return HTTP_ACCEPT_ENCODING_GZIP
;
1018 } else if (accept_encoding
& HTTP_ACCEPT_ENCODING_DEFLATE
) {
1020 return HTTP_ACCEPT_ENCODING_DEFLATE
;
1026 CONNECTION_FUNC(mod_deflate_handle_response_start
) {
1027 plugin_data
*p
= p_d
;
1033 int compression_type
;
1036 /*(current implementation requires response be complete)*/
1037 if (!con
->file_finished
) return HANDLER_GO_ON
;
1038 if (con
->request
.http_method
== HTTP_METHOD_HEAD
) return HANDLER_GO_ON
;
1039 if (con
->parsed_response
& HTTP_TRANSFER_ENCODING_CHUNKED
) return HANDLER_GO_ON
;
1041 /* disable compression for some http status types. */
1042 switch(con
->http_status
) {
1048 /* disable compression as we have no response entity */
1049 return HANDLER_GO_ON
;
1054 mod_deflate_patch_connection(srv
, con
, p
);
1056 /* check if deflate configured for any mimetypes */
1057 if (!p
->conf
.mimetypes
->used
) return HANDLER_GO_ON
;
1059 /* check if size of response is below min-compress-size or exceeds max*/
1060 /* (con->file_finished checked at top of routine) */
1061 len
= chunkqueue_length(con
->write_queue
);
1062 if (len
<= (off_t
)p
->conf
.min_compress_size
) return HANDLER_GO_ON
;
1063 if (p
->conf
.max_compress_size
/*(max_compress_size in KB)*/
1064 && len
> ((off_t
)p
->conf
.max_compress_size
<< 10)) {
1065 return HANDLER_GO_ON
;
1068 /* Check if response has a Content-Encoding. */
1069 ds
= (data_string
*)array_get_element(con
->response
.headers
, "Content-Encoding");
1070 if (NULL
!= ds
) return HANDLER_GO_ON
;
1072 /* Check Accept-Encoding for supported encoding. */
1073 ds
= (data_string
*)array_get_element(con
->request
.headers
, "Accept-Encoding");
1074 if (NULL
== ds
) return HANDLER_GO_ON
;
1076 /* find matching encodings */
1077 compression_type
= mod_deflate_choose_encoding(ds
->value
->ptr
, p
, &label
);
1078 if (!compression_type
) return HANDLER_GO_ON
;
1080 /* Check mimetype in response header "Content-Type" */
1081 if (NULL
!= (ds
= (data_string
*)array_get_element(con
->response
.headers
, "Content-Type"))) {
1084 for (m
= 0; m
< p
->conf
.mimetypes
->used
; ++m
) {
1085 data_string
*mimetype
= (data_string
*)p
->conf
.mimetypes
->data
[m
];
1086 if (0 == strncmp(mimetype
->value
->ptr
, ds
->value
->ptr
, buffer_string_length(mimetype
->value
))) {
1087 /* mimetype found */
1092 if (!found
) return HANDLER_GO_ON
;
1095 if (0 == strncasecmp(ds
->value
->ptr
, "application/x-javascript", 24)) {
1096 /*reset compress type to deflate for javascript
1097 * prevent buggy IE6 SP1 doesn't work for js in IFrame
1099 compression_type
= HTTP_ACCEPT_ENCODING_DEFLATE
;
1103 /* If no Content-Type set, compress only if first p->conf.mimetypes value is "" */
1104 data_string
*mimetype
= (data_string
*)p
->conf
.mimetypes
->data
[0];
1105 if (!buffer_string_is_empty(mimetype
->value
)) return HANDLER_GO_ON
;
1108 /* Vary: Accept-Encoding (response might change according to request Accept-Encoding) */
1109 if (NULL
!= (ds
= (data_string
*)array_get_element(con
->response
.headers
, "Vary"))) {
1110 if (NULL
== strstr(ds
->value
->ptr
, "Accept-Encoding")) {
1111 buffer_append_string_len(ds
->value
, CONST_STR_LEN(",Accept-Encoding"));
1114 response_header_insert(srv
, con
, CONST_STR_LEN("Vary"),
1115 CONST_STR_LEN("Accept-Encoding"));
1118 /* check ETag as is done in http_response_handle_cachable()
1119 * (slightly imperfect (close enough?) match of ETag "000000" to "000000-gzip") */
1120 ds
= (data_string
*)array_get_element(con
->response
.headers
, "ETag");
1122 etaglen
= buffer_string_length(ds
->value
);
1124 && con
->http_status
< 300 /*(want 2xx only)*/
1125 && con
->request
.http_if_none_match
1126 && 0 == strncmp(con
->request
.http_if_none_match
, ds
->value
->ptr
, etaglen
-1)
1127 && con
->request
.http_if_none_match
[etaglen
-1] == '-'
1128 && 0 == strncmp(con
->request
.http_if_none_match
+etaglen
, label
, strlen(label
))) {
1130 if ( HTTP_METHOD_GET
== con
->request
.http_method
1131 || HTTP_METHOD_HEAD
== con
->request
.http_method
) {
1132 /* modify ETag response header in-place to remove '"' and append '-label"' */
1133 ds
->value
->ptr
[etaglen
-1] = '-'; /*(overwrite end '"')*/
1134 buffer_append_string(ds
->value
, label
);
1135 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\""));
1136 /*buffer_copy_buffer(con->physical.etag, ds->value);*//*(keep in sync?)*/
1137 con
->http_status
= 304;
1139 con
->http_status
= 412;
1142 /* response_start hook occurs after error docs have been handled.
1143 * For now, send back empty response body.
1144 * In the future, might extract the error doc code so that it
1145 * might be run again if response_start hooks return with
1146 * changed http_status and con->mode = DIRECT */
1147 con
->response
.transfer_encoding
&= ~HTTP_TRANSFER_ENCODING_CHUNKED
;
1148 con
->parsed_response
&= ~HTTP_CONTENT_LENGTH
;
1149 chunkqueue_reset(con
->write_queue
);
1150 con
->file_finished
= 1;
1153 return HANDLER_GO_ON
;
1157 if (0.0 < p
->conf
.max_loadavg
&& p
->conf
.max_loadavg
< srv
->srvconf
.loadavg
[0]) {
1158 return HANDLER_GO_ON
;
1161 /* update ETag, if ETag response header is set */
1163 /* modify ETag response header in-place to remove '"' and append '-label"' */
1164 ds
->value
->ptr
[etaglen
-1] = '-'; /*(overwrite end '"')*/
1165 buffer_append_string(ds
->value
, label
);
1166 buffer_append_string_len(ds
->value
, CONST_STR_LEN("\""));
1167 /*buffer_copy_buffer(con->physical.etag, ds->value);*//*(keep in sync?)*/
1170 /* set Content-Encoding to show selected compression type */
1171 response_header_overwrite(srv
, con
, CONST_STR_LEN("Content-Encoding"), label
, strlen(label
));
1173 /* clear Content-Length and con->write_queue if HTTP HEAD request
1174 * (alternatively, could return original Content-Length with HEAD
1175 * request if ETag not modified and Content-Encoding not added) */
1176 if (HTTP_METHOD_HEAD
== con
->request
.http_method
) {
1177 /* ensure that uncompressed Content-Length is not sent in HEAD response */
1178 chunkqueue_reset(con
->write_queue
);
1179 if (con
->parsed_response
& HTTP_CONTENT_LENGTH
) {
1180 con
->parsed_response
&= ~HTTP_CONTENT_LENGTH
;
1181 if (NULL
!= (ds
= (data_string
*) array_get_element(con
->response
.headers
, "Content-Length"))) {
1182 buffer_reset(ds
->value
); /* headers with empty values are ignored for output */
1185 return HANDLER_GO_ON
;
1188 /* future: might use ETag to check if compressed content is in compressed file cache */
1189 /*if (etaglen) { ... } *//* return if in file cache after updating con->write_queue */
1191 /* enable compression */
1192 p
->conf
.sync_flush
=
1193 (con
->conf
.stream_response_body
&& 0 == p
->conf
.output_buffer_size
);
1194 hctx
= handler_ctx_init();
1195 hctx
->plugin_data
= p
;
1196 hctx
->compression_type
= compression_type
;
1197 /* setup output buffer */
1198 buffer_string_set_length(p
->tmp_buf
, 0);
1199 hctx
->output
= p
->tmp_buf
;
1200 if (0 != mod_deflate_stream_init(hctx
)) {
1201 /*(should not happen unless ENOMEM)*/
1202 handler_ctx_free(hctx
);
1203 log_error_write(srv
, __FILE__
, __LINE__
, "ss",
1204 "Failed to initialize compression", label
);
1205 /* restore prior Etag and unset Content-Encoding */
1207 ds
->value
->ptr
[etaglen
-1] = '"'; /*(overwrite '-')*/
1208 buffer_string_set_length(ds
->value
, etaglen
);
1210 ds
= (data_string
*)array_get_element(con
->response
.headers
, "Content-Encoding");
1211 if (ds
) buffer_reset(ds
->value
); /* headers with empty values are ignored for output */
1212 return HANDLER_GO_ON
;
1215 con
->parsed_response
&= ~HTTP_CONTENT_LENGTH
;
1216 con
->plugin_ctx
[p
->id
] = hctx
;
1218 rc
= deflate_compress_response(srv
, con
, hctx
);
1219 if (HANDLER_GO_ON
!= rc
) {
1220 if (HANDLER_FINISHED
== rc
) {
1221 mod_deflate_note_ratio(srv
, con
, hctx
);
1223 deflate_compress_cleanup(srv
, con
, hctx
);
1224 if (HANDLER_ERROR
== rc
) return HANDLER_ERROR
;
1227 return HANDLER_GO_ON
;
1230 static handler_t
mod_deflate_cleanup(server
*srv
, connection
*con
, void *p_d
) {
1231 plugin_data
*p
= p_d
;
1232 handler_ctx
*hctx
= con
->plugin_ctx
[p
->id
];
1234 if (NULL
!= hctx
) deflate_compress_cleanup(srv
, con
, hctx
);
1236 return HANDLER_GO_ON
;
1239 int mod_deflate_plugin_init(plugin
*p
);
1240 int mod_deflate_plugin_init(plugin
*p
) {
1241 p
->version
= LIGHTTPD_VERSION_ID
;
1242 p
->name
= buffer_init_string("deflate");
1244 p
->init
= mod_deflate_init
;
1245 p
->cleanup
= mod_deflate_free
;
1246 p
->set_defaults
= mod_deflate_setdefaults
;
1247 p
->connection_reset
= mod_deflate_cleanup
;
1248 p
->handle_connection_close
= mod_deflate_cleanup
;
1249 p
->handle_response_start
= mod_deflate_handle_response_start
;