/src/h2o/lib/http2/hpack.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2014-2016 DeNA Co., Ltd., Kazuho Oku, Fastly, Inc. |
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 <stddef.h> |
23 | | #include <stdint.h> |
24 | | #include <stdio.h> |
25 | | #include <stdlib.h> |
26 | | #include "h2o/hpack.h" |
27 | | #include "h2o/http2_common.h" |
28 | | |
29 | 856k | #define HEADER_TABLE_OFFSET 62 |
30 | 37.2k | #define HEADER_TABLE_ENTRY_SIZE_OFFSET 32 |
31 | 8.71k | #define DYNAMIC_TABLE_SIZE_UPDATE_MAX_SIZE 5 |
32 | 8.71k | #define STATUS_HEADER_MAX_SIZE 5 |
33 | | #define CONTENT_LENGTH_HEADER_MAX_SIZE \ |
34 | 7.80k | (3 + sizeof(H2O_SIZE_T_LONGEST_STR) - 1) /* uses Literal Header Field without Indexing (RFC7541 6.2.2) */ |
35 | | |
36 | | #include "hpack_huffman_table.h" |
37 | | |
38 | | static inline int value_is_part_of_static_table(const h2o_iovec_t *value) |
39 | 39.9k | { |
40 | 39.9k | return &h2o_hpack_static_table[0].value <= value && |
41 | 39.9k | value <= &h2o_hpack_static_table[sizeof(h2o_hpack_static_table) / sizeof(h2o_hpack_static_table[0]) - 1].value; |
42 | 39.9k | } |
43 | | |
44 | | static h2o_iovec_t *alloc_buf(h2o_mem_pool_t *pool, size_t len) |
45 | 129k | { |
46 | 129k | h2o_iovec_t *buf = h2o_mem_alloc_shared(pool, sizeof(h2o_iovec_t) + len + 1, NULL); |
47 | 129k | buf->base = (char *)buf + sizeof(h2o_iovec_t); |
48 | 129k | buf->len = len; |
49 | 129k | return buf; |
50 | 129k | } |
51 | | |
52 | | int64_t h2o_hpack_decode_int(const uint8_t **src, const uint8_t *src_end, unsigned prefix_bits) |
53 | 1.28M | { |
54 | 1.28M | uint64_t value; |
55 | 1.28M | unsigned shift; |
56 | 1.28M | uint8_t prefix_max = (1 << prefix_bits) - 1; |
57 | | |
58 | 1.28M | if (*src >= src_end) |
59 | 5 | return H2O_HTTP2_ERROR_INCOMPLETE; |
60 | | |
61 | 1.28M | value = *(*src)++ & prefix_max; |
62 | 1.28M | if (value != prefix_max) |
63 | 1.25M | return (int64_t)value; |
64 | | |
65 | | /* decode upto 8 octets (excluding prefix), that are guaranteed not to cause overflow */ |
66 | 32.6k | value = prefix_max; |
67 | 48.7k | for (shift = 0; shift < 56; shift += 7) { |
68 | 47.9k | if (*src == src_end) |
69 | 182 | return H2O_HTTP2_ERROR_INCOMPLETE; |
70 | 47.7k | value += (uint64_t)(**src & 127) << shift; |
71 | 47.7k | if ((*(*src)++ & 128) == 0) |
72 | 31.6k | return (int64_t)value; |
73 | 47.7k | } |
74 | | /* handling the 9th octet */ |
75 | 768 | if (*src == src_end) |
76 | 13 | return H2O_HTTP2_ERROR_INCOMPLETE; |
77 | 755 | if ((**src & 128) != 0) |
78 | 51 | return H2O_HTTP2_ERROR_COMPRESSION; |
79 | 704 | value += (uint64_t)(*(*src)++ & 127) << shift; |
80 | 704 | if (value > (uint64_t)INT64_MAX) |
81 | 9 | return H2O_HTTP2_ERROR_COMPRESSION; |
82 | 695 | return value; |
83 | 704 | } |
84 | | |
85 | | static char *huffdecode4(char *dst, uint8_t in, uint8_t *state, int *maybe_eos, uint8_t *seen_char_types) |
86 | 1.55M | { |
87 | 1.55M | const nghttp2_huff_decode *entry = huff_decode_table[*state] + in; |
88 | | |
89 | 1.55M | if ((entry->flags & NGHTTP2_HUFF_FAIL) != 0) |
90 | 24 | return NULL; |
91 | 1.55M | if ((entry->flags & NGHTTP2_HUFF_SYM) != 0) { |
92 | 1.00M | *dst++ = entry->sym; |
93 | 1.00M | *seen_char_types |= (entry->flags & NGHTTP2_HUFF_INVALID_CHARS); |
94 | 1.00M | } |
95 | 1.55M | *state = entry->state; |
96 | 1.55M | *maybe_eos = (entry->flags & NGHTTP2_HUFF_ACCEPTED) != 0; |
97 | | |
98 | 1.55M | return dst; |
99 | 1.55M | } |
100 | | |
101 | | const char h2o_hpack_err_found_upper_case_in_header_name[] = "found an upper-case letter in header name"; |
102 | | const char h2o_hpack_soft_err_found_invalid_char_in_header_name[] = "found an invalid character in header name"; |
103 | | const char h2o_hpack_soft_err_found_invalid_char_in_header_value[] = "found an invalid character in header value"; |
104 | | |
105 | | size_t h2o_hpack_decode_huffman(char *_dst, unsigned *soft_errors, const uint8_t *src, size_t len, int is_name, |
106 | | const char **err_desc) |
107 | 46.6k | { |
108 | 46.6k | char *dst = _dst; |
109 | 46.6k | const uint8_t *src_end = src + len; |
110 | 46.6k | uint8_t state = 0, seen_char_types = 0; |
111 | 46.6k | int maybe_eos = 1; |
112 | | |
113 | | /* decode */ |
114 | 826k | for (; src < src_end; src++) { |
115 | 779k | if ((dst = huffdecode4(dst, *src >> 4, &state, &maybe_eos, &seen_char_types)) == NULL) |
116 | 11 | return SIZE_MAX; |
117 | 779k | if ((dst = huffdecode4(dst, *src & 0xf, &state, &maybe_eos, &seen_char_types)) == NULL) |
118 | 13 | return SIZE_MAX; |
119 | 779k | } |
120 | 46.6k | if (!maybe_eos) |
121 | 196 | return SIZE_MAX; |
122 | | |
123 | | /* validate */ |
124 | 46.4k | if (is_name) { |
125 | 16.8k | if (dst == _dst) |
126 | 27 | return SIZE_MAX; |
127 | | /* pseudo-headers are checked later in `decode_header` */ |
128 | 16.7k | if ((seen_char_types & NGHTTP2_HUFF_INVALID_FOR_HEADER_NAME) != 0 && _dst[0] != ':') { |
129 | 11.7k | if ((seen_char_types & NGHTTP2_HUFF_UPPER_CASE_CHAR) != 0) { |
130 | 0 | *err_desc = h2o_hpack_err_found_upper_case_in_header_name; |
131 | 0 | return SIZE_MAX; |
132 | 11.7k | } else { |
133 | 11.7k | *soft_errors |= H2O_HPACK_SOFT_ERROR_BIT_INVALID_NAME; |
134 | 11.7k | } |
135 | 11.7k | } |
136 | 29.6k | } else { |
137 | 29.6k | if ((seen_char_types & NGHTTP2_HUFF_INVALID_FOR_HEADER_VALUE) != 0) |
138 | 238 | *soft_errors |= H2O_HPACK_SOFT_ERROR_BIT_INVALID_VALUE; |
139 | 29.6k | } |
140 | | |
141 | 46.4k | return dst - _dst; |
142 | 46.4k | } |
143 | | |
144 | | /* validate a header name against https://tools.ietf.org/html/rfc7230#section-3.2, |
145 | | * in addition to that, we disallow upper case chars as well. |
146 | | * This sets @err_desc for all invalid characters, but only returns true |
147 | | * for upper case characters, this is because we return a protocol error |
148 | | * in that case. */ |
149 | | int h2o_hpack_validate_header_name(unsigned *soft_errors, const char *s, size_t len, const char **err_desc) |
150 | 76.1k | { |
151 | | /* all printable chars, except upper case and separator characters */ |
152 | 76.1k | static const char valid_h2_header_name_char[] = { |
153 | 76.1k | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-31 */ |
154 | 76.1k | 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, /* 32-63 */ |
155 | 76.1k | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, /* 64-95 */ |
156 | 76.1k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, /* 96-127 */ |
157 | 76.1k | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 128-159 */ |
158 | 76.1k | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 160-191 */ |
159 | 76.1k | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 192-223 */ |
160 | 76.1k | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 224-255 */ |
161 | 76.1k | }; |
162 | | |
163 | 449k | for (; len != 0; ++s, --len) { |
164 | 373k | unsigned char ch = (unsigned char)*s; |
165 | 373k | if (!valid_h2_header_name_char[ch]) { |
166 | 104k | if (ch - 'A' < 26U) { |
167 | 121 | *err_desc = h2o_hpack_err_found_upper_case_in_header_name; |
168 | 121 | return 0; |
169 | 121 | } |
170 | 104k | *soft_errors |= H2O_HPACK_SOFT_ERROR_BIT_INVALID_NAME; |
171 | 104k | } |
172 | 373k | } |
173 | 75.9k | return 1; |
174 | 76.1k | } |
175 | | |
176 | | /* validate a header value against https://tools.ietf.org/html/rfc7230#section-3.2 */ |
177 | | void h2o_hpack_validate_header_value(unsigned *soft_errors, const char *s, size_t len) |
178 | 107k | { |
179 | | /* all printable chars + horizontal tab */ |
180 | 107k | static const char valid_h2_field_value_char[] = { |
181 | 107k | 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-31 */ |
182 | 107k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 32-63 */ |
183 | 107k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 64-95 */ |
184 | 107k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, /* 96-127 */ |
185 | 107k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 128-159 */ |
186 | 107k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 160-191 */ |
187 | 107k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 192-223 */ |
188 | 107k | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 224-255 */ |
189 | 107k | }; |
190 | | |
191 | 449k | for (; len != 0; ++s, --len) { |
192 | 371k | unsigned char ch = (unsigned char)*s; |
193 | 371k | if (!valid_h2_field_value_char[ch]) { |
194 | 29.8k | *soft_errors |= H2O_HPACK_SOFT_ERROR_BIT_INVALID_VALUE; |
195 | 29.8k | break; |
196 | 29.8k | } |
197 | 371k | } |
198 | 107k | } |
199 | | |
200 | | static h2o_iovec_t *decode_string(h2o_mem_pool_t *pool, unsigned *soft_errors, const uint8_t **src, const uint8_t *src_end, |
201 | | int is_header_name, const char **err_desc) |
202 | 125k | { |
203 | 125k | h2o_iovec_t *ret; |
204 | 125k | int is_huffman; |
205 | 125k | int64_t len; |
206 | | |
207 | 125k | if (*src >= src_end) |
208 | 512 | return NULL; |
209 | | |
210 | 124k | is_huffman = (**src & 0x80) != 0; |
211 | 124k | if ((len = h2o_hpack_decode_int(src, src_end, 7)) < 0) |
212 | 27 | return NULL; |
213 | | |
214 | 124k | if (is_huffman) { |
215 | 15.1k | if (len > src_end - *src) |
216 | 491 | return NULL; |
217 | 14.6k | ret = alloc_buf(pool, len * 2); /* max compression ratio is >= 0.5 */ |
218 | 14.6k | if ((ret->len = h2o_hpack_decode_huffman(ret->base, soft_errors, *src, len, is_header_name, err_desc)) == SIZE_MAX) |
219 | 131 | return NULL; |
220 | 14.5k | ret->base[ret->len] = '\0'; |
221 | 109k | } else { |
222 | 109k | if (len > src_end - *src) |
223 | 1.34k | return NULL; |
224 | 108k | if (is_header_name) { |
225 | | /* pseudo-headers are checked later in `decode_header` */ |
226 | 47.8k | if (**src != (uint8_t)':' && !h2o_hpack_validate_header_name(soft_errors, (char *)*src, len, err_desc)) |
227 | 59 | return NULL; |
228 | 60.5k | } else { |
229 | 60.5k | h2o_hpack_validate_header_value(soft_errors, (char *)*src, len); |
230 | 60.5k | } |
231 | 108k | ret = alloc_buf(pool, len); |
232 | 108k | memcpy(ret->base, *src, len); |
233 | 108k | ret->base[len] = '\0'; |
234 | 108k | } |
235 | 122k | *src += len; |
236 | | |
237 | 122k | return ret; |
238 | 124k | } |
239 | | |
240 | | static void header_table_evict_one(h2o_hpack_header_table_t *table) |
241 | 7.80k | { |
242 | 7.80k | struct st_h2o_hpack_header_table_entry_t *entry; |
243 | 7.80k | assert(table->num_entries != 0); |
244 | | |
245 | 0 | entry = h2o_hpack_header_table_get(table, --table->num_entries); |
246 | 7.80k | table->hpack_size -= entry->name->len + entry->value->len + HEADER_TABLE_ENTRY_SIZE_OFFSET; |
247 | 7.80k | if (!h2o_iovec_is_token(entry->name)) |
248 | 1.66k | h2o_mem_release_shared(entry->name); |
249 | 7.80k | if (!value_is_part_of_static_table(entry->value)) |
250 | 7.80k | h2o_mem_release_shared(entry->value); |
251 | 7.80k | memset(entry, 0, sizeof(*entry)); |
252 | 7.80k | } |
253 | | |
254 | | static struct st_h2o_hpack_header_table_entry_t *header_table_add(h2o_hpack_header_table_t *table, size_t size_add, |
255 | | size_t max_num_entries) |
256 | 29.4k | { |
257 | | /* adjust the size */ |
258 | 32.7k | while (table->num_entries != 0 && table->hpack_size + size_add > table->hpack_capacity) |
259 | 3.29k | header_table_evict_one(table); |
260 | 29.4k | while (max_num_entries <= table->num_entries) |
261 | 0 | header_table_evict_one(table); |
262 | 29.4k | if (table->num_entries == 0) { |
263 | 15.1k | assert(table->hpack_size == 0); |
264 | 15.1k | if (size_add > table->hpack_capacity) |
265 | 6.04k | return NULL; |
266 | 15.1k | } |
267 | 23.3k | table->hpack_size += size_add; |
268 | | |
269 | | /* grow the entries if full */ |
270 | 23.3k | if (table->num_entries == table->entry_capacity) { |
271 | 6.98k | size_t new_capacity = table->num_entries * 2; |
272 | 6.98k | if (new_capacity < 16) |
273 | 6.78k | new_capacity = 16; |
274 | 6.98k | struct st_h2o_hpack_header_table_entry_t *new_entries = |
275 | 6.98k | h2o_mem_alloc(new_capacity * sizeof(struct st_h2o_hpack_header_table_entry_t)); |
276 | 6.98k | if (table->num_entries != 0) { |
277 | 203 | size_t src_index = table->entry_start_index, dst_index = 0; |
278 | 5.39k | do { |
279 | 5.39k | new_entries[dst_index] = table->entries[src_index]; |
280 | 5.39k | ++dst_index; |
281 | 5.39k | src_index = (src_index + 1) % table->entry_capacity; |
282 | 5.39k | } while (dst_index != table->num_entries); |
283 | 203 | } |
284 | 6.98k | memset(new_entries + table->num_entries, 0, sizeof(*new_entries) * (new_capacity - table->num_entries)); |
285 | 6.98k | free(table->entries); |
286 | 6.98k | table->entries = new_entries; |
287 | 6.98k | table->entry_capacity = new_capacity; |
288 | 6.98k | table->entry_start_index = 0; |
289 | 6.98k | } |
290 | | |
291 | 23.3k | ++table->num_entries; |
292 | 23.3k | table->entry_start_index = (table->entry_start_index + table->entry_capacity - 1) % table->entry_capacity; |
293 | 23.3k | return table->entries + table->entry_start_index; |
294 | 29.4k | } |
295 | | |
296 | | int h2o_hpack_decode_header(h2o_mem_pool_t *pool, void *_hpack_header_table, h2o_iovec_t **_name, h2o_iovec_t *_value, |
297 | | const uint8_t **const src, const uint8_t *src_end, const char **err_desc) |
298 | 383k | { |
299 | 383k | h2o_hpack_header_table_t *hpack_header_table = _hpack_header_table; |
300 | 383k | h2o_iovec_t *name = NULL, *value = NULL; |
301 | 383k | int64_t index = 0; |
302 | 383k | int value_is_indexed = 0, do_index = 0; |
303 | | |
304 | 408k | Redo: |
305 | 408k | if (*src >= src_end) |
306 | 151 | return H2O_HTTP2_ERROR_COMPRESSION; |
307 | | |
308 | | /* determine the mode and handle accordingly */ |
309 | 408k | if (**src >= 128) { |
310 | | /* indexed header field representation */ |
311 | 306k | if ((index = h2o_hpack_decode_int(src, src_end, 7)) <= 0) |
312 | 61 | return H2O_HTTP2_ERROR_COMPRESSION; |
313 | 306k | value_is_indexed = 1; |
314 | 306k | } else if (**src >= 64) { |
315 | | /* literal header field with incremental handling */ |
316 | 23.2k | if (**src == 64) { |
317 | 4.84k | ++*src; |
318 | 18.3k | } else if ((index = h2o_hpack_decode_int(src, src_end, 6)) <= 0) { |
319 | 27 | return H2O_HTTP2_ERROR_COMPRESSION; |
320 | 27 | } |
321 | 23.1k | do_index = 1; |
322 | 78.5k | } else if (**src < 32) { |
323 | | /* literal header field without indexing / never indexed */ |
324 | 53.4k | if ((**src & 0xf) == 0) { |
325 | 44.6k | ++*src; |
326 | 44.6k | } else if ((index = h2o_hpack_decode_int(src, src_end, 4)) <= 0) { |
327 | 22 | return H2O_HTTP2_ERROR_COMPRESSION; |
328 | 22 | } |
329 | 53.4k | } else { |
330 | | /* size update */ |
331 | 25.1k | int64_t new_capacity; |
332 | 25.1k | if ((new_capacity = h2o_hpack_decode_int(src, src_end, 5)) < 0) { |
333 | 23 | return H2O_HTTP2_ERROR_COMPRESSION; |
334 | 23 | } |
335 | 25.1k | if (new_capacity > hpack_header_table->hpack_max_capacity) { |
336 | 201 | return H2O_HTTP2_ERROR_COMPRESSION; |
337 | 201 | } |
338 | 24.9k | hpack_header_table->hpack_capacity = (size_t)new_capacity; |
339 | 29.4k | while (hpack_header_table->num_entries != 0 && hpack_header_table->hpack_size > hpack_header_table->hpack_capacity) { |
340 | 4.47k | header_table_evict_one(hpack_header_table); |
341 | 4.47k | } |
342 | 24.9k | goto Redo; |
343 | 25.1k | } |
344 | | |
345 | | /* determine the header */ |
346 | 383k | unsigned soft_errors = 0; |
347 | 383k | if (index > 0) { |
348 | | /* existing name (and value?) */ |
349 | 333k | if (index < HEADER_TABLE_OFFSET) { |
350 | 76.8k | name = (h2o_iovec_t *)h2o_hpack_static_table[index - 1].name; |
351 | 76.8k | if (value_is_indexed) |
352 | 51.1k | value = (h2o_iovec_t *)&h2o_hpack_static_table[index - 1].value; |
353 | 256k | } else if (index - HEADER_TABLE_OFFSET < hpack_header_table->num_entries) { |
354 | 255k | struct st_h2o_hpack_header_table_entry_t *entry = |
355 | 255k | h2o_hpack_header_table_get(hpack_header_table, index - HEADER_TABLE_OFFSET); |
356 | 255k | soft_errors = entry->soft_errors; |
357 | 255k | name = entry->name; |
358 | 255k | if (!h2o_iovec_is_token(name)) |
359 | 103k | h2o_mem_link_shared(pool, name); |
360 | 255k | if (value_is_indexed) { |
361 | 254k | value = entry->value; |
362 | 254k | h2o_mem_link_shared(pool, value); |
363 | 254k | } |
364 | 255k | } else { |
365 | 847 | return H2O_HTTP2_ERROR_COMPRESSION; |
366 | 847 | } |
367 | 333k | } else { |
368 | | /* non-existing name */ |
369 | 49.5k | const h2o_token_t *name_token; |
370 | 49.5k | if ((name = decode_string(pool, &soft_errors, src, src_end, 1, err_desc)) == NULL) { |
371 | 384 | if (*err_desc == h2o_hpack_err_found_upper_case_in_header_name) |
372 | 59 | return H2O_HTTP2_ERROR_PROTOCOL; |
373 | 325 | return H2O_HTTP2_ERROR_COMPRESSION; |
374 | 384 | } |
375 | | /* predefined header names should be interned */ |
376 | 49.1k | if ((name_token = h2o_lookup_token(name->base, name->len)) != NULL) |
377 | 5.23k | name = (h2o_iovec_t *)&name_token->buf; |
378 | 49.1k | } |
379 | | |
380 | | /* determine the value (if necessary) */ |
381 | 381k | if (!value_is_indexed) { |
382 | 75.9k | soft_errors &= ~H2O_HPACK_SOFT_ERROR_BIT_INVALID_VALUE; |
383 | 75.9k | if ((value = decode_string(pool, &soft_errors, src, src_end, 0, err_desc)) == NULL) |
384 | 2.17k | return H2O_HTTP2_ERROR_COMPRESSION; |
385 | 75.9k | } |
386 | | |
387 | | /* add the decoded header to the header table if necessary */ |
388 | 379k | if (do_index) { |
389 | 22.0k | struct st_h2o_hpack_header_table_entry_t *entry = |
390 | 22.0k | header_table_add(hpack_header_table, name->len + value->len + HEADER_TABLE_ENTRY_SIZE_OFFSET, SIZE_MAX); |
391 | 22.0k | if (entry != NULL) { |
392 | 16.5k | entry->soft_errors = soft_errors; |
393 | 16.5k | entry->name = name; |
394 | 16.5k | if (!h2o_iovec_is_token(entry->name)) |
395 | 3.63k | h2o_mem_addref_shared(entry->name); |
396 | 16.5k | entry->value = value; |
397 | 16.5k | if (!value_is_part_of_static_table(entry->value)) |
398 | 16.5k | h2o_mem_addref_shared(entry->value); |
399 | 16.5k | } |
400 | 22.0k | } |
401 | | |
402 | 379k | *_name = name; |
403 | 379k | *_value = *value; |
404 | 379k | if (soft_errors != 0) { |
405 | 117k | *err_desc = (soft_errors & H2O_HPACK_SOFT_ERROR_BIT_INVALID_NAME) != 0 |
406 | 117k | ? h2o_hpack_soft_err_found_invalid_char_in_header_name |
407 | 117k | : h2o_hpack_soft_err_found_invalid_char_in_header_value; |
408 | 117k | return H2O_HTTP2_ERROR_INVALID_HEADER_CHAR; |
409 | 262k | } else { |
410 | 262k | return 0; |
411 | 262k | } |
412 | 379k | } |
413 | | |
414 | | static uint8_t *encode_status(uint8_t *dst, int status) |
415 | 8.71k | { |
416 | | /* see also: STATUS_HEADER_MAX_SIZE */ |
417 | | |
418 | 8.71k | assert(100 <= status && status <= 999); |
419 | | |
420 | 0 | switch (status) { |
421 | 0 | #define COMMON_CODE(code, st) \ |
422 | 8.71k | case st: \ |
423 | 8.71k | *dst++ = 0x80 | code; \ |
424 | 8.71k | break |
425 | 906 | COMMON_CODE(8, 200); |
426 | 0 | COMMON_CODE(9, 204); |
427 | 0 | COMMON_CODE(10, 206); |
428 | 0 | COMMON_CODE(11, 304); |
429 | 3.32k | COMMON_CODE(12, 400); |
430 | 4.47k | COMMON_CODE(13, 404); |
431 | 0 | COMMON_CODE(14, 500); |
432 | 0 | #undef COMMON_CODE |
433 | 1 | default: |
434 | | /* use literal header field without indexing - indexed name */ |
435 | 1 | *dst++ = 8; |
436 | 1 | *dst++ = 3; |
437 | 1 | sprintf((char *)dst, "%d", status); |
438 | 1 | dst += 3; |
439 | 1 | break; |
440 | 8.71k | } |
441 | | |
442 | 8.71k | return dst; |
443 | 8.71k | } |
444 | | |
445 | | static uint8_t *encode_content_length(uint8_t *dst, size_t value) |
446 | 7.80k | { |
447 | 7.80k | char buf[32], *p = buf + sizeof(buf); |
448 | 7.80k | size_t l; |
449 | | |
450 | 11.1k | do { |
451 | 11.1k | *--p = '0' + value % 10; |
452 | 11.1k | } while ((value /= 10) != 0); |
453 | 7.80k | l = buf + sizeof(buf) - p; |
454 | 7.80k | *dst++ = 0x0f; |
455 | 7.80k | *dst++ = 0x0d; |
456 | 7.80k | *dst++ = (uint8_t)l; |
457 | 7.80k | memcpy(dst, p, l); |
458 | 7.80k | dst += l; |
459 | | |
460 | 7.80k | return dst; |
461 | 7.80k | } |
462 | | |
463 | | void h2o_hpack_dispose_header_table(h2o_hpack_header_table_t *header_table) |
464 | 25.7k | { |
465 | 25.7k | if (header_table->num_entries != 0) { |
466 | 5.88k | size_t index = header_table->entry_start_index; |
467 | 15.5k | do { |
468 | 15.5k | struct st_h2o_hpack_header_table_entry_t *entry = header_table->entries + index; |
469 | 15.5k | if (!h2o_iovec_is_token(entry->name)) |
470 | 1.96k | h2o_mem_release_shared(entry->name); |
471 | 15.5k | if (!value_is_part_of_static_table(entry->value)) |
472 | 15.5k | h2o_mem_release_shared(entry->value); |
473 | 15.5k | index = (index + 1) % header_table->entry_capacity; |
474 | 15.5k | } while (--header_table->num_entries != 0); |
475 | 5.88k | } |
476 | 25.7k | free(header_table->entries); |
477 | 25.7k | } |
478 | | |
479 | | int h2o_hpack_parse_request(h2o_mem_pool_t *pool, h2o_hpack_decode_header_cb decode_cb, void *decode_ctx, h2o_iovec_t *method, |
480 | | const h2o_url_scheme_t **scheme, h2o_iovec_t *authority, h2o_iovec_t *path, h2o_headers_t *headers, |
481 | | int *pseudo_header_exists_map, size_t *content_length, h2o_cache_digests_t **digests, |
482 | | h2o_iovec_t *datagram_flow_id, const uint8_t *src, size_t len, const char **err_desc) |
483 | 23.7k | { |
484 | 23.7k | const uint8_t *src_end = src + len; |
485 | | |
486 | 23.7k | *content_length = SIZE_MAX; |
487 | | |
488 | 1.12M | while (src != src_end) { |
489 | 1.11M | h2o_iovec_t *name, value; |
490 | 1.11M | const char *decode_err = NULL; |
491 | 1.11M | int ret = decode_cb(pool, decode_ctx, &name, &value, &src, src_end, &decode_err); |
492 | 1.11M | if (ret != 0) { |
493 | 153k | if (ret == H2O_HTTP2_ERROR_INVALID_HEADER_CHAR) { |
494 | | /* this is a soft error, we continue parsing, but register only the first error */ |
495 | 146k | if (*err_desc == NULL) { |
496 | 6.45k | *err_desc = decode_err; |
497 | 6.45k | } |
498 | 146k | } else { |
499 | 7.28k | *err_desc = decode_err; |
500 | 7.28k | return ret; |
501 | 7.28k | } |
502 | 153k | } |
503 | 1.10M | if (name->base[0] == ':') { |
504 | 43.5k | if (pseudo_header_exists_map != NULL) { |
505 | | /* FIXME validate the chars in the value (e.g. reject SP in path) */ |
506 | 43.3k | if (name == &H2O_TOKEN_AUTHORITY->buf) { |
507 | 9.67k | if (authority->base != NULL) |
508 | 8 | return H2O_HTTP2_ERROR_PROTOCOL; |
509 | 9.66k | *authority = value; |
510 | 9.66k | *pseudo_header_exists_map |= H2O_HPACK_PARSE_HEADERS_AUTHORITY_EXISTS; |
511 | 33.6k | } else if (name == &H2O_TOKEN_METHOD->buf) { |
512 | 11.8k | if (method->base != NULL) |
513 | 8 | return H2O_HTTP2_ERROR_PROTOCOL; |
514 | 11.8k | *method = value; |
515 | 11.8k | *pseudo_header_exists_map |= H2O_HPACK_PARSE_HEADERS_METHOD_EXISTS; |
516 | 21.7k | } else if (name == &H2O_TOKEN_PATH->buf) { |
517 | 10.8k | if (path->base != NULL) |
518 | 8 | return H2O_HTTP2_ERROR_PROTOCOL; |
519 | 10.8k | if (value.len == 0) |
520 | 9 | return H2O_HTTP2_ERROR_PROTOCOL; |
521 | 10.7k | *path = value; |
522 | 10.7k | *pseudo_header_exists_map |= H2O_HPACK_PARSE_HEADERS_PATH_EXISTS; |
523 | 10.9k | } else if (name == &H2O_TOKEN_SCHEME->buf) { |
524 | 10.9k | if (*scheme != NULL) |
525 | 6 | return H2O_HTTP2_ERROR_PROTOCOL; |
526 | 10.9k | if (h2o_memis(value.base, value.len, H2O_STRLIT("https"))) { |
527 | 1.62k | *scheme = &H2O_URL_SCHEME_HTTPS; |
528 | 9.33k | } else if (h2o_memis(value.base, value.len, H2O_STRLIT("masque"))) { |
529 | 1 | *scheme = &H2O_URL_SCHEME_MASQUE; |
530 | 9.33k | } else { |
531 | | /* draft-16 8.1.2.3 suggests quote: ":scheme is not restricted to http and https schemed URIs" */ |
532 | 9.33k | *scheme = &H2O_URL_SCHEME_HTTP; |
533 | 9.33k | } |
534 | 10.9k | *pseudo_header_exists_map |= H2O_HPACK_PARSE_HEADERS_SCHEME_EXISTS; |
535 | 10.9k | } else { |
536 | 16 | return H2O_HTTP2_ERROR_PROTOCOL; |
537 | 16 | } |
538 | 43.3k | } else { |
539 | 235 | return H2O_HTTP2_ERROR_PROTOCOL; |
540 | 235 | } |
541 | 1.06M | } else { |
542 | 1.06M | pseudo_header_exists_map = NULL; |
543 | 1.06M | if (h2o_iovec_is_token(name)) { |
544 | 873k | h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, name); |
545 | 873k | if (token->flags.is_hpack_special) { |
546 | 120k | if (token == H2O_TOKEN_CONTENT_LENGTH) { |
547 | 8.75k | if ((*content_length = h2o_strtosize(value.base, value.len)) == SIZE_MAX) |
548 | 81 | return H2O_HTTP2_ERROR_PROTOCOL; |
549 | 8.67k | goto Next; |
550 | 111k | } else if (token == H2O_TOKEN_HOST) { |
551 | | /* HTTP2 allows the use of host header (in place of :authority) */ |
552 | 4.08k | if (authority->base == NULL) |
553 | 875 | *authority = value; |
554 | 4.08k | goto Next; |
555 | 107k | } else if (token == H2O_TOKEN_TE && h2o_lcstris(value.base, value.len, H2O_STRLIT("trailers"))) { |
556 | | /* do not reject */ |
557 | 106k | } else if (token == H2O_TOKEN_CACHE_DIGEST && digests != NULL) { |
558 | | /* TODO cache the decoded result in HPACK, as well as delay the decoding of the digest until being used */ |
559 | 106k | h2o_cache_digests_load_header(digests, value.base, value.len); |
560 | 106k | } else if (token == H2O_TOKEN_DATAGRAM_FLOW_ID) { |
561 | 361 | if (datagram_flow_id != NULL) |
562 | 243 | *datagram_flow_id = value; |
563 | 361 | goto Next; |
564 | 361 | } else { |
565 | | /* rest of the header fields that are marked as special are rejected */ |
566 | 71 | return H2O_HTTP2_ERROR_PROTOCOL; |
567 | 71 | } |
568 | 120k | } |
569 | 860k | h2o_add_header(pool, headers, token, NULL, value.base, value.len); |
570 | 860k | } else { |
571 | 187k | h2o_add_header_by_str(pool, headers, name->base, name->len, 0, NULL, value.base, value.len); |
572 | 187k | } |
573 | 1.06M | } |
574 | 1.10M | Next:; |
575 | 1.10M | } |
576 | | |
577 | 16.0k | if (*err_desc != NULL) |
578 | 4.00k | return H2O_HTTP2_ERROR_INVALID_HEADER_CHAR; |
579 | 12.0k | return 0; |
580 | 16.0k | } |
581 | | |
582 | | int h2o_hpack_parse_response(h2o_mem_pool_t *pool, h2o_hpack_decode_header_cb decode_cb, void *decode_ctx, int *status, |
583 | | h2o_headers_t *headers, h2o_iovec_t *datagram_flow_id, const uint8_t *src, size_t len, |
584 | | const char **err_desc) |
585 | 0 | { |
586 | 0 | *status = 0; |
587 | |
|
588 | 0 | const uint8_t *src_end = src + len; |
589 | | |
590 | | /* the response MUST contain a :status header as the first element */ |
591 | 0 | if (src == src_end) |
592 | 0 | return H2O_HTTP2_ERROR_PROTOCOL; |
593 | | |
594 | 0 | do { |
595 | 0 | h2o_iovec_t *name, value; |
596 | 0 | const char *decode_err = NULL; |
597 | 0 | int ret = decode_cb(pool, decode_ctx, &name, &value, &src, src_end, &decode_err); |
598 | 0 | if (ret != 0) { |
599 | 0 | if (ret == H2O_HTTP2_ERROR_INVALID_HEADER_CHAR) { |
600 | | /* this is a soft error, we continue parsing, but register only the first error */ |
601 | 0 | if (*err_desc == NULL) { |
602 | 0 | *err_desc = decode_err; |
603 | 0 | } |
604 | 0 | } else { |
605 | 0 | *err_desc = decode_err; |
606 | 0 | return ret; |
607 | 0 | } |
608 | 0 | } |
609 | 0 | if (name->base[0] == ':') { |
610 | 0 | if (name != &H2O_TOKEN_STATUS->buf) |
611 | 0 | return H2O_HTTP2_ERROR_PROTOCOL; |
612 | 0 | if (*status != 0) |
613 | 0 | return H2O_HTTP2_ERROR_PROTOCOL; |
614 | | /* parse status */ |
615 | 0 | if (value.len != 3) |
616 | 0 | return H2O_HTTP2_ERROR_PROTOCOL; |
617 | 0 | char *c = value.base; |
618 | 0 | #define PARSE_DIGIT(mul, min_digit) \ |
619 | 0 | do { \ |
620 | 0 | if (*c < '0' + (min_digit) || '9' < *c) \ |
621 | 0 | return H2O_HTTP2_ERROR_PROTOCOL; \ |
622 | 0 | *status += (*c - '0') * mul; \ |
623 | 0 | ++c; \ |
624 | 0 | } while (0) |
625 | 0 | PARSE_DIGIT(100, 1); |
626 | 0 | PARSE_DIGIT(10, 0); |
627 | 0 | PARSE_DIGIT(1, 0); |
628 | 0 | #undef PARSE_DIGIT |
629 | 0 | } else { |
630 | 0 | if (*status == 0) |
631 | 0 | return H2O_HTTP2_ERROR_PROTOCOL; |
632 | 0 | if (h2o_iovec_is_token(name)) { |
633 | 0 | h2o_token_t *token = H2O_STRUCT_FROM_MEMBER(h2o_token_t, buf, name); |
634 | | /* reject headers as defined in draft-16 8.1.2.2 */ |
635 | 0 | if (token->flags.is_hpack_special) { |
636 | 0 | if (token == H2O_TOKEN_CONTENT_LENGTH || token == H2O_TOKEN_CACHE_DIGEST) { |
637 | | /* pass them through when found in response headers (TODO reconsider?) */ |
638 | 0 | } else if (token == H2O_TOKEN_DATAGRAM_FLOW_ID) { |
639 | 0 | if (datagram_flow_id != NULL) |
640 | 0 | *datagram_flow_id = value; |
641 | 0 | goto Next; |
642 | 0 | } else { |
643 | 0 | return H2O_HTTP2_ERROR_PROTOCOL; |
644 | 0 | } |
645 | 0 | } |
646 | 0 | h2o_add_header(pool, headers, token, NULL, value.base, value.len); |
647 | 0 | } else { |
648 | 0 | h2o_add_header_by_str(pool, headers, name->base, name->len, 0, NULL, value.base, value.len); |
649 | 0 | } |
650 | 0 | } |
651 | 0 | Next:; |
652 | 0 | } while (src != src_end); |
653 | | |
654 | 0 | if (*err_desc) { |
655 | 0 | return H2O_HTTP2_ERROR_INVALID_HEADER_CHAR; |
656 | 0 | } |
657 | 0 | return 0; |
658 | 0 | } |
659 | | |
660 | | static inline int encode_int_is_onebyte(int64_t value, unsigned prefix_bits) |
661 | 40.5k | { |
662 | 40.5k | return value < (1 << prefix_bits) - 1; |
663 | 40.5k | } |
664 | | |
665 | | uint8_t *h2o_hpack_encode_int(uint8_t *dst, int64_t value, unsigned prefix_bits) |
666 | 33.2k | { |
667 | 33.2k | if (encode_int_is_onebyte(value, prefix_bits)) { |
668 | 29.5k | *dst++ |= value; |
669 | 29.5k | } else { |
670 | | /* see also: MAX_ENCODE_INT_LENGTH */ |
671 | 3.68k | assert(value >= 0); |
672 | 0 | value -= (1 << prefix_bits) - 1; |
673 | 3.68k | *dst++ |= (1 << prefix_bits) - 1; |
674 | 3.71k | for (; value >= 128; value >>= 7) { |
675 | 30 | *dst++ = 0x80 | value; |
676 | 30 | } |
677 | 3.68k | *dst++ = value; |
678 | 3.68k | } |
679 | 0 | return dst; |
680 | 33.2k | } |
681 | | |
682 | | size_t h2o_hpack_encode_huffman(uint8_t *_dst, const uint8_t *src, size_t len) |
683 | 12.4k | { |
684 | 12.4k | uint8_t *dst = _dst, *dst_end = dst + len; |
685 | 12.4k | const uint8_t *src_end = src + len; |
686 | 12.4k | uint64_t bits = 0; |
687 | 12.4k | int bits_left = 40; |
688 | | |
689 | 283k | while (src != src_end) { |
690 | 271k | const nghttp2_huff_sym *sym = huff_sym_table + *src++; |
691 | 271k | bits |= (uint64_t)sym->code << (bits_left - sym->nbits); |
692 | 271k | bits_left -= sym->nbits; |
693 | 464k | while (bits_left <= 32) { |
694 | 192k | *dst++ = bits >> 32; |
695 | 192k | bits <<= 8; |
696 | 192k | bits_left += 8; |
697 | 192k | if (dst == dst_end) { |
698 | 0 | return SIZE_MAX; |
699 | 0 | } |
700 | 192k | } |
701 | 271k | } |
702 | | |
703 | 12.4k | if (bits_left != 40) { |
704 | 12.4k | bits |= ((uint64_t)1 << bits_left) - 1; |
705 | 12.4k | *dst++ = bits >> 32; |
706 | 12.4k | } |
707 | 12.4k | if (dst == dst_end) { |
708 | 1.41k | return SIZE_MAX; |
709 | 1.41k | } |
710 | | |
711 | 11.0k | return dst - _dst; |
712 | 12.4k | } |
713 | | |
714 | | static size_t encode_as_is(uint8_t *dst, const char *s, size_t len) |
715 | 0 | { |
716 | 0 | uint8_t *start = dst; |
717 | 0 | *dst = '\0'; |
718 | 0 | dst = h2o_hpack_encode_int(dst, len, 7); |
719 | 0 | memcpy(dst, s, len); |
720 | 0 | dst += len; |
721 | 0 | return dst - start; |
722 | 0 | } |
723 | | |
724 | | size_t h2o_hpack_encode_string(uint8_t *dst, const char *s, size_t len) |
725 | 7.31k | { |
726 | 7.31k | if (H2O_LIKELY(len != 0)) { |
727 | | /* try to encode using huffman */ |
728 | 7.31k | size_t hufflen = h2o_hpack_encode_huffman(dst + 1, (const uint8_t *)s, len); |
729 | 7.31k | if (H2O_LIKELY(hufflen != SIZE_MAX)) { |
730 | 7.31k | size_t head_len; |
731 | 7.31k | if (H2O_LIKELY(encode_int_is_onebyte((uint32_t)hufflen, 7))) { |
732 | 7.31k | dst[0] = (uint8_t)(0x80 | hufflen); |
733 | 7.31k | head_len = 1; |
734 | 7.31k | } else { |
735 | 0 | uint8_t head[8]; |
736 | 0 | head[0] = '\x80'; |
737 | 0 | head_len = h2o_hpack_encode_int(head, hufflen, 7) - head; |
738 | 0 | memmove(dst + head_len, dst + 1, hufflen); |
739 | 0 | memcpy(dst, head, head_len); |
740 | 0 | } |
741 | 7.31k | return head_len + hufflen; |
742 | 7.31k | } |
743 | 7.31k | } |
744 | 0 | return encode_as_is(dst, s, len); |
745 | 7.31k | } |
746 | | |
747 | | static uint8_t *header_table_adjust_size(h2o_hpack_header_table_t *table, uint32_t new_capacity, uint8_t *dst) |
748 | 8.71k | { |
749 | | /* Do nothing if user-supplied value is greater than the current value. Because we never allow the peer to increase the table |
750 | | * size here, there is no need to worry about using excess memory. */ |
751 | 8.71k | if (new_capacity >= table->hpack_capacity) |
752 | 8.58k | return dst; |
753 | | |
754 | | /* update state */ |
755 | 124 | table->hpack_capacity = new_capacity; |
756 | 151 | while (table->num_entries != 0 && table->hpack_size > table->hpack_capacity) |
757 | 27 | header_table_evict_one(table); |
758 | | |
759 | | /* encode Dynamic Table Size Update */ |
760 | 124 | *dst = 0x20; |
761 | 124 | dst = h2o_hpack_encode_int(dst, table->hpack_capacity, 5); |
762 | | |
763 | 124 | return dst; |
764 | 8.71k | } |
765 | | |
766 | | static uint8_t *do_encode_header(h2o_hpack_header_table_t *header_table, uint8_t *dst, const h2o_iovec_t *name, |
767 | | const h2o_iovec_t *value, int dont_compress) |
768 | 17.4k | { |
769 | 17.4k | int is_token = h2o_iovec_is_token(name); |
770 | 17.4k | int name_index = is_token ? ((const h2o_token_t *)name)->flags.http2_static_table_name_index : 0; |
771 | | |
772 | | /* try to send as indexed */ |
773 | 17.4k | { |
774 | 17.4k | size_t header_table_index = header_table->entry_start_index, n; |
775 | 27.1k | for (n = header_table->num_entries; n != 0; --n) { |
776 | 19.8k | struct st_h2o_hpack_header_table_entry_t *entry = header_table->entries + header_table_index; |
777 | 19.8k | if (is_token) { |
778 | 19.8k | if (name != entry->name) |
779 | 9.67k | goto Next; |
780 | 19.8k | } else { |
781 | 0 | if (!h2o_memis(name->base, name->len, entry->name->base, entry->name->len)) |
782 | 0 | goto Next; |
783 | 0 | if (name_index == 0) |
784 | 0 | name_index = (int)(header_table->num_entries - n + HEADER_TABLE_OFFSET); |
785 | 0 | } |
786 | | /* name matched! */ |
787 | 10.1k | if (!h2o_memis(value->base, value->len, entry->value->base, entry->value->len)) |
788 | 28 | goto Next; |
789 | | /* name and value matched! */ |
790 | 10.1k | *dst = 0x80; |
791 | 10.1k | dst = h2o_hpack_encode_int(dst, header_table->num_entries - n + HEADER_TABLE_OFFSET, 7); |
792 | 10.1k | return dst; |
793 | 9.70k | Next: |
794 | 9.70k | ++header_table_index; |
795 | 9.70k | if (header_table_index == header_table->entry_capacity) |
796 | 2.76k | header_table_index = 0; |
797 | 9.70k | } |
798 | 17.4k | } |
799 | | |
800 | 7.31k | if (!dont_compress && is_token) |
801 | 7.31k | dont_compress = ((const h2o_token_t *)name)->flags.dont_compress; |
802 | 7.31k | if (dont_compress) |
803 | 0 | dont_compress = value->len < 20; |
804 | | |
805 | 7.31k | if (name_index != 0) { |
806 | | /* literal header field with indexing (indexed name). */ |
807 | 7.31k | if (dont_compress == 1) { |
808 | | /* mark the field as 'never indexed' */ |
809 | 0 | *dst = 0x10; |
810 | 0 | dst = h2o_hpack_encode_int(dst, name_index, 4); |
811 | 7.31k | } else { |
812 | 7.31k | *dst = 0x40; |
813 | 7.31k | dst = h2o_hpack_encode_int(dst, name_index, 6); |
814 | 7.31k | } |
815 | 7.31k | } else { |
816 | | /* literal header field with indexing (new name) */ |
817 | 0 | *dst++ = 0x40; |
818 | 0 | dst += h2o_hpack_encode_string(dst, name->base, name->len); |
819 | 0 | } |
820 | 7.31k | if (dont_compress == 1) { |
821 | | /* bypass huffman encoding */ |
822 | 0 | dst += encode_as_is(dst, value->base, value->len); |
823 | 7.31k | } else { |
824 | | /* add to header table (maximum number of entries in output header table is limited to 32 so that the search (see above) |
825 | | would |
826 | | not take too long) */ |
827 | 7.31k | dst += h2o_hpack_encode_string(dst, value->base, value->len); |
828 | 7.31k | struct st_h2o_hpack_header_table_entry_t *entry = |
829 | 7.31k | header_table_add(header_table, name->len + value->len + HEADER_TABLE_ENTRY_SIZE_OFFSET, 32); |
830 | 7.31k | if (entry != NULL) { |
831 | 6.82k | if (is_token) { |
832 | 6.82k | entry->name = (h2o_iovec_t *)name; |
833 | 6.82k | } else { |
834 | 0 | entry->name = alloc_buf(NULL, name->len); |
835 | 0 | entry->name->base[name->len] = '\0'; |
836 | 0 | memcpy(entry->name->base, name->base, name->len); |
837 | 0 | } |
838 | 6.82k | entry->value = alloc_buf(NULL, value->len); |
839 | 6.82k | entry->value->base[value->len] = '\0'; |
840 | 6.82k | memcpy(entry->value->base, value->base, value->len); |
841 | 6.82k | } |
842 | 7.31k | } |
843 | | |
844 | 7.31k | return dst; |
845 | 17.4k | } |
846 | | |
847 | | static uint8_t *encode_header(h2o_hpack_header_table_t *header_table, uint8_t *dst, const h2o_header_t *header) |
848 | 8.71k | { |
849 | 8.71k | return do_encode_header(header_table, dst, header->name, &header->value, header->flags.dont_compress); |
850 | 8.71k | } |
851 | | |
852 | | static uint8_t *encode_header_token(h2o_hpack_header_table_t *header_table, uint8_t *dst, const h2o_token_t *token, |
853 | | const h2o_iovec_t *value) |
854 | 8.71k | { |
855 | 8.71k | return do_encode_header(header_table, dst, &token->buf, value, token->flags.dont_compress); |
856 | 8.71k | } |
857 | | |
858 | | static uint8_t *encode_method(h2o_hpack_header_table_t *header_table, uint8_t *dst, h2o_iovec_t value) |
859 | 0 | { |
860 | 0 | if (h2o_memis(value.base, value.len, H2O_STRLIT("GET"))) { |
861 | 0 | *dst++ = 0x82; |
862 | 0 | return dst; |
863 | 0 | } |
864 | 0 | if (h2o_memis(value.base, value.len, H2O_STRLIT("POST"))) { |
865 | 0 | *dst++ = 0x83; |
866 | 0 | return dst; |
867 | 0 | } |
868 | 0 | return encode_header_token(header_table, dst, H2O_TOKEN_METHOD, &value); |
869 | 0 | } |
870 | | |
871 | | static uint8_t *encode_scheme(h2o_hpack_header_table_t *header_table, uint8_t *dst, const h2o_url_scheme_t *scheme) |
872 | 0 | { |
873 | 0 | if (scheme == &H2O_URL_SCHEME_HTTPS) { |
874 | 0 | *dst++ = 0x87; |
875 | 0 | return dst; |
876 | 0 | } |
877 | 0 | if (scheme == &H2O_URL_SCHEME_HTTP) { |
878 | 0 | *dst++ = 0x86; |
879 | 0 | return dst; |
880 | 0 | } |
881 | 0 | return encode_header_token(header_table, dst, H2O_TOKEN_SCHEME, &scheme->name); |
882 | 0 | } |
883 | | |
884 | | static uint8_t *encode_path(h2o_hpack_header_table_t *header_table, uint8_t *dst, h2o_iovec_t value) |
885 | 0 | { |
886 | 0 | if (h2o_memis(value.base, value.len, H2O_STRLIT("/"))) { |
887 | 0 | *dst++ = 0x84; |
888 | 0 | return dst; |
889 | 0 | } |
890 | 0 | if (h2o_memis(value.base, value.len, H2O_STRLIT("/index.html"))) { |
891 | 0 | *dst++ = 0x85; |
892 | 0 | return dst; |
893 | 0 | } |
894 | 0 | return encode_header_token(header_table, dst, H2O_TOKEN_PATH, &value); |
895 | 0 | } |
896 | | |
897 | | static uint8_t *encode_literal_header_without_indexing(uint8_t *dst, const h2o_iovec_t *name, const h2o_iovec_t *value) |
898 | 0 | { |
899 | 0 | /* literal header field without indexing / never indexed */ |
900 | 0 | *dst++ = 0; |
901 | 0 | dst += h2o_hpack_encode_string(dst, name->base, name->len); |
902 | 0 | dst += h2o_hpack_encode_string(dst, value->base, value->len); |
903 | 0 | return dst; |
904 | 0 | } |
905 | | |
906 | | static size_t calc_capacity(size_t name_len, size_t value_len) |
907 | 8.71k | { |
908 | 8.71k | return name_len + value_len + 1 + H2O_HPACK_ENCODE_INT_MAX_LENGTH * 2; |
909 | 8.71k | } |
910 | | |
911 | | static size_t calc_headers_capacity(const h2o_header_t *headers, size_t num_headers) |
912 | 8.71k | { |
913 | 8.71k | const h2o_header_t *header; |
914 | 8.71k | size_t capacity = 0; |
915 | 17.4k | for (header = headers; num_headers != 0; ++header, --num_headers) |
916 | 8.71k | capacity += calc_capacity(header->name->len, header->value.len); |
917 | 8.71k | return capacity; |
918 | 8.71k | } |
919 | | |
920 | | static void fixup_frame_headers(h2o_buffer_t **buf, size_t start_at, uint8_t type, uint32_t stream_id, size_t max_frame_size, |
921 | | int flags) |
922 | 8.71k | { |
923 | | /* try to fit all data into single frame, using the preallocated space for the frame header */ |
924 | 8.71k | size_t payload_size = (*buf)->size - start_at - H2O_HTTP2_FRAME_HEADER_SIZE; |
925 | 8.71k | if (payload_size <= max_frame_size) { |
926 | 8.71k | h2o_http2_encode_frame_header((uint8_t *)((*buf)->bytes + start_at), payload_size, type, |
927 | 8.71k | H2O_HTTP2_FRAME_FLAG_END_HEADERS | flags, stream_id); |
928 | 8.71k | return; |
929 | 8.71k | } |
930 | | |
931 | | /* need to setup continuation frames */ |
932 | 0 | size_t off; |
933 | 0 | h2o_http2_encode_frame_header((uint8_t *)((*buf)->bytes + start_at), max_frame_size, type, flags, stream_id); |
934 | 0 | off = start_at + H2O_HTTP2_FRAME_HEADER_SIZE + max_frame_size; |
935 | 0 | while (1) { |
936 | 0 | size_t left = (*buf)->size - off; |
937 | 0 | h2o_buffer_reserve(buf, H2O_HTTP2_FRAME_HEADER_SIZE); |
938 | 0 | memmove((*buf)->bytes + off + H2O_HTTP2_FRAME_HEADER_SIZE, (*buf)->bytes + off, left); |
939 | 0 | (*buf)->size += H2O_HTTP2_FRAME_HEADER_SIZE; |
940 | 0 | if (left <= max_frame_size) { |
941 | 0 | h2o_http2_encode_frame_header((uint8_t *)((*buf)->bytes + off), left, H2O_HTTP2_FRAME_TYPE_CONTINUATION, |
942 | 0 | H2O_HTTP2_FRAME_FLAG_END_HEADERS, stream_id); |
943 | 0 | break; |
944 | 0 | } else { |
945 | 0 | h2o_http2_encode_frame_header((uint8_t *)((*buf)->bytes + off), max_frame_size, H2O_HTTP2_FRAME_TYPE_CONTINUATION, 0, |
946 | 0 | stream_id); |
947 | 0 | off += H2O_HTTP2_FRAME_HEADER_SIZE + max_frame_size; |
948 | 0 | } |
949 | 0 | } |
950 | 0 | } |
951 | | |
952 | | void h2o_hpack_flatten_request(h2o_buffer_t **buf, h2o_hpack_header_table_t *header_table, uint32_t hpack_capacity, |
953 | | uint32_t stream_id, size_t max_frame_size, h2o_iovec_t method, h2o_url_t *url, |
954 | | const h2o_header_t *headers, size_t num_headers, int is_end_stream) |
955 | 0 | { |
956 | 0 | int is_connect = h2o_memis(method.base, method.len, H2O_STRLIT("CONNECT")); |
957 | |
|
958 | 0 | size_t capacity = calc_headers_capacity(headers, num_headers); |
959 | 0 | capacity += H2O_HTTP2_FRAME_HEADER_SIZE; |
960 | 0 | capacity += DYNAMIC_TABLE_SIZE_UPDATE_MAX_SIZE; |
961 | 0 | capacity += calc_capacity(H2O_TOKEN_METHOD->buf.len, method.len); |
962 | 0 | if (!is_connect) |
963 | 0 | capacity += calc_capacity(H2O_TOKEN_SCHEME->buf.len, url->scheme->name.len); |
964 | 0 | capacity += calc_capacity(H2O_TOKEN_AUTHORITY->buf.len, url->authority.len); |
965 | 0 | if (!is_connect) |
966 | 0 | capacity += calc_capacity(H2O_TOKEN_PATH->buf.len, url->path.len); |
967 | |
|
968 | 0 | size_t start_at = (*buf)->size; |
969 | 0 | uint8_t *dst = (void *)(h2o_buffer_reserve(buf, capacity).base + H2O_HTTP2_FRAME_HEADER_SIZE); |
970 | | |
971 | | /* encode */ |
972 | 0 | dst = header_table_adjust_size(header_table, hpack_capacity, dst); |
973 | 0 | dst = encode_method(header_table, dst, method); |
974 | 0 | if (!is_connect) |
975 | 0 | dst = encode_scheme(header_table, dst, url->scheme); |
976 | 0 | dst = encode_header_token(header_table, dst, H2O_TOKEN_AUTHORITY, &url->authority); |
977 | 0 | if (!is_connect) |
978 | 0 | dst = encode_path(header_table, dst, url->path); |
979 | 0 | for (size_t i = 0; i != num_headers; ++i) { |
980 | 0 | const h2o_header_t *header = headers + i; |
981 | 0 | if (header->name == &H2O_TOKEN_ACCEPT_ENCODING->buf && |
982 | 0 | h2o_memis(header->value.base, header->value.len, H2O_STRLIT("gzip, deflate"))) { |
983 | 0 | *dst++ = 0x90; |
984 | 0 | } else { |
985 | 0 | dst = encode_header(header_table, dst, header); |
986 | 0 | } |
987 | 0 | } |
988 | 0 | (*buf)->size = (char *)dst - (*buf)->bytes; |
989 | | |
990 | | /* setup the frame headers */ |
991 | 0 | fixup_frame_headers(buf, start_at, H2O_HTTP2_FRAME_TYPE_HEADERS, stream_id, max_frame_size, |
992 | 0 | is_end_stream ? H2O_HTTP2_FRAME_FLAG_END_STREAM : 0); |
993 | 0 | } |
994 | | |
995 | | void h2o_hpack_flatten_push_promise(h2o_buffer_t **buf, h2o_hpack_header_table_t *header_table, uint32_t hpack_capacity, |
996 | | uint32_t stream_id, size_t max_frame_size, const h2o_url_scheme_t *scheme, |
997 | | h2o_iovec_t authority, h2o_iovec_t method, h2o_iovec_t path, const h2o_header_t *headers, |
998 | | size_t num_headers, uint32_t parent_stream_id) |
999 | 0 | { |
1000 | 0 | size_t capacity = calc_headers_capacity(headers, num_headers); |
1001 | | capacity += H2O_HTTP2_FRAME_HEADER_SIZE /* first frame header */ |
1002 | 0 | + 4; /* promised stream id */ |
1003 | 0 | capacity += DYNAMIC_TABLE_SIZE_UPDATE_MAX_SIZE; |
1004 | 0 | capacity += calc_capacity(H2O_TOKEN_METHOD->buf.len, method.len); |
1005 | 0 | capacity += calc_capacity(H2O_TOKEN_SCHEME->buf.len, scheme->name.len); |
1006 | 0 | capacity += calc_capacity(H2O_TOKEN_AUTHORITY->buf.len, authority.len); |
1007 | 0 | capacity += calc_capacity(H2O_TOKEN_PATH->buf.len, path.len); |
1008 | |
|
1009 | 0 | size_t start_at = (*buf)->size; |
1010 | 0 | uint8_t *dst = (void *)(h2o_buffer_reserve(buf, capacity).base + H2O_HTTP2_FRAME_HEADER_SIZE); |
1011 | | |
1012 | | /* encode */ |
1013 | 0 | dst = h2o_http2_encode32u(dst, stream_id); |
1014 | 0 | dst = header_table_adjust_size(header_table, hpack_capacity, dst); |
1015 | 0 | dst = encode_method(header_table, dst, method); |
1016 | 0 | dst = encode_scheme(header_table, dst, scheme); |
1017 | 0 | dst = encode_header_token(header_table, dst, H2O_TOKEN_AUTHORITY, &authority); |
1018 | 0 | dst = encode_path(header_table, dst, path); |
1019 | 0 | for (size_t i = 0; i != num_headers; ++i) { |
1020 | 0 | const h2o_header_t *header = headers + i; |
1021 | 0 | if (header->name == &H2O_TOKEN_ACCEPT_ENCODING->buf && |
1022 | 0 | h2o_memis(header->value.base, header->value.len, H2O_STRLIT("gzip, deflate"))) { |
1023 | 0 | *dst++ = 0x90; |
1024 | 0 | } else { |
1025 | 0 | dst = encode_header(header_table, dst, header); |
1026 | 0 | } |
1027 | 0 | } |
1028 | 0 | (*buf)->size = (char *)dst - (*buf)->bytes; |
1029 | | |
1030 | | /* setup the frame headers */ |
1031 | 0 | fixup_frame_headers(buf, start_at, H2O_HTTP2_FRAME_TYPE_PUSH_PROMISE, parent_stream_id, max_frame_size, 0); |
1032 | 0 | } |
1033 | | |
1034 | | void h2o_hpack_flatten_response(h2o_buffer_t **buf, h2o_hpack_header_table_t *header_table, uint32_t hpack_capacity, |
1035 | | uint32_t stream_id, size_t max_frame_size, int status, const h2o_header_t *headers, |
1036 | | size_t num_headers, const h2o_iovec_t *server_name, size_t content_length) |
1037 | 8.71k | { |
1038 | 8.71k | size_t capacity = calc_headers_capacity(headers, num_headers); |
1039 | 8.71k | capacity += H2O_HTTP2_FRAME_HEADER_SIZE; /* for the first header */ |
1040 | 8.71k | capacity += DYNAMIC_TABLE_SIZE_UPDATE_MAX_SIZE; |
1041 | 8.71k | capacity += STATUS_HEADER_MAX_SIZE; /* for :status: */ |
1042 | 8.71k | #ifndef H2O_UNITTEST |
1043 | 8.71k | if (server_name != NULL && server_name->len) { |
1044 | 8.71k | capacity += 5 + server_name->len; /* for Server: */ |
1045 | 8.71k | } |
1046 | 8.71k | #endif |
1047 | 8.71k | if (content_length != SIZE_MAX) |
1048 | 7.80k | capacity += CONTENT_LENGTH_HEADER_MAX_SIZE; /* for content-length: UINT64_MAX (with huffman compression applied) */ |
1049 | | |
1050 | 8.71k | size_t start_at = (*buf)->size; |
1051 | 8.71k | uint8_t *dst = (void *)(h2o_buffer_reserve(buf, capacity).base + H2O_HTTP2_FRAME_HEADER_SIZE); /* skip frame header */ |
1052 | | |
1053 | | /* encode */ |
1054 | 8.71k | dst = header_table_adjust_size(header_table, hpack_capacity, dst); |
1055 | 8.71k | dst = encode_status(dst, status); |
1056 | 8.71k | #ifndef H2O_UNITTEST |
1057 | | /* TODO keep some kind of reference to the indexed Server header, and reuse it */ |
1058 | 8.71k | if (server_name != NULL && server_name->len) { |
1059 | 8.71k | dst = encode_header_token(header_table, dst, H2O_TOKEN_SERVER, server_name); |
1060 | 8.71k | } |
1061 | 8.71k | #endif |
1062 | 17.4k | for (size_t i = 0; i != num_headers; ++i) |
1063 | 8.71k | dst = encode_header(header_table, dst, headers + i); |
1064 | 8.71k | if (content_length != SIZE_MAX) |
1065 | 7.80k | dst = encode_content_length(dst, content_length); |
1066 | 8.71k | (*buf)->size = (char *)dst - (*buf)->bytes; |
1067 | | |
1068 | | /* setup the frame headers */ |
1069 | 8.71k | fixup_frame_headers(buf, start_at, H2O_HTTP2_FRAME_TYPE_HEADERS, stream_id, max_frame_size, 0); |
1070 | 8.71k | } |
1071 | | |
1072 | | void h2o_hpack_flatten_trailers(h2o_buffer_t **buf, h2o_hpack_header_table_t *header_table, uint32_t hpack_capacity, |
1073 | | uint32_t stream_id, size_t max_frame_size, const h2o_header_t *headers, size_t num_headers) |
1074 | 0 | { |
1075 | 0 | size_t capacity = calc_headers_capacity(headers, num_headers); |
1076 | 0 | capacity += H2O_HTTP2_FRAME_HEADER_SIZE; |
1077 | 0 | capacity += DYNAMIC_TABLE_SIZE_UPDATE_MAX_SIZE; |
1078 | |
|
1079 | 0 | size_t start_at = (*buf)->size; |
1080 | 0 | uint8_t *dst = (void *)(h2o_buffer_reserve(buf, capacity).base + H2O_HTTP2_FRAME_HEADER_SIZE); /* skip frame header */ |
1081 | |
|
1082 | 0 | dst = header_table_adjust_size(header_table, hpack_capacity, dst); |
1083 | 0 | for (size_t i = 0; i != num_headers; ++i) |
1084 | 0 | dst = encode_header(header_table, dst, headers + i); |
1085 | 0 | (*buf)->size = (char *)dst - (*buf)->bytes; |
1086 | | |
1087 | | /* setup the frame headers */ |
1088 | 0 | fixup_frame_headers(buf, start_at, H2O_HTTP2_FRAME_TYPE_HEADERS, stream_id, max_frame_size, H2O_HTTP2_FRAME_FLAG_END_STREAM); |
1089 | 0 | } |