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