Line data Source code
1 : #include "fd_hpack.h"
2 : #include "fd_h2_base.h"
3 : #include "fd_hpack_private.h"
4 : #include "nghttp2_hd_huffman.h"
5 : #include "../../util/log/fd_log.h"
6 :
7 : fd_hpack_static_entry_t const
8 : fd_hpack_static_table[ 62 ] = {
9 : [ 1 ] = { ":authority", 10, 0 },
10 : [ 2 ] = { ":method" "GET", 7, 3 },
11 : [ 3 ] = { ":method" "POST", 7, 4 },
12 : [ 4 ] = { ":path" "/", 5, 1 },
13 : [ 5 ] = { ":path" "/index.html", 5, 11 },
14 : [ 6 ] = { ":scheme" "http", 7, 4 },
15 : [ 7 ] = { ":scheme" "https", 7, 5 },
16 : [ 8 ] = { ":status" "200", 7, 3 },
17 : [ 9 ] = { ":status" "204", 7, 3 },
18 : [ 10 ] = { ":status" "206", 7, 3 },
19 : [ 11 ] = { ":status" "304", 7, 3 },
20 : [ 12 ] = { ":status" "400", 7, 3 },
21 : [ 13 ] = { ":status" "404", 7, 3 },
22 : [ 14 ] = { ":status" "500", 7, 3 },
23 : [ 15 ] = { "accept-charset", 14, 0 },
24 : [ 16 ] = { "accept-encoding" "gzip, deflate", 15, 13 },
25 : [ 17 ] = { "accept-language", 15, 0 },
26 : [ 18 ] = { "accept-ranges", 13, 0 },
27 : [ 19 ] = { "accept", 6, 0 },
28 : [ 20 ] = { "access-control-allow-origin", 27, 0 },
29 : [ 21 ] = { "age", 3, 0 },
30 : [ 22 ] = { "allow", 5, 0 },
31 : [ 23 ] = { "authorization", 13, 0 },
32 : [ 24 ] = { "cache-control", 13, 0 },
33 : [ 25 ] = { "content-disposition", 19, 0 },
34 : [ 26 ] = { "content-encoding", 16, 0 },
35 : [ 27 ] = { "content-language", 16, 0 },
36 : [ 28 ] = { "content-length", 14, 0 },
37 : [ 29 ] = { "content-location", 16, 0 },
38 : [ 30 ] = { "content-range", 13, 0 },
39 : [ 31 ] = { "content-type", 12, 0 },
40 : [ 32 ] = { "cookie", 6, 0 },
41 : [ 33 ] = { "date", 4, 0 },
42 : [ 34 ] = { "etag", 4, 0 },
43 : [ 35 ] = { "expect", 6, 0 },
44 : [ 36 ] = { "expires", 7, 0 },
45 : [ 37 ] = { "from", 4, 0 },
46 : [ 38 ] = { "host", 4, 0 },
47 : [ 39 ] = { "if-match", 8, 0 },
48 : [ 40 ] = { "if-modified-since", 17, 0 },
49 : [ 41 ] = { "if-none-match", 13, 0 },
50 : [ 42 ] = { "if-range", 8, 0 },
51 : [ 43 ] = { "if-unmodified-since", 19, 0 },
52 : [ 44 ] = { "last-modified", 13, 0 },
53 : [ 45 ] = { "link", 4, 0 },
54 : [ 46 ] = { "location", 8, 0 },
55 : [ 47 ] = { "max-forwards", 12, 0 },
56 : [ 48 ] = { "proxy-authenticate", 18, 0 },
57 : [ 49 ] = { "proxy-authorization", 19, 0 },
58 : [ 50 ] = { "range", 5, 0 },
59 : [ 51 ] = { "referer", 7, 0 },
60 : [ 52 ] = { "refresh", 7, 0 },
61 : [ 53 ] = { "retry-after", 11, 0 },
62 : [ 54 ] = { "server", 6, 0 },
63 : [ 55 ] = { "set-cookie", 10, 0 },
64 : [ 56 ] = { "strict-transport-security", 25, 0 },
65 : [ 57 ] = { "transfer-encoding", 17, 0 },
66 : [ 58 ] = { "user-agent", 10, 0 },
67 : [ 59 ] = { "vary", 4, 0 },
68 : [ 60 ] = { "via", 3, 0 },
69 : [ 61 ] = { "www-authenticate", 16, 0 }
70 : };
71 :
72 : fd_hpack_rd_t *
73 : fd_hpack_rd_init( fd_hpack_rd_t * rd,
74 : uchar const * src,
75 0 : ulong srcsz ) {
76 0 : *rd = (fd_hpack_rd_t) {
77 0 : .src = src,
78 0 : .src_end = src+srcsz
79 0 : };
80 : /* Skip Dynamic Table Size Updates to zero. Others fail in rd_next. */
81 0 : while( rd->src<rd->src_end && rd->src[0]==0x20 ) rd->src++;
82 0 : return rd;
83 0 : }
84 :
85 : /* fd_hpack_rd_indexed selects a header from HPACK dictionaries.
86 : Currently, only supports the static table. (Pretends that the
87 : dynamic table size is 0). */
88 :
89 : static uint
90 : fd_hpack_rd_indexed( fd_h2_hdr_t * hdr,
91 0 : ulong idx ) {
92 0 : if( FD_UNLIKELY( idx==0 || idx>61 ) ) return FD_H2_ERR_COMPRESSION;
93 0 : fd_hpack_static_entry_t const * entry = &fd_hpack_static_table[ idx ];
94 0 : *hdr = (fd_h2_hdr_t) {
95 0 : .name = entry->entry,
96 0 : .name_len = entry->name_len,
97 0 : .value = entry->entry + entry->name_len,
98 0 : .value_len = entry->value_len,
99 0 : .hint = (ushort)idx | FD_H2_HDR_HINT_NAME_INDEXED,
100 0 : };
101 0 : return FD_H2_SUCCESS;
102 0 : }
103 :
104 : static uint
105 : fd_hpack_rd_next_raw( fd_hpack_rd_t * rd,
106 0 : fd_h2_hdr_t * hdr ) {
107 0 : uchar const * end = rd->src_end;
108 0 : if( FD_UNLIKELY( rd->src >= end ) ) FD_LOG_CRIT(( "fd_hpack_rd_next called out of bounds" ));
109 :
110 0 : uint b0 = *(rd->src++);
111 :
112 0 : if( (b0&0xc0)==0x80 ) {
113 : /* name indexed, value indexed, index in [0,63], varint sz 0 */
114 0 : uint err = fd_hpack_rd_indexed( hdr, b0&0x7f );
115 0 : hdr->hint |= FD_H2_HDR_HINT_VALUE_INDEXED;
116 0 : return err;
117 0 : }
118 :
119 0 : if( b0==0x40 || b0==0x00 || b0==0x10 ) {
120 : /* name literal, value literal */
121 0 : if( FD_UNLIKELY( rd->src+2 > end ) ) return FD_H2_ERR_COMPRESSION;
122 :
123 0 : uint name_word = *(rd->src++);
124 0 : ulong name_len = fd_hpack_rd_varint( rd, name_word, 0x7f );
125 0 : if( FD_UNLIKELY( name_len==ULONG_MAX ) ) return FD_H2_ERR_COMPRESSION;
126 0 : if( FD_UNLIKELY( rd->src+name_len >= end ) ) return FD_H2_ERR_COMPRESSION;
127 0 : uchar const * name_p = rd->src;
128 0 : rd->src += name_len;
129 :
130 0 : uint value_word = *(rd->src++);
131 0 : ulong value_len = fd_hpack_rd_varint( rd, value_word, 0x7f );
132 0 : if( FD_UNLIKELY( value_len==ULONG_MAX ) ) return FD_H2_ERR_COMPRESSION;
133 0 : if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
134 0 : uchar const * value_p = rd->src;
135 0 : rd->src += value_len;
136 :
137 0 : hdr->name = (char const *)name_p;
138 0 : hdr->name_len = (ushort)name_len;
139 0 : hdr->value = (char const *)value_p;
140 0 : hdr->value_len = (uint)value_len;
141 0 : hdr->hint = fd_ushort_if( name_word&0x80, FD_H2_HDR_HINT_NAME_HUFFMAN, 0 ) |
142 0 : fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
143 0 : return FD_H2_SUCCESS;
144 0 : }
145 :
146 0 : if( (b0&0xc0)==0x40 || (b0&0xf0)==0x00 || (b0&0xf0)==0x10 ) {
147 : /* name indexed, value literal */
148 0 : uint name_mask = (b0&0xc0)==0x40 ? 0x3f : 0x0f;
149 0 : ulong name_idx = fd_hpack_rd_varint( rd, b0, name_mask );
150 :
151 0 : if( FD_UNLIKELY( rd->src >= end ) ) return FD_H2_ERR_COMPRESSION;
152 0 : uint value_word = *(rd->src++);
153 0 : ulong value_len = fd_hpack_rd_varint( rd, value_word, 0x7f );
154 0 : if( FD_UNLIKELY( value_len==ULONG_MAX ) ) return FD_H2_ERR_COMPRESSION;
155 0 : if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
156 0 : uchar const * value_p = rd->src;
157 0 : rd->src += value_len;
158 :
159 0 : uint err = fd_hpack_rd_indexed( hdr, name_idx );
160 0 : if( FD_UNLIKELY( err ) ) return FD_H2_ERR_COMPRESSION;
161 0 : hdr->value = (char const *)value_p;
162 0 : hdr->value_len = (uint)value_len;
163 0 : hdr->hint |= fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
164 0 : return FD_H2_SUCCESS;
165 0 : }
166 :
167 0 : if( FD_UNLIKELY( (b0&0xc0)==0xc0 ) ) {
168 : /* name indexed, value indexed, index >=128 */
169 0 : ulong idx = fd_hpack_rd_varint( rd, b0, 0x7f ); /* may fail */
170 0 : return fd_hpack_rd_indexed( hdr, idx );
171 0 : }
172 :
173 : /* FIXME slow */
174 : /* Skip over Dynamic Table Size Updates */
175 0 : while( FD_LIKELY( rd->src < end ) ) {
176 0 : b0 = rd->src[0];
177 0 : if( FD_UNLIKELY( (b0&0xe0)==0x20 ) ) {
178 0 : ulong max_sz = fd_hpack_rd_varint( rd, b0, 0x1f );
179 0 : if( FD_UNLIKELY( max_sz!=0UL ) ) return FD_H2_ERR_COMPRESSION;
180 0 : rd->src++;
181 0 : } else {
182 0 : break;
183 0 : }
184 0 : }
185 :
186 : /* Unknown HPACK instruction */
187 0 : return FD_H2_ERR_COMPRESSION;
188 0 : }
189 :
190 : /* fd_hpack_decoded_sz_max returns an upper bound for the number of
191 : decoded bytes given an arbitrary HPACK Huffman coding of enc_sz
192 : bytes. The smallest HPACK symbol is 5 bits large. Therefore, the
193 : true bound is closer to (enc_sz*8)/5. To defend against possible
194 : bugs in huff_decode_table, we use a more conservative estimate,
195 : namely the greatest amount of bytes that nghttp2_hd_huff_decode can
196 : produce regardless of the content of huff_decode_table. */
197 :
198 : static inline ulong
199 0 : fd_hpack_decoded_sz_max( ulong enc_sz ) {
200 0 : return enc_sz*2UL;
201 0 : }
202 :
203 : uint
204 : fd_hpack_rd_next( fd_hpack_rd_t * hpack_rd,
205 : fd_h2_hdr_t * hdr,
206 : uchar ** scratch,
207 0 : uchar * scratch_end ) {
208 0 : uint err = fd_hpack_rd_next_raw( hpack_rd, hdr );
209 0 : if( FD_UNLIKELY( err ) ) return err;
210 :
211 0 : uchar * scratch_ = *scratch;
212 :
213 0 : if( hdr->hint & FD_H2_HDR_HINT_NAME_HUFFMAN ) {
214 0 : if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->name_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
215 0 : nghttp2_hd_huff_decode_context ctx[1];
216 0 : nghttp2_hd_huff_decode_context_init( ctx );
217 0 : nghttp2_buf buf = { .last = scratch_ };
218 0 : if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->name, hdr->name_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
219 0 : hdr->name = (char const *)scratch_;
220 0 : hdr->name_len = (ushort)( buf.last-scratch_ );
221 0 : scratch_ = buf.last;
222 0 : }
223 :
224 0 : if( hdr->hint & FD_H2_HDR_HINT_VALUE_HUFFMAN ) {
225 0 : if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->value_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
226 0 : nghttp2_hd_huff_decode_context ctx[1];
227 0 : nghttp2_hd_huff_decode_context_init( ctx );
228 0 : nghttp2_buf buf = { .last = scratch_ };
229 0 : if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->value, hdr->value_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
230 0 : hdr->value = (char const *)scratch_;
231 0 : hdr->value_len = (uint)( buf.last-scratch_ );
232 0 : scratch_ = buf.last;
233 0 : }
234 :
235 0 : *scratch = scratch_;
236 0 : hdr->hint &= (ushort)~FD_H2_HDR_HINT_HUFFMAN;
237 0 : return FD_H2_SUCCESS;
238 0 : }
239 :
240 : /* Incremental decoding for fields that need not be retained. Only one
241 : integer or one encoded string is pending at a time. FIELD, NAME_LEN
242 : and VALUE_LEN each read one integer (name index or string length). */
243 :
244 0 : #define FD_HPACK_SKIP_FIELD 0U
245 : #define FD_HPACK_SKIP_NAME_LEN 1U
246 0 : #define FD_HPACK_SKIP_VALUE_LEN 2U
247 0 : #define FD_HPACK_SKIP_NAME 3U
248 : #define FD_HPACK_SKIP_VALUE 4U
249 0 : #define FD_HPACK_SKIP_ERROR 5U
250 :
251 : /* fd_hpack_skip_next[ phase ][ integer==0 ] follows a completed integer */
252 : static uchar const fd_hpack_skip_next[ 3 ][ 2 ] = {
253 : { FD_HPACK_SKIP_VALUE_LEN, FD_HPACK_SKIP_NAME_LEN },
254 : { FD_HPACK_SKIP_NAME, FD_HPACK_SKIP_VALUE_LEN },
255 : { FD_HPACK_SKIP_VALUE, FD_HPACK_SKIP_FIELD }
256 : };
257 :
258 : uint
259 : fd_hpack_skip_feed( fd_hpack_skip_t * skip,
260 : uchar const * src,
261 : ulong srcsz,
262 0 : int fin ) {
263 0 : if( FD_UNLIKELY( skip->phase==FD_HPACK_SKIP_ERROR ) ) return FD_H2_ERR_COMPRESSION;
264 0 : while( srcsz ) {
265 0 : if( skip->phase>=FD_HPACK_SKIP_NAME ) {
266 0 : ulong chunk_sz = fd_ulong_min( srcsz, skip->integer );
267 0 : if( skip->huff_state ) {
268 : /* The decoder emits at most two symbols per encoded octet.
269 : Reuse a small sink while retaining only its decoding state. */
270 0 : uchar sink[ 256 ];
271 0 : nghttp2_hd_huff_decode_context ctx = { .fstate = skip->huff_state };
272 0 : chunk_sz = fd_ulong_min( chunk_sz, sizeof(sink)/2UL );
273 0 : nghttp2_buf buf = { .last = sink };
274 0 : if( FD_UNLIKELY( nghttp2_hd_huff_decode( &ctx, &buf, src, chunk_sz, chunk_sz==skip->integer )<0 ) ) goto fail;
275 : /* A decoded EOS reaches the terminal failure state even before
276 : the announced encoded string length has been consumed. */
277 0 : if( FD_UNLIKELY( (ctx.fstate&0x1ffU)==256U ) ) goto fail;
278 0 : skip->huff_state = ctx.fstate;
279 0 : }
280 0 : src += chunk_sz;
281 0 : srcsz -= chunk_sz;
282 0 : skip->integer -= chunk_sz;
283 0 : if( !skip->integer ) skip->phase = skip->phase==FD_HPACK_SKIP_NAME ? FD_HPACK_SKIP_VALUE_LEN : FD_HPACK_SKIP_FIELD;
284 0 : continue;
285 0 : }
286 :
287 0 : uint byte = *src++;
288 0 : srcsz--;
289 0 : if( skip->int_shift ) {
290 : /* Continuation octet. At most eight, so the shift is at most 49. */
291 0 : skip->integer += (ulong)(byte&0x7fU) << (skip->int_shift-7U);
292 0 : if( FD_UNLIKELY( skip->phase==FD_HPACK_SKIP_FIELD && skip->integer>61UL ) ) goto fail;
293 0 : if( byte&0x80U ) {
294 0 : if( FD_UNLIKELY( skip->int_shift==56U ) ) goto fail;
295 0 : skip->int_shift = (uchar)( skip->int_shift+7U );
296 0 : continue;
297 0 : }
298 0 : } else {
299 0 : uint mask = 0x7fU;
300 0 : if( skip->phase==FD_HPACK_SKIP_FIELD ) {
301 0 : if( (byte&0xe0U)==0x20U ) {
302 0 : if( FD_UNLIKELY( byte!=0x20U || skip->field_seen ) ) goto fail;
303 0 : continue;
304 0 : }
305 0 : skip->field_seen = 1U;
306 : /* Every supported indexed field fits in its seven-bit prefix. */
307 0 : if( byte&0x80U ) {
308 0 : if( FD_UNLIKELY( !(byte&0x7fU) || (byte&0x7fU)>61U ) ) goto fail;
309 0 : continue;
310 0 : }
311 0 : mask = byte&0x40U ? 0x3fU : 0x0fU;
312 0 : if( FD_UNLIKELY( (byte&mask)>61U ) ) goto fail;
313 0 : } else {
314 : /* All Huffman DFA states are nonzero, leaving zero for raw data. */
315 0 : skip->huff_state = byte&0x80U ? NGHTTP2_HUFF_ACCEPTED : 0U;
316 0 : }
317 0 : skip->integer = byte&mask;
318 0 : if( skip->integer==mask ) {
319 0 : skip->int_shift = 7U;
320 0 : continue;
321 0 : }
322 0 : }
323 0 : skip->int_shift = 0U;
324 0 : skip->phase = fd_hpack_skip_next[ skip->phase ][ !skip->integer ];
325 0 : }
326 0 : if( FD_UNLIKELY( fin && ( skip->phase || skip->int_shift ) ) ) goto fail;
327 0 : return FD_H2_SUCCESS;
328 :
329 0 : fail:
330 0 : skip->phase = FD_HPACK_SKIP_ERROR;
331 0 : return FD_H2_ERR_COMPRESSION;
332 0 : }
|