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