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