Source code
Revision control
Copy as Markdown
Other Tools
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()
}