diff options
Diffstat (limited to 'vendor/http/tests/header_map_fuzz.rs')
-rw-r--r-- | vendor/http/tests/header_map_fuzz.rs | 375 |
1 files changed, 375 insertions, 0 deletions
diff --git a/vendor/http/tests/header_map_fuzz.rs b/vendor/http/tests/header_map_fuzz.rs new file mode 100644 index 000000000..68a8604f7 --- /dev/null +++ b/vendor/http/tests/header_map_fuzz.rs @@ -0,0 +1,375 @@ +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<Step>, + + // 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<HeaderValue>, // Old value + }, + Append { + name: HeaderName, + val: HeaderValue, + ret: bool, + }, + Remove { + name: HeaderName, // Name to remove + val: Option<HeaderValue>, // Value to get + }, +} + +// An alternate implementation of HeaderMap backed by HashMap +#[derive(Debug, Clone, Default)] +struct AltMap { + map: HashMap<HeaderName, Vec<HeaderValue>>, +} + +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: Gen>(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<HeaderName> { + 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<HeaderValue> { + let old = self.map.insert(name, vec![val]); + old.and_then(|v| v.into_iter().next()) + } + + fn remove(&mut self, name: &HeaderName) -> Option<HeaderValue> { + self.map.remove(name).and_then(|v| v.into_iter().next()) + } + + fn assert_identical(&self, other: &HeaderMap<HeaderValue>) { + 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<HeaderValue>) { + 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::<Vec<_>>(); + + 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() +} |