1 use crate::hpack::{Decoder, Encoder, Header};
2 
3 use http::header::{HeaderName, HeaderValue};
4 
5 use bytes::BytesMut;
6 use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult};
7 use rand::distributions::Slice;
8 use rand::rngs::StdRng;
9 use rand::{thread_rng, Rng, SeedableRng};
10 
11 use std::io::Cursor;
12 
13 const MAX_CHUNK: usize = 2 * 1024;
14 
15 #[test]
hpack_fuzz()16 fn hpack_fuzz() {
17     let _ = env_logger::try_init();
18     fn prop(fuzz: FuzzHpack) -> TestResult {
19         fuzz.run();
20         TestResult::from_bool(true)
21     }
22 
23     QuickCheck::new()
24         .tests(100)
25         .quickcheck(prop as fn(FuzzHpack) -> TestResult)
26 }
27 
28 /*
29 // If wanting to test with a specific feed, uncomment and fill in the seed.
30 #[test]
31 fn hpack_fuzz_seeded() {
32     let _ = env_logger::try_init();
33     let seed = [/* fill me in*/];
34     FuzzHpack::new(seed).run();
35 }
36 */
37 
38 #[derive(Debug, Clone)]
39 struct FuzzHpack {
40     // The set of headers to encode / decode
41     frames: Vec<HeaderFrame>,
42 }
43 
44 #[derive(Debug, Clone)]
45 struct HeaderFrame {
46     resizes: Vec<usize>,
47     headers: Vec<Header<Option<HeaderName>>>,
48 }
49 
50 impl FuzzHpack {
new(seed: [u8; 32]) -> FuzzHpack51     fn new(seed: [u8; 32]) -> FuzzHpack {
52         // Seed the RNG
53         let mut rng = StdRng::from_seed(seed);
54 
55         // Generates a bunch of source headers
56         let mut source: Vec<Header<Option<HeaderName>>> = vec![];
57 
58         for _ in 0..2000 {
59             source.push(gen_header(&mut rng));
60         }
61 
62         // Actual test run headers
63         let num: usize = rng.gen_range(40..500);
64 
65         let mut frames: Vec<HeaderFrame> = vec![];
66         let mut added = 0;
67 
68         let skew: i32 = rng.gen_range(1..5);
69 
70         // Rough number of headers to add
71         while added < num {
72             let mut frame = HeaderFrame {
73                 resizes: vec![],
74                 headers: vec![],
75             };
76 
77             match rng.gen_range(0..20) {
78                 0 => {
79                     // Two resizes
80                     let high = rng.gen_range(128..MAX_CHUNK * 2);
81                     let low = rng.gen_range(0..high);
82 
83                     frame.resizes.extend([low, high]);
84                 }
85                 1..=3 => {
86                     frame.resizes.push(rng.gen_range(128..MAX_CHUNK * 2));
87                 }
88                 _ => {}
89             }
90 
91             let mut is_name_required = true;
92 
93             for _ in 0..rng.gen_range(1..(num - added) + 1) {
94                 let x: f64 = rng.gen_range(0.0..1.0);
95                 let x = x.powi(skew);
96 
97                 let i = (x * source.len() as f64) as usize;
98 
99                 let header = &source[i];
100                 match header {
101                     Header::Field { name: None, .. } => {
102                         if is_name_required {
103                             continue;
104                         }
105                     }
106                     Header::Field { .. } => {
107                         is_name_required = false;
108                     }
109                     _ => {
110                         // pseudos can't be followed by a header with no name
111                         is_name_required = true;
112                     }
113                 }
114 
115                 frame.headers.push(header.clone());
116 
117                 added += 1;
118             }
119 
120             frames.push(frame);
121         }
122 
123         FuzzHpack { frames }
124     }
125 
run(self)126     fn run(self) {
127         let frames = self.frames;
128         let mut expect = vec![];
129 
130         let mut encoder = Encoder::default();
131         let mut decoder = Decoder::default();
132 
133         for frame in frames {
134             // build "expected" frames, such that decoding headers always
135             // includes a name
136             let mut prev_name = None;
137             for header in &frame.headers {
138                 match header.clone().reify() {
139                     Ok(h) => {
140                         prev_name = match h {
141                             Header::Field { ref name, .. } => Some(name.clone()),
142                             _ => None,
143                         };
144                         expect.push(h);
145                     }
146                     Err(value) => {
147                         expect.push(Header::Field {
148                             name: prev_name.as_ref().cloned().expect("previous header name"),
149                             value,
150                         });
151                     }
152                 }
153             }
154 
155             let mut buf = BytesMut::new();
156 
157             if let Some(max) = frame.resizes.iter().max() {
158                 decoder.queue_size_update(*max);
159             }
160 
161             // Apply resizes
162             for resize in &frame.resizes {
163                 encoder.update_max_size(*resize);
164             }
165 
166             encoder.encode(frame.headers, &mut buf);
167 
168             // Decode the chunk!
169             decoder
170                 .decode(&mut Cursor::new(&mut buf), |h| {
171                     let e = expect.remove(0);
172                     assert_eq!(h, e);
173                 })
174                 .expect("full decode");
175         }
176 
177         assert_eq!(0, expect.len());
178     }
179 }
180 
181 impl Arbitrary for FuzzHpack {
arbitrary(_: &mut Gen) -> Self182     fn arbitrary(_: &mut Gen) -> Self {
183         FuzzHpack::new(thread_rng().gen())
184     }
185 }
186 
gen_header(g: &mut StdRng) -> Header<Option<HeaderName>>187 fn gen_header(g: &mut StdRng) -> Header<Option<HeaderName>> {
188     use http::{Method, StatusCode};
189 
190     if g.gen_ratio(1, 10) {
191         match g.gen_range(0u32..5) {
192             0 => {
193                 let value = gen_string(g, 4, 20);
194                 Header::Authority(to_shared(value))
195             }
196             1 => {
197                 let method = match g.gen_range(0u32..6) {
198                     0 => Method::GET,
199                     1 => Method::POST,
200                     2 => Method::PUT,
201                     3 => Method::PATCH,
202                     4 => Method::DELETE,
203                     5 => {
204                         let n: usize = g.gen_range(3..7);
205                         let bytes: Vec<u8> = (0..n)
206                             .map(|_| *g.sample(Slice::new(b"ABCDEFGHIJKLMNOPQRSTUVWXYZ").unwrap()))
207                             .collect();
208 
209                         Method::from_bytes(&bytes).unwrap()
210                     }
211                     _ => unreachable!(),
212                 };
213 
214                 Header::Method(method)
215             }
216             2 => {
217                 let value = match g.gen_range(0u32..2) {
218                     0 => "http",
219                     1 => "https",
220                     _ => unreachable!(),
221                 };
222 
223                 Header::Scheme(to_shared(value.to_string()))
224             }
225             3 => {
226                 let value = match g.gen_range(0u32..100) {
227                     0 => "/".to_string(),
228                     1 => "/index.html".to_string(),
229                     _ => gen_string(g, 2, 20),
230                 };
231 
232                 Header::Path(to_shared(value))
233             }
234             4 => {
235                 let status = (g.gen::<u16>() % 500) + 100;
236 
237                 Header::Status(StatusCode::from_u16(status).unwrap())
238             }
239             _ => unreachable!(),
240         }
241     } else {
242         let name = if g.gen_ratio(1, 10) {
243             None
244         } else {
245             Some(gen_header_name(g))
246         };
247         let mut value = gen_header_value(g);
248 
249         if g.gen_ratio(1, 30) {
250             value.set_sensitive(true);
251         }
252 
253         Header::Field { name, value }
254     }
255 }
256 
gen_header_name(g: &mut StdRng) -> HeaderName257 fn gen_header_name(g: &mut StdRng) -> HeaderName {
258     use http::header;
259 
260     if g.gen_ratio(1, 2) {
261         g.sample(
262             Slice::new(&[
263                 header::ACCEPT,
264                 header::ACCEPT_CHARSET,
265                 header::ACCEPT_ENCODING,
266                 header::ACCEPT_LANGUAGE,
267                 header::ACCEPT_RANGES,
268                 header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
269                 header::ACCESS_CONTROL_ALLOW_HEADERS,
270                 header::ACCESS_CONTROL_ALLOW_METHODS,
271                 header::ACCESS_CONTROL_ALLOW_ORIGIN,
272                 header::ACCESS_CONTROL_EXPOSE_HEADERS,
273                 header::ACCESS_CONTROL_MAX_AGE,
274                 header::ACCESS_CONTROL_REQUEST_HEADERS,
275                 header::ACCESS_CONTROL_REQUEST_METHOD,
276                 header::AGE,
277                 header::ALLOW,
278                 header::ALT_SVC,
279                 header::AUTHORIZATION,
280                 header::CACHE_CONTROL,
281                 header::CONNECTION,
282                 header::CONTENT_DISPOSITION,
283                 header::CONTENT_ENCODING,
284                 header::CONTENT_LANGUAGE,
285                 header::CONTENT_LENGTH,
286                 header::CONTENT_LOCATION,
287                 header::CONTENT_RANGE,
288                 header::CONTENT_SECURITY_POLICY,
289                 header::CONTENT_SECURITY_POLICY_REPORT_ONLY,
290                 header::CONTENT_TYPE,
291                 header::COOKIE,
292                 header::DNT,
293                 header::DATE,
294                 header::ETAG,
295                 header::EXPECT,
296                 header::EXPIRES,
297                 header::FORWARDED,
298                 header::FROM,
299                 header::HOST,
300                 header::IF_MATCH,
301                 header::IF_MODIFIED_SINCE,
302                 header::IF_NONE_MATCH,
303                 header::IF_RANGE,
304                 header::IF_UNMODIFIED_SINCE,
305                 header::LAST_MODIFIED,
306                 header::LINK,
307                 header::LOCATION,
308                 header::MAX_FORWARDS,
309                 header::ORIGIN,
310                 header::PRAGMA,
311                 header::PROXY_AUTHENTICATE,
312                 header::PROXY_AUTHORIZATION,
313                 header::PUBLIC_KEY_PINS,
314                 header::PUBLIC_KEY_PINS_REPORT_ONLY,
315                 header::RANGE,
316                 header::REFERER,
317                 header::REFERRER_POLICY,
318                 header::REFRESH,
319                 header::RETRY_AFTER,
320                 header::SERVER,
321                 header::SET_COOKIE,
322                 header::STRICT_TRANSPORT_SECURITY,
323                 header::TE,
324                 header::TRAILER,
325                 header::TRANSFER_ENCODING,
326                 header::USER_AGENT,
327                 header::UPGRADE,
328                 header::UPGRADE_INSECURE_REQUESTS,
329                 header::VARY,
330                 header::VIA,
331                 header::WARNING,
332                 header::WWW_AUTHENTICATE,
333                 header::X_CONTENT_TYPE_OPTIONS,
334                 header::X_DNS_PREFETCH_CONTROL,
335                 header::X_FRAME_OPTIONS,
336                 header::X_XSS_PROTECTION,
337             ])
338             .unwrap(),
339         )
340         .clone()
341     } else {
342         let value = gen_string(g, 1, 25);
343         HeaderName::from_bytes(value.as_bytes()).unwrap()
344     }
345 }
346 
gen_header_value(g: &mut StdRng) -> HeaderValue347 fn gen_header_value(g: &mut StdRng) -> HeaderValue {
348     let value = gen_string(g, 0, 70);
349     HeaderValue::from_bytes(value.as_bytes()).unwrap()
350 }
351 
gen_string(g: &mut StdRng, min: usize, max: usize) -> String352 fn gen_string(g: &mut StdRng, min: usize, max: usize) -> String {
353     let bytes: Vec<_> = (min..max)
354         .map(|_| {
355             // Chars to pick from
356             *g.sample(Slice::new(b"ABCDEFGHIJKLMNOPQRSTUVabcdefghilpqrstuvwxyz----").unwrap())
357         })
358         .collect();
359 
360     String::from_utf8(bytes).unwrap()
361 }
362 
to_shared(src: String) -> crate::hpack::BytesStr363 fn to_shared(src: String) -> crate::hpack::BytesStr {
364     crate::hpack::BytesStr::from(src.as_str())
365 }
366