/src/h2o/lib/handler/compress.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2015,2016 Justin Zhu, DeNA Co., Ltd., Kazuho Oku |
3 | | * |
4 | | * Permission is hereby granted, free of charge, to any person obtaining a copy |
5 | | * of this software and associated documentation files (the "Software"), to |
6 | | * deal in the Software without restriction, including without limitation the |
7 | | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or |
8 | | * sell copies of the Software, and to permit persons to whom the Software is |
9 | | * furnished to do so, subject to the following conditions: |
10 | | * |
11 | | * The above copyright notice and this permission notice shall be included in |
12 | | * all copies or substantial portions of the Software. |
13 | | * |
14 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
15 | | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
16 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
17 | | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
18 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
19 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS |
20 | | * IN THE SOFTWARE. |
21 | | */ |
22 | | #include <assert.h> |
23 | | #include <stdlib.h> |
24 | | #include "h2o.h" |
25 | | |
26 | | #ifndef BUF_SIZE |
27 | 0 | #define BUF_SIZE 8192 |
28 | | #endif |
29 | | |
30 | | struct st_compress_filter_t { |
31 | | h2o_filter_t super; |
32 | | h2o_compress_args_t args; |
33 | | }; |
34 | | |
35 | | struct st_compress_encoder_t { |
36 | | h2o_ostream_t super; |
37 | | h2o_compress_context_t *compressor; |
38 | | }; |
39 | | |
40 | | static void do_send(h2o_ostream_t *_self, h2o_req_t *req, h2o_sendvec_t *inbufs, size_t inbufcnt, h2o_send_state_t state) |
41 | 0 | { |
42 | 0 | struct st_compress_encoder_t *self = (void *)_self; |
43 | 0 | h2o_sendvec_t *outbufs; |
44 | 0 | size_t outbufcnt; |
45 | |
|
46 | 0 | if (inbufcnt == 0 && h2o_send_state_is_in_progress(state)) { |
47 | 0 | h2o_ostream_send_next(&self->super, req, inbufs, inbufcnt, state); |
48 | 0 | return; |
49 | 0 | } |
50 | | |
51 | 0 | state = h2o_compress_transform(self->compressor, req, inbufs, inbufcnt, state, &outbufs, &outbufcnt); |
52 | 0 | h2o_ostream_send_next(&self->super, req, outbufs, outbufcnt, state); |
53 | 0 | } |
54 | | |
55 | | static void on_setup_ostream(h2o_filter_t *_self, h2o_req_t *req, h2o_ostream_t **slot) |
56 | 0 | { |
57 | 0 | struct st_compress_filter_t *self = (void *)_self; |
58 | 0 | struct st_compress_encoder_t *encoder; |
59 | 0 | int compressible_types; |
60 | 0 | int compressible_types_mask = H2O_COMPRESSIBLE_BROTLI | H2O_COMPRESSIBLE_GZIP | H2O_COMPRESSIBLE_ZSTD; |
61 | 0 | h2o_compress_context_t *compressor; |
62 | 0 | ssize_t i; |
63 | |
|
64 | 0 | if (req->version < 0x101) |
65 | 0 | goto Next; |
66 | 0 | if (req->res.status != 200) |
67 | 0 | goto Next; |
68 | 0 | if (h2o_memis(req->input.method.base, req->input.method.len, H2O_STRLIT("HEAD"))) |
69 | 0 | goto Next; |
70 | | |
71 | 0 | switch (req->compress_hint) { |
72 | 0 | case H2O_COMPRESS_HINT_DISABLE: |
73 | | /* compression was explicitly disabled, skip */ |
74 | 0 | goto Next; |
75 | 0 | case H2O_COMPRESS_HINT_ENABLE: |
76 | | /* compression was explicitly enabled */ |
77 | 0 | break; |
78 | 0 | case H2O_COMPRESS_HINT_ENABLE_BR: |
79 | 0 | compressible_types_mask = H2O_COMPRESSIBLE_BROTLI; |
80 | 0 | break; |
81 | 0 | case H2O_COMPRESS_HINT_ENABLE_GZIP: |
82 | 0 | compressible_types_mask = H2O_COMPRESSIBLE_GZIP; |
83 | 0 | break; |
84 | 0 | case H2O_COMPRESS_HINT_ENABLE_ZSTD: |
85 | 0 | compressible_types_mask = H2O_COMPRESSIBLE_ZSTD; |
86 | 0 | break; |
87 | 0 | case H2O_COMPRESS_HINT_AUTO: |
88 | 0 | default: |
89 | | /* no hint from the producer, decide whether to compress based |
90 | | on the configuration */ |
91 | 0 | if (req->res.mime_attr == NULL) |
92 | 0 | h2o_req_fill_mime_attributes(req); |
93 | 0 | if (!req->res.mime_attr->is_compressible) |
94 | 0 | goto Next; |
95 | 0 | if (req->res.content_length < self->args.min_size) |
96 | 0 | goto Next; |
97 | 0 | } |
98 | | |
99 | | /* skip if failed to gather the list of compressible types */ |
100 | 0 | compressible_types = h2o_get_compressible_types(&req->headers) & compressible_types_mask; |
101 | 0 | if (compressible_types == 0) |
102 | 0 | goto Next; |
103 | | |
104 | | /* skip if content-encoding header is being set (as well as obtain the location of accept-ranges moreover identify index of etag |
105 | | * to modified weaken) */ |
106 | 0 | size_t content_encoding_header_index = -1, accept_ranges_header_index = -1, etag_header_index = -1; |
107 | 0 | for (i = 0; i != req->res.headers.size; ++i) { |
108 | 0 | if (req->res.headers.entries[i].name == &H2O_TOKEN_CONTENT_ENCODING->buf) |
109 | 0 | content_encoding_header_index = i; |
110 | 0 | else if (req->res.headers.entries[i].name == &H2O_TOKEN_ACCEPT_RANGES->buf) |
111 | 0 | accept_ranges_header_index = i; |
112 | 0 | else if (req->res.headers.entries[i].name == &H2O_TOKEN_ETAG->buf) |
113 | 0 | etag_header_index = i; |
114 | 0 | else |
115 | 0 | continue; |
116 | 0 | } |
117 | 0 | if (content_encoding_header_index != -1) |
118 | 0 | goto Next; |
119 | | |
120 | | /* open the compressor (TODO add support for zstd) */ |
121 | | #if H2O_USE_BROTLI |
122 | | if (self->args.brotli.quality != -1 && (compressible_types & H2O_COMPRESSIBLE_BROTLI) != 0) { |
123 | | compressor = |
124 | | h2o_compress_brotli_open(&req->pool, self->args.brotli.quality, req->res.content_length, req->preferred_chunk_size); |
125 | | } else |
126 | | #endif |
127 | 0 | if (self->args.gzip.quality != -1 && (compressible_types & H2O_COMPRESSIBLE_GZIP) != 0) { |
128 | 0 | compressor = h2o_compress_gzip_open(&req->pool, self->args.gzip.quality); |
129 | 0 | } else { |
130 | | /* let proxies know that we looked at accept-encoding when deciding not to compress */ |
131 | 0 | h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding")); |
132 | 0 | goto Next; |
133 | 0 | } |
134 | | |
135 | | /* adjust the response headers */ |
136 | 0 | req->res.content_length = SIZE_MAX; |
137 | 0 | h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_ENCODING, NULL, compressor->name.base, compressor->name.len); |
138 | 0 | h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding")); |
139 | 0 | if (etag_header_index != -1) { |
140 | 0 | if (!(req->res.headers.entries[etag_header_index].value.len >= 2 && |
141 | 0 | h2o_memis(req->res.headers.entries[etag_header_index].value.base, 2, H2O_STRLIT("W/")))) { |
142 | 0 | req->res.headers.entries[etag_header_index].value = |
143 | 0 | h2o_concat(&req->pool, h2o_iovec_init(H2O_STRLIT("W/")), req->res.headers.entries[etag_header_index].value); |
144 | 0 | } |
145 | 0 | } |
146 | 0 | if (accept_ranges_header_index != -1) { |
147 | 0 | req->res.headers.entries[accept_ranges_header_index].value = h2o_iovec_init(H2O_STRLIT("none")); |
148 | 0 | } else { |
149 | 0 | h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCEPT_RANGES, NULL, H2O_STRLIT("none")); |
150 | 0 | } |
151 | | |
152 | | /* setup filter */ |
153 | 0 | encoder = (void *)h2o_add_ostream(req, H2O_ALIGNOF(*encoder), sizeof(*encoder), slot); |
154 | 0 | encoder->super.do_send = do_send; |
155 | 0 | slot = &encoder->super.next; |
156 | 0 | encoder->compressor = compressor; |
157 | | |
158 | | /* adjust preferred chunk size (compress by 8192 bytes) */ |
159 | 0 | if (req->preferred_chunk_size > BUF_SIZE) |
160 | 0 | req->preferred_chunk_size = BUF_SIZE; |
161 | |
|
162 | 0 | Next: |
163 | 0 | h2o_setup_next_ostream(req, slot); |
164 | 0 | } |
165 | | |
166 | | void h2o_compress_register(h2o_pathconf_t *pathconf, h2o_compress_args_t *args) |
167 | 0 | { |
168 | 0 | struct st_compress_filter_t *self = (void *)h2o_create_filter(pathconf, sizeof(*self)); |
169 | 0 | self->super.on_setup_ostream = on_setup_ostream; |
170 | 0 | self->args = *args; |
171 | 0 | } |
172 | | |
173 | | h2o_send_state_t h2o_compress_transform(h2o_compress_context_t *self, h2o_req_t *req, h2o_sendvec_t *inbufs, size_t inbufcnt, |
174 | | h2o_send_state_t state, h2o_sendvec_t **outbufs, size_t *outbufcnt) |
175 | 0 | { |
176 | 0 | h2o_sendvec_t flattened; |
177 | |
|
178 | 0 | if (inbufcnt != 0 && inbufs->callbacks->read_ != &h2o_sendvec_read_raw) { |
179 | 0 | assert(inbufcnt == 1); |
180 | 0 | size_t buflen = inbufs->len; |
181 | 0 | assert(buflen <= H2O_PULL_SENDVEC_MAX_SIZE); |
182 | 0 | if (self->push_buf == NULL) |
183 | 0 | self->push_buf = h2o_mem_alloc(h2o_send_state_is_in_progress(state) ? H2O_PULL_SENDVEC_MAX_SIZE : buflen); |
184 | 0 | if (!(*inbufs->callbacks->read_)(inbufs, self->push_buf, buflen)) { |
185 | 0 | *outbufs = NULL; |
186 | 0 | *outbufcnt = 0; |
187 | 0 | return H2O_SEND_STATE_ERROR; |
188 | 0 | } |
189 | 0 | h2o_sendvec_init_raw(&flattened, self->push_buf, buflen); |
190 | 0 | inbufs = &flattened; |
191 | 0 | } |
192 | | |
193 | 0 | return self->do_transform(self, inbufs, inbufcnt, state, outbufs, outbufcnt); |
194 | 0 | } |