/src/h2o/lib/handler/compress/gzip.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2016 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 <zlib.h> |
25 | | #include "h2o.h" |
26 | | |
27 | | #define WINDOW_BITS 31 |
28 | | #ifndef BUF_SIZE /* is altered by unit test */ |
29 | 0 | #define BUF_SIZE 8192 |
30 | | #endif |
31 | | |
32 | | struct st_gzip_context_t { |
33 | | h2o_compress_context_t super; |
34 | | z_stream zs; |
35 | | int zs_is_open; |
36 | | H2O_VECTOR(h2o_sendvec_t) bufs; |
37 | | }; |
38 | | |
39 | | static h2o_send_state_t do_compress(h2o_compress_context_t *_self, h2o_sendvec_t *inbufs, size_t inbufcnt, h2o_send_state_t state, |
40 | | h2o_sendvec_t **outbufs, size_t *outbufcnt); |
41 | | static h2o_send_state_t do_decompress(h2o_compress_context_t *_self, h2o_sendvec_t *inbufs, size_t inbufcnt, h2o_send_state_t state, |
42 | | h2o_sendvec_t **outbufs, size_t *outbufcnt); |
43 | | |
44 | | static void *alloc_cb(void *_unused, unsigned int cnt, unsigned int sz) |
45 | 0 | { |
46 | 0 | return h2o_mem_alloc(cnt * (size_t)sz); |
47 | 0 | } |
48 | | |
49 | | static void free_cb(void *_unused, void *p) |
50 | 0 | { |
51 | 0 | free(p); |
52 | 0 | } |
53 | | |
54 | | static void expand_buf(struct st_gzip_context_t *self) |
55 | 0 | { |
56 | 0 | h2o_vector_reserve(NULL, &self->bufs, self->bufs.size + 1); |
57 | 0 | h2o_sendvec_init_raw(self->bufs.entries + self->bufs.size++, h2o_mem_alloc(BUF_SIZE), 0); |
58 | 0 | } |
59 | | |
60 | | typedef int (*processor)(z_streamp strm, int flush); |
61 | | |
62 | | static size_t process_chunk(struct st_gzip_context_t *self, const void *src, size_t len, int flush, size_t bufindex, processor proc) |
63 | 0 | { |
64 | 0 | int ret; |
65 | |
|
66 | 0 | self->zs.next_in = (void *)src; |
67 | 0 | self->zs.avail_in = (unsigned)len; |
68 | | |
69 | | /* man says: If inflate/deflate returns with avail_out == 0, this function must be called again with the same value of the flush |
70 | | * parameter and more output space (updated avail_out), until the flush is complete (function returns with non-zero avail_out). |
71 | | */ |
72 | 0 | do { |
73 | | /* expand buffer (note: in case of Z_SYNC_FLUSH we need to supply at least 6 bytes of output buffer) */ |
74 | 0 | if (self->bufs.entries[bufindex].len + 32 > BUF_SIZE) { |
75 | 0 | ++bufindex; |
76 | 0 | if (bufindex == self->bufs.size) |
77 | 0 | expand_buf(self); |
78 | 0 | self->bufs.entries[bufindex].len = 0; |
79 | 0 | } |
80 | 0 | self->zs.next_out = (void *)(self->bufs.entries[bufindex].raw + self->bufs.entries[bufindex].len); |
81 | 0 | self->zs.avail_out = (unsigned)(BUF_SIZE - self->bufs.entries[bufindex].len); |
82 | 0 | ret = proc(&self->zs, flush); |
83 | | /* inflate() returns Z_BUF_ERROR if flush is set to Z_FINISH at the middle of the compressed data */ |
84 | 0 | assert(ret == Z_OK || ret == Z_STREAM_END || ret == Z_BUF_ERROR); |
85 | 0 | self->bufs.entries[bufindex].len = BUF_SIZE - self->zs.avail_out; |
86 | 0 | } while (self->zs.avail_out == 0 && ret != Z_STREAM_END); |
87 | | |
88 | 0 | return bufindex; |
89 | 0 | } |
90 | | |
91 | | static h2o_send_state_t do_process(h2o_compress_context_t *_self, h2o_sendvec_t *inbufs, size_t inbufcnt, h2o_send_state_t state, |
92 | | h2o_sendvec_t **outbufs, size_t *outbufcnt, processor proc) |
93 | 0 | { |
94 | 0 | struct st_gzip_context_t *self = (void *)_self; |
95 | 0 | size_t outbufindex; |
96 | 0 | h2o_sendvec_t *last_buf; |
97 | |
|
98 | 0 | outbufindex = 0; |
99 | 0 | self->bufs.entries[0].len = 0; |
100 | |
|
101 | 0 | if (inbufcnt != 0) { |
102 | 0 | size_t i; |
103 | 0 | for (i = 0; i != inbufcnt - 1; ++i) { |
104 | 0 | assert(inbufs[i].callbacks->read_ == h2o_sendvec_read_raw); |
105 | 0 | outbufindex = process_chunk(self, inbufs[i].raw, inbufs[i].len, Z_NO_FLUSH, outbufindex, proc); |
106 | 0 | } |
107 | 0 | assert(inbufs[i].callbacks->read_ == h2o_sendvec_read_raw); |
108 | 0 | last_buf = inbufs + i; |
109 | 0 | } else { |
110 | 0 | static const h2o_sendvec_t zero_buf = {0}; |
111 | 0 | last_buf = (h2o_sendvec_t *)&zero_buf; |
112 | 0 | } |
113 | 0 | outbufindex = process_chunk(self, last_buf->raw, last_buf->len, h2o_send_state_is_in_progress(state) ? Z_SYNC_FLUSH : Z_FINISH, |
114 | 0 | outbufindex, proc); |
115 | |
|
116 | 0 | *outbufs = self->bufs.entries; |
117 | 0 | *outbufcnt = outbufindex + 1; |
118 | |
|
119 | 0 | if (!h2o_send_state_is_in_progress(state)) { |
120 | 0 | if (self->super.do_transform == do_compress) { |
121 | 0 | deflateEnd(&self->zs); |
122 | 0 | } else { |
123 | 0 | inflateEnd(&self->zs); |
124 | 0 | } |
125 | 0 | self->zs_is_open = 0; |
126 | 0 | } |
127 | |
|
128 | 0 | return state; |
129 | 0 | } |
130 | | |
131 | | static h2o_send_state_t do_compress(h2o_compress_context_t *_self, h2o_sendvec_t *inbufs, size_t inbufcnt, h2o_send_state_t state, |
132 | | h2o_sendvec_t **outbufs, size_t *outbufcnt) |
133 | 0 | { |
134 | 0 | return do_process(_self, inbufs, inbufcnt, state, outbufs, outbufcnt, (processor)deflate); |
135 | 0 | } |
136 | | |
137 | | static h2o_send_state_t do_decompress(h2o_compress_context_t *_self, h2o_sendvec_t *inbufs, size_t inbufcnt, h2o_send_state_t state, |
138 | | h2o_sendvec_t **outbufs, size_t *outbufcnt) |
139 | 0 | { |
140 | 0 | return do_process(_self, inbufs, inbufcnt, state, outbufs, outbufcnt, (processor)inflate); |
141 | 0 | } |
142 | | |
143 | | static void do_free(void *_self) |
144 | 0 | { |
145 | 0 | struct st_gzip_context_t *self = _self; |
146 | 0 | size_t i; |
147 | |
|
148 | 0 | if (self->zs_is_open) { |
149 | 0 | if (self->super.do_transform == do_compress) { |
150 | 0 | deflateEnd(&self->zs); |
151 | 0 | } else { |
152 | 0 | inflateEnd(&self->zs); |
153 | 0 | } |
154 | 0 | } |
155 | |
|
156 | 0 | for (i = 0; i != self->bufs.size; ++i) |
157 | 0 | free(self->bufs.entries[i].raw); |
158 | 0 | free(self->bufs.entries); |
159 | 0 | free(self->super.push_buf); |
160 | 0 | } |
161 | | |
162 | | static struct st_gzip_context_t *gzip_open(h2o_mem_pool_t *pool) |
163 | 0 | { |
164 | 0 | struct st_gzip_context_t *self = h2o_mem_alloc_shared(pool, sizeof(*self), do_free); |
165 | |
|
166 | 0 | self->super.name = h2o_iovec_init(H2O_STRLIT("gzip")); |
167 | 0 | self->super.do_transform = NULL; |
168 | 0 | self->super.push_buf = NULL; |
169 | 0 | self->zs.zalloc = alloc_cb; |
170 | 0 | self->zs.zfree = free_cb; |
171 | 0 | self->zs.opaque = NULL; |
172 | 0 | self->zs_is_open = 1; |
173 | 0 | memset(&self->bufs, 0, sizeof(self->bufs)); |
174 | 0 | expand_buf(self); |
175 | |
|
176 | 0 | return self; |
177 | 0 | } |
178 | | |
179 | | h2o_compress_context_t *h2o_compress_gzip_open(h2o_mem_pool_t *pool, int quality) |
180 | 0 | { |
181 | 0 | struct st_gzip_context_t *self = gzip_open(pool); |
182 | 0 | self->super.do_transform = do_compress; |
183 | | /* Z_BEST_SPEED for on-the-fly compression, memlevel set to 8 as suggested by the manual */ |
184 | 0 | deflateInit2(&self->zs, quality, Z_DEFLATED, WINDOW_BITS, 8, Z_DEFAULT_STRATEGY); |
185 | |
|
186 | 0 | return &self->super; |
187 | 0 | } |
188 | | |
189 | | h2o_compress_context_t *h2o_compress_gunzip_open(h2o_mem_pool_t *pool) |
190 | 0 | { |
191 | 0 | struct st_gzip_context_t *self = gzip_open(pool); |
192 | 0 | self->super.name = h2o_iovec_init(H2O_STRLIT("gunzip")); |
193 | 0 | self->super.do_transform = do_decompress; |
194 | 0 | self->super.push_buf = NULL; |
195 | 0 | inflateInit2(&self->zs, WINDOW_BITS); |
196 | |
|
197 | 0 | return &self->super; |
198 | 0 | } |