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 48 : ulong srcsz ) {
76 48 : *rd = (fd_hpack_rd_t) {
77 48 : .src = src,
78 48 : .src_end = src+srcsz
79 48 : };
80 : /* FIXME slow */
81 : /* Skip over Dynamic Table Size Updates */
82 48 : while( FD_LIKELY( rd->src < rd->src_end ) ) {
83 33 : uint b0 = rd->src[0];
84 33 : if( FD_UNLIKELY( (b0&0xe0)==0x20 ) ) {
85 0 : ulong max_sz = fd_hpack_rd_varint( rd, b0, 0x1f );
86 0 : if( FD_UNLIKELY( max_sz!=0UL ) ) break; /* FIXME hacky */
87 0 : rd->src++;
88 33 : } else {
89 33 : break;
90 33 : }
91 33 : }
92 48 : return rd;
93 48 : }
94 :
95 : /* fd_hpack_rd_indexed selects a header from HPACK dictionaries.
96 : Currently, only supports the static table. (Pretends that the
97 : dynamic table size is 0). */
98 :
99 : static uint
100 : fd_hpack_rd_indexed( fd_h2_hdr_t * hdr,
101 129 : ulong idx ) {
102 129 : if( FD_UNLIKELY( idx==0 || idx>61 ) ) return FD_H2_ERR_COMPRESSION;
103 129 : fd_hpack_static_entry_t const * entry = &fd_hpack_static_table[ idx ];
104 129 : *hdr = (fd_h2_hdr_t) {
105 129 : .name = entry->entry,
106 129 : .name_len = entry->name_len,
107 129 : .value = entry->entry + entry->name_len,
108 129 : .value_len = entry->value_len,
109 129 : .hint = (ushort)idx | FD_H2_HDR_HINT_NAME_INDEXED,
110 129 : };
111 129 : return FD_H2_SUCCESS;
112 129 : }
113 :
114 : static uint
115 : fd_hpack_rd_next_raw( fd_hpack_rd_t * rd,
116 144 : fd_h2_hdr_t * hdr ) {
117 144 : uchar const * end = rd->src_end;
118 144 : if( FD_UNLIKELY( rd->src >= end ) ) FD_LOG_CRIT(( "fd_hpack_rd_next called out of bounds" ));
119 :
120 144 : uint b0 = *(rd->src++);
121 :
122 144 : if( (b0&0xc0)==0x80 ) {
123 : /* name indexed, value indexed, index in [0,63], varint sz 0 */
124 66 : uint err = fd_hpack_rd_indexed( hdr, b0&0x7f );
125 66 : hdr->hint |= FD_H2_HDR_HINT_VALUE_INDEXED;
126 66 : return err;
127 66 : }
128 :
129 78 : if( b0==0x40 || b0==0x00 || b0==0x10 ) {
130 : /* name literal, value literal */
131 12 : if( FD_UNLIKELY( rd->src+2 > end ) ) return FD_H2_ERR_COMPRESSION;
132 :
133 12 : uint name_word = *(rd->src++);
134 12 : ulong name_len = fd_hpack_rd_varint( rd, name_word, 0x7f );
135 12 : if( FD_UNLIKELY( name_len==ULONG_MAX ) ) return FD_H2_ERR_COMPRESSION;
136 12 : if( FD_UNLIKELY( rd->src+name_len >= end ) ) return FD_H2_ERR_COMPRESSION;
137 12 : uchar const * name_p = rd->src;
138 12 : rd->src += name_len;
139 :
140 12 : uint value_word = *(rd->src++);
141 12 : ulong value_len = fd_hpack_rd_varint( rd, value_word, 0x7f );
142 12 : if( FD_UNLIKELY( value_len==ULONG_MAX ) ) return FD_H2_ERR_COMPRESSION;
143 12 : if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
144 12 : uchar const * value_p = rd->src;
145 12 : rd->src += value_len;
146 :
147 12 : hdr->name = (char const *)name_p;
148 12 : hdr->name_len = (ushort)name_len;
149 12 : hdr->value = (char const *)value_p;
150 12 : hdr->value_len = (uint)value_len;
151 12 : hdr->hint = fd_ushort_if( name_word&0x80, FD_H2_HDR_HINT_NAME_HUFFMAN, 0 ) |
152 12 : fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
153 12 : return FD_H2_SUCCESS;
154 12 : }
155 :
156 66 : if( (b0&0xc0)==0x40 || (b0&0xf0)==0x00 || (b0&0xf0)==0x10 ) {
157 : /* name indexed, value literal */
158 66 : uint name_mask = (b0&0xc0)==0x40 ? 0x3f : 0x0f;
159 66 : ulong name_idx = fd_hpack_rd_varint( rd, b0, name_mask );
160 :
161 66 : if( FD_UNLIKELY( rd->src >= end ) ) return FD_H2_ERR_COMPRESSION;
162 66 : uint value_word = *(rd->src++);
163 66 : ulong value_len = fd_hpack_rd_varint( rd, value_word, 0x7f );
164 66 : if( FD_UNLIKELY( value_len==ULONG_MAX ) ) return FD_H2_ERR_COMPRESSION;
165 66 : if( FD_UNLIKELY( rd->src+value_len > end ) ) return FD_H2_ERR_COMPRESSION;
166 63 : uchar const * value_p = rd->src;
167 63 : rd->src += value_len;
168 :
169 63 : uint err = fd_hpack_rd_indexed( hdr, name_idx );
170 63 : if( FD_UNLIKELY( err ) ) return FD_H2_ERR_COMPRESSION;
171 63 : hdr->value = (char const *)value_p;
172 63 : hdr->value_len = (uint)value_len;
173 63 : hdr->hint |= fd_ushort_if( value_word&0x80, FD_H2_HDR_HINT_VALUE_HUFFMAN, 0 );
174 63 : return FD_H2_SUCCESS;
175 63 : }
176 :
177 0 : if( FD_UNLIKELY( (b0&0xc0)==0xc0 ) ) {
178 : /* name indexed, value indexed, index >=128 */
179 0 : ulong idx = fd_hpack_rd_varint( rd, b0, 0x7f ); /* may fail */
180 0 : return fd_hpack_rd_indexed( hdr, idx );
181 0 : }
182 :
183 : /* FIXME slow */
184 : /* Skip over Dynamic Table Size Updates */
185 0 : while( FD_LIKELY( rd->src < end ) ) {
186 0 : b0 = rd->src[0];
187 0 : if( FD_UNLIKELY( (b0&0xe0)==0x20 ) ) {
188 0 : ulong max_sz = fd_hpack_rd_varint( rd, b0, 0x1f );
189 0 : if( FD_UNLIKELY( max_sz!=0UL ) ) return FD_H2_ERR_COMPRESSION;
190 0 : rd->src++;
191 0 : } else {
192 0 : break;
193 0 : }
194 0 : }
195 :
196 : /* Unknown HPACK instruction */
197 0 : return FD_H2_ERR_COMPRESSION;
198 0 : }
199 :
200 : /* fd_hpack_decoded_sz_max returns an upper bound for the number of
201 : decoded bytes given an arbitrary HPACK Huffman coding of enc_sz
202 : bytes. The smallest HPACK symbol is 5 bits large. Therefore, the
203 : true bound is closer to (enc_sz*8)/5. To defend against possible
204 : bugs in huff_decode_table, we use a more conservative estimate,
205 : namely the greatest amount of bytes that nghttp2_hd_huff_decode can
206 : produce regardless of the content of huff_decode_table. */
207 :
208 : static inline ulong
209 24 : fd_hpack_decoded_sz_max( ulong enc_sz ) {
210 24 : return enc_sz*2UL;
211 24 : }
212 :
213 : uint
214 : fd_hpack_rd_next( fd_hpack_rd_t * hpack_rd,
215 : fd_h2_hdr_t * hdr,
216 : uchar ** scratch,
217 144 : uchar * scratch_end ) {
218 144 : uint err = fd_hpack_rd_next_raw( hpack_rd, hdr );
219 144 : if( FD_UNLIKELY( err ) ) return err;
220 :
221 141 : uchar * scratch_ = *scratch;
222 :
223 141 : if( hdr->hint & FD_H2_HDR_HINT_NAME_HUFFMAN ) {
224 3 : if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->name_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
225 3 : nghttp2_hd_huff_decode_context ctx[1];
226 3 : nghttp2_hd_huff_decode_context_init( ctx );
227 3 : nghttp2_buf buf = { .last = scratch_ };
228 3 : if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->name, hdr->name_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
229 3 : hdr->name = (char const *)scratch_;
230 3 : hdr->name_len = (ushort)( buf.last-scratch_ );
231 3 : scratch_ = buf.last;
232 3 : }
233 :
234 141 : if( hdr->hint & FD_H2_HDR_HINT_VALUE_HUFFMAN ) {
235 21 : if( FD_UNLIKELY( scratch_+fd_hpack_decoded_sz_max( hdr->value_len )>scratch_end ) ) return FD_H2_ERR_COMPRESSION;
236 21 : nghttp2_hd_huff_decode_context ctx[1];
237 21 : nghttp2_hd_huff_decode_context_init( ctx );
238 21 : nghttp2_buf buf = { .last = scratch_ };
239 21 : if( FD_UNLIKELY( nghttp2_hd_huff_decode( ctx, &buf, (uchar const *)hdr->value, hdr->value_len, 1 )<0 ) ) return FD_H2_ERR_COMPRESSION;
240 21 : hdr->value = (char const *)scratch_;
241 21 : hdr->value_len = (ushort)( buf.last-scratch_ );
242 21 : scratch_ = buf.last;
243 21 : }
244 :
245 141 : *scratch = scratch_;
246 141 : hdr->hint &= (ushort)~FD_H2_HDR_HINT_HUFFMAN;
247 141 : return FD_H2_SUCCESS;
248 141 : }
|