| 1 | use http::header::*; |
| 2 | use http::*; |
| 3 | |
| 4 | use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult}; |
| 5 | use rand::rngs::StdRng; |
| 6 | use rand::seq::SliceRandom; |
| 7 | use rand::{Rng, SeedableRng}; |
| 8 | |
| 9 | use std::collections::HashMap; |
| 10 | |
| 11 | #[cfg (not(miri))] |
| 12 | #[test] |
| 13 | fn header_map_fuzz() { |
| 14 | fn prop(fuzz: Fuzz) -> TestResult { |
| 15 | fuzz.run(); |
| 16 | TestResult::from_bool(true) |
| 17 | } |
| 18 | |
| 19 | QuickCheck::new().quickcheck(prop as fn(Fuzz) -> TestResult) |
| 20 | } |
| 21 | |
| 22 | #[derive(Debug, Clone)] |
| 23 | #[allow (dead_code)] |
| 24 | struct Fuzz { |
| 25 | // The magic seed that makes the test case reproducible |
| 26 | seed: [u8; 32], |
| 27 | |
| 28 | // Actions to perform |
| 29 | steps: Vec<Step>, |
| 30 | |
| 31 | // Number of steps to drop |
| 32 | reduce: usize, |
| 33 | } |
| 34 | |
| 35 | #[derive(Debug)] |
| 36 | struct Weight { |
| 37 | insert: usize, |
| 38 | remove: usize, |
| 39 | append: usize, |
| 40 | } |
| 41 | |
| 42 | #[derive(Debug, Clone)] |
| 43 | struct Step { |
| 44 | action: Action, |
| 45 | expect: AltMap, |
| 46 | } |
| 47 | |
| 48 | #[derive(Debug, Clone)] |
| 49 | enum Action { |
| 50 | Insert { |
| 51 | name: HeaderName, // Name to insert |
| 52 | val: HeaderValue, // Value to insert |
| 53 | old: Option<HeaderValue>, // Old value |
| 54 | }, |
| 55 | Append { |
| 56 | name: HeaderName, |
| 57 | val: HeaderValue, |
| 58 | ret: bool, |
| 59 | }, |
| 60 | Remove { |
| 61 | name: HeaderName, // Name to remove |
| 62 | val: Option<HeaderValue>, // Value to get |
| 63 | }, |
| 64 | } |
| 65 | |
| 66 | // An alternate implementation of HeaderMap backed by HashMap |
| 67 | #[derive(Debug, Clone, Default)] |
| 68 | struct AltMap { |
| 69 | map: HashMap<HeaderName, Vec<HeaderValue>>, |
| 70 | } |
| 71 | |
| 72 | impl Fuzz { |
| 73 | fn new(seed: [u8; 32]) -> Fuzz { |
| 74 | // Seed the RNG |
| 75 | let mut rng = StdRng::from_seed(seed); |
| 76 | |
| 77 | let mut steps = vec![]; |
| 78 | let mut expect = AltMap::default(); |
| 79 | let num = rng.gen_range(5, 500); |
| 80 | |
| 81 | let weight = Weight { |
| 82 | insert: rng.gen_range(1, 10), |
| 83 | remove: rng.gen_range(1, 10), |
| 84 | append: rng.gen_range(1, 10), |
| 85 | }; |
| 86 | |
| 87 | while steps.len() < num { |
| 88 | steps.push(expect.gen_step(&weight, &mut rng)); |
| 89 | } |
| 90 | |
| 91 | Fuzz { |
| 92 | seed: seed, |
| 93 | steps: steps, |
| 94 | reduce: 0, |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | fn run(self) { |
| 99 | // Create a new header map |
| 100 | let mut map = HeaderMap::new(); |
| 101 | |
| 102 | // Number of steps to perform |
| 103 | let take = self.steps.len() - self.reduce; |
| 104 | |
| 105 | for step in self.steps.into_iter().take(take) { |
| 106 | step.action.apply(&mut map); |
| 107 | |
| 108 | step.expect.assert_identical(&map); |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | impl Arbitrary for Fuzz { |
| 114 | fn arbitrary<G: Gen>(g: &mut G) -> Self { |
| 115 | Fuzz::new(Rng::gen(g)) |
| 116 | } |
| 117 | } |
| 118 | |
| 119 | impl AltMap { |
| 120 | fn gen_step(&mut self, weight: &Weight, rng: &mut StdRng) -> Step { |
| 121 | let action = self.gen_action(weight, rng); |
| 122 | |
| 123 | Step { |
| 124 | action: action, |
| 125 | expect: self.clone(), |
| 126 | } |
| 127 | } |
| 128 | |
| 129 | /// This will also apply the action against `self` |
| 130 | fn gen_action(&mut self, weight: &Weight, rng: &mut StdRng) -> Action { |
| 131 | let sum = weight.insert + weight.remove + weight.append; |
| 132 | |
| 133 | let mut num = rng.gen_range(0, sum); |
| 134 | |
| 135 | if num < weight.insert { |
| 136 | return self.gen_insert(rng); |
| 137 | } |
| 138 | |
| 139 | num -= weight.insert; |
| 140 | |
| 141 | if num < weight.remove { |
| 142 | return self.gen_remove(rng); |
| 143 | } |
| 144 | |
| 145 | num -= weight.remove; |
| 146 | |
| 147 | if num < weight.append { |
| 148 | return self.gen_append(rng); |
| 149 | } |
| 150 | |
| 151 | unreachable!(); |
| 152 | } |
| 153 | |
| 154 | fn gen_insert(&mut self, rng: &mut StdRng) -> Action { |
| 155 | let name = self.gen_name(4, rng); |
| 156 | let val = gen_header_value(rng); |
| 157 | let old = self.insert(name.clone(), val.clone()); |
| 158 | |
| 159 | Action::Insert { |
| 160 | name: name, |
| 161 | val: val, |
| 162 | old: old, |
| 163 | } |
| 164 | } |
| 165 | |
| 166 | fn gen_remove(&mut self, rng: &mut StdRng) -> Action { |
| 167 | let name = self.gen_name(-4, rng); |
| 168 | let val = self.remove(&name); |
| 169 | |
| 170 | Action::Remove { |
| 171 | name: name, |
| 172 | val: val, |
| 173 | } |
| 174 | } |
| 175 | |
| 176 | fn gen_append(&mut self, rng: &mut StdRng) -> Action { |
| 177 | let name = self.gen_name(-5, rng); |
| 178 | let val = gen_header_value(rng); |
| 179 | |
| 180 | let vals = self.map.entry(name.clone()).or_insert(vec![]); |
| 181 | |
| 182 | let ret = !vals.is_empty(); |
| 183 | vals.push(val.clone()); |
| 184 | |
| 185 | Action::Append { |
| 186 | name: name, |
| 187 | val: val, |
| 188 | ret: ret, |
| 189 | } |
| 190 | } |
| 191 | |
| 192 | /// Negative numbers weigh finding an existing header higher |
| 193 | fn gen_name(&self, weight: i32, rng: &mut StdRng) -> HeaderName { |
| 194 | let mut existing = rng.gen_ratio(1, weight.abs() as u32); |
| 195 | |
| 196 | if weight < 0 { |
| 197 | existing = !existing; |
| 198 | } |
| 199 | |
| 200 | if existing { |
| 201 | // Existing header |
| 202 | if let Some(name) = self.find_random_name(rng) { |
| 203 | name |
| 204 | } else { |
| 205 | gen_header_name(rng) |
| 206 | } |
| 207 | } else { |
| 208 | gen_header_name(rng) |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | fn find_random_name(&self, rng: &mut StdRng) -> Option<HeaderName> { |
| 213 | if self.map.is_empty() { |
| 214 | None |
| 215 | } else { |
| 216 | let n = rng.gen_range(0, self.map.len()); |
| 217 | self.map.keys().nth(n).map(Clone::clone) |
| 218 | } |
| 219 | } |
| 220 | |
| 221 | fn insert(&mut self, name: HeaderName, val: HeaderValue) -> Option<HeaderValue> { |
| 222 | let old = self.map.insert(name, vec![val]); |
| 223 | old.and_then(|v| v.into_iter().next()) |
| 224 | } |
| 225 | |
| 226 | fn remove(&mut self, name: &HeaderName) -> Option<HeaderValue> { |
| 227 | self.map.remove(name).and_then(|v| v.into_iter().next()) |
| 228 | } |
| 229 | |
| 230 | fn assert_identical(&self, other: &HeaderMap<HeaderValue>) { |
| 231 | assert_eq!(self.map.len(), other.keys_len()); |
| 232 | |
| 233 | for (key, val) in &self.map { |
| 234 | // Test get |
| 235 | assert_eq!(other.get(key), val.get(0)); |
| 236 | |
| 237 | // Test get_all |
| 238 | let vals = other.get_all(key); |
| 239 | let actual: Vec<_> = vals.iter().collect(); |
| 240 | assert_eq!(&actual[..], &val[..]); |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | |
| 245 | impl Action { |
| 246 | fn apply(self, map: &mut HeaderMap<HeaderValue>) { |
| 247 | match self { |
| 248 | Action::Insert { name, val, old } => { |
| 249 | let actual = map.insert(name, val); |
| 250 | assert_eq!(actual, old); |
| 251 | } |
| 252 | Action::Remove { name, val } => { |
| 253 | // Just to help track the state, load all associated values. |
| 254 | let _ = map.get_all(&name).iter().collect::<Vec<_>>(); |
| 255 | |
| 256 | let actual = map.remove(&name); |
| 257 | assert_eq!(actual, val); |
| 258 | } |
| 259 | Action::Append { name, val, ret } => { |
| 260 | assert_eq!(ret, map.append(name, val)); |
| 261 | } |
| 262 | } |
| 263 | } |
| 264 | } |
| 265 | |
| 266 | fn gen_header_name(g: &mut StdRng) -> HeaderName { |
| 267 | const STANDARD_HEADERS: &'static [HeaderName] = &[ |
| 268 | header::ACCEPT, |
| 269 | header::ACCEPT_CHARSET, |
| 270 | header::ACCEPT_ENCODING, |
| 271 | header::ACCEPT_LANGUAGE, |
| 272 | header::ACCEPT_RANGES, |
| 273 | header::ACCESS_CONTROL_ALLOW_CREDENTIALS, |
| 274 | header::ACCESS_CONTROL_ALLOW_HEADERS, |
| 275 | header::ACCESS_CONTROL_ALLOW_METHODS, |
| 276 | header::ACCESS_CONTROL_ALLOW_ORIGIN, |
| 277 | header::ACCESS_CONTROL_EXPOSE_HEADERS, |
| 278 | header::ACCESS_CONTROL_MAX_AGE, |
| 279 | header::ACCESS_CONTROL_REQUEST_HEADERS, |
| 280 | header::ACCESS_CONTROL_REQUEST_METHOD, |
| 281 | header::AGE, |
| 282 | header::ALLOW, |
| 283 | header::ALT_SVC, |
| 284 | header::AUTHORIZATION, |
| 285 | header::CACHE_CONTROL, |
| 286 | header::CACHE_STATUS, |
| 287 | header::CDN_CACHE_CONTROL, |
| 288 | header::CONNECTION, |
| 289 | header::CONTENT_DISPOSITION, |
| 290 | header::CONTENT_ENCODING, |
| 291 | header::CONTENT_LANGUAGE, |
| 292 | header::CONTENT_LENGTH, |
| 293 | header::CONTENT_LOCATION, |
| 294 | header::CONTENT_RANGE, |
| 295 | header::CONTENT_SECURITY_POLICY, |
| 296 | header::CONTENT_SECURITY_POLICY_REPORT_ONLY, |
| 297 | header::CONTENT_TYPE, |
| 298 | header::COOKIE, |
| 299 | header::DNT, |
| 300 | header::DATE, |
| 301 | header::ETAG, |
| 302 | header::EXPECT, |
| 303 | header::EXPIRES, |
| 304 | header::FORWARDED, |
| 305 | header::FROM, |
| 306 | header::HOST, |
| 307 | header::IF_MATCH, |
| 308 | header::IF_MODIFIED_SINCE, |
| 309 | header::IF_NONE_MATCH, |
| 310 | header::IF_RANGE, |
| 311 | header::IF_UNMODIFIED_SINCE, |
| 312 | header::LAST_MODIFIED, |
| 313 | header::LINK, |
| 314 | header::LOCATION, |
| 315 | header::MAX_FORWARDS, |
| 316 | header::ORIGIN, |
| 317 | header::PRAGMA, |
| 318 | header::PROXY_AUTHENTICATE, |
| 319 | header::PROXY_AUTHORIZATION, |
| 320 | header::PUBLIC_KEY_PINS, |
| 321 | header::PUBLIC_KEY_PINS_REPORT_ONLY, |
| 322 | header::RANGE, |
| 323 | header::REFERER, |
| 324 | header::REFERRER_POLICY, |
| 325 | header::REFRESH, |
| 326 | header::RETRY_AFTER, |
| 327 | header::SEC_WEBSOCKET_ACCEPT, |
| 328 | header::SEC_WEBSOCKET_EXTENSIONS, |
| 329 | header::SEC_WEBSOCKET_KEY, |
| 330 | header::SEC_WEBSOCKET_PROTOCOL, |
| 331 | header::SEC_WEBSOCKET_VERSION, |
| 332 | header::SERVER, |
| 333 | header::SET_COOKIE, |
| 334 | header::STRICT_TRANSPORT_SECURITY, |
| 335 | header::TE, |
| 336 | header::TRAILER, |
| 337 | header::TRANSFER_ENCODING, |
| 338 | header::UPGRADE, |
| 339 | header::UPGRADE_INSECURE_REQUESTS, |
| 340 | header::USER_AGENT, |
| 341 | header::VARY, |
| 342 | header::VIA, |
| 343 | header::WARNING, |
| 344 | header::WWW_AUTHENTICATE, |
| 345 | header::X_CONTENT_TYPE_OPTIONS, |
| 346 | header::X_DNS_PREFETCH_CONTROL, |
| 347 | header::X_FRAME_OPTIONS, |
| 348 | header::X_XSS_PROTECTION, |
| 349 | ]; |
| 350 | |
| 351 | if g.gen_ratio(1, 2) { |
| 352 | STANDARD_HEADERS.choose(g).unwrap().clone() |
| 353 | } else { |
| 354 | let value = gen_string(g, 1, 25); |
| 355 | HeaderName::from_bytes(value.as_bytes()).unwrap() |
| 356 | } |
| 357 | } |
| 358 | |
| 359 | fn gen_header_value(g: &mut StdRng) -> HeaderValue { |
| 360 | let value = gen_string(g, 0, 70); |
| 361 | HeaderValue::from_bytes(value.as_bytes()).unwrap() |
| 362 | } |
| 363 | |
| 364 | fn gen_string(g: &mut StdRng, min: usize, max: usize) -> String { |
| 365 | let bytes: Vec<_> = (min..max) |
| 366 | .map(|_| { |
| 367 | // Chars to pick from |
| 368 | b"ABCDEFGHIJKLMNOPQRSTUVabcdefghilpqrstuvwxyz----" |
| 369 | .choose(g) |
| 370 | .unwrap() |
| 371 | .clone() |
| 372 | }) |
| 373 | .collect(); |
| 374 | |
| 375 | String::from_utf8(bytes).unwrap() |
| 376 | } |
| 377 | |