/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; |
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_AUTO: |
85 | 0 | default: |
86 | | /* no hint from the producer, decide whether to compress based |
87 | | on the configuration */ |
88 | 0 | if (req->res.mime_attr == NULL) |
89 | 0 | h2o_req_fill_mime_attributes(req); |
90 | 0 | if (!req->res.mime_attr->is_compressible) |
91 | 0 | goto Next; |
92 | 0 | if (req->res.content_length < self->args.min_size) |
93 | 0 | goto Next; |
94 | 0 | } |
95 | | |
96 | | /* skip if failed to gather the list of compressible types */ |
97 | 0 | compressible_types = h2o_get_compressible_types(&req->headers) & compressible_types_mask; |
98 | 0 | if (compressible_types == 0) |
99 | 0 | goto Next; |
100 | | |
101 | | /* skip if content-encoding header is being set (as well as obtain the location of accept-ranges moreover identify index of etag |
102 | | * to modified weaken) */ |
103 | 0 | size_t content_encoding_header_index = -1, accept_ranges_header_index = -1, etag_header_index = -1; |
104 | 0 | for (i = 0; i != req->res.headers.size; ++i) { |
105 | 0 | if (req->res.headers.entries[i].name == &H2O_TOKEN_CONTENT_ENCODING->buf) |
106 | 0 | content_encoding_header_index = i; |
107 | 0 | else if (req->res.headers.entries[i].name == &H2O_TOKEN_ACCEPT_RANGES->buf) |
108 | 0 | accept_ranges_header_index = i; |
109 | 0 | else if (req->res.headers.entries[i].name == &H2O_TOKEN_ETAG->buf) |
110 | 0 | etag_header_index = i; |
111 | 0 | else |
112 | 0 | continue; |
113 | 0 | } |
114 | 0 | if (content_encoding_header_index != -1) |
115 | 0 | goto Next; |
116 | | |
117 | | /* open the compressor */ |
118 | | #if H2O_USE_BROTLI |
119 | | if (self->args.brotli.quality != -1 && (compressible_types & H2O_COMPRESSIBLE_BROTLI) != 0) { |
120 | | compressor = |
121 | | h2o_compress_brotli_open(&req->pool, self->args.brotli.quality, req->res.content_length, req->preferred_chunk_size); |
122 | | } else |
123 | | #endif |
124 | 0 | if (self->args.gzip.quality != -1 && (compressible_types & H2O_COMPRESSIBLE_GZIP) != 0) { |
125 | 0 | compressor = h2o_compress_gzip_open(&req->pool, self->args.gzip.quality); |
126 | 0 | } else { |
127 | | /* let proxies know that we looked at accept-encoding when deciding not to compress */ |
128 | 0 | h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding")); |
129 | 0 | goto Next; |
130 | 0 | } |
131 | | |
132 | | /* adjust the response headers */ |
133 | 0 | req->res.content_length = SIZE_MAX; |
134 | 0 | h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_CONTENT_ENCODING, NULL, compressor->name.base, compressor->name.len); |
135 | 0 | h2o_set_header_token(&req->pool, &req->res.headers, H2O_TOKEN_VARY, H2O_STRLIT("accept-encoding")); |
136 | 0 | if (etag_header_index != -1) { |
137 | 0 | if (!(req->res.headers.entries[etag_header_index].value.len >= 2 && |
138 | 0 | h2o_memis(req->res.headers.entries[etag_header_index].value.base, 2, H2O_STRLIT("W/")))) { |
139 | 0 | req->res.headers.entries[etag_header_index].value = |
140 | 0 | h2o_concat(&req->pool, h2o_iovec_init(H2O_STRLIT("W/")), req->res.headers.entries[etag_header_index].value); |
141 | 0 | } |
142 | 0 | } |
143 | 0 | if (accept_ranges_header_index != -1) { |
144 | 0 | req->res.headers.entries[accept_ranges_header_index].value = h2o_iovec_init(H2O_STRLIT("none")); |
145 | 0 | } else { |
146 | 0 | h2o_add_header(&req->pool, &req->res.headers, H2O_TOKEN_ACCEPT_RANGES, NULL, H2O_STRLIT("none")); |
147 | 0 | } |
148 | | |
149 | | /* setup filter */ |
150 | 0 | encoder = (void *)h2o_add_ostream(req, H2O_ALIGNOF(*encoder), sizeof(*encoder), slot); |
151 | 0 | encoder->super.do_send = do_send; |
152 | 0 | slot = &encoder->super.next; |
153 | 0 | encoder->compressor = compressor; |
154 | | |
155 | | /* adjust preferred chunk size (compress by 8192 bytes) */ |
156 | 0 | if (req->preferred_chunk_size > BUF_SIZE) |
157 | 0 | req->preferred_chunk_size = BUF_SIZE; |
158 | |
|
159 | 0 | Next: |
160 | 0 | h2o_setup_next_ostream(req, slot); |
161 | 0 | } |
162 | | |
163 | | void h2o_compress_register(h2o_pathconf_t *pathconf, h2o_compress_args_t *args) |
164 | 0 | { |
165 | 0 | struct st_compress_filter_t *self = (void *)h2o_create_filter(pathconf, sizeof(*self)); |
166 | 0 | self->super.on_setup_ostream = on_setup_ostream; |
167 | 0 | self->args = *args; |
168 | 0 | } |
169 | | |
170 | | h2o_send_state_t h2o_compress_transform(h2o_compress_context_t *self, h2o_req_t *req, h2o_sendvec_t *inbufs, size_t inbufcnt, |
171 | | h2o_send_state_t state, h2o_sendvec_t **outbufs, size_t *outbufcnt) |
172 | 0 | { |
173 | 0 | h2o_sendvec_t flattened; |
174 | |
|
175 | 0 | if (inbufcnt != 0 && inbufs->callbacks->read_ != &h2o_sendvec_read_raw) { |
176 | 0 | assert(inbufcnt == 1); |
177 | 0 | size_t buflen = inbufs->len; |
178 | 0 | assert(buflen <= H2O_PULL_SENDVEC_MAX_SIZE); |
179 | 0 | if (self->push_buf == NULL) |
180 | 0 | self->push_buf = h2o_mem_alloc(h2o_send_state_is_in_progress(state) ? H2O_PULL_SENDVEC_MAX_SIZE : buflen); |
181 | 0 | if (!(*inbufs->callbacks->read_)(inbufs, self->push_buf, buflen)) { |
182 | 0 | *outbufs = NULL; |
183 | 0 | *outbufcnt = 0; |
184 | 0 | return H2O_SEND_STATE_ERROR; |
185 | 0 | } |
186 | 0 | h2o_sendvec_init_raw(&flattened, self->push_buf, buflen); |
187 | 0 | inbufs = &flattened; |
188 | 0 | } |
189 | | |
190 | 0 | return self->do_transform(self, inbufs, inbufcnt, state, outbufs, outbufcnt); |
191 | 0 | } |