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