use http::header::*; use http::*; use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult}; use rand::rngs::StdRng; use rand::seq::SliceRandom; use rand::{Rng, SeedableRng}; use std::collections::HashMap; #[test] fn header_map_fuzz() { fn prop(fuzz: Fuzz) -> TestResult { fuzz.run(); TestResult::from_bool(true) } QuickCheck::new().quickcheck(prop as fn(Fuzz) -> TestResult) } #[derive(Debug, Clone)] #[allow(dead_code)] struct Fuzz { // The magic seed that makes the test case reproducible seed: [u8; 32], // Actions to perform steps: Vec, // Number of steps to drop reduce: usize, } #[derive(Debug)] struct Weight { insert: usize, remove: usize, append: usize, } #[derive(Debug, Clone)] struct Step { action: Action, expect: AltMap, } #[derive(Debug, Clone)] enum Action { Insert { name: HeaderName, // Name to insert val: HeaderValue, // Value to insert old: Option, // Old value }, Append { name: HeaderName, val: HeaderValue, ret: bool, }, Remove { name: HeaderName, // Name to remove val: Option, // Value to get }, } // An alternate implementation of HeaderMap backed by HashMap #[derive(Debug, Clone, Default)] struct AltMap { map: HashMap>, } impl Fuzz { fn new(seed: [u8; 32]) -> Fuzz { // Seed the RNG let mut rng = StdRng::from_seed(seed); let mut steps = vec![]; let mut expect = AltMap::default(); let num = rng.gen_range(5, 500); let weight = Weight { insert: rng.gen_range(1, 10), remove: rng.gen_range(1, 10), append: rng.gen_range(1, 10), }; while steps.len() < num { steps.push(expect.gen_step(&weight, &mut rng)); } Fuzz { seed: seed, steps: steps, reduce: 0, } } fn run(self) { // Create a new header map let mut map = HeaderMap::new(); // Number of steps to perform let take = self.steps.len() - self.reduce; for step in self.steps.into_iter().take(take) { step.action.apply(&mut map); step.expect.assert_identical(&map); } } } impl Arbitrary for Fuzz { fn arbitrary(g: &mut G) -> Self { Fuzz::new(Rng::gen(g)) } } impl AltMap { fn gen_step(&mut self, weight: &Weight, rng: &mut StdRng) -> Step { let action = self.gen_action(weight, rng); Step { action: action, expect: self.clone(), } } /// This will also apply the action against `self` fn gen_action(&mut self, weight: &Weight, rng: &mut StdRng) -> Action { let sum = weight.insert + weight.remove + weight.append; let mut num = rng.gen_range(0, sum); if num < weight.insert { return self.gen_insert(rng); } num -= weight.insert; if num < weight.remove { return self.gen_remove(rng); } num -= weight.remove; if num < weight.append { return self.gen_append(rng); } unreachable!(); } fn gen_insert(&mut self, rng: &mut StdRng) -> Action { let name = self.gen_name(4, rng); let val = gen_header_value(rng); let old = self.insert(name.clone(), val.clone()); Action::Insert { name: name, val: val, old: old, } } fn gen_remove(&mut self, rng: &mut StdRng) -> Action { let name = self.gen_name(-4, rng); let val = self.remove(&name); Action::Remove { name: name, val: val, } } fn gen_append(&mut self, rng: &mut StdRng) -> Action { let name = self.gen_name(-5, rng); let val = gen_header_value(rng); let vals = self.map.entry(name.clone()).or_insert(vec![]); let ret = !vals.is_empty(); vals.push(val.clone()); Action::Append { name: name, val: val, ret: ret, } } /// Negative numbers weigh finding an existing header higher fn gen_name(&self, weight: i32, rng: &mut StdRng) -> HeaderName { let mut existing = rng.gen_ratio(1, weight.abs() as u32); if weight < 0 { existing = !existing; } if existing { // Existing header if let Some(name) = self.find_random_name(rng) { name } else { gen_header_name(rng) } } else { gen_header_name(rng) } } fn find_random_name(&self, rng: &mut StdRng) -> Option { if self.map.is_empty() { None } else { let n = rng.gen_range(0, self.map.len()); self.map.keys().nth(n).map(Clone::clone) } } fn insert(&mut self, name: HeaderName, val: HeaderValue) -> Option { let old = self.map.insert(name, vec![val]); old.and_then(|v| v.into_iter().next()) } fn remove(&mut self, name: &HeaderName) -> Option { self.map.remove(name).and_then(|v| v.into_iter().next()) } fn assert_identical(&self, other: &HeaderMap) { assert_eq!(self.map.len(), other.keys_len()); for (key, val) in &self.map { // Test get assert_eq!(other.get(key), val.get(0)); // Test get_all let vals = other.get_all(key); let actual: Vec<_> = vals.iter().collect(); assert_eq!(&actual[..], &val[..]); } } } impl Action { fn apply(self, map: &mut HeaderMap) { match self { Action::Insert { name, val, old } => { let actual = map.insert(name, val); assert_eq!(actual, old); } Action::Remove { name, val } => { // Just to help track the state, load all associated values. let _ = map.get_all(&name).iter().collect::>(); let actual = map.remove(&name); assert_eq!(actual, val); } Action::Append { name, val, ret } => { assert_eq!(ret, map.append(name, val)); } } } } fn gen_header_name(g: &mut StdRng) -> HeaderName { const STANDARD_HEADERS: &'static [HeaderName] = &[ header::ACCEPT, header::ACCEPT_CHARSET, header::ACCEPT_ENCODING, header::ACCEPT_LANGUAGE, header::ACCEPT_RANGES, header::ACCESS_CONTROL_ALLOW_CREDENTIALS, header::ACCESS_CONTROL_ALLOW_HEADERS, header::ACCESS_CONTROL_ALLOW_METHODS, header::ACCESS_CONTROL_ALLOW_ORIGIN, header::ACCESS_CONTROL_EXPOSE_HEADERS, header::ACCESS_CONTROL_MAX_AGE, header::ACCESS_CONTROL_REQUEST_HEADERS, header::ACCESS_CONTROL_REQUEST_METHOD, header::AGE, header::ALLOW, header::ALT_SVC, header::AUTHORIZATION, header::CACHE_CONTROL, header::CACHE_STATUS, header::CDN_CACHE_CONTROL, header::CONNECTION, header::CONTENT_DISPOSITION, header::CONTENT_ENCODING, header::CONTENT_LANGUAGE, header::CONTENT_LENGTH, header::CONTENT_LOCATION, header::CONTENT_RANGE, header::CONTENT_SECURITY_POLICY, header::CONTENT_SECURITY_POLICY_REPORT_ONLY, header::CONTENT_TYPE, header::COOKIE, header::DNT, header::DATE, header::ETAG, header::EXPECT, header::EXPIRES, header::FORWARDED, header::FROM, header::HOST, header::IF_MATCH, header::IF_MODIFIED_SINCE, header::IF_NONE_MATCH, header::IF_RANGE, header::IF_UNMODIFIED_SINCE, header::LAST_MODIFIED, header::LINK, header::LOCATION, header::MAX_FORWARDS, header::ORIGIN, header::PRAGMA, header::PROXY_AUTHENTICATE, header::PROXY_AUTHORIZATION, header::PUBLIC_KEY_PINS, header::PUBLIC_KEY_PINS_REPORT_ONLY, header::RANGE, header::REFERER, header::REFERRER_POLICY, header::REFRESH, header::RETRY_AFTER, header::SEC_WEBSOCKET_ACCEPT, header::SEC_WEBSOCKET_EXTENSIONS, header::SEC_WEBSOCKET_KEY, header::SEC_WEBSOCKET_PROTOCOL, header::SEC_WEBSOCKET_VERSION, header::SERVER, header::SET_COOKIE, header::STRICT_TRANSPORT_SECURITY, header::TE, header::TRAILER, header::TRANSFER_ENCODING, header::UPGRADE, header::UPGRADE_INSECURE_REQUESTS, header::USER_AGENT, header::VARY, header::VIA, header::WARNING, header::WWW_AUTHENTICATE, header::X_CONTENT_TYPE_OPTIONS, header::X_DNS_PREFETCH_CONTROL, header::X_FRAME_OPTIONS, header::X_XSS_PROTECTION, ]; if g.gen_ratio(1, 2) { STANDARD_HEADERS.choose(g).unwrap().clone() } else { let value = gen_string(g, 1, 25); HeaderName::from_bytes(value.as_bytes()).unwrap() } } fn gen_header_value(g: &mut StdRng) -> HeaderValue { let value = gen_string(g, 0, 70); HeaderValue::from_bytes(value.as_bytes()).unwrap() } fn gen_string(g: &mut StdRng, min: usize, max: usize) -> String { let bytes: Vec<_> = (min..max) .map(|_| { // Chars to pick from b"ABCDEFGHIJKLMNOPQRSTUVabcdefghilpqrstuvwxyz----" .choose(g) .unwrap() .clone() }) .collect(); String::from_utf8(bytes).unwrap() }