diff -Nru rust-http-0.1.21/benches/header_map/basic.rs rust-http-0.2.7/benches/header_map/basic.rs --- rust-http-0.1.21/benches/header_map/basic.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/benches/header_map/basic.rs 1973-11-29 21:33:09.000000000 +0000 @@ -2,13 +2,13 @@ ($name:ident($map:ident, $b:ident) $body:expr) => { mod $name { #[allow(unused_imports)] - use test::{self, Bencher}; - use seahash::SeaHasher; + use super::custom_hdr; use fnv::FnvHasher; - use std::hash::BuildHasherDefault; use http::header::*; + use seahash::SeaHasher; + use std::hash::BuildHasherDefault; #[allow(unused_imports)] - use super::custom_hdr; + use test::{self, Bencher}; #[bench] fn header_map($b: &mut Bencher) { @@ -25,7 +25,7 @@ #[bench] fn vec_map($b: &mut Bencher) { - use vec_map::VecMap; + use crate::vec_map::VecMap; let $map = || VecMap::with_capacity(0); $body @@ -208,7 +208,6 @@ }) }); - bench!(set_10_get_1_custom_med(new_map, b) { let hdrs = super::med_custom_hdr(10); @@ -474,38 +473,48 @@ use http::header::*; fn custom_hdr(n: usize) -> Vec { - (0..n).map(|i| { - let s = format!("x-custom-{}", i); - s.parse().unwrap() - }).collect() + (0..n) + .map(|i| { + let s = format!("x-custom-{}", i); + s.parse().unwrap() + }) + .collect() } fn med_custom_hdr(n: usize) -> Vec { - (0..n).map(|i| { - let s = format!("content-length-{}", i); - s.parse().unwrap() - }).collect() + (0..n) + .map(|i| { + let s = format!("content-length-{}", i); + s.parse().unwrap() + }) + .collect() } fn long_custom_hdr(n: usize) -> Vec { - (0..n).map(|i| { - let s = format!("access-control-allow-headers-{}", i); - s.parse().unwrap() - }).collect() + (0..n) + .map(|i| { + let s = format!("access-control-allow-headers-{}", i); + s.parse().unwrap() + }) + .collect() } fn very_long_custom_hdr(n: usize) -> Vec { - (0..n).map(|i| { - let s = format!("access-control-allow-access-control-allow-headers-{}", i); - s.parse().unwrap() - }).collect() + (0..n) + .map(|i| { + let s = format!("access-control-allow-access-control-allow-headers-{}", i); + s.parse().unwrap() + }) + .collect() } fn custom_std(n: usize) -> Vec { - (0..n).map(|i| { - let s = format!("{}-{}", STD[i % STD.len()].as_str(), i); - s.parse().unwrap() - }).collect() + (0..n) + .map(|i| { + let s = format!("{}-{}", STD[i % STD.len()].as_str(), i); + s.parse().unwrap() + }) + .collect() } const STD: &'static [HeaderName] = &[ diff -Nru rust-http-0.1.21/benches/header_map/mod.rs rust-http-0.2.7/benches/header_map/mod.rs --- rust-http-0.1.21/benches/header_map/mod.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/benches/header_map/mod.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,10 +1,6 @@ #![feature(test)] -extern crate http; extern crate test; -extern crate indexmap; -extern crate seahash; -extern crate fnv; mod basic; mod vec_map; diff -Nru rust-http-0.1.21/benches/header_map/vec_map.rs rust-http-0.2.7/benches/header_map/vec_map.rs --- rust-http-0.1.21/benches/header_map/vec_map.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/benches/header_map/vec_map.rs 1973-11-29 21:33:09.000000000 +0000 @@ -9,7 +9,7 @@ #[inline] pub fn with_capacity(cap: usize) -> VecMap { VecMap { - vec: Vec::with_capacity(cap) + vec: Vec::with_capacity(cap), } } @@ -17,12 +17,12 @@ pub fn insert(&mut self, key: K, value: V) { match self.find(&key) { Some(pos) => self.vec[pos] = (key, value), - None => self.vec.push((key, value)) + None => self.vec.push((key, value)), } } #[inline] - pub fn entry(&mut self, key: K) -> Entry { + pub fn entry(&mut self, key: K) -> Entry<'_, K, V> { match self.find(&key) { Some(pos) => Entry::Occupied(OccupiedEntry { vec: self, @@ -31,7 +31,7 @@ None => Entry::Vacant(VacantEntry { vec: self, key: key, - }) + }), } } @@ -51,15 +51,19 @@ } #[inline] - pub fn len(&self) -> usize { self.vec.len() } + pub fn len(&self) -> usize { + self.vec.len() + } #[inline] - pub fn iter(&self) -> ::std::slice::Iter<(K, V)> { + pub fn iter(&self) -> ::std::slice::Iter<'_, (K, V)> { self.vec.iter() } #[inline] pub fn remove + ?Sized>(&mut self, key: &K2) -> Option { - self.find(key).map(|pos| self.vec.remove(pos)).map(|(_, v)| v) + self.find(key) + .map(|pos| self.vec.remove(pos)) + .map(|(_, v)| v) } #[inline] pub fn clear(&mut self) { @@ -74,10 +78,10 @@ pub enum Entry<'a, K: 'a, V: 'a> { Vacant(VacantEntry<'a, K, V>), - Occupied(OccupiedEntry<'a, K, V>) + Occupied(OccupiedEntry<'a, K, V>), } -pub struct VacantEntry<'a, K: 'a, V: 'a> { +pub struct VacantEntry<'a, K, V> { vec: &'a mut VecMap, key: K, } @@ -91,7 +95,7 @@ } } -pub struct OccupiedEntry<'a, K: 'a, V: 'a> { +pub struct OccupiedEntry<'a, K, V> { vec: &'a mut VecMap, pos: usize, } diff -Nru rust-http-0.1.21/benches/header_name.rs rust-http-0.2.7/benches/header_name.rs --- rust-http-0.1.21/benches/header_name.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/benches/header_name.rs 1973-11-29 21:33:09.000000000 +0000 @@ -130,6 +130,128 @@ ] } +static ALL_KNOWN_HEADERS: &[&str] = &[ + // Standard request headers + "a-im", + "accept", + "accept-charset", + "accept-datetime", + "accept-encoding", + "accept-language", + "access-control-request-method", + "authorization", + "cache-control", + "connection", + "permanent", + "content-length", + "content-md5", + "content-type", + "cookie", + "date", + "expect", + "forwarded", + "from", + "host", + "permanent", + "http2-settings", + "if-match", + "if-modified-since", + "if-none-match", + "if-range", + "if-unmodified-since", + "max-forwards", + "origin", + "pragma", + "proxy-authorization", + "range", + "referer", + "te", + "user-agent", + "upgrade", + "via", + "warning", + // common_non_standard + "upgrade-insecure-requests", + "upgrade-insecure-requests", + "x-requested-with", + "dnt", + "x-forwarded-for", + "x-forwarded-host", + "x-forwarded-proto", + "front-end-https", + "x-http-method-override", + "x-att-deviceid", + "x-wap-profile", + "proxy-connection", + "x-uidh", + "x-csrf-token", + "x-request-id", + "x-correlation-id", + "save-data", + // standard_response_headers + "accept-patch", + "accept-ranges", + "access-control-allow-credentials", + "access-control-allow-headers", + "access-control-allow-methods", + "access-control-allow-origin", + "access-control-expose-headers", + "access-control-max-age", + "age", + "allow", + "alt-svc", + "cache-control", + "connection", + "content-disposition", + "content-encoding", + "content-language", + "content-length", + "content-location", + "content-md5", + "content-range", + "content-type", + "date", + "delta-base", + "etag", + "expires", + "im", + "last-modified", + "link", + "location", + "p3p", + "permanent", + "pragma", + "proxy-authenticate", + "public-key-pins", + "retry-after", + "server", + "set-cookie", + "strict-transport-security", + "tk", + "trailer", + "transfer-encoding", + "upgrade", + "vary", + "via", + "warning", + "www-authenticate", + "x-frame-options", + // common_non_standard_response + "content-security-policy", + "refresh", + "status", + "timing-allow-origin", + "x-content-duration", + "x-content-security-policy", + "x-content-type-options", + "x-correlation-id", + "x-powered-by", + "x-request-id", + "x-ua-compatible", + "x-webkit-csp", + "x-xss-protection", +]; + #[bench] fn header_name_easy(b: &mut Bencher) { let name = b"Content-type"; @@ -139,6 +261,14 @@ } #[bench] +fn header_name_custom(b: &mut Bencher) { + let name = b"Foo-Bar-Baz-Blah"; + b.iter(|| { + HeaderName::from_bytes(&name[..]).unwrap(); + }); +} + +#[bench] fn header_name_bad(b: &mut Bencher) { let name = b"bad header name"; b.iter(|| { @@ -155,3 +285,12 @@ } }); } + +#[bench] +fn header_name_from_static(b: &mut Bencher) { + b.iter(|| { + for name in ALL_KNOWN_HEADERS { + HeaderName::from_static(name); + } + }); +} diff -Nru rust-http-0.1.21/benches/header_value.rs rust-http-0.2.7/benches/header_value.rs --- rust-http-0.1.21/benches/header_value.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/benches/header_value.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,7 +1,5 @@ #![feature(test)] -extern crate bytes; -extern crate http; extern crate test; use bytes::Bytes; @@ -11,13 +9,12 @@ static SHORT: &'static [u8] = b"localhost"; static LONG: &'static [u8] = b"Mozilla/5.0 (X11; CrOS x86_64 9592.71.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.80 Safari/537.36"; - #[bench] fn from_shared_short(b: &mut Bencher) { b.bytes = SHORT.len() as u64; let bytes = Bytes::from_static(SHORT); b.iter(|| { - HeaderValue::from_shared(bytes.clone()).unwrap(); + HeaderValue::from_maybe_shared(bytes.clone()).unwrap(); }); } @@ -26,19 +23,16 @@ b.bytes = LONG.len() as u64; let bytes = Bytes::from_static(LONG); b.iter(|| { - HeaderValue::from_shared(bytes.clone()).unwrap(); + HeaderValue::from_maybe_shared(bytes.clone()).unwrap(); }); } - #[bench] fn from_shared_unchecked_short(b: &mut Bencher) { b.bytes = SHORT.len() as u64; let bytes = Bytes::from_static(SHORT); - b.iter(|| { - unsafe { - HeaderValue::from_shared_unchecked(bytes.clone()); - } + b.iter(|| unsafe { + HeaderValue::from_maybe_shared_unchecked(bytes.clone()); }); } @@ -46,9 +40,7 @@ fn from_shared_unchecked_long(b: &mut Bencher) { b.bytes = LONG.len() as u64; let bytes = Bytes::from_static(LONG); - b.iter(|| { - unsafe { - HeaderValue::from_shared_unchecked(bytes.clone()); - } + b.iter(|| unsafe { + HeaderValue::from_maybe_shared_unchecked(bytes.clone()); }); } diff -Nru rust-http-0.1.21/benches/method.rs rust-http-0.2.7/benches/method.rs --- rust-http-0.1.21/benches/method.rs 1970-01-01 00:00:00.000000000 +0000 +++ rust-http-0.2.7/benches/method.rs 1973-11-29 21:33:09.000000000 +0000 @@ -0,0 +1,40 @@ +#![feature(test)] + +extern crate test; + +use http::method::Method; +use test::Bencher; + +fn make_all_methods() -> Vec> { + vec![ + b"OPTIONS".to_vec(), + b"GET".to_vec(), + b"POST".to_vec(), + b"PUT".to_vec(), + b"DELETE".to_vec(), + b"HEAD".to_vec(), + b"TRACE".to_vec(), + b"CONNECT".to_vec(), + b"PATCH".to_vec(), + b"CUSTOM_SHORT".to_vec(), + b"CUSTOM_LONG_METHOD".to_vec(), + ] +} + +#[bench] +fn method_easy(b: &mut Bencher) { + let name = b"GET"; + b.iter(|| { + Method::from_bytes(&name[..]).unwrap(); + }); +} + +#[bench] +fn method_various(b: &mut Bencher) { + let all_methods = make_all_methods(); + b.iter(|| { + for name in &all_methods { + Method::from_bytes(name.as_slice()).unwrap(); + } + }); +} diff -Nru rust-http-0.1.21/benches/uri.rs rust-http-0.2.7/benches/uri.rs --- rust-http-0.1.21/benches/uri.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/benches/uri.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,6 +1,5 @@ #![feature(test)] -extern crate http; extern crate test; use http::Uri; diff -Nru rust-http-0.1.21/Cargo.toml rust-http-0.2.7/Cargo.toml --- rust-http-0.1.21/Cargo.toml 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/Cargo.toml 1970-01-01 00:00:01.000000000 +0000 @@ -1,38 +1,33 @@ +# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO +# +# When uploading crates to the registry Cargo will automatically +# "normalize" Cargo.toml files for maximal compatibility +# with all versions of Cargo and also rewrite `path` dependencies +# to registry (e.g., crates.io) dependencies. +# +# If you are reading this file be aware that the original Cargo.toml +# will likely look very different (and much more reasonable). +# See Cargo.toml.orig for the original contents. + [package] +edition = "2018" +rust-version = "1.49.0" name = "http" -# When releasing to crates.io: -# - Update html_root_url in lib.rs. -# - Update CHANGELOG.md. -# - Create git tag -version = "0.1.21" -readme = "README.md" -documentation = "https://docs.rs/http" -repository = "https://github.com/hyperium/http" -license = "MIT/Apache-2.0" +version = "0.2.7" authors = [ - "Alex Crichton ", - "Carl Lerche ", - "Sean McArthur ", + "Alex Crichton ", + "Carl Lerche ", + "Sean McArthur ", ] description = """ A set of types for representing HTTP requests and responses. """ +documentation = "https://docs.rs/http" +readme = "README.md" keywords = ["http"] categories = ["web-programming"] - -[dependencies] -bytes = "0.4" -fnv = "1.0.5" -itoa = "0.4.1" - -[dev-dependencies] -indexmap = "1.0" -quickcheck = "0.6" -rand = "0.4" -seahash = "3.0.5" -serde = "1.0" -serde_json = "1.0" -doc-comment = "0.3" +license = "MIT OR Apache-2.0" +repository = "https://github.com/hyperium/http" [[bench]] name = "header_map" @@ -47,5 +42,39 @@ path = "benches/header_value.rs" [[bench]] +name = "method" +path = "benches/method.rs" + +[[bench]] name = "uri" path = "benches/uri.rs" + +[dependencies.bytes] +version = "1" + +[dependencies.fnv] +version = "1.0.5" + +[dependencies.itoa] +version = "1" + +[dev-dependencies.doc-comment] +version = "0.3" + +[dev-dependencies.indexmap] +version = "1.0" + +[dev-dependencies.quickcheck] +version = "0.9.0" + +[dev-dependencies.rand] +version = "0.7.0" + +[dev-dependencies.seahash] +version = "3.0.5" + +[dev-dependencies.serde] +version = "1.0" + +[dev-dependencies.serde_json] +version = "1.0" diff -Nru rust-http-0.1.21/Cargo.toml.orig rust-http-0.2.7/Cargo.toml.orig --- rust-http-0.1.21/Cargo.toml.orig 1970-01-01 00:00:00.000000000 +0000 +++ rust-http-0.2.7/Cargo.toml.orig 1973-11-29 21:33:09.000000000 +0000 @@ -0,0 +1,58 @@ +[package] +name = "http" +# When releasing to crates.io: +# - Update html_root_url in lib.rs. +# - Update CHANGELOG.md. +# - Create git tag +version = "0.2.7" +readme = "README.md" +documentation = "https://docs.rs/http" +repository = "https://github.com/hyperium/http" +license = "MIT OR Apache-2.0" +authors = [ + "Alex Crichton ", + "Carl Lerche ", + "Sean McArthur ", +] +description = """ +A set of types for representing HTTP requests and responses. +""" +keywords = ["http"] +categories = ["web-programming"] +edition = "2018" +# When updating this value, don't forget to also adjust the GitHub Actions config. +rust-version = "1.49.0" + +[dependencies] +bytes = "1" +fnv = "1.0.5" +itoa = "1" + +[dev-dependencies] +indexmap = "1.0" +quickcheck = "0.9.0" +rand = "0.7.0" +seahash = "3.0.5" +serde = "1.0" +serde_json = "1.0" +doc-comment = "0.3" + +[[bench]] +name = "header_map" +path = "benches/header_map/mod.rs" + +[[bench]] +name = "header_name" +path = "benches/header_name.rs" + +[[bench]] +name = "header_value" +path = "benches/header_value.rs" + +[[bench]] +name = "method" +path = "benches/method.rs" + +[[bench]] +name = "uri" +path = "benches/uri.rs" diff -Nru rust-http-0.1.21/.cargo_vcs_info.json rust-http-0.2.7/.cargo_vcs_info.json --- rust-http-0.1.21/.cargo_vcs_info.json 1970-01-01 00:00:00.000000000 +0000 +++ rust-http-0.2.7/.cargo_vcs_info.json 1970-01-01 00:00:01.000000000 +0000 @@ -0,0 +1,6 @@ +{ + "git": { + "sha1": "95ad79b2712973376ba2ff66930c9e2a0cfba8b2" + }, + "path_in_vcs": "" +} \ No newline at end of file diff -Nru rust-http-0.1.21/CHANGELOG.md rust-http-0.2.7/CHANGELOG.md --- rust-http-0.1.21/CHANGELOG.md 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/CHANGELOG.md 1973-11-29 21:33:09.000000000 +0000 @@ -1,6 +1,59 @@ -# 0.1.21 (December 2, 2019) +# 0.2.7 (April 28, 2022) -* Fix `Method::is_idempotent` returning `false` for `PUT` and `DELETE. +* Add `extend()` method to `Extensions`. +* Add `From` and `From` impls for `Uri`. +* Make `HeaderName::from_static` a `const fn`. + +# 0.2.6 (December 30, 2021) + +* Upgrade internal `itoa` dependency to 1.0. + +# 0.2.5 (September 21, 2021) + +* Add `is_empty()` and `len()` methods to `Extensions`. +* Add `version_ref()` method to `request::Builder`. +* Implement `TryFrom>` and `TryFrom` for `Authority`, `Uri`, `PathAndQuery`, and `HeaderName`. +* Make `HeaderValue::from_static` a `const fn`. + +# 0.2.4 (April 4, 2021) + +* Fix `Uri` parsing to allow `{`, `"`, and `}` in paths. + +# 0.2.3 (January 7, 2021) + +* Upgrade internal (private) `bytes` dependency to 1.0. + +# 0.2.2 (December 14, 2020) + +* Fix (potential double) panic of (`HeaderMap`) `OccupiedEntry::remove_entry` and + `remove_entry_mult` when multiple values are present. ([#446], [#449] dekellum) +* Safety audits of (priv) `ByteStr` and refactor of `Authority` ([#408], [#414] sbosnick) +* Fix `HeaderName` to error instead of panic when input is too long ([#432] [#433] acfoltzer) +* Allow `StatusCode` to encode values 100-999 without error. Use of the + unclassified range 600-999 remains discouraged. ([#144], [#438], [#443] quininer dekellum) +* Add `String` and `&String` fallible conversions to `PathAndQuery` ([#450] mkindahl) +* Fix `Authority` (and `Uri`) to error instead of panic on unbalanced brackets + ([#435], [#445] aeryz) + +# 0.2.1 (March 25, 2020) + +* Add `extensions_ref` and `extensions_mut` to `request::Builder` and `response::Builder`. + +# 0.2.0 (December 2, 2019) + +* Add `Version::HTTP_3` constant. +* Add `HeaderValue::from_maybe_shared`, `HeaderValue::from_maybe_shared_unchecked`, `Uri::from_maybe_shared`, `Authority::from_maybe_shared`, and `PathAndQuery::from_maybe_shared`. +* Change `request::Builder`, `response::Builder`, and `uri::Builder` to use by-value methods instead of by-ref. +* Change from `HttpTryFrom` trait to `std::convert::TryFrom`. +* Change `HeaderMap::entry` to no longer return a `Result`. +* Change `HeaderMap::drain` iterator to match the behavior of `IntoIter`. +* Change `Authority::port` to return an `Option` instead of `Option`. +* Change `Uri::scheme` to return `Option<&Scheme>` instead of `Option<&str>`. +* Change `Uri::authority` to return `Option<&Authority>` instead of `Option<&str>`. +* Remove `InvalidUriBytes`, `InvalidHeaderNameBytes`, and `InvalidHeaderValueBytes` error types. +* Remove `HeaderValue::from_shared`, `HeaderValue::from_shared_unchecked`, `Uri::from_shared`, `Authority::from_shared`, `Scheme::from_shared`, and `PathAndQuery::from_shared`. +* Remove `Authority::port_part`. +* Remove `Uri::scheme_part` and `Uri::authority_part`. # 0.1.20 (November 26, 2019) @@ -61,7 +114,7 @@ # 0.1.10 (August 8, 2018) -* impl HttpTryFrom for HeaderValue (#236) +* `impl HttpTryFrom` for HeaderValue (#236) # 0.1.9 (August 7, 2018) @@ -125,3 +178,17 @@ # 0.1.0 (September 8, 2017) * Initial release. + +[#144]: https://github.com/hyperium/http/issues/144 +[#408]: https://github.com/hyperium/http/pull/408 +[#414]: https://github.com/hyperium/http/pull/414 +[#432]: https://github.com/hyperium/http/issues/432 +[#433]: https://github.com/hyperium/http/pull/433 +[#438]: https://github.com/hyperium/http/pull/438 +[#443]: https://github.com/hyperium/http/pull/443 +[#446]: https://github.com/hyperium/http/issues/446 +[#449]: https://github.com/hyperium/http/pull/449 +[#450]: https://github.com/hyperium/http/pull/450 +[#435]: https://github.com/hyperium/http/issues/435 +[#445]: https://github.com/hyperium/http/pull/445 + diff -Nru rust-http-0.1.21/debian/changelog rust-http-0.2.7/debian/changelog --- rust-http-0.1.21/debian/changelog 2022-04-10 19:36:10.000000000 +0000 +++ rust-http-0.2.7/debian/changelog 2022-05-02 12:17:53.000000000 +0000 @@ -1,3 +1,15 @@ +rust-http (0.2.7-1) unstable; urgency=medium + + * Team upload. + * Package http 0.2.7 from crates.io using debcargo 2.4.2 + * Modify avoid_seahash.patch and avoid_quickcheck.patch to work with + tarballs from crates.io. + * Modify avoid_seahash.patch and avoid_quickcheck.patch for new + upstream version. + * Drop patches for bytes 1.x, upstream now depends on that version. + + -- Peter Michael Green Mon, 02 May 2022 12:17:53 +0000 + rust-http (0.1.21-0.1) unstable; urgency=medium * non-maintainer upload diff -Nru rust-http-0.1.21/debian/compat rust-http-0.2.7/debian/compat --- rust-http-0.1.21/debian/compat 2021-03-08 06:19:34.000000000 +0000 +++ rust-http-0.2.7/debian/compat 2022-05-02 12:17:53.000000000 +0000 @@ -1 +1 @@ -12 +11 diff -Nru rust-http-0.1.21/debian/control rust-http-0.2.7/debian/control --- rust-http-0.1.21/debian/control 2022-04-10 19:36:07.000000000 +0000 +++ rust-http-0.2.7/debian/control 2022-05-02 12:17:53.000000000 +0000 @@ -1,21 +1,20 @@ Source: rust-http Section: rust Priority: optional -Build-Depends: debhelper (>= 12), - dh-cargo (>= 24), +Build-Depends: debhelper (>= 11), + dh-cargo (>= 18), cargo:native , rustc:native , libstd-rust-dev , librust-bytes-1+default-dev , librust-fnv-1+default-dev (>= 1.0.5-~~) , - librust-itoa-0.4+default-dev (>= 0.4.1-~~) + librust-itoa-1+default-dev Maintainer: Debian Rust Maintainers Uploaders: Wolfgang Silbermayr -Standards-Version: 4.5.0 +Standards-Version: 4.4.1 Vcs-Git: https://salsa.debian.org/rust-team/debcargo-conf.git [src/http] Vcs-Browser: https://salsa.debian.org/rust-team/debcargo-conf/tree/master/src/http -Rules-Requires-Root: no Package: librust-http-dev Architecture: any @@ -24,15 +23,15 @@ ${misc:Depends}, librust-bytes-1+default-dev, librust-fnv-1+default-dev (>= 1.0.5-~~), - librust-itoa-0.4+default-dev (>= 0.4.1-~~) + librust-itoa-1+default-dev Provides: librust-http+default-dev (= ${binary:Version}), librust-http-0-dev (= ${binary:Version}), librust-http-0+default-dev (= ${binary:Version}), - librust-http-0.1-dev (= ${binary:Version}), - librust-http-0.1+default-dev (= ${binary:Version}), - librust-http-0.1.21-dev (= ${binary:Version}), - librust-http-0.1.21+default-dev (= ${binary:Version}) + librust-http-0.2-dev (= ${binary:Version}), + librust-http-0.2+default-dev (= ${binary:Version}), + librust-http-0.2.7-dev (= ${binary:Version}), + librust-http-0.2.7+default-dev (= ${binary:Version}) Description: Set of types for representing HTTP requests and responses - Rust source code This package contains the source for the Rust http crate, packaged by debcargo for use with cargo and dh-cargo. diff -Nru rust-http-0.1.21/debian/copyright.debcargo.hint rust-http-0.2.7/debian/copyright.debcargo.hint --- rust-http-0.1.21/debian/copyright.debcargo.hint 2021-03-08 06:19:34.000000000 +0000 +++ rust-http-0.2.7/debian/copyright.debcargo.hint 2022-05-02 12:17:53.000000000 +0000 @@ -34,8 +34,8 @@ Files: debian/* Copyright: - 2018-2021 Debian Rust Maintainers - 2018-2021 Wolfgang Silbermayr + 2018-2022 Debian Rust Maintainers + 2018-2022 Wolfgang Silbermayr License: MIT or Apache-2.0 License: Apache-2.0 diff -Nru rust-http-0.1.21/debian/patches/avoid_quickcheck.patch rust-http-0.2.7/debian/patches/avoid_quickcheck.patch --- rust-http-0.1.21/debian/patches/avoid_quickcheck.patch 2022-04-10 19:28:44.000000000 +0000 +++ rust-http-0.2.7/debian/patches/avoid_quickcheck.patch 2022-05-02 12:17:53.000000000 +0000 @@ -1,31 +1,39 @@ +Modified by Peter Michael Green for http 2.x package. + Description: avoid crate quickcheck Uses old release of quickcheck unavailable in Debian. Author: Jonas Smedegaard Last-Update: 2022-04-10 --- This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -27,8 +27,8 @@ - - [dev-dependencies] - indexmap = "1.0" --quickcheck = "0.6" --rand = "0.4" -+#quickcheck = "0.6" -+#rand = "0.4" - seahash = "3.0.5" - serde = "1.0" - serde_json = "1.0" ---- a/tests/header_map_fuzz.rs -+++ b/tests/header_map_fuzz.rs +Index: http/tests/header_map_fuzz.rs +=================================================================== +--- http.orig/tests/header_map_fuzz.rs ++++ http/tests/header_map_fuzz.rs @@ -1,3 +1,4 @@ +/* - extern crate http; - extern crate rand; - extern crate quickcheck; -@@ -363,3 +364,4 @@ + use http::header::*; + use http::*; + +@@ -370,3 +371,4 @@ fn gen_string(g: &mut StdRng, min: usize String::from_utf8(bytes).unwrap() } +*/ +Index: http/Cargo.toml +=================================================================== +--- http.orig/Cargo.toml ++++ http/Cargo.toml +@@ -56,12 +56,6 @@ version = "0.3" + [dev-dependencies.indexmap] + version = "1.0" + +-[dev-dependencies.quickcheck] +-version = "0.9.0" +- +-[dev-dependencies.rand] +-version = "0.7.0" +- + [dev-dependencies.seahash] + version = "3.0.5" + diff -Nru rust-http-0.1.21/debian/patches/avoid_seahash.patch rust-http-0.2.7/debian/patches/avoid_seahash.patch --- rust-http-0.1.21/debian/patches/avoid_seahash.patch 2022-04-10 19:27:52.000000000 +0000 +++ rust-http-0.2.7/debian/patches/avoid_seahash.patch 2022-05-02 12:17:53.000000000 +0000 @@ -1,3 +1,5 @@ +Modified by Peter Michael Green for rust-http 2.x packaging. + Description: avoid crate seahash Crate seahash is used only in a benchmark that cannot build with stable rust. @@ -8,42 +10,43 @@ Last-Update: 2022-04-10 --- This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -29,23 +29,23 @@ - indexmap = "1.0" - #quickcheck = "0.6" - #rand = "0.4" --seahash = "3.0.5" -+#seahash = "3.0.5" - serde = "1.0" - serde_json = "1.0" - doc-comment = "0.3" +Index: http/Cargo.toml +=================================================================== +--- http.orig/Cargo.toml ++++ http/Cargo.toml +@@ -30,25 +30,9 @@ license = "MIT OR Apache-2.0" + repository = "https://github.com/hyperium/http" --[[bench]] + [[bench]] -name = "header_map" -path = "benches/header_map/mod.rs" -+#[[bench]] -+#name = "header_map" -+#path = "benches/header_map/mod.rs" - +- -[[bench]] -name = "header_name" -path = "benches/header_name.rs" -+#[[bench]] -+#name = "header_name" -+#path = "benches/header_name.rs" - +- -[[bench]] -name = "header_value" -path = "benches/header_value.rs" -+#[[bench]] -+#name = "header_value" -+#path = "benches/header_value.rs" +- +-[[bench]] + name = "method" + path = "benches/method.rs" -[[bench]] -name = "uri" -path = "benches/uri.rs" -+#[[bench]] -+#name = "uri" -+#path = "benches/uri.rs" +- + [dependencies.bytes] + version = "1" + +@@ -64,9 +48,6 @@ version = "0.3" + [dev-dependencies.indexmap] + version = "1.0" + +-[dev-dependencies.seahash] +-version = "3.0.5" +- + [dev-dependencies.serde] + version = "1.0" + diff -Nru rust-http-0.1.21/debian/patches/series rust-http-0.2.7/debian/patches/series --- rust-http-0.1.21/debian/patches/series 2022-04-10 19:08:42.000000000 +0000 +++ rust-http-0.2.7/debian/patches/series 2022-05-02 12:17:53.000000000 +0000 @@ -1,4 +1,2 @@ -upgrade-to-bytes-0.5.patch -upgrade-to-bytes-1.patch avoid_quickcheck.patch avoid_seahash.patch diff -Nru rust-http-0.1.21/debian/patches/upgrade-to-bytes-0.5.patch rust-http-0.2.7/debian/patches/upgrade-to-bytes-0.5.patch --- rust-http-0.1.21/debian/patches/upgrade-to-bytes-0.5.patch 2022-04-10 17:40:20.000000000 +0000 +++ rust-http-0.2.7/debian/patches/upgrade-to-bytes-0.5.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,207 +0,0 @@ -Description: Upgrade to bytes 0.5 -Origin: upstream, https://github.com/hyperium/http/commit/43dffa1 -Author: Sean McArthur -Last-Update: 2022-03-29 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -21,7 +21,7 @@ - categories = ["web-programming"] - - [dependencies] --bytes = "0.4" -+bytes = "0.5" - fnv = "1.0.5" - itoa = "0.4.1" - ---- a/src/byte_str.rs -+++ b/src/byte_str.rs -@@ -50,7 +50,7 @@ - impl<'a> From<&'a str> for ByteStr { - #[inline] - fn from(src: &'a str) -> ByteStr { -- ByteStr { bytes: Bytes::from(src) } -+ ByteStr { bytes: Bytes::copy_from_slice(src.as_bytes()) } - } - } - ---- a/src/header/name.rs -+++ b/src/header/name.rs -@@ -1662,7 +1662,7 @@ - match parse_hdr(src, &mut buf, &HEADER_CHARS)?.inner { - Repr::Standard(std) => Ok(std.into()), - Repr::Custom(MaybeLower { buf, lower: true }) => { -- let buf = Bytes::from(buf); -+ let buf = Bytes::copy_from_slice(buf); - let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; - Ok(Custom(val).into()) - } -@@ -1677,7 +1677,7 @@ - return Err(InvalidHeaderName::new()); - } - -- dst.put(b); -+ dst.put_u8(b); - } - - let val = unsafe { ByteStr::from_utf8_unchecked(dst.freeze()) }; -@@ -1711,7 +1711,7 @@ - match parse_hdr(src, &mut buf, &HEADER_CHARS_H2)?.inner { - Repr::Standard(std) => Ok(std.into()), - Repr::Custom(MaybeLower { buf, lower: true }) => { -- let buf = Bytes::from(buf); -+ let buf = Bytes::copy_from_slice(buf); - let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; - Ok(Custom(val).into()) - } -@@ -1722,7 +1722,7 @@ - } - } - -- let buf = Bytes::from(buf); -+ let buf = Bytes::copy_from_slice(buf); - let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; - Ok(Custom(val).into()) - } -@@ -2089,7 +2089,7 @@ - } - Repr::Custom(maybe_lower) => { - if maybe_lower.lower { -- let buf = Bytes::from(&maybe_lower.buf[..]); -+ let buf = Bytes::copy_from_slice(&maybe_lower.buf[..]); - let byte_str = unsafe { ByteStr::from_utf8_unchecked(buf) }; - - HeaderName { -@@ -2100,7 +2100,7 @@ - let mut dst = BytesMut::with_capacity(maybe_lower.buf.len()); - - for b in maybe_lower.buf.iter() { -- dst.put(HEADER_CHARS[*b as usize]); -+ dst.put_u8(HEADER_CHARS[*b as usize]); - } - - let buf = unsafe { ByteStr::from_utf8_unchecked(dst.freeze()) }; ---- a/src/header/value.rs -+++ b/src/header/value.rs -@@ -104,7 +104,7 @@ - /// ``` - #[inline] - pub fn from_str(src: &str) -> Result { -- HeaderValue::try_from(src) -+ HeaderValue::try_from_generic(src, |s| Bytes::copy_from_slice(s.as_bytes())) - } - - /// Converts a HeaderName into a HeaderValue -@@ -150,7 +150,7 @@ - /// ``` - #[inline] - pub fn from_bytes(src: &[u8]) -> Result { -- HeaderValue::try_from(src) -+ HeaderValue::try_from_generic(src, Bytes::copy_from_slice) - } - - /// Attempt to convert a `Bytes` buffer to a `HeaderValue`. -@@ -163,7 +163,7 @@ - /// implementation once the trait is stabilized in std. - #[inline] - pub fn from_shared(src: Bytes) -> Result { -- HeaderValue::try_from(src).map_err(InvalidHeaderValueBytes) -+ HeaderValue::try_from_generic(src, std::convert::identity).map_err(InvalidHeaderValueBytes) - } - - /// Convert a `Bytes` directly into a `HeaderValue` without validating. -@@ -189,7 +189,7 @@ - } - } - -- fn try_from + Into>(src: T) -> Result { -+ fn try_from_generic, F: FnOnce(T) -> Bytes>(src: T, into: F) -> Result { - for &b in src.as_ref() { - if !is_valid(b) { - return Err(InvalidHeaderValue { -@@ -198,7 +198,7 @@ - } - } - Ok(HeaderValue { -- inner: src.into(), -+ inner: into(src), - is_sensitive: false, - }) - } -@@ -546,6 +546,15 @@ - } - } - -+impl HttpTryFrom> for HeaderValue { -+ type Error = InvalidHeaderValueBytes; -+ -+ #[inline] -+ fn try_from(vec: Vec) -> Result { -+ HeaderValue::from_shared(vec.into()) -+ } -+} -+ - impl HttpTryFrom for HeaderValue { - type Error = InvalidHeaderValueBytes; - ---- a/src/uri/authority.rs -+++ b/src/uri/authority.rs -@@ -1,6 +1,3 @@ --// Deprecated in 1.26, needed until our minimum version is >=1.23. --#[allow(unused, deprecated)] --use std::ascii::AsciiExt; - use std::{cmp, fmt, str}; - use std::hash::{Hash, Hasher}; - use std::str::FromStr; -@@ -457,7 +454,9 @@ - } - - Ok(Authority { -- data: unsafe { ByteStr::from_utf8_unchecked(s.into()) }, -+ data: unsafe { -+ ByteStr::from_utf8_unchecked(Bytes::copy_from_slice(s)) -+ }, - }) - } - } ---- a/src/uri/mod.rs -+++ b/src/uri/mod.rs -@@ -888,7 +888,7 @@ - - #[inline] - fn from_str(s: &str) -> Result { -- Uri::from_shared(s.into()).map_err(|e| e.0) -+ Uri::from_shared(Bytes::copy_from_slice(s.as_bytes())).map_err(|e| e.0) - } - } - ---- a/src/uri/path.rs -+++ b/src/uri/path.rs -@@ -296,7 +296,7 @@ - type Error = InvalidUri; - #[inline] - fn try_from(s: &'a [u8]) -> Result { -- PathAndQuery::from_shared(s.into()).map_err(|e| e.0) -+ PathAndQuery::from_shared(Bytes::copy_from_slice(s)).map_err(|e| e.0) - } - } - ---- a/src/uri/scheme.rs -+++ b/src/uri/scheme.rs -@@ -1,6 +1,3 @@ --// Deprecated in 1.26, needed until our minimum version is >=1.23. --#[allow(unused, deprecated)] --use std::ascii::AsciiExt; - use std::fmt; - use std::hash::{Hash, Hasher}; - use std::str::FromStr; -@@ -130,7 +127,7 @@ - Other(_) => { - // Unsafe: parse_exact already checks for a strict subset of UTF-8 - Ok(Other(Box::new(unsafe { -- ByteStr::from_utf8_unchecked(s.into()) -+ ByteStr::from_utf8_unchecked(Bytes::copy_from_slice(s)) - })).into()) - } - } diff -Nru rust-http-0.1.21/debian/patches/upgrade-to-bytes-1.patch rust-http-0.2.7/debian/patches/upgrade-to-bytes-1.patch --- rust-http-0.1.21/debian/patches/upgrade-to-bytes-1.patch 2022-04-10 17:41:00.000000000 +0000 +++ rust-http-0.2.7/debian/patches/upgrade-to-bytes-1.patch 1970-01-01 00:00:00.000000000 +0000 @@ -1,17 +0,0 @@ -Description: Upgrade to Bytes 1.0 -Origin: upstream, https://github.com/hyperium/http/commit/95c338e -Author: Sean McArthur -Last-Update: 2022-03-29 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -21,7 +21,7 @@ - categories = ["web-programming"] - - [dependencies] --bytes = "0.5" -+bytes = "1" - fnv = "1.0.5" - itoa = "0.4.1" - diff -Nru rust-http-0.1.21/debian/tests/control rust-http-0.2.7/debian/tests/control --- rust-http-0.1.21/debian/tests/control 2022-04-10 19:08:24.000000000 +0000 +++ rust-http-0.2.7/debian/tests/control 2022-05-02 12:17:53.000000000 +0000 @@ -1,14 +1,9 @@ -Test-Command: /usr/share/cargo/bin/cargo-auto-test http 0.1.21 --all-targets --all-features -Features: test-name=rust-http:@ +Test-Command: /usr/share/cargo/bin/cargo-auto-test http 0.2.7 --all-targets --all-features +Features: test-name=@ Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev, librust-indexmap-1+default-dev, librust-serde-1+default-dev, librust-serde-json-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable -Test-Command: /usr/share/cargo/bin/cargo-auto-test http 0.1.21 --all-targets -Features: test-name=librust-http-dev:default -Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev, librust-indexmap-1+default-dev, librust-serde-1+default-dev, librust-serde-json-1+default-dev, @ -Restrictions: allow-stderr, skip-not-installable - -Test-Command: /usr/share/cargo/bin/cargo-auto-test http 0.1.21 --all-targets --no-default-features -Features: test-name=librust-http-dev: +Test-Command: /usr/share/cargo/bin/cargo-auto-test http 0.2.7 --all-targets --no-default-features +Features: test-name=librust-http-dev Depends: dh-cargo (>= 18), librust-doc-comment-0.3+default-dev, librust-indexmap-1+default-dev, librust-serde-1+default-dev, librust-serde-json-1+default-dev, @ Restrictions: allow-stderr, skip-not-installable diff -Nru rust-http-0.1.21/debian/watch rust-http-0.2.7/debian/watch --- rust-http-0.1.21/debian/watch 2021-03-08 06:19:34.000000000 +0000 +++ rust-http-0.2.7/debian/watch 2022-05-02 12:17:53.000000000 +0000 @@ -1,2 +1,4 @@ version=4 -opts=filenamemangle=s/.*\/(.*)\/download/http-$1\.tar\.gz/g,\\nuversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha)\d*)$/$1~$2/ \\nhttps://qa.debian.org/cgi-bin/fakeupstream.cgi?upstream=crates.io/http .*/crates/http/@ANY_VERSION@/download +opts=filenamemangle=s/.*\/(.*)\/download/http-$1\.tar\.gz/g,\ +uversionmangle=s/(\d)[_\.\-\+]?((RC|rc|pre|dev|beta|alpha)\d*)$/$1~$2/ \ +https://qa.debian.org/cgi-bin/fakeupstream.cgi?upstream=crates.io/http .*/crates/http/@ANY_VERSION@/download diff -Nru rust-http-0.1.21/.github/workflows/ci.yml rust-http-0.2.7/.github/workflows/ci.yml --- rust-http-0.1.21/.github/workflows/ci.yml 1970-01-01 00:00:00.000000000 +0000 +++ rust-http-0.2.7/.github/workflows/ci.yml 1973-11-29 21:33:09.000000000 +0000 @@ -0,0 +1,79 @@ +name: CI +on: + pull_request: + push: + branches: + - master + +env: + RUST_BACKTRACE: 1 + +jobs: + + test: + name: Test ${{ matrix.rust }} + #needs: [style] + strategy: + matrix: + rust: + - stable + - beta + - nightly + # When updating this value, don't forget to also adjust the + # `rust-version` field in the `Cargo.toml` file. + - 1.49.0 + + include: + - rust: nightly + benches: true + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Install Rust (${{ matrix.rust }}) + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: ${{ matrix.rust }} + override: true + + - name: Test + uses: actions-rs/cargo@v1 + with: + command: test + + - name: Test all benches + if: matrix.benches + uses: actions-rs/cargo@v1 + with: + command: test + args: --benches ${{ matrix.features }} + + wasm: + name: WASM + #needs: [style] + + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v1 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + target: wasm32-unknown-unknown + override: true + + - name: Check + uses: actions-rs/cargo@v1 + with: + command: check + args: --target wasm32-unknown-unknown + + diff -Nru rust-http-0.1.21/README.md rust-http-0.2.7/README.md --- rust-http-0.1.21/README.md 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/README.md 1973-11-29 21:33:09.000000000 +0000 @@ -2,7 +2,7 @@ A general purpose library of common HTTP types -[![Build Status](https://travis-ci.org/hyperium/http.svg?branch=master)](https://travis-ci.org/hyperium/http) +[![CI](https://github.com/hyperium/http/workflows/CI/badge.svg)](https://github.com/hyperium/http/actions?query=workflow%3ACI) [![Crates.io](https://img.shields.io/crates/v/http.svg)](https://crates.io/crates/http) [![Documentation](https://docs.rs/http/badge.svg)][dox] @@ -17,14 +17,12 @@ ```toml [dependencies] -http = "0.1" +http = "0.2" ``` Next, add this to your crate: ```rust -extern crate http; - use http::{Request, Response}; fn main() { @@ -37,8 +35,6 @@ Create an HTTP request: ```rust -extern crate http; - use http::Request; fn main() { @@ -53,8 +49,6 @@ Create an HTTP response: ```rust -extern crate http; - use http::{Response, StatusCode}; fn main() { @@ -70,8 +64,8 @@ Licensed under either of -- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or http://apache.org/licenses/LICENSE-2.0) -- MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) +- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or https://apache.org/licenses/LICENSE-2.0) +- MIT license ([LICENSE-MIT](LICENSE-MIT) or https://opensource.org/licenses/MIT) # Contribution diff -Nru rust-http-0.1.21/src/byte_str.rs rust-http-0.2.7/src/byte_str.rs --- rust-http-0.1.21/src/byte_str.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/byte_str.rs 1973-11-29 21:33:09.000000000 +0000 @@ -4,28 +4,45 @@ #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] pub(crate) struct ByteStr { + // Invariant: bytes contains valid UTF-8 bytes: Bytes, } impl ByteStr { #[inline] pub fn new() -> ByteStr { - ByteStr { bytes: Bytes::new() } + ByteStr { + // Invariant: the empty slice is trivially valid UTF-8. + bytes: Bytes::new(), + } } #[inline] - pub fn from_static(val: &'static str) -> ByteStr { - ByteStr { bytes: Bytes::from_static(val.as_bytes()) } + pub const fn from_static(val: &'static str) -> ByteStr { + ByteStr { + // Invariant: val is a str so contains vaid UTF-8. + bytes: Bytes::from_static(val.as_bytes()), + } } #[inline] + /// ## Panics + /// In a debug build this will panic if `bytes` is not valid UTF-8. + /// + /// ## Safety + /// `bytes` must contain valid UTF-8. In a release build it is undefined + /// behaviour to call this with `bytes` that is not valid UTF-8. pub unsafe fn from_utf8_unchecked(bytes: Bytes) -> ByteStr { if cfg!(debug_assertions) { match str::from_utf8(&bytes) { Ok(_) => (), - Err(err) => panic!("ByteStr::from_utf8_unchecked() with invalid bytes; error = {}, bytes = {:?}", err, bytes), + Err(err) => panic!( + "ByteStr::from_utf8_unchecked() with invalid bytes; error = {}, bytes = {:?}", + err, bytes + ), } } + // Invariant: assumed by the safety requirements of this function. ByteStr { bytes: bytes } } } @@ -36,6 +53,7 @@ #[inline] fn deref(&self) -> &str { let b: &[u8] = self.bytes.as_ref(); + // Safety: the invariant of `bytes` is that it contains valid UTF-8. unsafe { str::from_utf8_unchecked(b) } } } @@ -43,14 +61,20 @@ impl From for ByteStr { #[inline] fn from(src: String) -> ByteStr { - ByteStr { bytes: Bytes::from(src) } + ByteStr { + // Invariant: src is a String so contains valid UTF-8. + bytes: Bytes::from(src), + } } } impl<'a> From<&'a str> for ByteStr { #[inline] fn from(src: &'a str) -> ByteStr { - ByteStr { bytes: Bytes::from(src) } + ByteStr { + // Invariant: src is a str so contains valid UTF-8. + bytes: Bytes::copy_from_slice(src.as_bytes()), + } } } diff -Nru rust-http-0.1.21/src/convert.rs rust-http-0.2.7/src/convert.rs --- rust-http-0.1.21/src/convert.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/convert.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,76 +1,17 @@ -use Error; -use header::{HeaderName, HeaderValue, HeaderMap}; -use method::Method; -use sealed::Sealed; -use status::StatusCode; -use uri::{Scheme, Authority, PathAndQuery, Uri}; - -/// Private trait for the `http` crate to have generic methods with fallible -/// conversions. -/// -/// This trait is similar to the `TryFrom` trait proposed in the standard -/// library, except this is specialized for the `http` crate and isn't intended -/// for general consumption. -/// -/// This trait cannot be implemented types outside of the `http` crate, and is -/// only intended for use as a generic bound on methods in the `http` crate. -pub trait HttpTryFrom: Sized + Sealed { - /// Associated error with the conversion this implementation represents. - type Error: Into; - - #[doc(hidden)] - fn try_from(t: T) -> Result; -} - -pub(crate) trait HttpTryInto: Sized { - fn http_try_into(self) -> Result; -} - -#[doc(hidden)] -impl HttpTryInto for T -where - U: HttpTryFrom, - T: Sized, -{ - fn http_try_into(self) -> Result { - HttpTryFrom::try_from(self) - .map_err(|e: U::Error| e.into()) - } -} - -macro_rules! reflexive { - ($($t:ty,)*) => ($( - impl HttpTryFrom<$t> for $t { - type Error = Error; - - fn try_from(t: Self) -> Result { - Ok(t) - } +macro_rules! if_downcast_into { + ($in_ty:ty, $out_ty:ty, $val:ident, $body:expr) => ({ + if std::any::TypeId::of::<$in_ty>() == std::any::TypeId::of::<$out_ty>() { + // Store the value in an `Option` so we can `take` + // it after casting to `&mut dyn Any`. + let mut slot = Some($val); + // Re-write the `$val` ident with the downcasted value. + let $val = (&mut slot as &mut dyn std::any::Any) + .downcast_mut::>() + .unwrap() + .take() + .unwrap(); + // Run the $body in scope of the replaced val. + $body } - - impl Sealed for $t {} - )*) -} - -reflexive! { - Uri, - Method, - StatusCode, - HeaderName, - HeaderValue, - Scheme, - Authority, - PathAndQuery, -} - -// HeaderMap can't use reflexive easily due to the generic T - -impl HttpTryFrom> for HeaderMap { - type Error = Error; - - fn try_from(t: Self) -> Result { - Ok(t) - } + }) } - -impl Sealed for HeaderMap {} diff -Nru rust-http-0.1.21/src/error.rs rust-http-0.2.7/src/error.rs --- rust-http-0.1.21/src/error.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/error.rs 1973-11-29 21:33:09.000000000 +0000 @@ -2,10 +2,10 @@ use std::fmt; use std::result; -use header; -use method; -use status; -use uri; +use crate::header; +use crate::method; +use crate::status; +use crate::uri; /// A generic "error" for HTTP connections /// @@ -24,12 +24,9 @@ StatusCode(status::InvalidStatusCode), Method(method::InvalidMethod), Uri(uri::InvalidUri), - UriShared(uri::InvalidUriBytes), UriParts(uri::InvalidUriParts), HeaderName(header::InvalidHeaderName), - HeaderNameShared(header::InvalidHeaderNameBytes), HeaderValue(header::InvalidHeaderValue), - HeaderValueShared(header::InvalidHeaderValueBytes), } impl fmt::Debug for Error { @@ -42,7 +39,7 @@ } impl fmt::Display for Error { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.get_ref(), f) } } @@ -54,130 +51,79 @@ } /// Return a reference to the lower level, inner error. - #[allow(warnings)] - pub fn get_ref(&self) -> &(error::Error + 'static) { + pub fn get_ref(&self) -> &(dyn error::Error + 'static) { use self::ErrorKind::*; match self.inner { StatusCode(ref e) => e, Method(ref e) => e, Uri(ref e) => e, - UriShared(ref e) => e, UriParts(ref e) => e, HeaderName(ref e) => e, - HeaderNameShared(ref e) => e, HeaderValue(ref e) => e, - HeaderValueShared(ref e) => e, } } } impl error::Error for Error { - fn description(&self) -> &str { - use self::ErrorKind::*; - - match self.inner { - StatusCode(ref e) => e.description(), - Method(ref e) => e.description(), - Uri(ref e) => e.description(), - UriShared(ref e) => e.description(), - UriParts(ref e) => e.description(), - HeaderName(ref e) => e.description(), - HeaderNameShared(ref e) => e.description(), - HeaderValue(ref e) => e.description(), - HeaderValueShared(ref e) => e.description(), - } - } - // Return any available cause from the inner error. Note the inner error is // not itself the cause. - #[allow(warnings)] - fn cause(&self) -> Option<&error::Error> { - self.get_ref().cause() + fn source(&self) -> Option<&(dyn error::Error + 'static)> { + self.get_ref().source() } } impl From for Error { fn from(err: status::InvalidStatusCode) -> Error { - Error { inner: ErrorKind::StatusCode(err) } + Error { + inner: ErrorKind::StatusCode(err), + } } } impl From for Error { fn from(err: method::InvalidMethod) -> Error { - Error { inner: ErrorKind::Method(err) } + Error { + inner: ErrorKind::Method(err), + } } } impl From for Error { fn from(err: uri::InvalidUri) -> Error { - Error { inner: ErrorKind::Uri(err) } - } -} - -impl From for Error { - fn from(err: uri::InvalidUriBytes) -> Error { - Error { inner: ErrorKind::UriShared(err) } + Error { + inner: ErrorKind::Uri(err), + } } } impl From for Error { fn from(err: uri::InvalidUriParts) -> Error { - Error { inner: ErrorKind::UriParts(err) } + Error { + inner: ErrorKind::UriParts(err), + } } } impl From for Error { fn from(err: header::InvalidHeaderName) -> Error { - Error { inner: ErrorKind::HeaderName(err) } - } -} - -impl From for Error { - fn from(err: header::InvalidHeaderNameBytes) -> Error { - Error { inner: ErrorKind::HeaderNameShared(err) } + Error { + inner: ErrorKind::HeaderName(err), + } } } impl From for Error { fn from(err: header::InvalidHeaderValue) -> Error { - Error { inner: ErrorKind::HeaderValue(err) } - } -} - -impl From for Error { - fn from(err: header::InvalidHeaderValueBytes) -> Error { - Error { inner: ErrorKind::HeaderValueShared(err) } - } -} - -// A crate-private type until we can use !. -// -// Being crate-private, we should be able to swap the type out in a -// backwards compatible way. -pub enum Never {} - -impl From for Error { - fn from(never: Never) -> Error { - match never {} - } -} - -impl fmt::Debug for Never { - fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { - match *self {} - } -} - -impl fmt::Display for Never { - fn fmt(&self, _f: &mut fmt::Formatter) -> fmt::Result { - match *self {} + Error { + inner: ErrorKind::HeaderValue(err), + } } } -impl error::Error for Never { - fn description(&self) -> &str { - match *self {} +impl From for Error { + fn from(err: std::convert::Infallible) -> Error { + match err {} } } @@ -191,11 +137,11 @@ let err: Error = e.into(); let ie = err.get_ref(); assert!(!ie.is::()); - assert!( ie.is::()); + assert!(ie.is::()); ie.downcast_ref::().unwrap(); assert!(!err.is::()); - assert!( err.is::()); + assert!(err.is::()); } else { panic!("Bad status allowed!"); } diff -Nru rust-http-0.1.21/src/extensions.rs rust-http-0.2.7/src/extensions.rs --- rust-http-0.1.21/src/extensions.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/extensions.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,11 +1,9 @@ use std::any::{Any, TypeId}; use std::collections::HashMap; -use std::hash::{BuildHasherDefault, Hasher}; use std::fmt; +use std::hash::{BuildHasherDefault, Hasher}; -// `Box` is now `Box`, but we can't change yet (minimum Rust) -#[allow(warnings)] -type AnyMap = HashMap, BuildHasherDefault>; +type AnyMap = HashMap, BuildHasherDefault>; // With TypeIds as keys, there's no need to hash them. They are already hashes // themselves, coming from the compiler. The IdHasher just holds the u64 of @@ -29,8 +27,6 @@ } } - - /// A type map of protocol extensions. /// /// `Extensions` can be used by `Request` and `Response` to store @@ -46,9 +42,7 @@ /// Create an empty `Extensions`. #[inline] pub fn new() -> Extensions { - Extensions { - map: None, - } + Extensions { map: None } } /// Insert a type into this `Extensions`. @@ -66,18 +60,14 @@ /// assert_eq!(ext.insert(9i32), Some(5i32)); /// ``` pub fn insert(&mut self, val: T) -> Option { - self - .map + self.map .get_or_insert_with(|| Box::new(HashMap::default())) .insert(TypeId::of::(), Box::new(val)) .and_then(|boxed| { - #[allow(warnings)] - { - (boxed as Box) + (boxed as Box) .downcast() .ok() .map(|boxed| *boxed) - } }) } @@ -94,16 +84,10 @@ /// assert_eq!(ext.get::(), Some(&5i32)); /// ``` pub fn get(&self) -> Option<&T> { - self - .map + self.map .as_ref() .and_then(|map| map.get(&TypeId::of::())) - .and_then(|boxed| { - #[allow(warnings)] - { - (&**boxed as &(Any + 'static)).downcast_ref() - } - }) + .and_then(|boxed| (&**boxed as &(dyn Any + 'static)).downcast_ref()) } /// Get a mutable reference to a type previously inserted on this `Extensions`. @@ -119,19 +103,12 @@ /// assert_eq!(ext.get::().unwrap(), "Hello World"); /// ``` pub fn get_mut(&mut self) -> Option<&mut T> { - self - .map + self.map .as_mut() .and_then(|map| map.get_mut(&TypeId::of::())) - .and_then(|boxed| { - #[allow(warnings)] - { - (&mut **boxed as &mut (Any + 'static)).downcast_mut() - } - }) + .and_then(|boxed| (&mut **boxed as &mut (dyn Any + 'static)).downcast_mut()) } - /// Remove a type from this `Extensions`. /// /// If a extension of this type existed, it will be returned. @@ -146,18 +123,14 @@ /// assert!(ext.get::().is_none()); /// ``` pub fn remove(&mut self) -> Option { - self - .map + self.map .as_mut() .and_then(|map| map.remove(&TypeId::of::())) .and_then(|boxed| { - #[allow(warnings)] - { - (boxed as Box) + (boxed as Box) .downcast() .ok() .map(|boxed| *boxed) - } }) } @@ -179,12 +152,80 @@ map.clear(); } } + + /// Check whether the extension set is empty or not. + /// + /// # Example + /// + /// ``` + /// # use http::Extensions; + /// let mut ext = Extensions::new(); + /// assert!(ext.is_empty()); + /// ext.insert(5i32); + /// assert!(!ext.is_empty()); + /// ``` + #[inline] + pub fn is_empty(&self) -> bool { + self.map + .as_ref() + .map_or(true, |map| map.is_empty()) + } + + /// Get the numer of extensions available. + /// + /// # Example + /// + /// ``` + /// # use http::Extensions; + /// let mut ext = Extensions::new(); + /// assert_eq!(ext.len(), 0); + /// ext.insert(5i32); + /// assert_eq!(ext.len(), 1); + /// ``` + #[inline] + pub fn len(&self) -> usize { + self.map + .as_ref() + .map_or(0, |map| map.len()) + } + + /// Extends `self` with another `Extensions`. + /// + /// If an instance of a specific type exists in both, the one in `self` is overwritten with the + /// one from `other`. + /// + /// # Example + /// + /// ``` + /// # use http::Extensions; + /// let mut ext_a = Extensions::new(); + /// ext_a.insert(8u8); + /// ext_a.insert(16u16); + /// + /// let mut ext_b = Extensions::new(); + /// ext_b.insert(4u8); + /// ext_b.insert("hello"); + /// + /// ext_a.extend(ext_b); + /// assert_eq!(ext_a.len(), 3); + /// assert_eq!(ext_a.get::(), Some(&4u8)); + /// assert_eq!(ext_a.get::(), Some(&16u16)); + /// assert_eq!(ext_a.get::<&'static str>().copied(), Some("hello")); + /// ``` + pub fn extend(&mut self, other: Self) { + if let Some(other) = other.map { + if let Some(map) = &mut self.map { + map.extend(*other); + } else { + self.map = Some(other); + } + } + } } impl fmt::Debug for Extensions { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Extensions") - .finish() + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Extensions").finish() } } diff -Nru rust-http-0.1.21/src/header/map.rs rust-http-0.2.7/src/header/map.rs --- rust-http-0.1.21/src/header/map.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/header/map.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,12 +1,12 @@ -use std::{fmt, mem, ops, ptr, vec}; -use std::collections::hash_map::RandomState; use std::collections::HashMap; +use std::collections::hash_map::RandomState; +use std::convert::TryFrom; use std::hash::{BuildHasher, Hash, Hasher}; -use std::iter::FromIterator; +use std::iter::{FromIterator, FusedIterator}; use std::marker::PhantomData; +use std::{fmt, mem, ops, ptr, vec}; -use convert::{HttpTryFrom, HttpTryInto}; -use Error; +use crate::Error; use super::HeaderValue; use super::name::{HdrName, HeaderName, InvalidHeaderName}; @@ -81,7 +81,7 @@ /// Yields `(&HeaderName, &value)` tuples. The same header name may be yielded /// more than once if it has more than one associated value. #[derive(Debug)] -pub struct Iter<'a, T: 'a> { +pub struct Iter<'a, T> { inner: IterMut<'a, T>, } @@ -90,7 +90,7 @@ /// Yields `(&HeaderName, &mut value)` tuples. The same header name may be /// yielded more than once if it has more than one associated value. #[derive(Debug)] -pub struct IterMut<'a, T: 'a> { +pub struct IterMut<'a, T> { map: *mut HeaderMap, entry: usize, cursor: Option, @@ -113,7 +113,7 @@ /// Each header name is yielded only once, even if it has more than one /// associated value. #[derive(Debug)] -pub struct Keys<'a, T: 'a> { +pub struct Keys<'a, T> { inner: ::std::slice::Iter<'a, Bucket>, } @@ -121,22 +121,24 @@ /// /// Each value contained in the `HeaderMap` will be yielded. #[derive(Debug)] -pub struct Values<'a, T: 'a> { +pub struct Values<'a, T> { inner: Iter<'a, T>, } /// `HeaderMap` mutable value iterator #[derive(Debug)] -pub struct ValuesMut<'a, T: 'a> { +pub struct ValuesMut<'a, T> { inner: IterMut<'a, T>, } /// A drain iterator for `HeaderMap`. #[derive(Debug)] -pub struct Drain<'a, T: 'a> { +pub struct Drain<'a, T> { idx: usize, len: usize, entries: *mut [Bucket], + // If None, pull from `entries` + next: Option, extra_values: *mut Vec>, lt: PhantomData<&'a mut HeaderMap>, } @@ -145,7 +147,7 @@ /// /// This struct is returned by `HeaderMap::get_all`. #[derive(Debug)] -pub struct GetAll<'a, T: 'a> { +pub struct GetAll<'a, T> { map: &'a HeaderMap, index: Option, } @@ -164,7 +166,7 @@ /// /// This struct is returned as part of the `Entry` enum. #[derive(Debug)] -pub struct VacantEntry<'a, T: 'a> { +pub struct VacantEntry<'a, T> { map: &'a mut HeaderMap, key: HeaderName, hash: HashValue, @@ -176,7 +178,7 @@ /// /// This struct is returned as part of the `Entry` enum. #[derive(Debug)] -pub struct OccupiedEntry<'a, T: 'a> { +pub struct OccupiedEntry<'a, T> { map: &'a mut HeaderMap, probe: usize, index: usize, @@ -184,7 +186,7 @@ /// An iterator of all values associated with a single header name. #[derive(Debug)] -pub struct ValueIter<'a, T: 'a> { +pub struct ValueIter<'a, T> { map: &'a HeaderMap, index: usize, front: Option, @@ -193,7 +195,7 @@ /// A mutable iterator of all values associated with a single header name. #[derive(Debug)] -pub struct ValueIterMut<'a, T: 'a> { +pub struct ValueIterMut<'a, T> { map: *mut HeaderMap, index: usize, front: Option, @@ -203,7 +205,7 @@ /// An drain iterator of all values associated with a single header name. #[derive(Debug)] -pub struct ValueDrain<'a, T: 'a> { +pub struct ValueDrain<'a, T> { first: Option, next: Option<::std::vec::IntoIter>, lt: PhantomData<&'a mut HeaderMap>, @@ -228,10 +230,10 @@ /// You may notice that `u16` may represent more than 32,768 values. This is /// true, but 32,768 should be plenty and it allows us to reserve the top bit /// for future usage. -type Size = usize; +type Size = u16; /// This limit falls out from above. -const MAX_SIZE: usize = (1 << 15); +const MAX_SIZE: usize = 1 << 15; /// An entry in the hash table. This represents the full hash code for an entry /// as well as the position of the entry in the `entries` vector. @@ -248,7 +250,7 @@ /// bits is fine since we know that the `indices` vector will never grow beyond /// that size. #[derive(Debug, Copy, Clone, Eq, PartialEq)] -struct HashValue(usize); +struct HashValue(u16); /// Stores the data associated with a `HeaderMap` entry. Only the first value is /// included in this struct. If a header name has more than one associated @@ -461,8 +463,6 @@ /// assert_eq!(12, map.capacity()); /// ``` pub fn with_capacity(capacity: usize) -> HeaderMap { - assert!(capacity <= MAX_SIZE, "requested capacity too large"); - if capacity == 0 { HeaderMap { mask: 0, @@ -473,6 +473,7 @@ } } else { let raw_cap = to_raw_capacity(capacity).next_power_of_two(); + assert!(raw_cap <= MAX_SIZE, "requested capacity too large"); debug_assert!(raw_cap > 0); HeaderMap { @@ -630,17 +631,19 @@ pub fn reserve(&mut self, additional: usize) { // TODO: This can't overflow if done properly... since the max # of // elements is u16::MAX. - let cap = self.entries.len() + let cap = self + .entries + .len() .checked_add(additional) .expect("reserve overflow"); if cap > self.indices.len() { let cap = cap.next_power_of_two(); - assert!(cap < MAX_SIZE, "header map reserve over max capacity"); + assert!(cap <= MAX_SIZE, "header map reserve over max capacity"); assert!(cap != 0, "header map reserve overflowed"); if self.entries.len() == 0 { - self.mask = cap - 1; + self.mask = cap as Size - 1; self.indices = vec![Pos::none(); cap].into_boxed_slice(); self.entries = Vec::with_capacity(usable_capacity(cap)); } else { @@ -671,13 +674,15 @@ /// assert_eq!(map.get("host").unwrap(), &"hello"); /// ``` pub fn get(&self, key: K) -> Option<&T> - where K: AsHeaderName + where + K: AsHeaderName, { self.get2(&key) } fn get2(&self, key: &K) -> Option<&T> - where K: AsHeaderName + where + K: AsHeaderName, { match key.find(self) { Some((_, found)) => { @@ -706,7 +711,8 @@ /// assert_eq!(map.get(HOST).unwrap(), &"hello-world"); /// ``` pub fn get_mut(&mut self, key: K) -> Option<&mut T> - where K: AsHeaderName + where + K: AsHeaderName, { match key.find(self) { Some((_, found)) => { @@ -742,8 +748,9 @@ /// assert_eq!(&"goodbye", iter.next().unwrap()); /// assert!(iter.next().is_none()); /// ``` - pub fn get_all(&self, key: K) -> GetAll - where K: AsHeaderName + pub fn get_all(&self, key: K) -> GetAll<'_, T> + where + K: AsHeaderName, { GetAll { map: self, @@ -765,7 +772,8 @@ /// assert!(map.contains_key("host")); /// ``` pub fn contains_key(&self, key: K) -> bool - where K: AsHeaderName + where + K: AsHeaderName, { key.find(self).is_some() } @@ -791,14 +799,14 @@ /// println!("{:?}: {:?}", key, value); /// } /// ``` - pub fn iter(&self) -> Iter { + pub fn iter(&self) -> Iter<'_, T> { Iter { inner: IterMut { map: self as *const _ as *mut _, entry: 0, cursor: self.entries.first().map(|_| Cursor::Head), lt: PhantomData, - } + }, } } @@ -823,7 +831,7 @@ /// value.push_str("-boop"); /// } /// ``` - pub fn iter_mut(&mut self) -> IterMut { + pub fn iter_mut(&mut self) -> IterMut<'_, T> { IterMut { map: self as *mut _, entry: 0, @@ -853,8 +861,10 @@ /// println!("{:?}", key); /// } /// ``` - pub fn keys(&self) -> Keys { - Keys { inner: self.entries.iter() } + pub fn keys(&self) -> Keys<'_, T> { + Keys { + inner: self.entries.iter(), + } } /// An iterator visiting all values. @@ -877,7 +887,7 @@ /// println!("{:?}", value); /// } /// ``` - pub fn values(&self) -> Values { + pub fn values(&self) -> Values<'_, T> { Values { inner: self.iter() } } @@ -901,14 +911,20 @@ /// value.push_str("-boop"); /// } /// ``` - pub fn values_mut(&mut self) -> ValuesMut { - ValuesMut { inner: self.iter_mut() } + pub fn values_mut(&mut self) -> ValuesMut<'_, T> { + ValuesMut { + inner: self.iter_mut(), + } } /// Clears the map, returning all entries as an iterator. /// /// The internal memory is kept for reuse. /// + /// For each yielded item that has `None` provided for the `HeaderName`, + /// then the associated header name is the same as that of the previously + /// yielded item. The first yielded item will have `HeaderName` set. + /// /// # Examples /// /// ``` @@ -922,20 +938,15 @@ /// /// let mut drain = map.drain(); /// - /// let (key, mut vals) = drain.next().unwrap(); /// - /// assert_eq!("host", key); - /// assert_eq!("hello", vals.next().unwrap()); - /// assert_eq!("goodbye", vals.next().unwrap()); - /// assert!(vals.next().is_none()); + /// assert_eq!(drain.next(), Some((Some(HOST), "hello".parse().unwrap()))); + /// assert_eq!(drain.next(), Some((None, "goodbye".parse().unwrap()))); /// - /// let (key, mut vals) = drain.next().unwrap(); + /// assert_eq!(drain.next(), Some((Some(CONTENT_LENGTH), "123".parse().unwrap()))); /// - /// assert_eq!("content-length", key); - /// assert_eq!("123", vals.next().unwrap()); - /// assert!(vals.next().is_none()); + /// assert_eq!(drain.next(), None); /// ``` - pub fn drain(&mut self) -> Drain { + pub fn drain(&mut self) -> Drain<'_, T> { for i in self.indices.iter_mut() { *i = Pos::none(); } @@ -957,20 +968,19 @@ len, entries, extra_values, + next: None, lt: PhantomData, } } - fn value_iter(&self, idx: Option) -> ValueIter { + fn value_iter(&self, idx: Option) -> ValueIter<'_, T> { use self::Cursor::*; if let Some(idx) = idx { let back = { let entry = &self.entries[idx]; - entry.links - .map(|l| Values(l.tail)) - .unwrap_or(Head) + entry.links.map(|l| Values(l.tail)).unwrap_or(Head) }; ValueIter { @@ -989,15 +999,13 @@ } } - fn value_iter_mut(&mut self, idx: usize) -> ValueIterMut { + fn value_iter_mut(&mut self, idx: usize) -> ValueIterMut<'_, T> { use self::Cursor::*; let back = { let entry = &self.entries[idx]; - entry.links - .map(|l| Values(l.tail)) - .unwrap_or(Head) + entry.links.map(|l| Values(l.tail)).unwrap_or(Head) }; ValueIterMut { @@ -1026,22 +1034,40 @@ /// ]; /// /// for &header in headers { - /// let counter = map.entry(header).unwrap().or_insert(0); + /// let counter = map.entry(header).or_insert(0); /// *counter += 1; /// } /// /// assert_eq!(map["content-length"], 2); /// assert_eq!(map["x-hello"], 1); /// ``` - pub fn entry(&mut self, key: K) -> Result, InvalidHeaderName> - where K: AsHeaderName, + pub fn entry(&mut self, key: K) -> Entry<'_, T> + where + K: IntoHeaderName, { key.entry(self) } - fn entry2(&mut self, key: K) -> Entry - where K: Hash + Into, - HeaderName: PartialEq, + /// Gets the given key's corresponding entry in the map for in-place + /// manipulation. + /// + /// # Errors + /// + /// This method differs from `entry` by allowing types that may not be + /// valid `HeaderName`s to passed as the key (such as `String`). If they + /// do not parse as a valid `HeaderName`, this returns an + /// `InvalidHeaderName` error. + pub fn try_entry(&mut self, key: K) -> Result, InvalidHeaderName> + where + K: AsHeaderName, + { + key.try_entry(self) + } + + fn entry2(&mut self, key: K) -> Entry<'_, T> + where + K: Hash + Into, + HeaderName: PartialEq, { // Ensure that there is space in the map self.reserve_one(); @@ -1071,7 +1097,8 @@ key: key.into(), probe: probe, danger: danger, - })) + }) + ) } /// Inserts a key-value pair into the map. @@ -1102,20 +1129,27 @@ /// assert_eq!("world", prev); /// ``` pub fn insert(&mut self, key: K, val: T) -> Option - where K: IntoHeaderName, + where + K: IntoHeaderName, { key.insert(self, val) } #[inline] fn insert2(&mut self, key: K, value: T) -> Option - where K: Hash + Into, - HeaderName: PartialEq, + where + K: Hash + Into, + HeaderName: PartialEq, { self.reserve_one(); insert_phase_one!( - self, key, probe, pos, hash, danger, + self, + key, + probe, + pos, + hash, + danger, // Vacant { drop(danger); // Make lint happy @@ -1128,14 +1162,10 @@ Some(self.insert_occupied(pos, value)), // Robinhood { - self.insert_phase_two( - key.into(), - value, - hash, - probe, - danger); + self.insert_phase_two(key.into(), value, hash, probe, danger); None - }) + } + ) } /// Set an occupied bucket to the given value @@ -1149,7 +1179,7 @@ mem::replace(&mut entry.value, value) } - fn insert_occupied_mult(&mut self, index: usize, value: T) -> ValueDrain { + fn insert_occupied_mult(&mut self, index: usize, value: T) -> ValueDrain<'_, T> { let old; let links; @@ -1202,20 +1232,27 @@ /// assert_eq!("earth", *i.next().unwrap()); /// ``` pub fn append(&mut self, key: K, value: T) -> bool - where K: IntoHeaderName, + where + K: IntoHeaderName, { key.append(self, value) } #[inline] fn append2(&mut self, key: K, value: T) -> bool - where K: Hash + Into, - HeaderName: PartialEq, + where + K: Hash + Into, + HeaderName: PartialEq, { self.reserve_one(); insert_phase_one!( - self, key, probe, pos, hash, danger, + self, + key, + probe, + pos, + hash, + danger, // Vacant { drop(danger); @@ -1231,21 +1268,18 @@ }, // Robinhood { - self.insert_phase_two( - key.into(), - value, - hash, - probe, - danger); + self.insert_phase_two(key.into(), value, hash, probe, danger); false - }) + } + ) } #[inline] fn find(&self, key: &K) -> Option<(usize, usize)> - where K: Hash + Into, - HeaderName: PartialEq, + where + K: Hash + Into, + HeaderName: PartialEq, { if self.entries.is_empty() { return None; @@ -1274,21 +1308,19 @@ /// phase 2 is post-insert where we forward-shift `Pos` in the indices. #[inline] - fn insert_phase_two(&mut self, - key: HeaderName, - value: T, - hash: HashValue, - probe: usize, - danger: bool) -> usize - { + fn insert_phase_two( + &mut self, + key: HeaderName, + value: T, + hash: HashValue, + probe: usize, + danger: bool, + ) -> usize { // Push the value and get the index let index = self.entries.len(); self.insert_entry(hash, key, value); - let num_displaced = do_insert_phase_two( - &mut self.indices, - probe, - Pos::new(index, hash)); + let num_displaced = do_insert_phase_two(&mut self.indices, probe, Pos::new(index, hash)); if danger || num_displaced >= DISPLACEMENT_THRESHOLD { // Increase danger level @@ -1319,7 +1351,8 @@ /// assert!(map.remove(HOST).is_none()); /// ``` pub fn remove(&mut self, key: K) -> Option - where K: AsHeaderName + where + K: AsHeaderName, { match key.find(self) { Some((probe, idx)) => { @@ -1336,6 +1369,10 @@ } /// Remove an entry from the map. + /// + /// Warning: To avoid inconsistent state, extra values _must_ be removed + /// for the `found` index (via `remove_all_extra_values` or similar) + /// _before_ this method is called. #[inline] fn remove_found(&mut self, probe: usize, found: usize) -> Bucket { // index `probe` and entry `found` is to be removed @@ -1425,8 +1462,7 @@ fn rebuild(&mut self) { // Loop over all entries and re-insert them into the map - 'outer: - for (index, entry) in self.entries.iter_mut().enumerate() { + 'outer: for (index, entry) in self.entries.iter_mut().enumerate() { let hash = hash_elem_using(&self.danger, &entry.key); let mut probe = desired_pos(self.mask, hash); let mut dist = 0; @@ -1452,10 +1488,7 @@ dist += 1; }); - do_insert_phase_two( - &mut self.indices, - probe, - Pos::new(index, hash)); + do_insert_phase_two(&mut self.indices, probe, Pos::new(index, hash)); } } @@ -1514,6 +1547,7 @@ #[inline] fn grow(&mut self, new_raw_cap: usize) { + assert!(new_raw_cap <= MAX_SIZE, "requested capacity too large"); // This path can never be reached when handling the first allocation in // the map. @@ -1531,7 +1565,10 @@ // visit the entries in an order where we can simply reinsert them // into self.indices without any bucket stealing. - let old_indices = mem::replace(&mut self.indices, vec![Pos::none(); new_raw_cap].into_boxed_slice()); + let old_indices = mem::replace( + &mut self.indices, + vec![Pos::none(); new_raw_cap].into_boxed_slice(), + ); self.mask = new_raw_cap.wrapping_sub(1) as Size; for &pos in &old_indices[first_ideal..] { @@ -1555,7 +1592,12 @@ /// Removes the `ExtraValue` at the given index. #[inline] -fn remove_extra_value(mut raw_links: RawLinks, extra_values: &mut Vec>, idx: usize) -> ExtraValue { +fn remove_extra_value( + mut raw_links: RawLinks, + extra_values: &mut Vec>, + idx: usize) + -> ExtraValue +{ let prev; let next; @@ -1671,8 +1713,12 @@ extra } - -fn drain_all_extra_values(raw_links: RawLinks, extra_values: &mut Vec>, mut head: usize) -> Vec { +fn drain_all_extra_values( + raw_links: RawLinks, + extra_values: &mut Vec>, + mut head: usize) + -> Vec +{ let mut vec = Vec::new(); loop { let extra = remove_extra_value(raw_links, extra_values, head); @@ -1767,14 +1813,14 @@ } } -impl FromIterator<(HeaderName, T)> for HeaderMap -{ +impl FromIterator<(HeaderName, T)> for HeaderMap { fn from_iter(iter: I) -> Self - where I: IntoIterator + where + I: IntoIterator, { - let mut map = HeaderMap::default(); - map.extend(iter); - map + let mut map = HeaderMap::default(); + map.extend(iter); + map } } @@ -1784,27 +1830,30 @@ /// /// ``` /// use std::collections::HashMap; -/// use http::{HttpTryFrom, header::HeaderMap}; +/// use std::convert::TryInto; +/// use http::HeaderMap; /// /// let mut map = HashMap::new(); /// map.insert("X-Custom-Header".to_string(), "my value".to_string()); /// -/// let headers: HeaderMap = HttpTryFrom::try_from(&map).expect("valid headers"); +/// let headers: HeaderMap = (&map).try_into().expect("valid headers"); /// assert_eq!(headers["X-Custom-Header"], "my value"); /// ``` -impl<'a, K, V, T> HttpTryFrom<&'a HashMap> for HeaderMap +impl<'a, K, V, T> TryFrom<&'a HashMap> for HeaderMap where K: Eq + Hash, - HeaderName: HttpTryFrom<&'a K>, - T: HttpTryFrom<&'a V> + HeaderName: TryFrom<&'a K>, + >::Error: Into, + T: TryFrom<&'a V>, + T::Error: Into, { type Error = Error; fn try_from(c: &'a HashMap) -> Result { c.into_iter() - .map(|(k, v)| { - let name = k.http_try_into()?; - let value = v.http_try_into()?; + .map(|(k, v)| -> crate::Result<(HeaderName, T)> { + let name = TryFrom::try_from(k).map_err(Into::into)?; + let value = TryFrom::try_from(v).map_err(Into::into)?; Ok((name, value)) }) .collect() @@ -1859,8 +1908,7 @@ None => return, }; - 'outer: - loop { + 'outer: loop { let mut entry = match self.entry2(key) { Entry::Occupied(mut e) => { // Replace all previous values while maintaining a handle to @@ -1868,9 +1916,7 @@ e.insert(val); e } - Entry::Vacant(e) => { - e.insert_entry(val) - } + Entry::Vacant(e) => e.insert_entry(val), }; // As long as `HeaderName` is none, keep inserting the value into @@ -1894,8 +1940,7 @@ } } -impl Extend<(HeaderName, T)> for HeaderMap -{ +impl Extend<(HeaderName, T)> for HeaderMap { fn extend>(&mut self, iter: I) { // Keys may be already present or show multiple times in the iterator. // Reserve the entire hint lower bound if the map is empty. @@ -1923,16 +1968,15 @@ return false; } - self.keys().all(|key| { - self.get_all(key) == other.get_all(key) - }) + self.keys() + .all(|key| self.get_all(key) == other.get_all(key)) } } impl Eq for HeaderMap {} impl fmt::Debug for HeaderMap { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_map().entries(self.iter()).finish() } } @@ -1944,7 +1988,8 @@ } impl<'a, K, T> ops::Index for HeaderMap - where K: AsHeaderName, +where + K: AsHeaderName, { type Output = T; @@ -1963,11 +2008,7 @@ /// /// returns the number of displaced elements #[inline] -fn do_insert_phase_two(indices: &mut [Pos], - mut probe: usize, - mut old_pos: Pos) - -> usize -{ +fn do_insert_phase_two(indices: &mut [Pos], mut probe: usize, mut old_pos: Pos) -> usize { let mut num_displaced = 0; probe_loop!(probe < indices.len(), { @@ -1986,11 +2027,12 @@ } #[inline] -fn append_value(entry_idx: usize, - entry: &mut Bucket, - extra: &mut Vec>, - value: T) -{ +fn append_value( + entry_idx: usize, + entry: &mut Bucket, + extra: &mut Vec>, + value: T, +) { match entry.links { Some(links) => { let idx = extra.len(); @@ -2002,10 +2044,7 @@ extra[links.tail].next = Link::Extra(idx); - entry.links = Some(Links { - tail: idx, - .. links - }); + entry.links = Some(Links { tail: idx, ..links }); } None => { let idx = extra.len(); @@ -2029,9 +2068,9 @@ type Item = (&'a HeaderName, &'a T); fn next(&mut self) -> Option { - self.inner.next_unsafe().map(|(key, ptr)| { - (key, unsafe { &*ptr }) - }) + self.inner + .next_unsafe() + .map(|(key, ptr)| (key, unsafe { &*ptr })) } fn size_hint(&self) -> (usize, Option) { @@ -2039,6 +2078,8 @@ } } +impl<'a, T> FusedIterator for Iter<'a, T> {} + unsafe impl<'a, T: Sync> Sync for Iter<'a, T> {} unsafe impl<'a, T: Sync> Send for Iter<'a, T> {} @@ -2082,9 +2123,8 @@ type Item = (&'a HeaderName, &'a mut T); fn next(&mut self) -> Option { - self.next_unsafe().map(|(key, ptr)| { - (key, unsafe { &mut *ptr }) - }) + self.next_unsafe() + .map(|(key, ptr)| (key, unsafe { &mut *ptr })) } fn size_hint(&self) -> (usize, Option) { @@ -2100,6 +2140,8 @@ } } +impl<'a, T> FusedIterator for IterMut<'a, T> {} + unsafe impl<'a, T: Sync> Sync for IterMut<'a, T> {} unsafe impl<'a, T: Send> Send for IterMut<'a, T> {} @@ -2118,6 +2160,7 @@ } impl<'a, T> ExactSizeIterator for Keys<'a, T> {} +impl<'a, T> FusedIterator for Keys<'a, T> {} // ===== impl Values ==== @@ -2133,6 +2176,8 @@ } } +impl<'a, T> FusedIterator for Values<'a, T> {} + // ===== impl ValuesMut ==== impl<'a, T> Iterator for ValuesMut<'a, T> { @@ -2147,12 +2192,30 @@ } } +impl<'a, T> FusedIterator for ValuesMut<'a, T> {} + // ===== impl Drain ===== impl<'a, T> Iterator for Drain<'a, T> { - type Item = (HeaderName, ValueDrain<'a, T>); + type Item = (Option, T); fn next(&mut self) -> Option { + if let Some(next) = self.next { + // Remove the extra value + + let raw_links = RawLinks(self.entries); + let extra = unsafe { + remove_extra_value(raw_links, &mut *self.extra_values, next) + }; + + match extra.next { + Link::Extra(idx) => self.next = Some(idx), + Link::Entry(_) => self.next = None, + } + + return Some((None, extra.value)); + } + let idx = self.idx; if idx == self.len { @@ -2161,40 +2224,32 @@ self.idx += 1; - let key; - let value; - let next; - - let values = unsafe { + unsafe { let entry = &(*self.entries)[idx]; // Read the header name - key = ptr::read(&entry.key as *const _); - value = ptr::read(&entry.value as *const _); + let key = ptr::read(&entry.key as *const _); + let value = ptr::read(&entry.value as *const _); + self.next = entry.links.map(|l| l.next); - let raw_links = RawLinks(self.entries); - let extra_values = &mut *self.extra_values; - next = entry.links.map(|l| { - drain_all_extra_values(raw_links, extra_values, l.next) - .into_iter() - }); - - ValueDrain { - first: Some(value), - next, - lt: PhantomData, - } - }; - - Some((key, values)) + Some((Some(key), value)) + } } fn size_hint(&self) -> (usize, Option) { + // At least this many names... It's unknown if the user wants + // to count the extra_values on top. + // + // For instance, extending a new `HeaderMap` wouldn't need to + // reserve the upper-bound in `entries`, only the lower-bound. let lower = self.len - self.idx; - (lower, Some(lower)) + let upper = unsafe { (*self.extra_values).len() } + lower; + (lower, Some(upper)) } } +impl<'a, T> FusedIterator for Drain<'a, T> {} + impl<'a, T> Drop for Drain<'a, T> { fn drop(&mut self) { for _ in self {} @@ -2226,7 +2281,6 @@ /// /// for &header in headers { /// let counter = map.entry(header) - /// .expect("valid header names") /// .or_insert(0); /// *counter += 1; /// } @@ -2257,7 +2311,7 @@ /// # use http::HeaderMap; /// let mut map = HeaderMap::new(); /// - /// let res = map.entry("x-hello").unwrap() + /// let res = map.entry("x-hello") /// .or_insert_with(|| "world".parse().unwrap()); /// /// assert_eq!(res, "world"); @@ -2272,7 +2326,6 @@ /// map.insert(HOST, "world".parse().unwrap()); /// /// let res = map.entry("host") - /// .expect("host is a valid string") /// .or_insert_with(|| unreachable!()); /// /// @@ -2295,7 +2348,7 @@ /// # use http::HeaderMap; /// let mut map = HeaderMap::new(); /// - /// assert_eq!(map.entry("x-hello").unwrap().key(), "x-hello"); + /// assert_eq!(map.entry("x-hello").key(), "x-hello"); /// ``` pub fn key(&self) -> &HeaderName { use self::Entry::*; @@ -2318,7 +2371,7 @@ /// # use http::HeaderMap; /// let mut map = HeaderMap::new(); /// - /// assert_eq!(map.entry("x-hello").unwrap().key().as_str(), "x-hello"); + /// assert_eq!(map.entry("x-hello").key().as_str(), "x-hello"); /// ``` pub fn key(&self) -> &HeaderName { &self.key @@ -2332,7 +2385,7 @@ /// # use http::header::{HeaderMap, Entry}; /// let mut map = HeaderMap::new(); /// - /// if let Entry::Vacant(v) = map.entry("x-hello").unwrap() { + /// if let Entry::Vacant(v) = map.entry("x-hello") { /// assert_eq!(v.into_key().as_str(), "x-hello"); /// } /// ``` @@ -2351,7 +2404,7 @@ /// # use http::header::{HeaderMap, Entry}; /// let mut map = HeaderMap::new(); /// - /// if let Entry::Vacant(v) = map.entry("x-hello").unwrap() { + /// if let Entry::Vacant(v) = map.entry("x-hello") { /// v.insert("world".parse().unwrap()); /// } /// @@ -2359,12 +2412,9 @@ /// ``` pub fn insert(self, value: T) -> &'a mut T { // Ensure that there is space in the map - let index = self.map.insert_phase_two( - self.key, - value.into(), - self.hash, - self.probe, - self.danger); + let index = + self.map + .insert_phase_two(self.key, value.into(), self.hash, self.probe, self.danger); &mut self.map.entries[index].value } @@ -2380,7 +2430,7 @@ /// # use http::header::*; /// let mut map = HeaderMap::new(); /// - /// if let Entry::Vacant(v) = map.entry("x-hello").unwrap() { + /// if let Entry::Vacant(v) = map.entry("x-hello") { /// let mut e = v.insert_entry("world".parse().unwrap()); /// e.insert("world2".parse().unwrap()); /// } @@ -2389,12 +2439,9 @@ /// ``` pub fn insert_entry(self, value: T) -> OccupiedEntry<'a, T> { // Ensure that there is space in the map - let index = self.map.insert_phase_two( - self.key, - value.into(), - self.hash, - self.probe, - self.danger); + let index = + self.map + .insert_phase_two(self.key, value.into(), self.hash, self.probe, self.danger); OccupiedEntry { map: self.map, @@ -2404,7 +2451,6 @@ } } - // ===== impl GetAll ===== impl<'a, T: 'a> GetAll<'a, T> { @@ -2433,7 +2479,8 @@ GetAll { map: self.map, index: self.index, - }.into_iter() + } + .into_iter() } } @@ -2469,7 +2516,6 @@ fn next(&mut self) -> Option { use self::Cursor::*; - match self.front { Some(Head) => { let entry = &self.map.entries[self.index]; @@ -2524,7 +2570,6 @@ fn next_back(&mut self) -> Option { use self::Cursor::*; - match self.back { Some(Head) => { self.front = None; @@ -2551,6 +2596,8 @@ } } +impl<'a, T> FusedIterator for ValueIter<'a, T> {} + // ===== impl ValueIterMut ===== impl<'a, T: 'a> Iterator for ValueIterMut<'a, T> { @@ -2630,6 +2677,8 @@ } } +impl<'a, T> FusedIterator for ValueIterMut<'a, T> {} + unsafe impl<'a, T: Sync> Sync for ValueIterMut<'a, T> {} unsafe impl<'a, T: Send> Send for ValueIterMut<'a, T> {} @@ -2670,13 +2719,17 @@ } } +impl FusedIterator for IntoIter {} + impl Drop for IntoIter { fn drop(&mut self) { // Ensure the iterator is consumed - for _ in self.by_ref() { } + for _ in self.by_ref() {} // All the values have already been yielded out. - unsafe { self.extra_values.set_len(0); } + unsafe { + self.extra_values.set_len(0); + } } } @@ -2692,7 +2745,7 @@ /// let mut map = HeaderMap::new(); /// map.insert(HOST, "world".parse().unwrap()); /// - /// if let Entry::Occupied(e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(e) = map.entry("host") { /// assert_eq!("host", e.key()); /// } /// ``` @@ -2715,7 +2768,7 @@ /// let mut map = HeaderMap::new(); /// map.insert(HOST, "hello.world".parse().unwrap()); /// - /// if let Entry::Occupied(mut e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(mut e) = map.entry("host") { /// assert_eq!(e.get(), &"hello.world"); /// /// e.append("hello.earth".parse().unwrap()); @@ -2742,7 +2795,7 @@ /// let mut map = HeaderMap::default(); /// map.insert(HOST, "hello.world".to_string()); /// - /// if let Entry::Occupied(mut e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(mut e) = map.entry("host") { /// e.get_mut().push_str("-2"); /// assert_eq!(e.get(), &"hello.world-2"); /// } @@ -2768,7 +2821,7 @@ /// map.insert(HOST, "hello.world".to_string()); /// map.append(HOST, "hello.earth".to_string()); /// - /// if let Entry::Occupied(e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(e) = map.entry("host") { /// e.into_mut().push_str("-2"); /// } /// @@ -2790,7 +2843,7 @@ /// let mut map = HeaderMap::new(); /// map.insert(HOST, "hello.world".parse().unwrap()); /// - /// if let Entry::Occupied(mut e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(mut e) = map.entry("host") { /// let mut prev = e.insert("earth".parse().unwrap()); /// assert_eq!("hello.world", prev); /// } @@ -2814,7 +2867,7 @@ /// map.insert(HOST, "world".parse().unwrap()); /// map.append(HOST, "world2".parse().unwrap()); /// - /// if let Entry::Occupied(mut e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(mut e) = map.entry("host") { /// let mut prev = e.insert_mult("earth".parse().unwrap()); /// assert_eq!("world", prev.next().unwrap()); /// assert_eq!("world2", prev.next().unwrap()); @@ -2823,7 +2876,7 @@ /// /// assert_eq!("earth", map["host"]); /// ``` - pub fn insert_mult(&mut self, value: T) -> ValueDrain { + pub fn insert_mult(&mut self, value: T) -> ValueDrain<'_, T> { self.map.insert_occupied_mult(self.index, value.into()) } @@ -2839,7 +2892,7 @@ /// let mut map = HeaderMap::new(); /// map.insert(HOST, "world".parse().unwrap()); /// - /// if let Entry::Occupied(mut e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(mut e) = map.entry("host") { /// e.append("earth".parse().unwrap()); /// } /// @@ -2866,7 +2919,7 @@ /// let mut map = HeaderMap::new(); /// map.insert(HOST, "world".parse().unwrap()); /// - /// if let Entry::Occupied(e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(e) = map.entry("host") { /// let mut prev = e.remove(); /// assert_eq!("world", prev); /// } @@ -2890,7 +2943,7 @@ /// let mut map = HeaderMap::new(); /// map.insert(HOST, "world".parse().unwrap()); /// - /// if let Entry::Occupied(e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(e) = map.entry("host") { /// let (key, mut prev) = e.remove_entry(); /// assert_eq!("host", key.as_str()); /// assert_eq!("world", prev); @@ -2899,12 +2952,12 @@ /// assert!(!map.contains_key("host")); /// ``` pub fn remove_entry(self) -> (HeaderName, T) { - let entry = self.map.remove_found(self.probe, self.index); - - if let Some(links) = entry.links { + if let Some(links) = self.map.entries[self.index].links { self.map.remove_all_extra_values(links.next); } + let entry = self.map.remove_found(self.probe, self.index); + (entry.key, entry.value) } @@ -2913,14 +2966,16 @@ /// The key and all values associated with the entry are removed and /// returned. pub fn remove_entry_mult(self) -> (HeaderName, ValueDrain<'a, T>) { - let entry = self.map.remove_found(self.probe, self.index); let raw_links = self.map.raw_links(); let extra_values = &mut self.map.extra_values; - let next = entry.links.map(|l| { + let next = self.map.entries[self.index].links.map(|l| { drain_all_extra_values(raw_links, extra_values, l.next) .into_iter() }); + + let entry = self.map.remove_found(self.probe, self.index); + let drain = ValueDrain { first: Some(entry.value), next, @@ -2941,14 +2996,14 @@ /// map.insert(HOST, "world".parse().unwrap()); /// map.append(HOST, "earth".parse().unwrap()); /// - /// if let Entry::Occupied(e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(e) = map.entry("host") { /// let mut iter = e.iter(); /// assert_eq!(&"world", iter.next().unwrap()); /// assert_eq!(&"earth", iter.next().unwrap()); /// assert!(iter.next().is_none()); /// } /// ``` - pub fn iter(&self) -> ValueIter { + pub fn iter(&self) -> ValueIter<'_, T> { self.map.value_iter(Some(self.index)) } @@ -2965,7 +3020,7 @@ /// map.insert(HOST, "world".to_string()); /// map.append(HOST, "earth".to_string()); /// - /// if let Entry::Occupied(mut e) = map.entry("host").unwrap() { + /// if let Entry::Occupied(mut e) = map.entry("host") { /// for e in e.iter_mut() { /// e.push_str("-boop"); /// } @@ -2976,7 +3031,7 @@ /// assert_eq!(&"world-boop", i.next().unwrap()); /// assert_eq!(&"earth-boop", i.next().unwrap()); /// ``` - pub fn iter_mut(&mut self) -> ValueIterMut { + pub fn iter_mut(&mut self) -> ValueIterMut<'_, T> { self.map.value_iter_mut(self.index) } } @@ -3040,10 +3095,11 @@ } } +impl<'a, T> FusedIterator for ValueDrain<'a, T> {} + impl<'a, T> Drop for ValueDrain<'a, T> { fn drop(&mut self) { - while let Some(_) = self.next() { - } + while let Some(_) = self.next() {} } } @@ -3083,6 +3139,7 @@ impl Pos { #[inline] fn new(index: usize, hash: HashValue) -> Self { + debug_assert!(index < MAX_SIZE); Pos { index: index as Size, hash: hash, @@ -3110,7 +3167,7 @@ #[inline] fn resolve(&self) -> Option<(usize, HashValue)> { if self.is_some() { - Some((self.index, self.hash)) + Some((self.index as usize, self.hash)) } else { None } @@ -3166,17 +3223,18 @@ #[inline] fn desired_pos(mask: Size, hash: HashValue) -> usize { - (hash.0 & mask) + (hash.0 & mask) as usize } /// The number of steps that `current` is forward of the desired position for hash #[inline] fn probe_distance(mask: Size, hash: HashValue, current: usize) -> usize { - current.wrapping_sub(desired_pos(mask, hash)) & mask + current.wrapping_sub(desired_pos(mask, hash)) & mask as usize } fn hash_elem_using(danger: &Danger, k: &K) -> HashValue - where K: Hash +where + K: Hash, { use fnv::FnvHasher; @@ -3197,7 +3255,7 @@ } }; - HashValue((hash & MASK) as usize) + HashValue((hash & MASK) as u16) } /* @@ -3206,9 +3264,8 @@ * */ - mod into_header_name { - use super::{HdrName, HeaderMap, HeaderName}; + use super::{Entry, HdrName, HeaderMap, HeaderName}; /// A marker trait used to identify values that can be used as insert keys /// to a `HeaderMap`. @@ -3228,6 +3285,9 @@ #[doc(hidden)] fn append(self, map: &mut HeaderMap, val: T) -> bool; + + #[doc(hidden)] + fn entry(self, map: &mut HeaderMap) -> Entry<'_, T>; } // ==== impls ==== @@ -3244,6 +3304,12 @@ fn append(self, map: &mut HeaderMap, val: T) -> bool { map.append2(self, val) } + + #[doc(hidden)] + #[inline] + fn entry(self, map: &mut HeaderMap) -> Entry<'_, T> { + map.entry2(self) + } } impl IntoHeaderName for HeaderName {} @@ -3259,6 +3325,12 @@ fn append(self, map: &mut HeaderMap, val: T) -> bool { map.append2(self, val) } + + #[doc(hidden)] + #[inline] + fn entry(self, map: &mut HeaderMap) -> Entry<'_, T> { + map.entry2(self) + } } impl<'a> IntoHeaderName for &'a HeaderName {} @@ -3274,6 +3346,12 @@ fn append(self, map: &mut HeaderMap, val: T) -> bool { HdrName::from_static(self, move |hdr| map.append2(hdr, val)) } + + #[doc(hidden)] + #[inline] + fn entry(self, map: &mut HeaderMap) -> Entry<'_, T> { + HdrName::from_static(self, move |hdr| map.entry2(hdr)) + } } impl IntoHeaderName for &'static str {} @@ -3296,7 +3374,7 @@ // without breaking any external crate. pub trait Sealed { #[doc(hidden)] - fn entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName>; + fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName>; #[doc(hidden)] fn find(&self, map: &HeaderMap) -> Option<(usize, usize)>; @@ -3310,7 +3388,7 @@ impl Sealed for HeaderName { #[doc(hidden)] #[inline] - fn entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { + fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { Ok(map.entry2(self)) } @@ -3331,7 +3409,7 @@ impl<'a> Sealed for &'a HeaderName { #[doc(hidden)] #[inline] - fn entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { + fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { Ok(map.entry2(self)) } @@ -3352,7 +3430,7 @@ impl<'a> Sealed for &'a str { #[doc(hidden)] #[inline] - fn entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { + fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { HdrName::from_bytes(self.as_bytes(), move |hdr| map.entry2(hdr)) } @@ -3373,8 +3451,8 @@ impl Sealed for String { #[doc(hidden)] #[inline] - fn entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { - self.as_str().entry(map) + fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { + self.as_str().try_entry(map) } #[doc(hidden)] @@ -3394,8 +3472,8 @@ impl<'a> Sealed for &'a String { #[doc(hidden)] #[inline] - fn entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { - self.as_str().entry(map) + fn try_entry(self, map: &mut HeaderMap) -> Result, InvalidHeaderName> { + self.as_str().try_entry(map) } #[doc(hidden)] @@ -3413,7 +3491,6 @@ impl<'a> AsHeaderName for &'a String {} } - #[test] fn test_bounds() { fn check_bounds() {} diff -Nru rust-http-0.1.21/src/header/mod.rs rust-http-0.2.7/src/header/mod.rs --- rust-http-0.1.21/src/header/mod.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/header/mod.rs 1973-11-29 21:33:09.000000000 +0000 @@ -75,35 +75,11 @@ mod value; pub use self::map::{ - HeaderMap, - AsHeaderName, - IntoHeaderName, - Iter, - IterMut, - Keys, - Values, - ValuesMut, - Drain, - GetAll, - Entry, - VacantEntry, - OccupiedEntry, - ValueIter, - ValueIterMut, - ValueDrain, - IntoIter, -}; -pub use self::name::{ - HeaderName, - InvalidHeaderName, - InvalidHeaderNameBytes, -}; -pub use self::value::{ - HeaderValue, - InvalidHeaderValue, - InvalidHeaderValueBytes, - ToStrError, + AsHeaderName, Drain, Entry, GetAll, HeaderMap, IntoHeaderName, IntoIter, Iter, IterMut, Keys, + OccupiedEntry, VacantEntry, ValueDrain, ValueIter, ValueIterMut, Values, ValuesMut, }; +pub use self::name::{HeaderName, InvalidHeaderName}; +pub use self::value::{HeaderValue, InvalidHeaderValue, ToStrError}; // Use header name constants pub use self::name::{ @@ -193,4 +169,4 @@ /// Generally, 64kb for a header name is WAY too much than would ever be needed /// in practice. Restricting it to this size enables using `u16` values to /// represent offsets when dealing with header names. -const MAX_HEADER_NAME_LEN: usize = 1 << 16; +const MAX_HEADER_NAME_LEN: usize = (1 << 16) - 1; diff -Nru rust-http-0.1.21/src/header/name.rs rust-http-0.2.7/src/header/name.rs --- rust-http-0.1.21/src/header/name.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/header/name.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,12 +1,12 @@ -use HttpTryFrom; -use byte_str::ByteStr; +use crate::byte_str::ByteStr; use bytes::{Bytes, BytesMut}; -use std::{fmt, mem}; use std::borrow::Borrow; +use std::error::Error; +use std::convert::{TryFrom}; use std::hash::{Hash, Hasher}; use std::str::FromStr; -use std::error::Error; +use std::{fmt, mem}; /// Represents an HTTP header field name /// @@ -60,15 +60,11 @@ _priv: (), } -/// A possible error when converting a `HeaderName` from another type. -#[derive(Debug)] -pub struct InvalidHeaderNameBytes(InvalidHeaderName) ; - macro_rules! standard_headers { ( $( $(#[$docs:meta])* - ($konst:ident, $upcase:ident, $name:expr); + ($konst:ident, $upcase:ident, $name_bytes:literal); )+ ) => { #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] @@ -89,52 +85,60 @@ #[inline] fn as_str(&self) -> &'static str { match *self { + // Safety: test_parse_standard_headers ensures these &[u8]s are &str-safe. + $( + StandardHeader::$konst => unsafe { std::str::from_utf8_unchecked( $name_bytes ) }, + )+ + } + } + + const fn from_bytes(name_bytes: &[u8]) -> Option { + match name_bytes { $( - StandardHeader::$konst => $name, + $name_bytes => Some(StandardHeader::$konst), )+ + _ => None, } } } #[cfg(test)] - const TEST_HEADERS: &'static [(StandardHeader, &'static str)] = &[ + const TEST_HEADERS: &'static [(StandardHeader, &'static [u8])] = &[ $( - (StandardHeader::$konst, $name), + (StandardHeader::$konst, $name_bytes), )+ ]; #[test] fn test_parse_standard_headers() { - for &(std, name) in TEST_HEADERS { + for &(std, name_bytes) in TEST_HEADERS { // Test lower case - assert_eq!(HeaderName::from_bytes(name.as_bytes()).unwrap(), HeaderName::from(std)); + assert_eq!(HeaderName::from_bytes(name_bytes).unwrap(), HeaderName::from(std)); // Test upper case - let upper = name.to_uppercase().to_string(); + let upper = std::str::from_utf8(name_bytes).expect("byte string constants are all utf-8").to_uppercase(); assert_eq!(HeaderName::from_bytes(upper.as_bytes()).unwrap(), HeaderName::from(std)); } } #[test] fn test_standard_headers_into_bytes() { - for &(std, name) in TEST_HEADERS { + for &(std, name_bytes) in TEST_HEADERS { + let name = std::str::from_utf8(name_bytes).unwrap(); let std = HeaderName::from(std); // Test lower case - let name_bytes = name.as_bytes(); let bytes: Bytes = - HeaderName::from_bytes(name_bytes).unwrap().into(); - assert_eq!(bytes, name_bytes); + HeaderName::from_bytes(name_bytes).unwrap().inner.into(); + assert_eq!(bytes, name); assert_eq!(HeaderName::from_bytes(name_bytes).unwrap(), std); // Test upper case - let upper = name.to_uppercase().to_string(); + let upper = name.to_uppercase(); let bytes: Bytes = - HeaderName::from_bytes(upper.as_bytes()).unwrap().into(); - assert_eq!(bytes, name.as_bytes()); + HeaderName::from_bytes(upper.as_bytes()).unwrap().inner.into(); + assert_eq!(bytes, name_bytes); assert_eq!(HeaderName::from_bytes(upper.as_bytes()).unwrap(), std); - - } } @@ -158,7 +162,7 @@ /// where the request is done: when fetching a CSS stylesheet a different /// value is set for the request than when fetching an image, video or a /// script. - (Accept, ACCEPT, "accept"); + (Accept, ACCEPT, b"accept"); /// Advertises which character set the client is able to understand. /// @@ -173,7 +177,7 @@ /// theoretically send back a 406 (Not Acceptable) error code. But, for a /// better user experience, this is rarely done and the more common way is /// to ignore the Accept-Charset header in this case. - (AcceptCharset, ACCEPT_CHARSET, "accept-charset"); + (AcceptCharset, ACCEPT_CHARSET, b"accept-charset"); /// Advertises which content encoding the client is able to understand. /// @@ -201,7 +205,7 @@ /// forbidden, by an identity;q=0 or a *;q=0 without another explicitly set /// value for identity, the server must never send back a 406 Not Acceptable /// error. - (AcceptEncoding, ACCEPT_ENCODING, "accept-encoding"); + (AcceptEncoding, ACCEPT_ENCODING, b"accept-encoding"); /// Advertises which languages the client is able to understand. /// @@ -226,7 +230,7 @@ /// send back a 406 (Not Acceptable) error code. But, for a better user /// experience, this is rarely done and more common way is to ignore the /// Accept-Language header in this case. - (AcceptLanguage, ACCEPT_LANGUAGE, "accept-language"); + (AcceptLanguage, ACCEPT_LANGUAGE, b"accept-language"); /// Marker used by the server to advertise partial request support. /// @@ -236,7 +240,7 @@ /// /// In presence of an Accept-Ranges header, the browser may try to resume an /// interrupted download, rather than to start it from the start again. - (AcceptRanges, ACCEPT_RANGES, "accept-ranges"); + (AcceptRanges, ACCEPT_RANGES, b"accept-ranges"); /// Preflight response indicating if the response to the request can be /// exposed to the page. @@ -261,7 +265,7 @@ /// be set on both sides (the Access-Control-Allow-Credentials header and in /// the XHR or Fetch request) in order for the CORS request with credentials /// to succeed. - (AccessControlAllowCredentials, ACCESS_CONTROL_ALLOW_CREDENTIALS, "access-control-allow-credentials"); + (AccessControlAllowCredentials, ACCESS_CONTROL_ALLOW_CREDENTIALS, b"access-control-allow-credentials"); /// Preflight response indicating permitted HTTP headers. /// @@ -277,33 +281,33 @@ /// /// This header is required if the request has an /// Access-Control-Request-Headers header. - (AccessControlAllowHeaders, ACCESS_CONTROL_ALLOW_HEADERS, "access-control-allow-headers"); + (AccessControlAllowHeaders, ACCESS_CONTROL_ALLOW_HEADERS, b"access-control-allow-headers"); /// Preflight header response indicating permitted access methods. /// /// The Access-Control-Allow-Methods response header specifies the method or /// methods allowed when accessing the resource in response to a preflight /// request. - (AccessControlAllowMethods, ACCESS_CONTROL_ALLOW_METHODS, "access-control-allow-methods"); + (AccessControlAllowMethods, ACCESS_CONTROL_ALLOW_METHODS, b"access-control-allow-methods"); /// Indicates whether the response can be shared with resources with the /// given origin. - (AccessControlAllowOrigin, ACCESS_CONTROL_ALLOW_ORIGIN, "access-control-allow-origin"); + (AccessControlAllowOrigin, ACCESS_CONTROL_ALLOW_ORIGIN, b"access-control-allow-origin"); /// Indicates which headers can be exposed as part of the response by /// listing their names. - (AccessControlExposeHeaders, ACCESS_CONTROL_EXPOSE_HEADERS, "access-control-expose-headers"); + (AccessControlExposeHeaders, ACCESS_CONTROL_EXPOSE_HEADERS, b"access-control-expose-headers"); /// Indicates how long the results of a preflight request can be cached. - (AccessControlMaxAge, ACCESS_CONTROL_MAX_AGE, "access-control-max-age"); + (AccessControlMaxAge, ACCESS_CONTROL_MAX_AGE, b"access-control-max-age"); /// Informs the server which HTTP headers will be used when an actual /// request is made. - (AccessControlRequestHeaders, ACCESS_CONTROL_REQUEST_HEADERS, "access-control-request-headers"); + (AccessControlRequestHeaders, ACCESS_CONTROL_REQUEST_HEADERS, b"access-control-request-headers"); /// Informs the server know which HTTP method will be used when the actual /// request is made. - (AccessControlRequestMethod, ACCESS_CONTROL_REQUEST_METHOD, "access-control-request-method"); + (AccessControlRequestMethod, ACCESS_CONTROL_REQUEST_METHOD, b"access-control-request-method"); /// Indicates the time in seconds the object has been in a proxy cache. /// @@ -311,7 +315,7 @@ /// probably just fetched from the origin server; otherwise It is usually /// calculated as a difference between the proxy's current date and the Date /// general header included in the HTTP response. - (Age, AGE, "age"); + (Age, AGE, b"age"); /// Lists the set of methods support by a resource. /// @@ -320,16 +324,16 @@ /// empty Allow header indicates that the resource allows no request /// methods, which might occur temporarily for a given resource, for /// example. - (Allow, ALLOW, "allow"); + (Allow, ALLOW, b"allow"); /// Advertises the availability of alternate services to clients. - (AltSvc, ALT_SVC, "alt-svc"); + (AltSvc, ALT_SVC, b"alt-svc"); /// Contains the credentials to authenticate a user agent with a server. /// /// Usually this header is included after the server has responded with a /// 401 Unauthorized status and the WWW-Authenticate header. - (Authorization, AUTHORIZATION, "authorization"); + (Authorization, AUTHORIZATION, b"authorization"); /// Specifies directives for caching mechanisms in both requests and /// responses. @@ -337,7 +341,7 @@ /// Caching directives are unidirectional, meaning that a given directive in /// a request is not implying that the same directive is to be given in the /// response. - (CacheControl, CACHE_CONTROL, "cache-control"); + (CacheControl, CACHE_CONTROL, b"cache-control"); /// Controls whether or not the network connection stays open after the /// current transaction finishes. @@ -352,7 +356,7 @@ /// to consume them and not to forward them further. Standard hop-by-hop /// headers can be listed too (it is often the case of Keep-Alive, but this /// is not mandatory. - (Connection, CONNECTION, "connection"); + (Connection, CONNECTION, b"connection"); /// Indicates if the content is expected to be displayed inline. /// @@ -372,7 +376,7 @@ /// to HTTP forms and POST requests. Only the value form-data, as well as /// the optional directive name and filename, can be used in the HTTP /// context. - (ContentDisposition, CONTENT_DISPOSITION, "content-disposition"); + (ContentDisposition, CONTENT_DISPOSITION, b"content-disposition"); /// Used to compress the media-type. /// @@ -384,7 +388,7 @@ /// use this field, but some types of resources, like jpeg images, are /// already compressed. Sometimes using additional compression doesn't /// reduce payload size and can even make the payload longer. - (ContentEncoding, CONTENT_ENCODING, "content-encoding"); + (ContentEncoding, CONTENT_ENCODING, b"content-encoding"); /// Used to describe the languages intended for the audience. /// @@ -399,13 +403,13 @@ /// intended for all language audiences. Multiple language tags are also /// possible, as well as applying the Content-Language header to various /// media types and not only to textual documents. - (ContentLanguage, CONTENT_LANGUAGE, "content-language"); + (ContentLanguage, CONTENT_LANGUAGE, b"content-language"); - /// Indicates the size fo the entity-body. + /// Indicates the size of the entity-body. /// /// The header value must be a decimal indicating the number of octets sent /// to the recipient. - (ContentLength, CONTENT_LENGTH, "content-length"); + (ContentLength, CONTENT_LENGTH, b"content-length"); /// Indicates an alternate location for the returned data. /// @@ -418,10 +422,10 @@ /// without the need of further content negotiation. Location is a header /// associated with the response, while Content-Location is associated with /// the entity returned. - (ContentLocation, CONTENT_LOCATION, "content-location"); + (ContentLocation, CONTENT_LOCATION, b"content-location"); /// Indicates where in a full body message a partial message belongs. - (ContentRange, CONTENT_RANGE, "content-range"); + (ContentRange, CONTENT_RANGE, b"content-range"); /// Allows controlling resources the user agent is allowed to load for a /// given page. @@ -429,7 +433,7 @@ /// With a few exceptions, policies mostly involve specifying server origins /// and script endpoints. This helps guard against cross-site scripting /// attacks (XSS). - (ContentSecurityPolicy, CONTENT_SECURITY_POLICY, "content-security-policy"); + (ContentSecurityPolicy, CONTENT_SECURITY_POLICY, b"content-security-policy"); /// Allows experimenting with policies by monitoring their effects. /// @@ -437,7 +441,7 @@ /// developers to experiment with policies by monitoring (but not enforcing) /// their effects. These violation reports consist of JSON documents sent /// via an HTTP POST request to the specified URI. - (ContentSecurityPolicyReportOnly, CONTENT_SECURITY_POLICY_REPORT_ONLY, "content-security-policy-report-only"); + (ContentSecurityPolicyReportOnly, CONTENT_SECURITY_POLICY_REPORT_ONLY, b"content-security-policy-report-only"); /// Used to indicate the media type of the resource. /// @@ -449,23 +453,23 @@ /// /// In requests, (such as POST or PUT), the client tells the server what /// type of data is actually sent. - (ContentType, CONTENT_TYPE, "content-type"); + (ContentType, CONTENT_TYPE, b"content-type"); /// Contains stored HTTP cookies previously sent by the server with the /// Set-Cookie header. /// /// The Cookie header might be omitted entirely, if the privacy setting of /// the browser are set to block them, for example. - (Cookie, COOKIE, "cookie"); + (Cookie, COOKIE, b"cookie"); /// Indicates the client's tracking preference. /// /// This header lets users indicate whether they would prefer privacy rather /// than personalized content. - (Dnt, DNT, "dnt"); + (Dnt, DNT, b"dnt"); /// Contains the date and time at which the message was originated. - (Date, DATE, "date"); + (Date, DATE, b"date"); /// Identifier for a specific version of a resource. /// @@ -481,7 +485,7 @@ /// to quickly determine whether two representations of a resource are the /// same, but they might also be set to persist indefinitely by a tracking /// server. - (Etag, ETAG, "etag"); + (Etag, ETAG, b"etag"); /// Indicates expectations that need to be fulfilled by the server in order /// to properly handle the request. @@ -500,7 +504,7 @@ /// /// No common browsers send the Expect header, but some other clients such /// as cURL do so by default. - (Expect, EXPECT, "expect"); + (Expect, EXPECT, b"expect"); /// Contains the date/time after which the response is considered stale. /// @@ -509,7 +513,7 @@ /// /// If there is a Cache-Control header with the "max-age" or "s-max-age" /// directive in the response, the Expires header is ignored. - (Expires, EXPIRES, "expires"); + (Expires, EXPIRES, b"expires"); /// Contains information from the client-facing side of proxy servers that /// is altered or lost when a proxy is involved in the path of the request. @@ -521,7 +525,7 @@ /// location-dependent content and by design it exposes privacy sensitive /// information, such as the IP address of the client. Therefore the user's /// privacy must be kept in mind when deploying this header. - (Forwarded, FORWARDED, "forwarded"); + (Forwarded, FORWARDED, b"forwarded"); /// Contains an Internet email address for a human user who controls the /// requesting user agent. @@ -530,7 +534,7 @@ /// header should be sent, so you can be contacted if problems occur on /// servers, such as if the robot is sending excessive, unwanted, or invalid /// requests. - (From, FROM, "from"); + (From, FROM, b"from"); /// Specifies the domain name of the server and (optionally) the TCP port /// number on which the server is listening. @@ -541,7 +545,7 @@ /// A Host header field must be sent in all HTTP/1.1 request messages. A 400 /// (Bad Request) status code will be sent to any HTTP/1.1 request message /// that lacks a Host header field or contains more than one. - (Host, HOST, "host"); + (Host, HOST, b"host"); /// Makes a request conditional based on the E-Tag. /// @@ -566,7 +570,7 @@ /// that has been done since the original resource was fetched. If the /// request cannot be fulfilled, the 412 (Precondition Failed) response is /// returned. - (IfMatch, IF_MATCH, "if-match"); + (IfMatch, IF_MATCH, b"if-match"); /// Makes a request conditional based on the modification date. /// @@ -583,7 +587,7 @@ /// /// The most common use case is to update a cached entity that has no /// associated ETag. - (IfModifiedSince, IF_MODIFIED_SINCE, "if-modified-since"); + (IfModifiedSince, IF_MODIFIED_SINCE, b"if-modified-since"); /// Makes a request conditional based on the E-Tag. /// @@ -619,7 +623,7 @@ /// guaranteeing that another upload didn't happen before, losing the data /// of the previous put; this problems is the variation of the lost update /// problem. - (IfNoneMatch, IF_NONE_MATCH, "if-none-match"); + (IfNoneMatch, IF_NONE_MATCH, b"if-none-match"); /// Makes a request conditional based on range. /// @@ -635,7 +639,7 @@ /// The most common use case is to resume a download, to guarantee that the /// stored resource has not been modified since the last fragment has been /// received. - (IfRange, IF_RANGE, "if-range"); + (IfRange, IF_RANGE, b"if-range"); /// Makes the request conditional based on the last modification date. /// @@ -656,14 +660,14 @@ /// * In conjunction with a range request with a If-Range header, it can be /// used to ensure that the new fragment requested comes from an unmodified /// document. - (IfUnmodifiedSince, IF_UNMODIFIED_SINCE, "if-unmodified-since"); + (IfUnmodifiedSince, IF_UNMODIFIED_SINCE, b"if-unmodified-since"); /// Content-Types that are acceptable for the response. - (LastModified, LAST_MODIFIED, "last-modified"); + (LastModified, LAST_MODIFIED, b"last-modified"); /// Allows the server to point an interested client to another resource /// containing metadata about the requested resource. - (Link, LINK, "link"); + (Link, LINK, b"link"); /// Indicates the URL to redirect a page to. /// @@ -694,11 +698,11 @@ /// when content negotiation happened, without the need of further content /// negotiation. Location is a header associated with the response, while /// Content-Location is associated with the entity returned. - (Location, LOCATION, "location"); + (Location, LOCATION, b"location"); /// Indicates the max number of intermediaries the request should be sent /// through. - (MaxForwards, MAX_FORWARDS, "max-forwards"); + (MaxForwards, MAX_FORWARDS, b"max-forwards"); /// Indicates where a fetch originates from. /// @@ -706,7 +710,7 @@ /// sent with CORS requests, as well as with POST requests. It is similar to /// the Referer header, but, unlike this header, it doesn't disclose the /// whole path. - (Origin, ORIGIN, "origin"); + (Origin, ORIGIN, b"origin"); /// HTTP/1.0 header usually used for backwards compatibility. /// @@ -714,7 +718,7 @@ /// that may have various effects along the request-response chain. It is /// used for backwards compatibility with HTTP/1.0 caches where the /// Cache-Control HTTP/1.1 header is not yet present. - (Pragma, PRAGMA, "pragma"); + (Pragma, PRAGMA, b"pragma"); /// Defines the authentication method that should be used to gain access to /// a proxy. @@ -732,14 +736,14 @@ /// /// The `proxy-authenticate` header is sent along with a `407 Proxy /// Authentication Required`. - (ProxyAuthenticate, PROXY_AUTHENTICATE, "proxy-authenticate"); + (ProxyAuthenticate, PROXY_AUTHENTICATE, b"proxy-authenticate"); /// Contains the credentials to authenticate a user agent to a proxy server. /// /// This header is usually included after the server has responded with a /// 407 Proxy Authentication Required status and the Proxy-Authenticate /// header. - (ProxyAuthorization, PROXY_AUTHORIZATION, "proxy-authorization"); + (ProxyAuthorization, PROXY_AUTHORIZATION, b"proxy-authorization"); /// Associates a specific cryptographic public key with a certain server. /// @@ -747,14 +751,14 @@ /// or several keys are pinned and none of them are used by the server, the /// browser will not accept the response as legitimate, and will not display /// it. - (PublicKeyPins, PUBLIC_KEY_PINS, "public-key-pins"); + (PublicKeyPins, PUBLIC_KEY_PINS, b"public-key-pins"); /// Sends reports of pinning violation to the report-uri specified in the /// header. /// /// Unlike `Public-Key-Pins`, this header still allows browsers to connect /// to the server if the pinning is violated. - (PublicKeyPinsReportOnly, PUBLIC_KEY_PINS_REPORT_ONLY, "public-key-pins-report-only"); + (PublicKeyPinsReportOnly, PUBLIC_KEY_PINS_REPORT_ONLY, b"public-key-pins-report-only"); /// Indicates the part of a document that the server should return. /// @@ -764,7 +768,7 @@ /// the ranges are invalid, the server returns the 416 Range Not Satisfiable /// error. The server can also ignore the Range header and return the whole /// document with a 200 status code. - (Range, RANGE, "range"); + (Range, RANGE, b"range"); /// Contains the address of the previous web page from which a link to the /// currently requested page was followed. @@ -772,15 +776,15 @@ /// The Referer header allows servers to identify where people are visiting /// them from and may use that data for analytics, logging, or optimized /// caching, for example. - (Referer, REFERER, "referer"); + (Referer, REFERER, b"referer"); /// Governs which referrer information should be included with requests /// made. - (ReferrerPolicy, REFERRER_POLICY, "referrer-policy"); + (ReferrerPolicy, REFERRER_POLICY, b"referrer-policy"); /// Informs the web browser that the current page or frame should be /// refreshed. - (Refresh, REFRESH, "refresh"); + (Refresh, REFRESH, b"refresh"); /// The Retry-After response HTTP header indicates how long the user agent /// should wait before making a follow-up request. There are two main cases @@ -792,20 +796,20 @@ /// * When sent with a redirect response, such as 301 (Moved Permanently), /// it indicates the minimum time that the user agent is asked to wait /// before issuing the redirected request. - (RetryAfter, RETRY_AFTER, "retry-after"); + (RetryAfter, RETRY_AFTER, b"retry-after"); /// The |Sec-WebSocket-Accept| header field is used in the WebSocket /// opening handshake. It is sent from the server to the client to /// confirm that the server is willing to initiate the WebSocket /// connection. - (SecWebSocketAccept, SEC_WEBSOCKET_ACCEPT, "sec-websocket-accept"); + (SecWebSocketAccept, SEC_WEBSOCKET_ACCEPT, b"sec-websocket-accept"); /// The |Sec-WebSocket-Extensions| header field is used in the WebSocket /// opening handshake. It is initially sent from the client to the /// server, and then subsequently sent from the server to the client, to /// agree on a set of protocol-level extensions to use for the duration /// of the connection. - (SecWebSocketExtensions, SEC_WEBSOCKET_EXTENSIONS, "sec-websocket-extensions"); + (SecWebSocketExtensions, SEC_WEBSOCKET_EXTENSIONS, b"sec-websocket-extensions"); /// The |Sec-WebSocket-Key| header field is used in the WebSocket opening /// handshake. It is sent from the client to the server to provide part @@ -814,14 +818,14 @@ /// does not accept connections from non-WebSocket clients (e.g., HTTP /// clients) that are being abused to send data to unsuspecting WebSocket /// servers. - (SecWebSocketKey, SEC_WEBSOCKET_KEY, "sec-websocket-key"); + (SecWebSocketKey, SEC_WEBSOCKET_KEY, b"sec-websocket-key"); /// The |Sec-WebSocket-Protocol| header field is used in the WebSocket /// opening handshake. It is sent from the client to the server and back /// from the server to the client to confirm the subprotocol of the /// connection. This enables scripts to both select a subprotocol and be /// sure that the server agreed to serve that subprotocol. - (SecWebSocketProtocol, SEC_WEBSOCKET_PROTOCOL, "sec-websocket-protocol"); + (SecWebSocketProtocol, SEC_WEBSOCKET_PROTOCOL, b"sec-websocket-protocol"); /// The |Sec-WebSocket-Version| header field is used in the WebSocket /// opening handshake. It is sent from the client to the server to @@ -829,7 +833,7 @@ /// servers to correctly interpret the opening handshake and subsequent /// data being sent from the data, and close the connection if the server /// cannot interpret that data in a safe manner. - (SecWebSocketVersion, SEC_WEBSOCKET_VERSION, "sec-websocket-version"); + (SecWebSocketVersion, SEC_WEBSOCKET_VERSION, b"sec-websocket-version"); /// Contains information about the software used by the origin server to /// handle the request. @@ -838,13 +842,13 @@ /// potentially reveal internal implementation details that might make it /// (slightly) easier for attackers to find and exploit known security /// holes. - (Server, SERVER, "server"); + (Server, SERVER, b"server"); /// Used to send cookies from the server to the user agent. - (SetCookie, SET_COOKIE, "set-cookie"); + (SetCookie, SET_COOKIE, b"set-cookie"); /// Tells the client to communicate with HTTPS instead of using HTTP. - (StrictTransportSecurity, STRICT_TRANSPORT_SECURITY, "strict-transport-security"); + (StrictTransportSecurity, STRICT_TRANSPORT_SECURITY, b"strict-transport-security"); /// Informs the server of transfer encodings willing to be accepted as part /// of the response. @@ -854,11 +858,11 @@ /// recipients and you that don't have to specify "chunked" using the TE /// header. However, it is useful for setting if the client is accepting /// trailer fields in a chunked transfer coding using the "trailers" value. - (Te, TE, "te"); + (Te, TE, b"te"); /// Allows the sender to include additional fields at the end of chunked /// messages. - (Trailer, TRAILER, "trailer"); + (Trailer, TRAILER, b"trailer"); /// Specifies the form of encoding used to safely transfer the entity to the /// client. @@ -872,18 +876,18 @@ /// When present on a response to a `HEAD` request that has no body, it /// indicates the value that would have applied to the corresponding `GET` /// message. - (TransferEncoding, TRANSFER_ENCODING, "transfer-encoding"); + (TransferEncoding, TRANSFER_ENCODING, b"transfer-encoding"); /// Contains a string that allows identifying the requesting client's /// software. - (UserAgent, USER_AGENT, "user-agent"); + (UserAgent, USER_AGENT, b"user-agent"); /// Used as part of the exchange to upgrade the protocol. - (Upgrade, UPGRADE, "upgrade"); + (Upgrade, UPGRADE, b"upgrade"); /// Sends a signal to the server expressing the client’s preference for an /// encrypted and authenticated response. - (UpgradeInsecureRequests, UPGRADE_INSECURE_REQUESTS, "upgrade-insecure-requests"); + (UpgradeInsecureRequests, UPGRADE_INSECURE_REQUESTS, b"upgrade-insecure-requests"); /// Determines how to match future requests with cached responses. /// @@ -895,7 +899,7 @@ /// /// The `vary` header should be set on a 304 Not Modified response exactly /// like it would have been set on an equivalent 200 OK response. - (Vary, VARY, "vary"); + (Vary, VARY, b"vary"); /// Added by proxies to track routing. /// @@ -904,7 +908,7 @@ /// It is used for tracking message forwards, avoiding request loops, and /// identifying the protocol capabilities of senders along the /// request/response chain. - (Via, VIA, "via"); + (Via, VIA, b"via"); /// General HTTP header contains information about possible problems with /// the status of the message. @@ -912,11 +916,11 @@ /// More than one `warning` header may appear in a response. Warning header /// fields can in general be applied to any message, however some warn-codes /// are specific to caches and can only be applied to response messages. - (Warning, WARNING, "warning"); + (Warning, WARNING, b"warning"); /// Defines the authentication method that should be used to gain access to /// a resource. - (WwwAuthenticate, WWW_AUTHENTICATE, "www-authenticate"); + (WwwAuthenticate, WWW_AUTHENTICATE, b"www-authenticate"); /// Marker used by the server to indicate that the MIME types advertised in /// the `content-type` headers should not be changed and be followed. @@ -931,7 +935,7 @@ /// less aggressive. /// /// Site security testers usually expect this header to be set. - (XContentTypeOptions, X_CONTENT_TYPE_OPTIONS, "x-content-type-options"); + (XContentTypeOptions, X_CONTENT_TYPE_OPTIONS, b"x-content-type-options"); /// Controls DNS prefetching. /// @@ -944,7 +948,7 @@ /// This prefetching is performed in the background, so that the DNS is /// likely to have been resolved by the time the referenced items are /// needed. This reduces latency when the user clicks a link. - (XDnsPrefetchControl, X_DNS_PREFETCH_CONTROL, "x-dns-prefetch-control"); + (XDnsPrefetchControl, X_DNS_PREFETCH_CONTROL, b"x-dns-prefetch-control"); /// Indicates whether or not a browser should be allowed to render a page in /// a frame. @@ -954,7 +958,7 @@ /// /// The added security is only provided if the user accessing the document /// is using a browser supporting `x-frame-options`. - (XFrameOptions, X_FRAME_OPTIONS, "x-frame-options"); + (XFrameOptions, X_FRAME_OPTIONS, b"x-frame-options"); /// Stop pages from loading when an XSS attack is detected. /// @@ -965,7 +969,7 @@ /// implement a strong Content-Security-Policy that disables the use of /// inline JavaScript ('unsafe-inline'), they can still provide protections /// for users of older web browsers that don't yet support CSP. - (XXssProtection, X_XSS_PROTECTION, "x-xss-protection"); + (XXssProtection, X_XSS_PROTECTION, b"x-xss-protection"); } /// Valid header name characters @@ -1012,6 +1016,7 @@ 0, 0, 0, 0, 0, 0 // 25x ]; +/// Valid header name characters for HTTP/2.0 and HTTP/3.0 const HEADER_CHARS_H2: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x @@ -1042,605 +1047,30 @@ 0, 0, 0, 0, 0, 0 // 25x ]; -#[cfg(any(not(debug_assertions), not(target_arch = "wasm32")))] -macro_rules! eq { - (($($cmp:expr,)*) $v:ident[$n:expr] ==) => { - $($cmp) && * - }; - (($($cmp:expr,)*) $v:ident[$n:expr] == $a:tt $($rest:tt)*) => { - eq!(($($cmp,)* $v[$n] == $a,) $v[$n+1] == $($rest)*) - }; - ($v:ident == $($rest:tt)+) => { - eq!(() $v[0] == $($rest)+) - }; - ($v:ident[$n:expr] == $($rest:tt)+) => { - eq!(() $v[$n] == $($rest)+) - }; -} - -#[cfg(any(not(debug_assertions), not(target_arch = "wasm32")))] -/// This version is best under optimized mode, however in a wasm debug compile, -/// the `eq` macro expands to 1 + 1 + 1 + 1... and wasm explodes when this chain gets too long -/// See https://github.com/DenisKolodin/yew/issues/478 -fn parse_hdr<'a>(data: &'a [u8], b: &'a mut [u8; 64], table: &[u8; 256]) - -> Result, InvalidHeaderName> -{ - use self::StandardHeader::*; - - let len = data.len(); - - let validate = |buf: &'a [u8], len: usize| { - let buf = &buf[..len]; - if buf.iter().any(|&b| b == 0) { - Err(InvalidHeaderName::new()) - } else { - Ok(HdrName::custom(buf, true)) - } - }; - - - macro_rules! to_lower { - ($d:ident, $src:ident, 1) => { $d[0] = table[$src[0] as usize]; }; - ($d:ident, $src:ident, 2) => { to_lower!($d, $src, 1); $d[1] = table[$src[1] as usize]; }; - ($d:ident, $src:ident, 3) => { to_lower!($d, $src, 2); $d[2] = table[$src[2] as usize]; }; - ($d:ident, $src:ident, 4) => { to_lower!($d, $src, 3); $d[3] = table[$src[3] as usize]; }; - ($d:ident, $src:ident, 5) => { to_lower!($d, $src, 4); $d[4] = table[$src[4] as usize]; }; - ($d:ident, $src:ident, 6) => { to_lower!($d, $src, 5); $d[5] = table[$src[5] as usize]; }; - ($d:ident, $src:ident, 7) => { to_lower!($d, $src, 6); $d[6] = table[$src[6] as usize]; }; - ($d:ident, $src:ident, 8) => { to_lower!($d, $src, 7); $d[7] = table[$src[7] as usize]; }; - ($d:ident, $src:ident, 9) => { to_lower!($d, $src, 8); $d[8] = table[$src[8] as usize]; }; - ($d:ident, $src:ident, 10) => { to_lower!($d, $src, 9); $d[9] = table[$src[9] as usize]; }; - ($d:ident, $src:ident, 11) => { to_lower!($d, $src, 10); $d[10] = table[$src[10] as usize]; }; - ($d:ident, $src:ident, 12) => { to_lower!($d, $src, 11); $d[11] = table[$src[11] as usize]; }; - ($d:ident, $src:ident, 13) => { to_lower!($d, $src, 12); $d[12] = table[$src[12] as usize]; }; - ($d:ident, $src:ident, 14) => { to_lower!($d, $src, 13); $d[13] = table[$src[13] as usize]; }; - ($d:ident, $src:ident, 15) => { to_lower!($d, $src, 14); $d[14] = table[$src[14] as usize]; }; - ($d:ident, $src:ident, 16) => { to_lower!($d, $src, 15); $d[15] = table[$src[15] as usize]; }; - ($d:ident, $src:ident, 17) => { to_lower!($d, $src, 16); $d[16] = table[$src[16] as usize]; }; - ($d:ident, $src:ident, 18) => { to_lower!($d, $src, 17); $d[17] = table[$src[17] as usize]; }; - ($d:ident, $src:ident, 19) => { to_lower!($d, $src, 18); $d[18] = table[$src[18] as usize]; }; - ($d:ident, $src:ident, 20) => { to_lower!($d, $src, 19); $d[19] = table[$src[19] as usize]; }; - ($d:ident, $src:ident, 21) => { to_lower!($d, $src, 20); $d[20] = table[$src[20] as usize]; }; - ($d:ident, $src:ident, 22) => { to_lower!($d, $src, 21); $d[21] = table[$src[21] as usize]; }; - ($d:ident, $src:ident, 23) => { to_lower!($d, $src, 22); $d[22] = table[$src[22] as usize]; }; - ($d:ident, $src:ident, 24) => { to_lower!($d, $src, 23); $d[23] = table[$src[23] as usize]; }; - ($d:ident, $src:ident, 25) => { to_lower!($d, $src, 24); $d[24] = table[$src[24] as usize]; }; - ($d:ident, $src:ident, 26) => { to_lower!($d, $src, 25); $d[25] = table[$src[25] as usize]; }; - ($d:ident, $src:ident, 27) => { to_lower!($d, $src, 26); $d[26] = table[$src[26] as usize]; }; - ($d:ident, $src:ident, 28) => { to_lower!($d, $src, 27); $d[27] = table[$src[27] as usize]; }; - ($d:ident, $src:ident, 29) => { to_lower!($d, $src, 28); $d[28] = table[$src[28] as usize]; }; - ($d:ident, $src:ident, 30) => { to_lower!($d, $src, 29); $d[29] = table[$src[29] as usize]; }; - ($d:ident, $src:ident, 31) => { to_lower!($d, $src, 30); $d[30] = table[$src[30] as usize]; }; - ($d:ident, $src:ident, 32) => { to_lower!($d, $src, 31); $d[31] = table[$src[31] as usize]; }; - ($d:ident, $src:ident, 33) => { to_lower!($d, $src, 32); $d[32] = table[$src[32] as usize]; }; - ($d:ident, $src:ident, 34) => { to_lower!($d, $src, 33); $d[33] = table[$src[33] as usize]; }; - ($d:ident, $src:ident, 35) => { to_lower!($d, $src, 34); $d[34] = table[$src[34] as usize]; }; - } - - assert!(len < super::MAX_HEADER_NAME_LEN, - "header name too long -- max length is {}", - super::MAX_HEADER_NAME_LEN); - - match len { - 0 => { - Err(InvalidHeaderName::new()) - } - 2 => { - to_lower!(b, data, 2); - - if eq!(b == b't' b'e') { - Ok(Te.into()) - } else { - validate(b, len) - } - } - 3 => { - to_lower!(b, data, 3); - - if eq!(b == b'a' b'g' b'e') { - Ok(Age.into()) - } else if eq!(b == b'v' b'i' b'a') { - Ok(Via.into()) - } else if eq!(b == b'd' b'n' b't') { - Ok(Dnt.into()) - } else { - validate(b, len) - } - } - 4 => { - to_lower!(b, data, 4); - - if eq!(b == b'd' b'a' b't' b'e') { - Ok(Date.into()) - } else if eq!(b == b'e' b't' b'a' b'g') { - Ok(Etag.into()) - } else if eq!(b == b'f' b'r' b'o' b'm') { - Ok(From.into()) - } else if eq!(b == b'h' b'o' b's' b't') { - Ok(Host.into()) - } else if eq!(b == b'l' b'i' b'n' b'k') { - Ok(Link.into()) - } else if eq!(b == b'v' b'a' b'r' b'y') { - Ok(Vary.into()) - } else { - validate(b, len) - } - } - 5 => { - to_lower!(b, data, 5); - - if eq!(b == b'a' b'l' b'l' b'o' b'w') { - Ok(Allow.into()) - } else if eq!(b == b'r' b'a' b'n' b'g' b'e') { - Ok(Range.into()) - } else { - validate(b, len) - } - } - 6 => { - to_lower!(b, data, 6); - - if eq!(b == b'a' b'c' b'c' b'e' b'p' b't') { - return Ok(Accept.into()) - } else if eq!(b == b'c' b'o' b'o' b'k' b'i' b'e') { - return Ok(Cookie.into()) - } else if eq!(b == b'e' b'x' b'p' b'e' b'c' b't') { - return Ok(Expect.into()) - } else if eq!(b == b'o' b'r' b'i' b'g' b'i' b'n') { - return Ok(Origin.into()) - } else if eq!(b == b'p' b'r' b'a' b'g' b'm' b'a') { - return Ok(Pragma.into()) - } else if b[0] == b's' { - if eq!(b[1] == b'e' b'r' b'v' b'e' b'r') { - return Ok(Server.into()) - } - } - - validate(b, len) - } - 7 => { - to_lower!(b, data, 7); - - if eq!(b == b'a' b'l' b't' b'-' b's' b'v' b'c') { - Ok(AltSvc.into()) - } else if eq!(b == b'e' b'x' b'p' b'i' b'r' b'e' b's') { - Ok(Expires.into()) - } else if eq!(b == b'r' b'e' b'f' b'e' b'r' b'e' b'r') { - Ok(Referer.into()) - } else if eq!(b == b'r' b'e' b'f' b'r' b'e' b's' b'h') { - Ok(Refresh.into()) - } else if eq!(b == b't' b'r' b'a' b'i' b'l' b'e' b'r') { - Ok(Trailer.into()) - } else if eq!(b == b'u' b'p' b'g' b'r' b'a' b'd' b'e') { - Ok(Upgrade.into()) - } else if eq!(b == b'w' b'a' b'r' b'n' b'i' b'n' b'g') { - Ok(Warning.into()) - } else { - validate(b, len) - } - } - 8 => { - to_lower!(b, data, 8); - - if eq!(b == b'i' b'f' b'-') { - if eq!(b[3] == b'm' b'a' b't' b'c' b'h') { - return Ok(IfMatch.into()) - } else if eq!(b[3] == b'r' b'a' b'n' b'g' b'e') { - return Ok(IfRange.into()) - } - } else if eq!(b == b'l' b'o' b'c' b'a' b't' b'i' b'o' b'n') { - return Ok(Location.into()) - } - - validate(b, len) - } - 9 => { - to_lower!(b, data, 9); - - if eq!(b == b'f' b'o' b'r' b'w' b'a' b'r' b'd' b'e' b'd') { - Ok(Forwarded.into()) - } else { - validate(b, len) - } - } - 10 => { - to_lower!(b, data, 10); - - if eq!(b == b'c' b'o' b'n' b'n' b'e' b'c' b't' b'i' b'o' b'n') { - Ok(Connection.into()) - } else if eq!(b == b's' b'e' b't' b'-' b'c' b'o' b'o' b'k' b'i' b'e') { - Ok(SetCookie.into()) - } else if eq!(b == b'u' b's' b'e' b'r' b'-' b'a' b'g' b'e' b'n' b't') { - Ok(UserAgent.into()) - } else { - validate(b, len) - } - } - 11 => { - to_lower!(b, data, 11); - - if eq!(b == b'r' b'e' b't' b'r' b'y' b'-' b'a' b'f' b't' b'e' b'r') { - Ok(RetryAfter.into()) - } else { - validate(b, len) - } - } - 12 => { - to_lower!(b, data, 12); - - if eq!(b == b'c' b'o' b'n' b't' b'e' b'n' b't' b'-' b't' b'y' b'p' b'e') { - Ok(ContentType.into()) - } else if eq!(b == b'm' b'a' b'x' b'-' b'f' b'o' b'r' b'w' b'a' b'r' b'd' b's') { - Ok(MaxForwards.into()) - } else { - validate(b, len) - } - } - 13 => { - to_lower!(b, data, 13); - - if b[0] == b'a' { - if eq!(b[1] == b'c' b'c' b'e' b'p' b't' b'-' b'r' b'a' b'n' b'g' b'e' b's') { - return Ok(AcceptRanges.into()) - } else if eq!(b[1] == b'u' b't' b'h' b'o' b'r' b'i' b'z' b'a' b't' b'i' b'o' b'n') { - return Ok(Authorization.into()) - } - } else if b[0] == b'c' { - if eq!(b[1] == b'a' b'c' b'h' b'e' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l') { - return Ok(CacheControl.into()) - } else if eq!(b[1] == b'o' b'n' b't' b'e' b'n' b't' b'-' b'r' b'a' b'n' b'g' b'e' ) { - return Ok(ContentRange.into()) - } - } else if eq!(b == b'i' b'f' b'-' b'n' b'o' b'n' b'e' b'-' b'm' b'a' b't' b'c' b'h') { - return Ok(IfNoneMatch.into()) - } else if eq!(b == b'l' b'a' b's' b't' b'-' b'm' b'o' b'd' b'i' b'f' b'i' b'e' b'd') { - return Ok(LastModified.into()) - } - - validate(b, len) - } - 14 => { - to_lower!(b, data, 14); - - if eq!(b == b'a' b'c' b'c' b'e' b'p' b't' b'-' b'c' b'h' b'a' b'r' b's' b'e' b't') { - Ok(AcceptCharset.into()) - } else if eq!(b == b'c' b'o' b'n' b't' b'e' b'n' b't' b'-' b'l' b'e' b'n' b'g' b't' b'h') { - Ok(ContentLength.into()) - } else { - validate(b, len) - } - } - 15 => { - to_lower!(b, data, 15); - - if eq!(b == b'a' b'c' b'c' b'e' b'p' b't' b'-') { // accept- - if eq!(b[7] == b'e' b'n' b'c' b'o' b'd' b'i' b'n' b'g') { - return Ok(AcceptEncoding.into()) - } else if eq!(b[7] == b'l' b'a' b'n' b'g' b'u' b'a' b'g' b'e') { - return Ok(AcceptLanguage.into()) - } - } else if eq!(b == b'p' b'u' b'b' b'l' b'i' b'c' b'-' b'k' b'e' b'y' b'-' b'p' b'i' b'n' b's') { - return Ok(PublicKeyPins.into()) - } else if eq!(b == b'x' b'-' b'f' b'r' b'a' b'm' b'e' b'-' b'o' b'p' b't' b'i' b'o' b'n' b's') { - return Ok(XFrameOptions.into()) - } - else if eq!(b == b'r' b'e' b'f' b'e' b'r' b'r' b'e' b'r' b'-' b'p' b'o' b'l' b'i' b'c' b'y') { - return Ok(ReferrerPolicy.into()) - } - - validate(b, len) - } - 16 => { - to_lower!(b, data, 16); - - if eq!(b == b'c' b'o' b'n' b't' b'e' b'n' b't' b'-') { - if eq!(b[8] == b'l' b'a' b'n' b'g' b'u' b'a' b'g' b'e') { - return Ok(ContentLanguage.into()) - } else if eq!(b[8] == b'l' b'o' b'c' b'a' b't' b'i' b'o' b'n') { - return Ok(ContentLocation.into()) - } else if eq!(b[8] == b'e' b'n' b'c' b'o' b'd' b'i' b'n' b'g') { - return Ok(ContentEncoding.into()) - } - } else if eq!(b == b'w' b'w' b'w' b'-' b'a' b'u' b't' b'h' b'e' b'n' b't' b'i' b'c' b'a' b't' b'e') { - return Ok(WwwAuthenticate.into()) - } else if eq!(b == b'x' b'-' b'x' b's' b's' b'-' b'p' b'r' b'o' b't' b'e' b'c' b't' b'i' b'o' b'n') { - return Ok(XXssProtection.into()) - } - - validate(b, len) - } - 17 => { - to_lower!(b, data, 17); - - if eq!(b == b't' b'r' b'a' b'n' b's' b'f' b'e' b'r' b'-' b'e' b'n' b'c' b'o' b'd' b'i' b'n' b'g') { - Ok(TransferEncoding.into()) - } else if eq!(b == b'i' b'f' b'-' b'm' b'o' b'd' b'i' b'f' b'i' b'e' b'd' b'-' b's' b'i' b'n' b'c' b'e') { - Ok(IfModifiedSince.into()) - } else if eq!(b == b's' b'e' b'c' b'-' b'w' b'e' b'b' b's' b'o' b'c' b'k' b'e' b't' b'-' b'k' b'e' b'y') { - Ok(SecWebSocketKey.into()) - } else { - validate(b, len) - } - } - 18 => { - to_lower!(b, data, 18); - - if eq!(b == b'p' b'r' b'o' b'x' b'y' b'-' b'a' b'u' b't' b'h' b'e' b'n' b't' b'i' b'c' b'a' b't' b'e') { - Ok(ProxyAuthenticate.into()) - } else { - validate(b, len) - } - } - 19 => { - to_lower!(b, data, 19); - - if eq!(b == b'c' b'o' b'n' b't' b'e' b'n' b't' b'-' b'd' b'i' b's' b'p' b'o' b's' b'i' b't' b'i' b'o' b'n') { - Ok(ContentDisposition.into()) - } else if eq!(b == b'i' b'f' b'-' b'u' b'n' b'm' b'o' b'd' b'i' b'f' b'i' b'e' b'd' b'-' b's' b'i' b'n' b'c' b'e') { - Ok(IfUnmodifiedSince.into()) - } else if eq!(b == b'p' b'r' b'o' b'x' b'y' b'-' b'a' b'u' b't' b'h' b'o' b'r' b'i' b'z' b'a' b't' b'i' b'o' b'n') { - Ok(ProxyAuthorization.into()) - } else { - validate(b, len) - } - } - 20 => { - to_lower!(b, data, 20); - - if eq!(b == b's' b'e' b'c' b'-' b'w' b'e' b'b' b's' b'o' b'c' b'k' b'e' b't' b'-' b'a' b'c' b'c' b'e' b'p' b't') { - Ok(SecWebSocketAccept.into()) - } else { - validate(b, len) - } - } - 21 => { - to_lower!(b, data, 21); - - if eq!(b == b's' b'e' b'c' b'-' b'w' b'e' b'b' b's' b'o' b'c' b'k' b'e' b't' b'-' b'v' b'e' b'r' b's' b'i' b'o' b'n') { - Ok(SecWebSocketVersion.into()) - } else { - validate(b, len) - } - } - 22 => { - to_lower!(b, data, 22); - - if eq!(b == b'a' b'c' b'c' b'e' b's' b's' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l' b'-' b'm' b'a' b'x' b'-' b'a' b'g' b'e') { - Ok(AccessControlMaxAge.into()) - } else if eq!(b == b'x' b'-' b'c' b'o' b'n' b't' b'e' b'n' b't' b'-' b't' b'y' b'p' b'e' b'-' b'o' b'p' b't' b'i' b'o' b'n' b's') { - Ok(XContentTypeOptions.into()) - } else if eq!(b == b'x' b'-' b'd' b'n' b's' b'-' b'p' b'r' b'e' b'f' b'e' b't' b'c' b'h' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l') { - Ok(XDnsPrefetchControl.into()) - } else if eq!(b == b's' b'e' b'c' b'-' b'w' b'e' b'b' b's' b'o' b'c' b'k' b'e' b't' b'-' b'p' b'r' b'o' b't' b'o' b'c' b'o' b'l') { - Ok(SecWebSocketProtocol.into()) - } else { - validate(b, len) - } - } - 23 => { - to_lower!(b, data, 23); - - if eq!(b == b'c' b'o' b'n' b't' b'e' b'n' b't' b'-' b's' b'e' b'c' b'u' b'r' b'i' b't' b'y' b'-' b'p' b'o' b'l' b'i' b'c' b'y') { - Ok(ContentSecurityPolicy.into()) - } else { - validate(b, len) - } - } - 24 => { - to_lower!(b, data, 24); - - if eq!(b == b's' b'e' b'c' b'-' b'w' b'e' b'b' b's' b'o' b'c' b'k' b'e' b't' b'-' b'e' b'x' b't' b'e' b'n' b's' b'i' b'o' b'n' b's') { - Ok(SecWebSocketExtensions.into()) - } else { - validate(b, len) - } - } - 25 => { - to_lower!(b, data, 25); - - if eq!(b == b's' b't' b'r' b'i' b'c' b't' b'-' b't' b'r' b'a' b'n' b's' b'p' b'o' b'r' b't' b'-' b's' b'e' b'c' b'u' b'r' b'i' b't' b'y') { - Ok(StrictTransportSecurity.into()) - } else if eq!(b == b'u' b'p' b'g' b'r' b'a' b'd' b'e' b'-' b'i' b'n' b's' b'e' b'c' b'u' b'r' b'e' b'-' b'r' b'e' b'q' b'u' b'e' b's' b't' b's') { - Ok(UpgradeInsecureRequests.into()) - } else { - validate(b, len) - } - } - 27 => { - to_lower!(b, data, 27); - - if eq!(b == b'a' b'c' b'c' b'e' b's' b's' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l' b'-' b'a' b'l' b'l' b'o' b'w' b'-' b'o' b'r' b'i' b'g' b'i' b'n') { - Ok(AccessControlAllowOrigin.into()) - } else if eq!(b == b'p' b'u' b'b' b'l' b'i' b'c' b'-' b'k' b'e' b'y' b'-' b'p' b'i' b'n' b's' b'-' b'r' b'e' b'p' b'o' b'r' b't' b'-' b'o' b'n' b'l' b'y') { - Ok(PublicKeyPinsReportOnly.into()) - } else { - validate(b, len) - } - } - 28 => { - to_lower!(b, data, 28); - - if eq!(b == b'a' b'c' b'c' b'e' b's' b's' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l' b'-' b'a' b'l' b'l' b'o' b'w' b'-') { - if eq!(b[21] == b'h' b'e' b'a' b'd' b'e' b'r' b's') { - return Ok(AccessControlAllowHeaders.into()) - } else if eq!(b[21] == b'm' b'e' b't' b'h' b'o' b'd' b's') { - return Ok(AccessControlAllowMethods.into()) - } - } - - validate(b, len) - } - 29 => { - to_lower!(b, data, 29); - - if eq!(b == b'a' b'c' b'c' b'e' b's' b's' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l' b'-') { - if eq!(b[15] == b'e' b'x' b'p' b'o' b's' b'e' b'-' b'h' b'e' b'a' b'd' b'e' b'r' b's') { - return Ok(AccessControlExposeHeaders.into()) - } else if eq!(b[15] == b'r' b'e' b'q' b'u' b'e' b's' b't' b'-' b'm' b'e' b't' b'h' b'o' b'd') { - return Ok(AccessControlRequestMethod.into()) - } - } - - validate(b, len) - } - 30 => { - to_lower!(b, data, 30); - - if eq!(b == b'a' b'c' b'c' b'e' b's' b's' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l' b'-' b'r' b'e' b'q' b'u' b'e' b's' b't' b'-' b'h' b'e' b'a' b'd' b'e' b'r' b's') { - Ok(AccessControlRequestHeaders.into()) - } else { - validate(b, len) - } - } - 32 => { - to_lower!(b, data, 32); - - if eq!(b == b'a' b'c' b'c' b'e' b's' b's' b'-' b'c' b'o' b'n' b't' b'r' b'o' b'l' b'-' b'a' b'l' b'l' b'o' b'w' b'-' b'c' b'r' b'e' b'd' b'e' b'n' b't' b'i' b'a' b'l' b's') { - Ok(AccessControlAllowCredentials.into()) - } else { - validate(b, len) - } - } - 35 => { - to_lower!(b, data, 35); - - if eq!(b == b'c' b'o' b'n' b't' b'e' b'n' b't' b'-' b's' b'e' b'c' b'u' b'r' b'i' b't' b'y' b'-' b'p' b'o' b'l' b'i' b'c' b'y' b'-' b'r' b'e' b'p' b'o' b'r' b't' b'-' b'o' b'n' b'l' b'y') { - Ok(ContentSecurityPolicyReportOnly.into()) - } else { - validate(b, len) - } - } - _ => { - if len < 64 { - for i in 0..len { - b[i] = table[data[i] as usize]; - } - - validate(b, len) - } else { - Ok(HdrName::custom(data, false)) - } - } - } -} - -#[cfg(all(debug_assertions, target_arch = "wasm32"))] -/// This version works best in debug mode in wasm fn parse_hdr<'a>( data: &'a [u8], b: &'a mut [u8; 64], table: &[u8; 256], ) -> Result, InvalidHeaderName> { - use self::StandardHeader::*; - - let len = data.len(); - - let validate = |buf: &'a [u8], len: usize| { - let buf = &buf[..len]; - if buf.iter().any(|&b| b == 0) { - Err(InvalidHeaderName::new()) - } else { - Ok(HdrName::custom(buf, true)) - } - }; - - assert!( - len < super::MAX_HEADER_NAME_LEN, - "header name too long -- max length is {}", - super::MAX_HEADER_NAME_LEN - ); - - match len { + match data.len() { 0 => Err(InvalidHeaderName::new()), - len if len > 64 => Ok(HdrName::custom(data, false)), - len => { + len @ 1..=64 => { // Read from data into the buffer - transforming using `table` as we go data.iter().zip(b.iter_mut()).for_each(|(index, out)| *out = table[*index as usize]); - match &b[0..len] { - b"te" => Ok(Te.into()), - b"age" => Ok(Age.into()), - b"via" => Ok(Via.into()), - b"dnt" => Ok(Dnt.into()), - b"date" => Ok(Date.into()), - b"etag" => Ok(Etag.into()), - b"from" => Ok(From.into()), - b"host" => Ok(Host.into()), - b"link" => Ok(Link.into()), - b"vary" => Ok(Vary.into()), - b"allow" => Ok(Allow.into()), - b"range" => Ok(Range.into()), - b"accept" => Ok(Accept.into()), - b"cookie" => Ok(Cookie.into()), - b"expect" => Ok(Expect.into()), - b"origin" => Ok(Origin.into()), - b"pragma" => Ok(Pragma.into()), - b"server" => Ok(Server.into()), - b"alt-svc" => Ok(AltSvc.into()), - b"expires" => Ok(Expires.into()), - b"referer" => Ok(Referer.into()), - b"refresh" => Ok(Refresh.into()), - b"trailer" => Ok(Trailer.into()), - b"upgrade" => Ok(Upgrade.into()), - b"warning" => Ok(Warning.into()), - b"if-match" => Ok(IfMatch.into()), - b"if-range" => Ok(IfRange.into()), - b"location" => Ok(Location.into()), - b"forwarded" => Ok(Forwarded.into()), - b"connection" => Ok(Connection.into()), - b"set-cookie" => Ok(SetCookie.into()), - b"user-agent" => Ok(UserAgent.into()), - b"retry-after" => Ok(RetryAfter.into()), - b"content-type" => Ok(ContentType.into()), - b"max-forwards" => Ok(MaxForwards.into()), - b"accept-ranges" => Ok(AcceptRanges.into()), - b"authorization" => Ok(Authorization.into()), - b"cache-control" => Ok(CacheControl.into()), - b"content-range" => Ok(ContentRange.into()), - b"if-none-match" => Ok(IfNoneMatch.into()), - b"last-modified" => Ok(LastModified.into()), - b"accept-charset" => Ok(AcceptCharset.into()), - b"content-length" => Ok(ContentLength.into()), - b"accept-encoding" => Ok(AcceptEncoding.into()), - b"accept-language" => Ok(AcceptLanguage.into()), - b"public-key-pins" => Ok(PublicKeyPins.into()), - b"x-frame-options" => Ok(XFrameOptions.into()), - b"referrer-policy" => Ok(ReferrerPolicy.into()), - b"content-language" => Ok(ContentLanguage.into()), - b"content-location" => Ok(ContentLocation.into()), - b"content-encoding" => Ok(ContentEncoding.into()), - b"www-authenticate" => Ok(WwwAuthenticate.into()), - b"x-xss-protection" => Ok(XXssProtection.into()), - b"transfer-encoding" => Ok(TransferEncoding.into()), - b"if-modified-since" => Ok(IfModifiedSince.into()), - b"sec-websocket-key" => Ok(SecWebSocketKey.into()), - b"proxy-authenticate" => Ok(ProxyAuthenticate.into()), - b"content-disposition" => Ok(ContentDisposition.into()), - b"if-unmodified-since" => Ok(IfUnmodifiedSince.into()), - b"proxy-authorization" => Ok(ProxyAuthorization.into()), - b"sec-websocket-accept" => Ok(SecWebSocketAccept.into()), - b"sec-websocket-version" => Ok(SecWebSocketVersion.into()), - b"access-control-max-age" => Ok(AccessControlMaxAge.into()), - b"x-content-type-options" => Ok(XContentTypeOptions.into()), - b"x-dns-prefetch-control" => Ok(XDnsPrefetchControl.into()), - b"sec-websocket-protocol" => Ok(SecWebSocketProtocol.into()), - b"content-security-policy" => Ok(ContentSecurityPolicy.into()), - b"sec-websocket-extensions" => Ok(SecWebSocketExtensions.into()), - b"strict-transport-security" => Ok(StrictTransportSecurity.into()), - b"upgrade-insecure-requests" => Ok(UpgradeInsecureRequests.into()), - b"access-control-allow-origin" => Ok(AccessControlAllowOrigin.into()), - b"public-key-pins-report-only" => Ok(PublicKeyPinsReportOnly.into()), - b"access-control-allow-headers" => Ok(AccessControlAllowHeaders.into()), - b"access-control-allow-methods" => Ok(AccessControlAllowMethods.into()), - b"access-control-expose-headers" => Ok(AccessControlExposeHeaders.into()), - b"access-control-request-method" => Ok(AccessControlRequestMethod.into()), - b"access-control-request-headers" => Ok(AccessControlRequestHeaders.into()), - b"access-control-allow-credentials" => Ok(AccessControlAllowCredentials.into()), - b"content-security-policy-report-only" => { - Ok(ContentSecurityPolicyReportOnly.into()) + let name = &b[0..len]; + match StandardHeader::from_bytes(name) { + Some(sh) => Ok(sh.into()), + None => { + if name.contains(&0) { + Err(InvalidHeaderName::new()) + } else { + Ok(HdrName::custom(name, true)) + } } - other => validate(other, len), } } + 65..=super::MAX_HEADER_NAME_LEN => Ok(HdrName::custom(data, false)), + _ => Err(InvalidHeaderName::new()), } } @@ -1658,11 +1088,12 @@ /// This function normalizes the input. #[allow(deprecated)] pub fn from_bytes(src: &[u8]) -> Result { + #[allow(deprecated)] let mut buf = unsafe { mem::uninitialized() }; match parse_hdr(src, &mut buf, &HEADER_CHARS)?.inner { Repr::Standard(std) => Ok(std.into()), Repr::Custom(MaybeLower { buf, lower: true }) => { - let buf = Bytes::from(buf); + let buf = Bytes::copy_from_slice(buf); let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; Ok(Custom(val).into()) } @@ -1677,7 +1108,7 @@ return Err(InvalidHeaderName::new()); } - dst.put(b); + dst.put_u8(b); } let val = unsafe { ByteStr::from_utf8_unchecked(dst.freeze()) }; @@ -1690,8 +1121,8 @@ /// Converts a slice of bytes to an HTTP header name. /// /// This function expects the input to only contain lowercase characters. - /// This is useful when decoding HTTP/2.0 headers. The HTTP/2.0 - /// specification requires that all headers be represented in lower case. + /// This is useful when decoding HTTP/2.0 or HTTP/3.0 headers. Both + /// require that all headers be represented in lower case. /// /// # Examples /// @@ -1707,11 +1138,12 @@ /// ``` #[allow(deprecated)] pub fn from_lowercase(src: &[u8]) -> Result { + #[allow(deprecated)] let mut buf = unsafe { mem::uninitialized() }; match parse_hdr(src, &mut buf, &HEADER_CHARS_H2)?.inner { Repr::Standard(std) => Ok(std.into()), Repr::Custom(MaybeLower { buf, lower: true }) => { - let buf = Bytes::from(buf); + let buf = Bytes::copy_from_slice(buf); let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; Ok(Custom(val).into()) } @@ -1722,7 +1154,7 @@ } } - let buf = Bytes::from(buf); + let buf = Bytes::copy_from_slice(buf); let val = unsafe { ByteStr::from_utf8_unchecked(buf) }; Ok(Custom(val).into()) } @@ -1731,12 +1163,34 @@ /// Converts a static string to a HTTP header name. /// - /// This function panics when the static string is a invalid header. - /// - /// This function requires the static string to only contain lowercase - /// characters, numerals and symbols, as per the HTTP/2.0 specification + /// This function requires the static string to only contain lowercase + /// characters, numerals and symbols, as per the HTTP/2.0 specification /// and header names internal representation within this library. - /// + /// + /// # Panics + /// + /// This function panics when the static string is a invalid header. + /// + /// Until [Allow panicking in constants](https://github.com/rust-lang/rfcs/pull/2345) + /// makes its way into stable, the panic message at compile-time is + /// going to look cryptic, but should at least point at your header value: + /// + /// ```text + /// error: any use of this value will cause an error + /// --> http/src/header/name.rs:1241:13 + /// | + /// 1241 | ([] as [u8; 0])[0]; // Invalid header name + /// | ^^^^^^^^^^^^^^^^^^ + /// | | + /// | index out of bounds: the length is 0 but the index is 0 + /// | inside `http::HeaderName::from_static` at http/src/header/name.rs:1241:13 + /// | inside `INVALID_NAME` at src/main.rs:3:34 + /// | + /// ::: src/main.rs:3:1 + /// | + /// 3 | const INVALID_NAME: HeaderName = HeaderName::from_static("Capitalized"); + /// | ------------------------------------------------------------------------ + /// ``` /// /// # Examples /// @@ -1745,51 +1199,50 @@ /// // Parsing a standard header /// let hdr = HeaderName::from_static("content-length"); /// assert_eq!(CONTENT_LENGTH, hdr); - /// + /// /// // Parsing a custom header /// let CUSTOM_HEADER: &'static str = "custom-header"; - /// + /// /// let a = HeaderName::from_lowercase(b"custom-header").unwrap(); /// let b = HeaderName::from_static(CUSTOM_HEADER); /// assert_eq!(a, b); /// ``` - /// + /// /// ```should_panic /// # use http::header::*; /// # /// // Parsing a header that contains invalid symbols(s): /// HeaderName::from_static("content{}{}length"); // This line panics! - /// + /// /// // Parsing a header that contains invalid uppercase characters. /// let a = HeaderName::from_static("foobar"); /// let b = HeaderName::from_static("FOOBAR"); // This line panics! /// ``` - #[allow(deprecated)] - pub fn from_static(src: &'static str) -> HeaderName { - let bytes = src.as_bytes(); - let mut buf = unsafe { mem::uninitialized() }; - match parse_hdr(bytes, &mut buf, &HEADER_CHARS_H2) { - Ok(hdr_name) => match hdr_name.inner { - Repr::Standard(std) => std.into(), - Repr::Custom(MaybeLower { buf: _, lower: true }) => { - let val = ByteStr::from_static(src); - Custom(val).into() - }, - Repr::Custom(MaybeLower { buf: _, lower: false }) => { - // With lower false, the string is left unchecked by - // parse_hdr and must be validated manually. - for &b in bytes.iter() { - if HEADER_CHARS_H2[b as usize] == 0 { - panic!("invalid header name") - } - } + #[allow(unconditional_panic)] // required for the panic circumvention + pub const fn from_static(src: &'static str) -> HeaderName { + let name_bytes = src.as_bytes(); + if let Some(standard) = StandardHeader::from_bytes(name_bytes) { + return HeaderName{ + inner: Repr::Standard(standard), + }; + } - let val = ByteStr::from_static(src); - Custom(val).into() + if name_bytes.len() == 0 || name_bytes.len() > super::MAX_HEADER_NAME_LEN || { + let mut i = 0; + loop { + if i >= name_bytes.len() { + break false; + } else if HEADER_CHARS_H2[name_bytes[i] as usize] == 0 { + break true; } - }, + i += 1; + } + } { + ([] as [u8; 0])[0]; // Invalid header name + } - Err(_) => panic!("invalid header name") + HeaderName { + inner: Repr::Custom(Custom(ByteStr::from_static(src))) } } @@ -1803,16 +1256,17 @@ Repr::Custom(ref v) => &*v.0, } } + + pub(super) fn into_bytes(self) -> Bytes { + self.inner.into() + } } impl FromStr for HeaderName { type Err = InvalidHeaderName; fn from_str(s: &str) -> Result { - HeaderName::from_bytes(s.as_bytes()) - .map_err(|_| InvalidHeaderName { - _priv: (), - }) + HeaderName::from_bytes(s.as_bytes()).map_err(|_| InvalidHeaderName { _priv: () }) } } @@ -1835,13 +1289,13 @@ } impl fmt::Debug for HeaderName { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_str(), fmt) } } impl fmt::Display for HeaderName { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self.as_str(), fmt) } } @@ -1860,12 +1314,13 @@ #[doc(hidden)] impl From> for Bytes -where T: Into { +where + T: Into, +{ fn from(repr: Repr) -> Bytes { match repr { - Repr::Standard(header) => - Bytes::from_static(header.as_str().as_bytes()), - Repr::Custom(header) => header.into() + Repr::Standard(header) => Bytes::from_static(header.as_str().as_bytes()), + Repr::Custom(header) => header.into(), } } } @@ -1877,23 +1332,7 @@ } } -impl From for Bytes { - #[inline] - fn from(name: HeaderName) -> Bytes { - name.inner.into() - } -} - -impl<'a> HttpTryFrom<&'a HeaderName> for HeaderName { - type Error = ::error::Never; - - #[inline] - fn try_from(t: &'a HeaderName) -> Result { - Ok(t.clone()) - } -} - -impl<'a> HttpTryFrom<&'a str> for HeaderName { +impl<'a> TryFrom<&'a str> for HeaderName { type Error = InvalidHeaderName; #[inline] fn try_from(s: &'a str) -> Result { @@ -1901,7 +1340,7 @@ } } -impl<'a> HttpTryFrom<&'a String> for HeaderName { +impl<'a> TryFrom<&'a String> for HeaderName { type Error = InvalidHeaderName; #[inline] fn try_from(s: &'a String) -> Result { @@ -1909,7 +1348,7 @@ } } -impl<'a> HttpTryFrom<&'a [u8]> for HeaderName { +impl<'a> TryFrom<&'a [u8]> for HeaderName { type Error = InvalidHeaderName; #[inline] fn try_from(s: &'a [u8]) -> Result { @@ -1917,11 +1356,21 @@ } } -impl HttpTryFrom for HeaderName { - type Error = InvalidHeaderNameBytes; +impl TryFrom for HeaderName { + type Error = InvalidHeaderName; + #[inline] - fn try_from(bytes: Bytes) -> Result { - Self::from_bytes(bytes.as_ref()).map_err(InvalidHeaderNameBytes) + fn try_from(s: String) -> Result { + Self::from_bytes(s.as_bytes()) + } +} + +impl TryFrom> for HeaderName { + type Error = InvalidHeaderName; + + #[inline] + fn try_from(vec: Vec) -> Result { + Self::from_bytes(&vec) } } @@ -1937,7 +1386,9 @@ #[doc(hidden)] impl From for HeaderName { fn from(src: Custom) -> HeaderName { - HeaderName { inner: Repr::Custom(src) } + HeaderName { + inner: Repr::Custom(src), + } } } @@ -1948,7 +1399,6 @@ } } - impl<'a> PartialEq for &'a HeaderName { #[inline] fn eq(&self, other: &HeaderName) -> bool { @@ -1975,7 +1425,6 @@ } } - impl PartialEq for str { /// Performs a case-insensitive comparison of the string against the header /// name @@ -2004,7 +1453,6 @@ } } - impl<'a> PartialEq for &'a str { /// Performs a case-insensitive comparison of the string against the header /// name @@ -2023,28 +1471,12 @@ } impl fmt::Display for InvalidHeaderName { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.description().fmt(f) - } -} - -impl Error for InvalidHeaderName { - fn description(&self) -> &str { - "invalid HTTP header name" + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid HTTP header name") } } -impl fmt::Display for InvalidHeaderNameBytes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Error for InvalidHeaderNameBytes { - fn description(&self) -> &str { - self.0.description() - } -} +impl Error for InvalidHeaderName {} // ===== HdrName ===== @@ -2060,8 +1492,9 @@ #[allow(deprecated)] pub fn from_bytes(hdr: &[u8], f: F) -> Result - where F: FnOnce(HdrName) -> U, + where F: FnOnce(HdrName<'_>) -> U, { + #[allow(deprecated)] let mut buf = unsafe { mem::uninitialized() }; let hdr = parse_hdr(hdr, &mut buf, &HEADER_CHARS)?; Ok(f(hdr)) @@ -2069,11 +1502,13 @@ #[allow(deprecated)] pub fn from_static(hdr: &'static str, f: F) -> U - where F: FnOnce(HdrName) -> U, + where + F: FnOnce(HdrName<'_>) -> U, { + #[allow(deprecated)] let mut buf = unsafe { mem::uninitialized() }; - let hdr = parse_hdr(hdr.as_bytes(), &mut buf, &HEADER_CHARS) - .expect("static str is invalid name"); + let hdr = + parse_hdr(hdr.as_bytes(), &mut buf, &HEADER_CHARS).expect("static str is invalid name"); f(hdr) } } @@ -2082,25 +1517,23 @@ impl<'a> From> for HeaderName { fn from(src: HdrName<'a>) -> HeaderName { match src.inner { - Repr::Standard(s) => { - HeaderName { - inner: Repr::Standard(s), - } - } + Repr::Standard(s) => HeaderName { + inner: Repr::Standard(s), + }, Repr::Custom(maybe_lower) => { if maybe_lower.lower { - let buf = Bytes::from(&maybe_lower.buf[..]); + let buf = Bytes::copy_from_slice(&maybe_lower.buf[..]); let byte_str = unsafe { ByteStr::from_utf8_unchecked(buf) }; HeaderName { inner: Repr::Custom(Custom(byte_str)), } } else { - use bytes::{BufMut}; + use bytes::BufMut; let mut dst = BytesMut::with_capacity(maybe_lower.buf.len()); for b in maybe_lower.buf.iter() { - dst.put(HEADER_CHARS[*b as usize]); + dst.put_u8(HEADER_CHARS[*b as usize]); } let buf = unsafe { ByteStr::from_utf8_unchecked(dst.freeze()) }; @@ -2119,24 +1552,20 @@ #[inline] fn eq(&self, other: &HdrName<'a>) -> bool { match self.inner { - Repr::Standard(a) => { - match other.inner { - Repr::Standard(b) => a == b, - _ => false, - } - } - Repr::Custom(Custom(ref a)) => { - match other.inner { - Repr::Custom(ref b) => { - if b.lower { - a.as_bytes() == b.buf - } else { - eq_ignore_ascii_case(a.as_bytes(), b.buf) - } + Repr::Standard(a) => match other.inner { + Repr::Standard(b) => a == b, + _ => false, + }, + Repr::Custom(Custom(ref a)) => match other.inner { + Repr::Custom(ref b) => { + if b.lower { + a.as_bytes() == b.buf + } else { + eq_ignore_ascii_case(a.as_bytes(), b.buf) } - _ => false, } - } + _ => false, + }, } } } @@ -2196,6 +1625,36 @@ } } + const ONE_TOO_LONG: &[u8] = &[b'a'; super::super::MAX_HEADER_NAME_LEN+1]; + + #[test] + fn test_invalid_name_lengths() { + assert!( + HeaderName::from_bytes(&[]).is_err(), + "zero-length header name is an error", + ); + + let long = &ONE_TOO_LONG[0..super::super::MAX_HEADER_NAME_LEN]; + + let long_str = std::str::from_utf8(long).unwrap(); + assert_eq!(HeaderName::from_static(long_str), long_str); // shouldn't panic! + + assert!( + HeaderName::from_bytes(long).is_ok(), + "max header name length is ok", + ); + assert!( + HeaderName::from_bytes(ONE_TOO_LONG).is_err(), + "longer than max header name length is an error", + ); + } + + #[test] + #[should_panic] + fn test_static_invalid_name_lengths() { + let _ = HeaderName::from_static(unsafe { std::str::from_utf8_unchecked(ONE_TOO_LONG) }); + } + #[test] fn test_from_hdr_name() { use self::StandardHeader::Vary; @@ -2265,7 +1724,7 @@ #[test] fn test_from_static_std() { let a = HeaderName { inner: Repr::Standard(Vary) }; - + let b = HeaderName::from_static("vary"); assert_eq!(a, b); @@ -2277,13 +1736,13 @@ #[should_panic] fn test_from_static_std_uppercase() { HeaderName::from_static("Vary"); - } + } #[test] #[should_panic] fn test_from_static_std_symbol() { HeaderName::from_static("vary{}"); - } + } // MaybeLower { lower: true } #[test] diff -Nru rust-http-0.1.21/src/header/value.rs rust-http-0.2.7/src/header/value.rs --- rust-http-0.1.21/src/header/value.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/header/value.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,12 +1,12 @@ use bytes::{Bytes, BytesMut}; -use std::{cmp, fmt, mem, str}; +use std::convert::TryFrom; use std::error::Error; +use std::fmt::Write; use std::str::FromStr; +use std::{cmp, fmt, mem, str}; -use ::convert::HttpTryFrom; -use ::error::Never; -use header::name::HeaderName; +use crate::header::name::HeaderName; /// Represents an HTTP header field value. /// @@ -29,11 +29,6 @@ _priv: (), } -/// A possible error when converting a `HeaderValue` from a string or byte -/// slice. -#[derive(Debug)] -pub struct InvalidHeaderValueBytes(InvalidHeaderValue); - /// A possible error when converting a `HeaderValue` to a string representation. /// /// Header field values may contain opaque bytes, in which case it is not @@ -55,6 +50,27 @@ /// This function panics if the argument contains invalid header value /// characters. /// + /// Until [Allow panicking in constants](https://github.com/rust-lang/rfcs/pull/2345) + /// makes its way into stable, the panic message at compile-time is + /// going to look cryptic, but should at least point at your header value: + /// + /// ```text + /// error: any use of this value will cause an error + /// --> http/src/header/value.rs:67:17 + /// | + /// 67 | ([] as [u8; 0])[0]; // Invalid header value + /// | ^^^^^^^^^^^^^^^^^^ + /// | | + /// | index out of bounds: the length is 0 but the index is 0 + /// | inside `HeaderValue::from_static` at http/src/header/value.rs:67:17 + /// | inside `INVALID_HEADER` at src/main.rs:73:33 + /// | + /// ::: src/main.rs:73:1 + /// | + /// 73 | const INVALID_HEADER: HeaderValue = HeaderValue::from_static("жsome value"); + /// | ---------------------------------------------------------------------------- + /// ``` + /// /// # Examples /// /// ``` @@ -63,12 +79,15 @@ /// assert_eq!(val, "hello"); /// ``` #[inline] - pub fn from_static(src: &'static str) -> HeaderValue { + #[allow(unconditional_panic)] // required for the panic circumvention + pub const fn from_static(src: &'static str) -> HeaderValue { let bytes = src.as_bytes(); - for &b in bytes { - if !is_visible_ascii(b) { - panic!("invalid header value"); + let mut i = 0; + while i < bytes.len() { + if !is_visible_ascii(bytes[i]) { + ([] as [u8; 0])[0]; // Invalid header value } + i += 1; } HeaderValue { @@ -104,7 +123,7 @@ /// ``` #[inline] pub fn from_str(src: &str) -> Result { - HeaderValue::try_from(src) + HeaderValue::try_from_generic(src, |s| Bytes::copy_from_slice(s.as_bytes())) } /// Converts a HeaderName into a HeaderValue @@ -150,38 +169,49 @@ /// ``` #[inline] pub fn from_bytes(src: &[u8]) -> Result { - HeaderValue::try_from(src) + HeaderValue::try_from_generic(src, Bytes::copy_from_slice) } /// Attempt to convert a `Bytes` buffer to a `HeaderValue`. /// - /// If the argument contains invalid header value bytes, an error is - /// returned. Only byte values between 32 and 255 (inclusive) are permitted, - /// excluding byte 127 (DEL). - /// - /// This function is intended to be replaced in the future by a `TryFrom` - /// implementation once the trait is stabilized in std. - #[inline] - pub fn from_shared(src: Bytes) -> Result { - HeaderValue::try_from(src).map_err(InvalidHeaderValueBytes) + /// This will try to prevent a copy if the type passed is the type used + /// internally, and will copy the data if it is not. + pub fn from_maybe_shared(src: T) -> Result + where + T: AsRef<[u8]> + 'static, + { + if_downcast_into!(T, Bytes, src, { + return HeaderValue::from_shared(src); + }); + + HeaderValue::from_bytes(src.as_ref()) } /// Convert a `Bytes` directly into a `HeaderValue` without validating. /// /// This function does NOT validate that illegal bytes are not contained /// within the buffer. - #[inline] - pub unsafe fn from_shared_unchecked(src: Bytes) -> HeaderValue { + pub unsafe fn from_maybe_shared_unchecked(src: T) -> HeaderValue + where + T: AsRef<[u8]> + 'static, + { if cfg!(debug_assertions) { - match HeaderValue::from_shared(src) { + match HeaderValue::from_maybe_shared(src) { Ok(val) => val, Err(_err) => { - //TODO: if the Bytes were part of the InvalidHeaderValueBytes, - //this message could include the invalid bytes. - panic!("HeaderValue::from_shared_unchecked() with invalid bytes"); - }, + panic!("HeaderValue::from_maybe_shared_unchecked() with invalid bytes"); + } } } else { + + if_downcast_into!(T, Bytes, src, { + return HeaderValue { + inner: src, + is_sensitive: false, + }; + }); + + let src = Bytes::copy_from_slice(src.as_ref()); HeaderValue { inner: src, is_sensitive: false, @@ -189,16 +219,18 @@ } } - fn try_from + Into>(src: T) -> Result { + fn from_shared(src: Bytes) -> Result { + HeaderValue::try_from_generic(src, std::convert::identity) + } + + fn try_from_generic, F: FnOnce(T) -> Bytes>(src: T, into: F) -> Result { for &b in src.as_ref() { if !is_valid(b) { - return Err(InvalidHeaderValue { - _priv: (), - }); + return Err(InvalidHeaderValue { _priv: () }); } } Ok(HeaderValue { - inner: src.into(), + inner: into(src), is_sensitive: false, }) } @@ -297,9 +329,14 @@ /// Returns `true` if the value represents sensitive data. /// /// Sensitive data could represent passwords or other data that should not - /// be stored on disk or in memory. This setting can be used by components - /// like caches to avoid storing the value. HPACK encoders must set the - /// header field to never index when `is_sensitive` returns true. + /// be stored on disk or in memory. By marking header values as sensitive, + /// components using this crate can be instructed to treat them with special + /// care for security reasons. For example, caches can avoid storing + /// sensitive values, and HPACK encoders used by HTTP/2.0 implementations + /// can choose not to compress them. + /// + /// Additionally, sensitive values will be masked by the `Debug` + /// implementation of `HeaderValue`. /// /// Note that sensitivity is not factored into equality or ordering. /// @@ -329,7 +366,7 @@ } impl fmt::Debug for HeaderValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if self.is_sensitive { f.write_str("Sensitive") } else { @@ -339,9 +376,7 @@ for (i, &b) in bytes.iter().enumerate() { if !is_visible_ascii(b) || b == b'"' { if from != i { - f.write_str(unsafe { - str::from_utf8_unchecked(&bytes[from..i]) - })?; + f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..i]) })?; } if b == b'"' { f.write_str("\\\"")?; @@ -352,9 +387,7 @@ } } - f.write_str(unsafe { - str::from_utf8_unchecked(&bytes[from..]) - })?; + f.write_str(unsafe { str::from_utf8_unchecked(&bytes[from..]) })?; f.write_str("\"") } } @@ -364,7 +397,7 @@ #[inline] fn from(h: HeaderName) -> HeaderValue { HeaderValue { - inner: h.into(), + inner: h.into_bytes(), is_sensitive: false, } } @@ -395,7 +428,7 @@ // full value fits inline, so don't allocate! BytesMut::new() }; - let _ = ::itoa::fmt(&mut buf, num); + let _ = buf.write_str(::itoa::Buffer::new().format(num)); HeaderValue { inner: buf.freeze(), is_sensitive: false, @@ -403,15 +436,6 @@ } } - impl HttpTryFrom<$t> for HeaderValue { - type Error = Never; - - #[inline] - fn try_from(num: $t) -> Result { - Ok(num.into()) - } - } - #[test] fn $name() { let n: $t = 55; @@ -458,14 +482,17 @@ #[cfg(test)] mod from_header_name_tests { use super::*; - use header::map::HeaderMap; - use header::name; + use crate::header::map::HeaderMap; + use crate::header::name; #[test] fn it_can_insert_header_name_as_header_value() { let mut map = HeaderMap::new(); map.insert(name::UPGRADE, name::SEC_WEBSOCKET_PROTOCOL.into()); - map.insert(name::ACCEPT, name::HeaderName::from_bytes(b"hello-world").unwrap().into()); + map.insert( + name::ACCEPT, + name::HeaderName::from_bytes(b"hello-world").unwrap().into(), + ); assert_eq!( map.get(name::UPGRADE).unwrap(), @@ -488,13 +515,6 @@ } } -impl From for Bytes { - #[inline] - fn from(value: HeaderValue) -> Bytes { - value.inner - } -} - impl<'a> From<&'a HeaderValue> for HeaderValue { #[inline] fn from(t: &'a HeaderValue) -> Self { @@ -502,16 +522,7 @@ } } -impl<'a> HttpTryFrom<&'a HeaderValue> for HeaderValue { - type Error = ::error::Never; - - #[inline] - fn try_from(t: &'a HeaderValue) -> Result { - Ok(t.clone()) - } -} - -impl<'a> HttpTryFrom<&'a str> for HeaderValue { +impl<'a> TryFrom<&'a str> for HeaderValue { type Error = InvalidHeaderValue; #[inline] @@ -520,7 +531,7 @@ } } -impl<'a> HttpTryFrom<&'a String> for HeaderValue { +impl<'a> TryFrom<&'a String> for HeaderValue { type Error = InvalidHeaderValue; #[inline] fn try_from(s: &'a String) -> Result { @@ -528,7 +539,7 @@ } } -impl<'a> HttpTryFrom<&'a [u8]> for HeaderValue { +impl<'a> TryFrom<&'a [u8]> for HeaderValue { type Error = InvalidHeaderValue; #[inline] @@ -537,8 +548,8 @@ } } -impl HttpTryFrom for HeaderValue { - type Error = InvalidHeaderValueBytes; +impl TryFrom for HeaderValue { + type Error = InvalidHeaderValue; #[inline] fn try_from(t: String) -> Result { @@ -546,29 +557,19 @@ } } -impl HttpTryFrom for HeaderValue { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_from(bytes: Bytes) -> Result { - HeaderValue::from_shared(bytes) - } -} - -impl HttpTryFrom for HeaderValue { +impl TryFrom> for HeaderValue { type Error = InvalidHeaderValue; #[inline] - fn try_from(name: HeaderName) -> Result { - // Infallable as header names have the same validations - Ok(name.into()) + fn try_from(vec: Vec) -> Result { + HeaderValue::from_shared(vec.into()) } } #[cfg(test)] mod try_from_header_name_tests { use super::*; - use header::name; + use crate::header::name; #[test] fn it_converts_using_try_from() { @@ -579,7 +580,7 @@ } } -fn is_visible_ascii(b: u8) -> bool { +const fn is_visible_ascii(b: u8) -> bool { b >= 32 && b < 127 || b == b'\t' } @@ -597,40 +598,20 @@ } impl fmt::Display for InvalidHeaderValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.description().fmt(f) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("failed to parse header value") } } -impl Error for InvalidHeaderValue { - fn description(&self) -> &str { - "failed to parse header value" - } -} - -impl fmt::Display for InvalidHeaderValueBytes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Error for InvalidHeaderValueBytes { - fn description(&self) -> &str { - self.0.description() - } -} +impl Error for InvalidHeaderValue {} impl fmt::Display for ToStrError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.description().fmt(f) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("failed to convert header to a str") } } -impl Error for ToStrError { - fn description(&self) -> &str { - "failed to convert header to a str" - } -} +impl Error for ToStrError {} // ===== PartialEq / PartialOrd ===== @@ -756,7 +737,8 @@ } impl<'a, T: ?Sized> PartialEq<&'a T> for HeaderValue - where HeaderValue: PartialEq +where + HeaderValue: PartialEq, { #[inline] fn eq(&self, other: &&'a T) -> bool { @@ -765,7 +747,8 @@ } impl<'a, T: ?Sized> PartialOrd<&'a T> for HeaderValue - where HeaderValue: PartialOrd +where + HeaderValue: PartialOrd, { #[inline] fn partial_cmp(&self, other: &&'a T) -> Option { diff -Nru rust-http-0.1.21/src/lib.rs rust-http-0.2.7/src/lib.rs --- rust-http-0.1.21/src/lib.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/lib.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,4 +1,4 @@ -#![doc(html_root_url = "https://docs.rs/http/0.1.21")] +#![doc(html_root_url = "https://docs.rs/http/0.2.7")] //! A general purpose library of common HTTP types //! @@ -148,10 +148,11 @@ //! //! ``` //! use http::Uri; +//! use http::uri::Scheme; //! //! let uri = "https://www.rust-lang.org/index.html".parse::().unwrap(); //! -//! assert_eq!(uri.scheme_str(), Some("https")); +//! assert_eq!(uri.scheme(), Some(&Scheme::HTTPS)); //! assert_eq!(uri.host(), Some("www.rust-lang.org")); //! assert_eq!(uri.path(), "/index.html"); //! assert_eq!(uri.query(), None); @@ -159,10 +160,6 @@ #![deny(warnings, missing_docs, missing_debug_implementations)] -extern crate bytes; -extern crate fnv; -extern crate itoa; - #[cfg(test)] #[macro_use] extern crate doc_comment; @@ -170,30 +167,31 @@ #[cfg(test)] doctest!("../README.md"); +#[macro_use] +mod convert; + pub mod header; pub mod method; pub mod request; pub mod response; pub mod status; -pub mod version; pub mod uri; +pub mod version; mod byte_str; -mod convert; mod error; mod extensions; -pub use convert::HttpTryFrom; -pub use error::{Error, Result}; -pub use extensions::Extensions; +pub use crate::error::{Error, Result}; +pub use crate::extensions::Extensions; #[doc(no_inline)] -pub use header::{HeaderMap, HeaderValue}; -pub use method::Method; -pub use request::Request; -pub use response::Response; -pub use status::StatusCode; -pub use uri::Uri; -pub use version::Version; +pub use crate::header::{HeaderMap, HeaderValue}; +pub use crate::method::Method; +pub use crate::request::Request; +pub use crate::response::Response; +pub use crate::status::StatusCode; +pub use crate::uri::Uri; +pub use crate::version::Version; fn _assert_types() { fn assert_send() {} diff -Nru rust-http-0.1.21/src/method.rs rust-http-0.2.7/src/method.rs --- rust-http-0.1.21/src/method.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/method.rs 1973-11-29 21:33:09.000000000 +0000 @@ -15,13 +15,14 @@ //! assert_eq!(Method::POST.as_str(), "POST"); //! ``` -use HttpTryFrom; use self::Inner::*; +use self::extension::{InlineExtension, AllocatedExtension}; -use std::{fmt, str}; use std::convert::AsRef; use std::error::Error; use std::str::FromStr; +use std::convert::TryFrom; +use std::{fmt, str}; /// The Request Method (VERB) /// @@ -61,55 +62,11 @@ Connect, Patch, // If the extension is short enough, store it inline - ExtensionInline([u8; MAX_INLINE], u8), + ExtensionInline(InlineExtension), // Otherwise, allocate it - ExtensionAllocated(Box<[u8]>), + ExtensionAllocated(AllocatedExtension), } -const MAX_INLINE: usize = 15; - -// From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can -// contain the following characters: -// -// ``` -// method = token -// token = 1*tchar -// tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / -// "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA -// ``` -// -// https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method -// -const METHOD_CHARS: [u8; 256] = [ - // 0 1 2 3 4 5 6 7 8 9 - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 1x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 2x - b'\0', b'\0', b'\0', b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 3x - b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', // 4x - b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', // 5x - b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', // 6x - b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x - b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x - b'Z', b'\0', b'\0', b'\0', b'^', b'_', b'`', b'a', b'b', b'c', // 9x - b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x - b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x - b'x', b'y', b'z', b'\0', b'|', b'\0', b'~', b'\0', b'\0', b'\0', // 12x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x - b'\0', b'\0', b'\0', b'\0', b'\0', b'\0' // 25x -]; - impl Method { /// GET @@ -142,63 +99,47 @@ /// Converts a slice of bytes to an HTTP method. pub fn from_bytes(src: &[u8]) -> Result { match src.len() { - 0 => { - Err(InvalidMethod::new()) - } - 3 => { - match src { - b"GET" => Ok(Method(Get)), - b"PUT" => Ok(Method(Put)), - _ => Method::extension_inline(src), - } - } - 4 => { - match src { - b"POST" => Ok(Method(Post)), - b"HEAD" => Ok(Method(Head)), - _ => Method::extension_inline(src), - } - } - 5 => { - match src { - b"PATCH" => Ok(Method(Patch)), - b"TRACE" => Ok(Method(Trace)), - _ => Method::extension_inline(src), - } - } - 6 => { - match src { - b"DELETE" => Ok(Method(Delete)), - _ => Method::extension_inline(src), - } - } - 7 => { - match src { - b"OPTIONS" => Ok(Method(Options)), - b"CONNECT" => Ok(Method(Connect)), - _ => Method::extension_inline(src), - } - } + 0 => Err(InvalidMethod::new()), + 3 => match src { + b"GET" => Ok(Method(Get)), + b"PUT" => Ok(Method(Put)), + _ => Method::extension_inline(src), + }, + 4 => match src { + b"POST" => Ok(Method(Post)), + b"HEAD" => Ok(Method(Head)), + _ => Method::extension_inline(src), + }, + 5 => match src { + b"PATCH" => Ok(Method(Patch)), + b"TRACE" => Ok(Method(Trace)), + _ => Method::extension_inline(src), + }, + 6 => match src { + b"DELETE" => Ok(Method(Delete)), + _ => Method::extension_inline(src), + }, + 7 => match src { + b"OPTIONS" => Ok(Method(Options)), + b"CONNECT" => Ok(Method(Connect)), + _ => Method::extension_inline(src), + }, _ => { - if src.len() < MAX_INLINE { + if src.len() < InlineExtension::MAX { Method::extension_inline(src) } else { - let mut data: Vec = vec![0; src.len()]; + let allocated = AllocatedExtension::new(src)?; - write_checked(src, &mut data)?; - - Ok(Method(ExtensionAllocated(data.into_boxed_slice()))) + Ok(Method(ExtensionAllocated(allocated))) } } } } fn extension_inline(src: &[u8]) -> Result { - let mut data: [u8; MAX_INLINE] = Default::default(); - - write_checked(src, &mut data)?; + let inline = InlineExtension::new(src)?; - Ok(Method(ExtensionInline(data, src.len() as u8))) + Ok(Method(ExtensionInline(inline))) } /// Whether a method is considered "safe", meaning the request is @@ -209,7 +150,7 @@ pub fn is_safe(&self) -> bool { match self.0 { Get | Head | Options | Trace => true, - _ => false + _ => false, } } @@ -238,34 +179,12 @@ Trace => "TRACE", Connect => "CONNECT", Patch => "PATCH", - ExtensionInline(ref data, len) => { - unsafe { - str::from_utf8_unchecked(&data[..len as usize]) - } - } - ExtensionAllocated(ref data) => { - unsafe { - str::from_utf8_unchecked(data) - } - } + ExtensionInline(ref inline) => inline.as_str(), + ExtensionAllocated(ref allocated) => allocated.as_str(), } } } -fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> { - for (i, &b) in src.iter().enumerate() { - let b = METHOD_CHARS[b as usize]; - - if b == 0 { - return Err(InvalidMethod::new()); - } - - dst[i] = b; - } - - Ok(()) -} - impl AsRef for Method { #[inline] fn as_ref(&self) -> &str { @@ -275,7 +194,7 @@ impl<'a> PartialEq<&'a Method> for Method { #[inline] - fn eq(&self, other: & &'a Method) -> bool { + fn eq(&self, other: &&'a Method) -> bool { self == *other } } @@ -316,13 +235,13 @@ } impl fmt::Debug for Method { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_ref()) } } impl fmt::Display for Method { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fmt.write_str(self.as_ref()) } } @@ -341,16 +260,7 @@ } } -impl<'a> HttpTryFrom<&'a Method> for Method { - type Error = ::error::Never; - - #[inline] - fn try_from(t: &'a Method) -> Result { - Ok(t.clone()) - } -} - -impl<'a> HttpTryFrom<&'a [u8]> for Method { +impl<'a> TryFrom<&'a [u8]> for Method { type Error = InvalidMethod; #[inline] @@ -359,12 +269,12 @@ } } -impl<'a> HttpTryFrom<&'a str> for Method { +impl<'a> TryFrom<&'a str> for Method { type Error = InvalidMethod; #[inline] fn try_from(t: &'a str) -> Result { - HttpTryFrom::try_from(t.as_bytes()) + TryFrom::try_from(t.as_bytes()) } } @@ -373,15 +283,13 @@ #[inline] fn from_str(t: &str) -> Result { - HttpTryFrom::try_from(t) + TryFrom::try_from(t) } } impl InvalidMethod { fn new() -> InvalidMethod { - InvalidMethod { - _priv: (), - } + InvalidMethod { _priv: () } } } @@ -394,46 +302,172 @@ } impl fmt::Display for InvalidMethod { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.description()) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid HTTP method") } } -impl Error for InvalidMethod { - fn description(&self) -> &str { - "invalid HTTP method" +impl Error for InvalidMethod {} + +mod extension { + use super::InvalidMethod; + use std::str; + + #[derive(Clone, PartialEq, Eq, Hash)] + // Invariant: the first self.1 bytes of self.0 are valid UTF-8. + pub struct InlineExtension([u8; InlineExtension::MAX], u8); + + #[derive(Clone, PartialEq, Eq, Hash)] + // Invariant: self.0 contains valid UTF-8. + pub struct AllocatedExtension(Box<[u8]>); + + impl InlineExtension { + // Method::from_bytes() assumes this is at least 7 + pub const MAX: usize = 15; + + pub fn new(src: &[u8]) -> Result { + let mut data: [u8; InlineExtension::MAX] = Default::default(); + + write_checked(src, &mut data)?; + + // Invariant: write_checked ensures that the first src.len() bytes + // of data are valid UTF-8. + Ok(InlineExtension(data, src.len() as u8)) + } + + pub fn as_str(&self) -> &str { + let InlineExtension(ref data, len) = self; + // Safety: the invariant of InlineExtension ensures that the first + // len bytes of data contain valid UTF-8. + unsafe {str::from_utf8_unchecked(&data[..*len as usize])} + } } -} -#[test] -fn test_method_eq() { - assert_eq!(Method::GET, Method::GET); - assert_eq!(Method::GET, "GET"); - assert_eq!(&Method::GET, "GET"); + impl AllocatedExtension { + pub fn new(src: &[u8]) -> Result { + let mut data: Vec = vec![0; src.len()]; + + write_checked(src, &mut data)?; + + // Invariant: data is exactly src.len() long and write_checked + // ensures that the first src.len() bytes of data are valid UTF-8. + Ok(AllocatedExtension(data.into_boxed_slice())) + } - assert_eq!("GET", Method::GET); - assert_eq!("GET", &Method::GET); + pub fn as_str(&self) -> &str { + // Safety: the invariant of AllocatedExtension ensures that self.0 + // contains valid UTF-8. + unsafe {str::from_utf8_unchecked(&self.0)} + } + } - assert_eq!(&Method::GET, Method::GET); - assert_eq!(Method::GET, &Method::GET); -} + // From the HTTP spec section 5.1.1, the HTTP method is case-sensitive and can + // contain the following characters: + // + // ``` + // method = token + // token = 1*tchar + // tchar = "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" / "." / + // "^" / "_" / "`" / "|" / "~" / DIGIT / ALPHA + // ``` + // + // https://www.w3.org/Protocols/HTTP/1.1/draft-ietf-http-v11-spec-01#Method + // + // Note that this definition means that any &[u8] that consists solely of valid + // characters is also valid UTF-8 because the valid method characters are a + // subset of the valid 1 byte UTF-8 encoding. + const METHOD_CHARS: [u8; 256] = [ + // 0 1 2 3 4 5 6 7 8 9 + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 1x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 2x + b'\0', b'\0', b'\0', b'!', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 3x + b'\0', b'\0', b'*', b'+', b'\0', b'-', b'.', b'\0', b'0', b'1', // 4x + b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'\0', b'\0', // 5x + b'\0', b'\0', b'\0', b'\0', b'\0', b'A', b'B', b'C', b'D', b'E', // 6x + b'F', b'G', b'H', b'I', b'J', b'K', b'L', b'M', b'N', b'O', // 7x + b'P', b'Q', b'R', b'S', b'T', b'U', b'V', b'W', b'X', b'Y', // 8x + b'Z', b'\0', b'\0', b'\0', b'^', b'_', b'`', b'a', b'b', b'c', // 9x + b'd', b'e', b'f', b'g', b'h', b'i', b'j', b'k', b'l', b'm', // 10x + b'n', b'o', b'p', b'q', b'r', b's', b't', b'u', b'v', b'w', // 11x + b'x', b'y', b'z', b'\0', b'|', b'\0', b'~', b'\0', b'\0', b'\0', // 12x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 13x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 14x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 15x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 16x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 17x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 18x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 19x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 20x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 21x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 22x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 23x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', b'\0', // 24x + b'\0', b'\0', b'\0', b'\0', b'\0', b'\0' // 25x + ]; + + // write_checked ensures (among other things) that the first src.len() bytes + // of dst are valid UTF-8 + fn write_checked(src: &[u8], dst: &mut [u8]) -> Result<(), InvalidMethod> { + for (i, &b) in src.iter().enumerate() { + let b = METHOD_CHARS[b as usize]; -#[test] -fn test_invalid_method() { - assert!(Method::from_str("").is_err()); - assert!(Method::from_bytes(b"").is_err()); + if b == 0 { + return Err(InvalidMethod::new()); + } + + dst[i] = b; + } + + Ok(()) + } } -#[test] -fn test_is_idempotent() { - assert!(Method::OPTIONS.is_idempotent()); - assert!(Method::GET.is_idempotent()); - assert!(Method::PUT.is_idempotent()); - assert!(Method::DELETE.is_idempotent()); - assert!(Method::HEAD.is_idempotent()); - assert!(Method::TRACE.is_idempotent()); +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_method_eq() { + assert_eq!(Method::GET, Method::GET); + assert_eq!(Method::GET, "GET"); + assert_eq!(&Method::GET, "GET"); + + assert_eq!("GET", Method::GET); + assert_eq!("GET", &Method::GET); + + assert_eq!(&Method::GET, Method::GET); + assert_eq!(Method::GET, &Method::GET); + } + + #[test] + fn test_invalid_method() { + assert!(Method::from_str("").is_err()); + assert!(Method::from_bytes(b"").is_err()); + assert!(Method::from_bytes(&[0xC0]).is_err()); // invalid utf-8 + assert!(Method::from_bytes(&[0x10]).is_err()); // invalid method characters + } + + #[test] + fn test_is_idempotent() { + assert!(Method::OPTIONS.is_idempotent()); + assert!(Method::GET.is_idempotent()); + assert!(Method::PUT.is_idempotent()); + assert!(Method::DELETE.is_idempotent()); + assert!(Method::HEAD.is_idempotent()); + assert!(Method::TRACE.is_idempotent()); - assert!(!Method::POST.is_idempotent()); - assert!(!Method::CONNECT.is_idempotent()); - assert!(!Method::PATCH.is_idempotent()); + assert!(!Method::POST.is_idempotent()); + assert!(!Method::CONNECT.is_idempotent()); + assert!(!Method::PATCH.is_idempotent()); + } + + #[test] + fn test_extention_method() { + assert_eq!(Method::from_str("WOW").unwrap(), "WOW"); + assert_eq!(Method::from_str("wOw!!").unwrap(), "wOw!!"); + + let long_method = "This_is_a_very_long_method.It_is_valid_but_unlikely."; + assert_eq!(Method::from_str(&long_method).unwrap(), long_method); + } } diff -Nru rust-http-0.1.21/src/request.rs rust-http-0.2.7/src/request.rs --- rust-http-0.1.21/src/request.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/request.rs 1973-11-29 21:33:09.000000000 +0000 @@ -12,12 +12,12 @@ //! ```no_run //! use http::{Request, Response}; //! -//! let mut request = Request::builder(); -//! request.uri("https://www.rust-lang.org/") -//! .header("User-Agent", "my-awesome-agent/1.0"); +//! let mut request = Request::builder() +//! .uri("https://www.rust-lang.org/") +//! .header("User-Agent", "my-awesome-agent/1.0"); //! //! if needs_awesome_header() { -//! request.header("Awesome", "yes"); +//! request = request.header("Awesome", "yes"); //! } //! //! let response = send(request.body(()).unwrap()); @@ -53,12 +53,13 @@ //! ``` use std::any::Any; +use std::convert::{TryFrom}; use std::fmt; -use {Uri, Error, Result, HttpTryFrom, Extensions}; -use header::{HeaderMap, HeaderName, HeaderValue}; -use method::Method; -use version::Version; +use crate::header::{HeaderMap, HeaderName, HeaderValue}; +use crate::method::Method; +use crate::version::Version; +use crate::{Extensions, Result, Uri}; /// Represents an HTTP request. /// @@ -74,12 +75,12 @@ /// ```no_run /// use http::{Request, Response}; /// -/// let mut request = Request::builder(); -/// request.uri("https://www.rust-lang.org/") -/// .header("User-Agent", "my-awesome-agent/1.0"); +/// let mut request = Request::builder() +/// .uri("https://www.rust-lang.org/") +/// .header("User-Agent", "my-awesome-agent/1.0"); /// /// if needs_awesome_header() { -/// request.header("Awesome", "yes"); +/// request = request.header("Awesome", "yes"); /// } /// /// let response = send(request.body(()).unwrap()); @@ -187,8 +188,7 @@ /// through a builder-like pattern. #[derive(Debug)] pub struct Builder { - head: Option, - err: Option, + inner: Result, } impl Request<()> { @@ -213,7 +213,6 @@ Builder::new() } - /// Creates a new `Builder` initialized with a GET method and the given URI. /// /// This method returns an instance of `Builder` which can be used to @@ -229,10 +228,12 @@ /// .unwrap(); /// ``` pub fn get(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::GET).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + + { + Builder::new().method(Method::GET).uri(uri) } /// Creates a new `Builder` initialized with a PUT method and the given URI. @@ -250,10 +251,12 @@ /// .unwrap(); /// ``` pub fn put(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::PUT).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + + { + Builder::new().method(Method::PUT).uri(uri) } /// Creates a new `Builder` initialized with a POST method and the given URI. @@ -271,10 +274,12 @@ /// .unwrap(); /// ``` pub fn post(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::POST).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + + { + Builder::new().method(Method::POST).uri(uri) } /// Creates a new `Builder` initialized with a DELETE method and the given URI. @@ -292,10 +297,12 @@ /// .unwrap(); /// ``` pub fn delete(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::DELETE).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + + { + Builder::new().method(Method::DELETE).uri(uri) } /// Creates a new `Builder` initialized with an OPTIONS method and the given URI. @@ -314,10 +321,12 @@ /// # assert_eq!(*request.method(), Method::OPTIONS); /// ``` pub fn options(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::OPTIONS).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + + { + Builder::new().method(Method::OPTIONS).uri(uri) } /// Creates a new `Builder` initialized with a HEAD method and the given URI. @@ -335,10 +344,12 @@ /// .unwrap(); /// ``` pub fn head(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::HEAD).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + + { + Builder::new().method(Method::HEAD).uri(uri) } /// Creates a new `Builder` initialized with a CONNECT method and the given URI. @@ -356,10 +367,12 @@ /// .unwrap(); /// ``` pub fn connect(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::CONNECT).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + + { + Builder::new().method(Method::CONNECT).uri(uri) } /// Creates a new `Builder` initialized with a PATCH method and the given URI. @@ -377,10 +390,11 @@ /// .unwrap(); /// ``` pub fn patch(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::PATCH).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + { + Builder::new().method(Method::PATCH).uri(uri) } /// Creates a new `Builder` initialized with a TRACE method and the given URI. @@ -398,10 +412,11 @@ /// .unwrap(); /// ``` pub fn trace(uri: T) -> Builder - where Uri: HttpTryFrom { - let mut b = Builder::new(); - b.method(Method::TRACE).uri(uri); - b + where + Uri: TryFrom, + >::Error: Into, + { + Builder::new().method(Method::TRACE).uri(uri) } } @@ -624,7 +639,6 @@ &mut self.body } - /// Consumes the request, returning just the body. /// /// # Examples @@ -671,9 +685,13 @@ /// ``` #[inline] pub fn map(self, f: F) -> Request - where F: FnOnce(T) -> U + where + F: FnOnce(T) -> U, { - Request { body: f(self.body), head: self.head } + Request { + body: f(self.body), + head: self.head, + } } } @@ -684,7 +702,7 @@ } impl fmt::Debug for Request { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Request") .field("method", self.method()) .field("uri", self.uri()) @@ -699,7 +717,7 @@ impl Parts { /// Creates a new default instance of `Parts` fn new() -> Parts { - Parts{ + Parts { method: Method::default(), uri: Uri::default(), version: Version::default(), @@ -711,7 +729,7 @@ } impl fmt::Debug for Parts { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Parts") .field("method", &self.method) .field("uri", &self.uri) @@ -758,44 +776,35 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn method(&mut self, method: T) -> &mut Builder - where Method: HttpTryFrom, + pub fn method(self, method: T) -> Builder + where + Method: TryFrom, + >::Error: Into, { - if let Some(head) = head(&mut self.head, &self.err) { - match HttpTryFrom::try_from(method) { - Ok(s) => head.method = s, - Err(e) => self.err = Some(e.into()), - } - } - self + self.and_then(move |mut head| { + let method = TryFrom::try_from(method).map_err(Into::into)?; + head.method = method; + Ok(head) + }) } /// Get the HTTP Method for this request. - /// - /// By default this is `GET`. - /// if builder has error, returns None. - /// + /// + /// By default this is `GET`. If builder has error, returns None. + /// /// # Examples - /// + /// /// ``` /// # use http::*; - /// + /// /// let mut req = Request::builder(); /// assert_eq!(req.method_ref(),Some(&Method::GET)); - /// req.method("POST"); + /// + /// req = req.method("POST"); /// assert_eq!(req.method_ref(),Some(&Method::POST)); - /// req.method("DELETE"); - /// assert_eq!(req.method_ref(),Some(&Method::DELETE)); /// ``` - pub fn method_ref(&self) -> Option<&Method> - { - if self.err.is_some() { - return None - } - match self.head { - Some(ref head) => Some(&head.method), - None => None - } + pub fn method_ref(&self) -> Option<&Method> { + self.inner.as_ref().ok().map(|h| &h.method) } /// Set the URI for this request. @@ -815,41 +824,34 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn uri(&mut self, uri: T) -> &mut Builder - where Uri: HttpTryFrom, + pub fn uri(self, uri: T) -> Builder + where + Uri: TryFrom, + >::Error: Into, { - if let Some(head) = head(&mut self.head, &self.err) { - match HttpTryFrom::try_from(uri) { - Ok(s) => head.uri = s, - Err(e) => self.err = Some(e.into()), - } - } - self + self.and_then(move |mut head| { + head.uri = TryFrom::try_from(uri).map_err(Into::into)?; + Ok(head) + }) } /// Get the URI for this request - /// - /// By default this is `/` + /// + /// By default this is `/`. + /// /// # Examples - /// + /// /// ``` /// # use http::*; - /// + /// /// let mut req = Request::builder(); - /// assert_eq!(req.uri_ref().unwrap().to_string(), "/" ); - /// req.uri("https://www.rust-lang.org/"); - /// assert_eq!(req.uri_ref().unwrap().to_string(), "https://www.rust-lang.org/" ); + /// assert_eq!(req.uri_ref().unwrap(), "/" ); + /// + /// req = req.uri("https://www.rust-lang.org/"); + /// assert_eq!(req.uri_ref().unwrap(), "https://www.rust-lang.org/" ); /// ``` - pub fn uri_ref(&self) -> Option<&Uri> - { - if self.err.is_some() { - return None; - } - match self.head - { - Some(ref head) => Some(&head.uri), - None => None - } + pub fn uri_ref(&self) -> Option<&Uri> { + self.inner.as_ref().ok().map(|h| &h.uri) } /// Set the HTTP version for this request. @@ -869,11 +871,30 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn version(&mut self, version: Version) -> &mut Builder { - if let Some(head) = head(&mut self.head, &self.err) { + pub fn version(self, version: Version) -> Builder { + self.and_then(move |mut head| { head.version = version; - } - self + Ok(head) + }) + } + + /// Get the HTTP version for this request + /// + /// By default this is HTTP/1.1. + /// + /// # Examples + /// + /// ``` + /// # use http::*; + /// + /// let mut req = Request::builder(); + /// assert_eq!(req.version_ref().unwrap(), &Version::HTTP_11 ); + /// + /// req = req.version(Version::HTTP_2); + /// assert_eq!(req.version_ref().unwrap(), &Version::HTTP_2 ); + /// ``` + pub fn version_ref(&self) -> Option<&Version> { + self.inner.as_ref().ok().map(|h| &h.version) } /// Appends a header to this request builder. @@ -894,60 +915,47 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Builder - where HeaderName: HttpTryFrom, - HeaderValue: HttpTryFrom + pub fn header(self, key: K, value: V) -> Builder + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, { - if let Some(head) = head(&mut self.head, &self.err) { - match >::try_from(key) { - Ok(key) => { - match >::try_from(value) { - Ok(value) => { head.headers.append(key, value); } - Err(e) => self.err = Some(e.into()), - } - }, - Err(e) => self.err = Some(e.into()), - }; - } - self + self.and_then(move |mut head| { + let name = >::try_from(key).map_err(Into::into)?; + let value = >::try_from(value).map_err(Into::into)?; + head.headers.append(name, value); + Ok(head) + }) } /// Get header on this request builder. /// when builder has error returns None - /// + /// /// # Example - /// + /// /// ``` - /// # use http::*; - /// # use http::header::HeaderValue; - /// # use http::request::Builder; - /// let mut req = Request::builder(); - /// req.header("Accept", "text/html") - /// .header("X-Custom-Foo", "bar"); + /// # use http::Request; + /// let req = Request::builder() + /// .header("Accept", "text/html") + /// .header("X-Custom-Foo", "bar"); /// let headers = req.headers_ref().unwrap(); /// assert_eq!( headers["Accept"], "text/html" ); /// assert_eq!( headers["X-Custom-Foo"], "bar" ); /// ``` pub fn headers_ref(&self) -> Option<&HeaderMap> { - if self.err.is_some() { - return None; - } - match self.head - { - Some(ref head) => Some(&head.headers), - None => None - } + self.inner.as_ref().ok().map(|h| &h.headers) } - /// Get header on this request builder. - /// when builder has error returns None - /// + /// Get headers on this request builder. + /// + /// When builder has error returns None. + /// /// # Example - /// + /// /// ``` - /// # use http::*; - /// # use http::header::HeaderValue; - /// # use http::request::Builder; + /// # use http::{header::HeaderValue, Request}; /// let mut req = Request::builder(); /// { /// let headers = req.headers_mut().unwrap(); @@ -959,14 +967,7 @@ /// assert_eq!( headers["X-Custom-Foo"], "bar" ); /// ``` pub fn headers_mut(&mut self) -> Option<&mut HeaderMap> { - if self.err.is_some() { - return None; - } - match self.head - { - Some(ref mut head) => Some(&mut head.headers), - None => None - } + self.inner.as_mut().ok().map(|h| &mut h.headers) } /// Adds an extension to this builder @@ -984,21 +985,49 @@ /// assert_eq!(req.extensions().get::<&'static str>(), /// Some(&"My Extension")); /// ``` - pub fn extension(&mut self, extension: T) -> &mut Builder - where T: Any + Send + Sync + 'static, + pub fn extension(self, extension: T) -> Builder + where + T: Any + Send + Sync + 'static, { - if let Some(head) = head(&mut self.head, &self.err) { + self.and_then(move |mut head| { head.extensions.insert(extension); - } - self + Ok(head) + }) } - fn take_parts(&mut self) -> Result { - let ret = self.head.take().expect("cannot reuse request builder"); - if let Some(e) = self.err.take() { - return Err(e) - } - Ok(ret) + /// Get a reference to the extensions for this request builder. + /// + /// If the builder has an error, this returns `None`. + /// + /// # Example + /// + /// ``` + /// # use http::Request; + /// let req = Request::builder().extension("My Extension").extension(5u32); + /// let extensions = req.extensions_ref().unwrap(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_ref(&self) -> Option<&Extensions> { + self.inner.as_ref().ok().map(|h| &h.extensions) + } + + /// Get a mutable reference to the extensions for this request builder. + /// + /// If the builder has an error, this returns `None`. + /// + /// # Example + /// + /// ``` + /// # use http::Request; + /// let mut req = Request::builder().extension("My Extension"); + /// let mut extensions = req.extensions_mut().unwrap(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// extensions.insert(5u32); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_mut(&mut self) -> Option<&mut Extensions> { + self.inner.as_mut().ok().map(|h| &mut h.extensions) } /// "Consumes" this builder, using the provided `body` to return a @@ -1012,11 +1041,6 @@ /// "Bar\r\n")` the error will be returned when this function is called /// rather than when `header` was called. /// - /// # Panics - /// - /// This method will panic if the builder is reused. The `body` function can - /// only be called once. - /// /// # Examples /// /// ``` @@ -1026,29 +1050,32 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn body(&mut self, body: T) -> Result> { - Ok(Request { - head: self.take_parts()?, - body: body, + pub fn body(self, body: T) -> Result> { + self.inner.map(move |head| { + Request { + head, + body, + } }) } -} -fn head<'a>(head: &'a mut Option, err: &Option) - -> Option<&'a mut Parts> -{ - if err.is_some() { - return None + // private + + fn and_then(self, func: F) -> Self + where + F: FnOnce(Parts) -> Result + { + Builder { + inner: self.inner.and_then(func), + } } - head.as_mut() } impl Default for Builder { #[inline] fn default() -> Builder { Builder { - head: Some(Parts::new()), - err: None, + inner: Ok(Parts::new()), } } } @@ -1059,7 +1086,7 @@ #[test] fn it_can_map_a_body_from_one_type_to_another() { - let request= Request::builder().body("some string").unwrap(); + let request = Request::builder().body("some string").unwrap(); let mapped_request = request.map(|s| { assert_eq!(s, "some string"); 123u32 diff -Nru rust-http-0.1.21/src/response.rs rust-http-0.2.7/src/response.rs --- rust-http-0.1.21/src/response.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/response.rs 1973-11-29 21:33:09.000000000 +0000 @@ -13,15 +13,15 @@ //! use http::{Request, Response, StatusCode}; //! //! fn respond_to(req: Request<()>) -> http::Result> { -//! let mut response = Response::builder(); -//! response.header("Foo", "Bar") -//! .status(StatusCode::OK); +//! let mut builder = Response::builder() +//! .header("Foo", "Bar") +//! .status(StatusCode::OK); //! //! if req.headers().contains_key("Another-Header") { -//! response.header("Another-Header", "Ack"); +//! builder = builder.header("Another-Header", "Ack"); //! } //! -//! response.body(()) +//! builder.body(()) //! } //! ``` //! @@ -62,12 +62,13 @@ //! ``` use std::any::Any; +use std::convert::TryFrom; use std::fmt; -use {Error, Result, HttpTryFrom, Extensions}; -use header::{HeaderMap, HeaderName, HeaderValue}; -use status::StatusCode; -use version::Version; +use crate::header::{HeaderMap, HeaderName, HeaderValue}; +use crate::status::StatusCode; +use crate::version::Version; +use crate::{Extensions, Result}; /// Represents an HTTP response /// @@ -77,7 +78,7 @@ /// value that has been deserialized. /// /// Typically you'll work with responses on the client side as the result of -/// sending a `Request` and on the server you'll be generating a `Request` to +/// sending a `Request` and on the server you'll be generating a `Response` to /// send back to the client. /// /// # Examples @@ -88,15 +89,15 @@ /// use http::{Request, Response, StatusCode}; /// /// fn respond_to(req: Request<()>) -> http::Result> { -/// let mut response = Response::builder(); -/// response.header("Foo", "Bar") -/// .status(StatusCode::OK); +/// let mut builder = Response::builder() +/// .header("Foo", "Bar") +/// .status(StatusCode::OK); /// /// if req.headers().contains_key("Another-Header") { -/// response.header("Another-Header", "Ack"); +/// builder = builder.header("Another-Header", "Ack"); /// } /// -/// response.body(()) +/// builder.body(()) /// } /// ``` /// @@ -145,10 +146,10 @@ /// use http::Response; /// use serde::de; /// -/// fn deserialize(req: Response>) -> serde_json::Result> +/// fn deserialize(res: Response>) -> serde_json::Result> /// where for<'de> T: de::Deserialize<'de>, /// { -/// let (parts, body) = req.into_parts(); +/// let (parts, body) = res.into_parts(); /// let body = serde_json::from_slice(&body)?; /// Ok(Response::from_parts(parts, body)) /// } @@ -165,10 +166,10 @@ /// use http::Response; /// use serde::ser; /// -/// fn serialize(req: Response) -> serde_json::Result>> +/// fn serialize(res: Response) -> serde_json::Result>> /// where T: ser::Serialize, /// { -/// let (parts, body) = req.into_parts(); +/// let (parts, body) = res.into_parts(); /// let body = serde_json::to_vec(&body)?; /// Ok(Response::from_parts(parts, body)) /// } @@ -206,8 +207,7 @@ /// builder-like pattern. #[derive(Debug)] pub struct Builder { - head: Option, - err: Option, + inner: Result, } impl Response<()> { @@ -471,9 +471,13 @@ /// ``` #[inline] pub fn map(self, f: F) -> Response - where F: FnOnce(T) -> U + where + F: FnOnce(T) -> U, { - Response { body: f(self.body), head: self.head } + Response { + body: f(self.body), + head: self.head, + } } } @@ -485,7 +489,7 @@ } impl fmt::Debug for Response { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Response") .field("status", &self.status()) .field("version", &self.version()) @@ -499,7 +503,7 @@ impl Parts { /// Creates a new default instance of `Parts` fn new() -> Parts { - Parts{ + Parts { status: StatusCode::default(), version: Version::default(), headers: HeaderMap::default(), @@ -510,7 +514,7 @@ } impl fmt::Debug for Parts { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Parts") .field("status", &self.status) .field("version", &self.version) @@ -557,16 +561,15 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn status(&mut self, status: T) -> &mut Builder - where StatusCode: HttpTryFrom, + pub fn status(self, status: T) -> Builder + where + StatusCode: TryFrom, + >::Error: Into, { - if let Some(head) = head(&mut self.head, &self.err) { - match HttpTryFrom::try_from(status) { - Ok(s) => head.status = s, - Err(e) => self.err = Some(e.into()), - } - } - self + self.and_then(move |mut head| { + head.status = TryFrom::try_from(status).map_err(Into::into)?; + Ok(head) + }) } /// Set the HTTP version for this response. @@ -586,11 +589,11 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn version(&mut self, version: Version) -> &mut Builder { - if let Some(head) = head(&mut self.head, &self.err) { + pub fn version(self, version: Version) -> Builder { + self.and_then(move |mut head| { head.version = version; - } - self + Ok(head) + }) } /// Appends a header to this response builder. @@ -612,56 +615,46 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Builder - where HeaderName: HttpTryFrom, - HeaderValue: HttpTryFrom + pub fn header(self, key: K, value: V) -> Builder + where + HeaderName: TryFrom, + >::Error: Into, + HeaderValue: TryFrom, + >::Error: Into, { - if let Some(head) = head(&mut self.head, &self.err) { - match >::try_from(key) { - Ok(key) => { - match >::try_from(value) { - Ok(value) => { head.headers.append(key, value); } - Err(e) => self.err = Some(e.into()), - } - }, - Err(e) => self.err = Some(e.into()), - }; - } - self + self.and_then(move |mut head| { + let name = >::try_from(key).map_err(Into::into)?; + let value = >::try_from(value).map_err(Into::into)?; + head.headers.append(name, value); + Ok(head) + }) } /// Get header on this response builder. - /// when builder has error returns None - /// + /// + /// When builder has error returns None. + /// /// # Example - /// + /// /// ``` - /// # use http::*; + /// # use http::Response; /// # use http::header::HeaderValue; - /// # use http::response::Builder; - /// let mut res = Response::builder(); - /// res.header("Accept", "text/html") - /// .header("X-Custom-Foo", "bar"); + /// let res = Response::builder() + /// .header("Accept", "text/html") + /// .header("X-Custom-Foo", "bar"); /// let headers = res.headers_ref().unwrap(); /// assert_eq!( headers["Accept"], "text/html" ); /// assert_eq!( headers["X-Custom-Foo"], "bar" ); /// ``` pub fn headers_ref(&self) -> Option<&HeaderMap> { - if self.err.is_some() { - return None; - } - match self.head - { - Some(ref head) => Some(&head.headers), - None => None - } + self.inner.as_ref().ok().map(|h| &h.headers) } /// Get header on this response builder. /// when builder has error returns None - /// + /// /// # Example - /// + /// /// ``` /// # use http::*; /// # use http::header::HeaderValue; @@ -677,14 +670,7 @@ /// assert_eq!( headers["X-Custom-Foo"], "bar" ); /// ``` pub fn headers_mut(&mut self) -> Option<&mut HeaderMap> { - if self.err.is_some() { - return None; - } - match self.head - { - Some(ref mut head) => Some(&mut head.headers), - None => None - } + self.inner.as_mut().ok().map(|h| &mut h.headers) } /// Adds an extension to this builder @@ -702,21 +688,49 @@ /// assert_eq!(response.extensions().get::<&'static str>(), /// Some(&"My Extension")); /// ``` - pub fn extension(&mut self, extension: T) -> &mut Builder - where T: Any + Send + Sync + 'static, + pub fn extension(self, extension: T) -> Builder + where + T: Any + Send + Sync + 'static, { - if let Some(head) = head(&mut self.head, &self.err) { + self.and_then(move |mut head| { head.extensions.insert(extension); - } - self + Ok(head) + }) } - fn take_parts(&mut self) -> Result { - let ret = self.head.take().expect("cannot reuse response builder"); - if let Some(e) = self.err.take() { - return Err(e) - } - Ok(ret) + /// Get a reference to the extensions for this response builder. + /// + /// If the builder has an error, this returns `None`. + /// + /// # Example + /// + /// ``` + /// # use http::Response; + /// let res = Response::builder().extension("My Extension").extension(5u32); + /// let extensions = res.extensions_ref().unwrap(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_ref(&self) -> Option<&Extensions> { + self.inner.as_ref().ok().map(|h| &h.extensions) + } + + /// Get a mutable reference to the extensions for this response builder. + /// + /// If the builder has an error, this returns `None`. + /// + /// # Example + /// + /// ``` + /// # use http::Response; + /// let mut res = Response::builder().extension("My Extension"); + /// let mut extensions = res.extensions_mut().unwrap(); + /// assert_eq!(extensions.get::<&'static str>(), Some(&"My Extension")); + /// extensions.insert(5u32); + /// assert_eq!(extensions.get::(), Some(&5u32)); + /// ``` + pub fn extensions_mut(&mut self) -> Option<&mut Extensions> { + self.inner.as_mut().ok().map(|h| &mut h.extensions) } /// "Consumes" this builder, using the provided `body` to return a @@ -730,11 +744,6 @@ /// "Bar\r\n")` the error will be returned when this function is called /// rather than when `header` was called. /// - /// # Panics - /// - /// This method will panic if the builder is reused. The `body` function can - /// only be called once. - /// /// # Examples /// /// ``` @@ -744,29 +753,32 @@ /// .body(()) /// .unwrap(); /// ``` - pub fn body(&mut self, body: T) -> Result> { - Ok(Response { - head: self.take_parts()?, - body: body, + pub fn body(self, body: T) -> Result> { + self.inner.map(move |head| { + Response { + head, + body, + } }) } -} -fn head<'a>(head: &'a mut Option, err: &Option) - -> Option<&'a mut Parts> -{ - if err.is_some() { - return None + // private + + fn and_then(self, func: F) -> Self + where + F: FnOnce(Parts) -> Result + { + Builder { + inner: self.inner.and_then(func), + } } - head.as_mut() } impl Default for Builder { #[inline] fn default() -> Builder { Builder { - head: Some(Parts::new()), - err: None, + inner: Ok(Parts::new()), } } } diff -Nru rust-http-0.1.21/src/status.rs rust-http-0.2.7/src/status.rs --- rust-http-0.1.21/src/status.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/status.rs 1973-11-29 21:33:09.000000000 +0000 @@ -14,21 +14,23 @@ //! assert!(StatusCode::OK.is_success()); //! ``` -use std::fmt; +use std::convert::TryFrom; +use std::num::NonZeroU16; use std::error::Error; +use std::fmt; use std::str::FromStr; -use HttpTryFrom; - /// An HTTP status code (`status-code` in RFC 7230 et al.). /// -/// This type contains constants for all common status codes. -/// It allows status codes in the range [100, 599]. +/// Constants are provided for known status codes, including those in the IANA +/// [HTTP Status Code Registry]( +/// https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml). /// -/// IANA maintain the [Hypertext Transfer Protocol (HTTP) Status Code -/// Registry](http://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml) which is -/// the source for this enum (with one exception, 418 I'm a teapot, which is -/// inexplicably not in the register). +/// Status code values in the range 100-999 (inclusive) are supported by this +/// type. Values in the range 100-599 are semantically classified by the most +/// significant digit. See [`StatusCode::is_success`], etc. Values above 599 +/// are unclassified but allowed for legacy compatibility, though their use is +/// discouraged. Applications may interpret such values as protocol errors. /// /// # Examples /// @@ -40,12 +42,12 @@ /// assert!(StatusCode::OK.is_success()); /// ``` #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct StatusCode(u16); +pub struct StatusCode(NonZeroU16); /// A possible error value when converting a `StatusCode` from a `u16` or `&str` /// /// This error indicates that the supplied input was not a valid number, was less -/// than 100, or was greater than 599. +/// than 100, or was greater than 999. pub struct InvalidStatusCode { _priv: (), } @@ -54,7 +56,7 @@ /// Converts a u16 to a status code. /// /// The function validates the correctness of the supplied u16. It must be - /// greater or equal to 100 but less than 600. + /// greater or equal to 100 and less than 1000. /// /// # Example /// @@ -69,11 +71,13 @@ /// ``` #[inline] pub fn from_u16(src: u16) -> Result { - if src < 100 || src >= 600 { + if src < 100 || src >= 1000 { return Err(InvalidStatusCode::new()); } - Ok(StatusCode(src)) + NonZeroU16::new(src) + .map(StatusCode) + .ok_or_else(InvalidStatusCode::new) } /// Converts a &[u8] to a status code @@ -86,12 +90,14 @@ let b = src[1].wrapping_sub(b'0') as u16; let c = src[2].wrapping_sub(b'0') as u16; - if a == 0 || a > 5 || b > 9 || c > 9 { + if a == 0 || a > 9 || b > 9 || c > 9 { return Err(InvalidStatusCode::new()); } let status = (a * 100) + (b * 10) + c; - Ok(StatusCode(status)) + NonZeroU16::new(status) + .map(StatusCode) + .ok_or_else(InvalidStatusCode::new) } /// Returns the `u16` corresponding to this `StatusCode`. @@ -127,7 +133,17 @@ /// ``` #[inline] pub fn as_str(&self) -> &str { - CODES_AS_STR[(self.0 - 100) as usize] + let offset = (self.0.get() - 100) as usize; + let offset = offset * 3; + + // Invariant: self has checked range [100, 999] and CODE_DIGITS is + // ASCII-only, of length 900 * 3 = 2700 bytes + + #[cfg(debug_assertions)] + { &CODE_DIGITS[offset..offset+3] } + + #[cfg(not(debug_assertions))] + unsafe { CODE_DIGITS.get_unchecked(offset..offset+3) } } /// Get the standardised `reason-phrase` for this status code. @@ -138,8 +154,9 @@ /// The reason phrase is defined as being exclusively for human readers. You should avoid /// deriving any meaning from it at all costs. /// - /// Bear in mind also that in HTTP/2.0 the reason phrase is abolished from transmission, and so - /// this canonical reason phrase really is the only reason phrase you’ll find. + /// Bear in mind also that in HTTP/2.0 and HTTP/3.0 the reason phrase is abolished from + /// transmission, and so this canonical reason phrase really is the only reason phrase you’ll + /// find. /// /// # Example /// @@ -148,43 +165,42 @@ /// assert_eq!(status.canonical_reason(), Some("OK")); /// ``` pub fn canonical_reason(&self) -> Option<&'static str> { - canonical_reason(self.0) + canonical_reason(self.0.get()) } - /// Check if status is within 100-199. #[inline] pub fn is_informational(&self) -> bool { - 200 > self.0 && self.0 >= 100 + 200 > self.0.get() && self.0.get() >= 100 } /// Check if status is within 200-299. #[inline] pub fn is_success(&self) -> bool { - 300 > self.0 && self.0 >= 200 + 300 > self.0.get() && self.0.get() >= 200 } /// Check if status is within 300-399. #[inline] pub fn is_redirection(&self) -> bool { - 400 > self.0 && self.0 >= 300 + 400 > self.0.get() && self.0.get() >= 300 } /// Check if status is within 400-499. #[inline] pub fn is_client_error(&self) -> bool { - 500 > self.0 && self.0 >= 400 + 500 > self.0.get() && self.0.get() >= 400 } /// Check if status is within 500-599. #[inline] pub fn is_server_error(&self) -> bool { - 600 > self.0 && self.0 >= 500 + 600 > self.0.get() && self.0.get() >= 500 } } impl fmt::Debug for StatusCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(&self.0, f) } } @@ -198,9 +214,13 @@ /// assert_eq!(format!("{}", StatusCode::OK), "200 OK"); /// ``` impl fmt::Display for StatusCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", u16::from(*self), - self.canonical_reason().unwrap_or("")) + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{} {}", + u16::from(*self), + self.canonical_reason().unwrap_or("") + ) } } @@ -228,7 +248,7 @@ impl From for u16 { #[inline] fn from(status: StatusCode) -> u16 { - status.0 + status.0.get() } } @@ -247,16 +267,7 @@ } } -impl<'a> HttpTryFrom<&'a StatusCode> for StatusCode { - type Error = ::error::Never; - - #[inline] - fn try_from(t: &'a StatusCode) -> Result { - Ok(t.clone()) - } -} - -impl<'a> HttpTryFrom<&'a [u8]> for StatusCode { +impl<'a> TryFrom<&'a [u8]> for StatusCode { type Error = InvalidStatusCode; #[inline] @@ -265,7 +276,7 @@ } } -impl<'a> HttpTryFrom<&'a str> for StatusCode { +impl<'a> TryFrom<&'a str> for StatusCode { type Error = InvalidStatusCode; #[inline] @@ -274,7 +285,7 @@ } } -impl HttpTryFrom for StatusCode { +impl TryFrom for StatusCode { type Error = InvalidStatusCode; #[inline] @@ -293,7 +304,7 @@ impl StatusCode { $( $(#[$docs])* - pub const $konst: StatusCode = StatusCode($num); + pub const $konst: StatusCode = StatusCode(unsafe { NonZeroU16::new_unchecked($num) }); )+ } @@ -520,51 +531,58 @@ } impl fmt::Display for InvalidStatusCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.description()) - } -} - -impl Error for InvalidStatusCode { - fn description(&self) -> &str { - "invalid status code" + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid status code") } } -macro_rules! status_code_strs { - ($($num:expr,)+) => { - const CODES_AS_STR: [&'static str; 500] = [ $( stringify!($num), )+ ]; - } -} - -status_code_strs!( - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, - - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, - 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, - 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, - - 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, - 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, - 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, - 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, - 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, - - 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, - 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, - 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, - 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, - 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, +impl Error for InvalidStatusCode {} - 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, - 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, - 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, - 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, - 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, - ); +// A string of packed 3-ASCII-digit status code values for the supported range +// of [100, 999] (900 codes, 2700 bytes). +const CODE_DIGITS: &'static str = "\ +100101102103104105106107108109110111112113114115116117118119\ +120121122123124125126127128129130131132133134135136137138139\ +140141142143144145146147148149150151152153154155156157158159\ +160161162163164165166167168169170171172173174175176177178179\ +180181182183184185186187188189190191192193194195196197198199\ +200201202203204205206207208209210211212213214215216217218219\ +220221222223224225226227228229230231232233234235236237238239\ +240241242243244245246247248249250251252253254255256257258259\ +260261262263264265266267268269270271272273274275276277278279\ +280281282283284285286287288289290291292293294295296297298299\ +300301302303304305306307308309310311312313314315316317318319\ +320321322323324325326327328329330331332333334335336337338339\ +340341342343344345346347348349350351352353354355356357358359\ +360361362363364365366367368369370371372373374375376377378379\ +380381382383384385386387388389390391392393394395396397398399\ +400401402403404405406407408409410411412413414415416417418419\ +420421422423424425426427428429430431432433434435436437438439\ +440441442443444445446447448449450451452453454455456457458459\ +460461462463464465466467468469470471472473474475476477478479\ +480481482483484485486487488489490491492493494495496497498499\ +500501502503504505506507508509510511512513514515516517518519\ +520521522523524525526527528529530531532533534535536537538539\ +540541542543544545546547548549550551552553554555556557558559\ +560561562563564565566567568569570571572573574575576577578579\ +580581582583584585586587588589590591592593594595596597598599\ +600601602603604605606607608609610611612613614615616617618619\ +620621622623624625626627628629630631632633634635636637638639\ +640641642643644645646647648649650651652653654655656657658659\ +660661662663664665666667668669670671672673674675676677678679\ +680681682683684685686687688689690691692693694695696697698699\ +700701702703704705706707708709710711712713714715716717718719\ +720721722723724725726727728729730731732733734735736737738739\ +740741742743744745746747748749750751752753754755756757758759\ +760761762763764765766767768769770771772773774775776777778779\ +780781782783784785786787788789790791792793794795796797798799\ +800801802803804805806807808809810811812813814815816817818819\ +820821822823824825826827828829830831832833834835836837838839\ +840841842843844845846847848849850851852853854855856857858859\ +860861862863864865866867868869870871872873874875876877878879\ +880881882883884885886887888889890891892893894895896897898899\ +900901902903904905906907908909910911912913914915916917918919\ +920921922923924925926927928929930931932933934935936937938939\ +940941942943944945946947948949950951952953954955956957958959\ +960961962963964965966967968969970971972973974975976977978979\ +980981982983984985986987988989990991992993994995996997998999"; diff -Nru rust-http-0.1.21/src/uri/authority.rs rust-http-0.2.7/src/uri/authority.rs --- rust-http-0.1.21/src/uri/authority.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/uri/authority.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,15 +1,12 @@ -// Deprecated in 1.26, needed until our minimum version is >=1.23. -#[allow(unused, deprecated)] -use std::ascii::AsciiExt; -use std::{cmp, fmt, str}; +use std::convert::TryFrom; use std::hash::{Hash, Hasher}; use std::str::FromStr; +use std::{cmp, fmt, str}; use bytes::Bytes; -use byte_str::ByteStr; -use convert::HttpTryFrom; -use super::{ErrorKind, InvalidUri, InvalidUriBytes, URI_CHARS, Port}; +use super::{ErrorKind, InvalidUri, Port, URI_CHARS}; +use crate::byte_str::ByteStr; /// Represents the authority component of a URI. #[derive(Clone)] @@ -19,40 +16,16 @@ impl Authority { pub(super) fn empty() -> Self { - Authority { data: ByteStr::new() } - } - - /// Attempt to convert an `Authority` from `Bytes`. - /// - /// This function will be replaced by a `TryFrom` implementation once the - /// trait lands in stable. - /// - /// # Examples - /// - /// ``` - /// # extern crate http; - /// # use http::uri::*; - /// extern crate bytes; - /// - /// use bytes::Bytes; - /// - /// # pub fn main() { - /// let bytes = Bytes::from("example.com"); - /// let authority = Authority::from_shared(bytes).unwrap(); - /// - /// assert_eq!(authority.host(), "example.com"); - /// # } - /// ``` - pub fn from_shared(s: Bytes) -> Result { - let authority_end = Authority::parse_non_empty(&s[..]).map_err(InvalidUriBytes)?; - - if authority_end != s.len() { - return Err(ErrorKind::InvalidUriChar.into()); + Authority { + data: ByteStr::new(), } + } - Ok(Authority { - data: unsafe { ByteStr::from_utf8_unchecked(s) }, - }) + // Not public while `bytes` is unstable. + pub(super) fn from_shared(s: Bytes) -> Result { + // Precondition on create_authority: trivially satisfied by the + // identity clousre + create_authority(s, |s| s) } /// Attempt to convert an `Authority` from a static string. @@ -73,20 +46,28 @@ /// assert_eq!(authority.host(), "example.com"); /// ``` pub fn from_static(src: &'static str) -> Self { - let s = src.as_bytes(); - let b = Bytes::from_static(s); - let authority_end = Authority::parse_non_empty(&b[..]).expect("static str is not valid authority"); + Authority::from_shared(Bytes::from_static(src.as_bytes())) + .expect("static str is not valid authority") + } - if authority_end != b.len() { - panic!("static str is not valid authority"); - } + /// Attempt to convert a `Bytes` buffer to a `Authority`. + /// + /// This will try to prevent a copy if the type passed is the type used + /// internally, and will copy the data if it is not. + pub fn from_maybe_shared(src: T) -> Result + where + T: AsRef<[u8]> + 'static, + { + if_downcast_into!(T, Bytes, src, { + return Authority::from_shared(src); + }); - Authority { - data: unsafe { ByteStr::from_utf8_unchecked(b) }, - } + Authority::try_from(src.as_ref()) } // Note: this may return an *empty* Authority. You might want `parse_non_empty`. + // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where + // ret is the return value. pub(super) fn parse(s: &[u8]) -> Result { let mut colon_cnt = 0; let mut start_bracket = false; @@ -95,6 +76,10 @@ let mut end = s.len(); let mut at_sign_pos = None; + // Among other things, this loop checks that every byte in s up to the + // first '/', '?', or '#' is a valid URI character (or in some contexts, + // a '%'). This means that each such byte is a valid single-byte UTF-8 + // code point. for (i, &b) in s.iter().enumerate() { match URI_CHARS[b as usize] { b'/' | b'?' | b'#' => { @@ -105,13 +90,16 @@ colon_cnt += 1; } b'[' => { - start_bracket = true; - if has_percent { + if has_percent || start_bracket { // Something other than the userinfo has a `%`, so reject it. return Err(ErrorKind::InvalidAuthority.into()); } + start_bracket = true; } b']' => { + if end_bracket { + return Err(ErrorKind::InvalidAuthority.into()); + } end_bracket = true; // Those were part of an IPv6 hostname, so forget them... @@ -172,6 +160,9 @@ // // This should be used by functions that allow a user to parse // an `Authority` by itself. + // + // Postcondition: for all Ok() returns, s[..ret.unwrap()] is valid UTF-8 where + // ret is the return value. fn parse_non_empty(s: &[u8]) -> Result { if s.is_empty() { return Err(ErrorKind::Empty.into()); @@ -205,12 +196,6 @@ host(self.as_str()) } - #[deprecated(since="0.1.14", note="use `port_part` or `port_u16` instead")] - #[doc(hidden)] - pub fn port(&self) -> Option { - self.port_u16() - } - /// Get the port part of this `Authority`. /// /// The port subcomponent of authority is designated by an optional port @@ -233,7 +218,7 @@ /// # use http::uri::Authority; /// let authority: Authority = "example.org:80".parse().unwrap(); /// - /// let port = authority.port_part().unwrap(); + /// let port = authority.port().unwrap(); /// assert_eq!(port.as_u16(), 80); /// assert_eq!(port.as_str(), "80"); /// ``` @@ -244,9 +229,9 @@ /// # use http::uri::Authority; /// let authority: Authority = "example.org".parse().unwrap(); /// - /// assert!(authority.port_part().is_none()); + /// assert!(authority.port().is_none()); /// ``` - pub fn port_part(&self) -> Option> { + pub fn port(&self) -> Option> { let bytes = self.as_str(); bytes .rfind(":") @@ -264,7 +249,7 @@ /// assert_eq!(authority.port_u16(), Some(80)); /// ``` pub fn port_u16(&self) -> Option { - self.port_part().and_then(|p| Some(p.as_u16())) + self.port().and_then(|p| Some(p.as_u16())) } /// Return a str representation of the authority @@ -272,14 +257,11 @@ pub fn as_str(&self) -> &str { &self.data[..] } - - /// Converts this `Authority` back to a sequence of bytes - #[inline] - pub fn into_bytes(self) -> Bytes { - self.into() - } } +// Purposefully not public while `bytes` is unstable. +// impl TryFrom for Authority + impl AsRef for Authority { fn as_ref(&self) -> &str { self.as_str() @@ -429,7 +411,10 @@ /// assert_eq!(a, b); /// ``` impl Hash for Authority { - fn hash(&self, state: &mut H) where H: Hasher { + fn hash(&self, state: &mut H) + where + H: Hasher, + { self.data.len().hash(state); for &b in self.data.as_bytes() { state.write_u8(b.to_ascii_lowercase()); @@ -437,36 +422,41 @@ } } -impl HttpTryFrom for Authority { - type Error = InvalidUriBytes; +impl<'a> TryFrom<&'a [u8]> for Authority { + type Error = InvalidUri; #[inline] - fn try_from(bytes: Bytes) -> Result { - Authority::from_shared(bytes) + fn try_from(s: &'a [u8]) -> Result { + // parse first, and only turn into Bytes if valid + + // Preconditon on create_authority: copy_from_slice() copies all of + // bytes from the [u8] parameter into a new Bytes + create_authority(s, |s| Bytes::copy_from_slice(s)) } } -impl<'a> HttpTryFrom<&'a [u8]> for Authority { +impl<'a> TryFrom<&'a str> for Authority { type Error = InvalidUri; #[inline] - fn try_from(s: &'a [u8]) -> Result { - // parse first, and only turn into Bytes if valid - let end = Authority::parse_non_empty(s)?; + fn try_from(s: &'a str) -> Result { + TryFrom::try_from(s.as_bytes()) + } +} - if end != s.len() { - return Err(ErrorKind::InvalidAuthority.into()); - } +impl TryFrom> for Authority { + type Error = InvalidUri; - Ok(Authority { - data: unsafe { ByteStr::from_utf8_unchecked(s.into()) }, - }) + #[inline] + fn try_from(vec: Vec) -> Result { + Authority::from_shared(vec.into()) } } -impl<'a> HttpTryFrom<&'a str> for Authority { +impl TryFrom for Authority { type Error = InvalidUri; + #[inline] - fn try_from(s: &'a str) -> Result { - HttpTryFrom::try_from(s.as_bytes()) + fn try_from(t: String) -> Result { + Authority::from_shared(t.into()) } } @@ -474,46 +464,66 @@ type Err = InvalidUri; fn from_str(s: &str) -> Result { - HttpTryFrom::try_from(s) - } -} - -impl From for Bytes { - #[inline] - fn from(src: Authority) -> Bytes { - src.data.into() + TryFrom::try_from(s) } } impl fmt::Debug for Authority { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } impl fmt::Display for Authority { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } fn host(auth: &str) -> &str { - let host_port = auth.rsplitn(2, '@') + let host_port = auth + .rsplitn(2, '@') .next() .expect("split always has at least 1 item"); if host_port.as_bytes()[0] == b'[' { - let i = host_port.find(']') + let i = host_port + .find(']') .expect("parsing should validate brackets"); // ..= ranges aren't available in 1.20, our minimum Rust version... - &host_port[0 .. i + 1] + &host_port[0..i + 1] } else { - host_port.split(':') + host_port + .split(':') .next() .expect("split always has at least 1 item") } } +// Precondition: f converts all of the bytes in the passed in B into the +// returned Bytes. +fn create_authority(b: B, f: F) -> Result +where + B: AsRef<[u8]>, + F: FnOnce(B) -> Bytes, +{ + let s = b.as_ref(); + let authority_end = Authority::parse_non_empty(s)?; + + if authority_end != s.len() { + return Err(ErrorKind::InvalidUriChar.into()); + } + + let bytes = f(b); + + Ok(Authority { + // Safety: the postcondition on parse_non_empty() and the check against + // s.len() ensure that b is valid UTF-8. The precondition on f ensures + // that this is carried through to bytes. + data: unsafe { ByteStr::from_utf8_unchecked(bytes) }, + }) +} + #[cfg(test)] mod tests { use super::*; @@ -550,6 +560,12 @@ } #[test] + fn from_static_equates_with_a_str() { + let authority = Authority::from_static("example.com"); + assert_eq!(authority, "example.com"); + } + + #[test] fn not_equal_with_a_str_of_a_different_authority() { let authority: Authority = "example.com".parse().unwrap(); assert_ne!(&authority, "test.com"); @@ -636,4 +652,20 @@ let err = Authority::parse_non_empty(b"[fe80::1:2:3:4]%20").unwrap_err(); assert_eq!(err.0, ErrorKind::InvalidAuthority); } + + #[test] + fn rejects_invalid_utf8() { + let err = Authority::try_from([0xc0u8].as_ref()).unwrap_err(); + assert_eq!(err.0, ErrorKind::InvalidUriChar); + + let err = Authority::from_shared(Bytes::from_static([0xc0u8].as_ref())) + .unwrap_err(); + assert_eq!(err.0, ErrorKind::InvalidUriChar); + } + + #[test] + fn rejects_invalid_use_of_brackets() { + let err = Authority::parse_non_empty(b"[]@[").unwrap_err(); + assert_eq!(err.0, ErrorKind::InvalidAuthority); + } } diff -Nru rust-http-0.1.21/src/uri/builder.rs rust-http-0.2.7/src/uri/builder.rs --- rust-http-0.1.21/src/uri/builder.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/uri/builder.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,6 +1,7 @@ -use {Uri, Result}; -use convert::{HttpTryFrom, HttpTryInto}; -use super::{Authority, Scheme, Parts, PathAndQuery}; +use std::convert::{TryFrom, TryInto}; + +use super::{Authority, Parts, PathAndQuery, Scheme}; +use crate::Uri; /// A builder for `Uri`s. /// @@ -8,7 +9,7 @@ /// through a builder pattern. #[derive(Debug)] pub struct Builder { - parts: Option>, + parts: Result, } impl Builder { @@ -41,13 +42,15 @@ /// let mut builder = uri::Builder::new(); /// builder.scheme("https"); /// ``` - pub fn scheme(&mut self, scheme: T) -> &mut Self + pub fn scheme(self, scheme: T) -> Self where - Scheme: HttpTryFrom, + Scheme: TryFrom, + >::Error: Into, { - self.map(|parts| { - parts.scheme = Some(scheme.http_try_into()?); - Ok(()) + self.map(move |mut parts| { + let scheme = scheme.try_into().map_err(Into::into)?; + parts.scheme = Some(scheme); + Ok(parts) }) } @@ -63,13 +66,15 @@ /// .build() /// .unwrap(); /// ``` - pub fn authority(&mut self, auth: T) -> &mut Self + pub fn authority(self, auth: T) -> Self where - Authority: HttpTryFrom, + Authority: TryFrom, + >::Error: Into, { - self.map(|parts| { - parts.authority = Some(auth.http_try_into()?); - Ok(()) + self.map(move |mut parts| { + let auth = auth.try_into().map_err(Into::into)?; + parts.authority = Some(auth); + Ok(parts) }) } @@ -85,13 +90,15 @@ /// .build() /// .unwrap(); /// ``` - pub fn path_and_query(&mut self, p_and_q: T) -> &mut Self + pub fn path_and_query(self, p_and_q: T) -> Self where - PathAndQuery: HttpTryFrom, + PathAndQuery: TryFrom, + >::Error: Into, { - self.map(|parts| { - parts.path_and_query = Some(p_and_q.http_try_into()?); - Ok(()) + self.map(move |mut parts| { + let p_and_q = p_and_q.try_into().map_err(Into::into)?; + parts.path_and_query = Some(p_and_q); + Ok(parts) }) } @@ -119,29 +126,21 @@ /// .build() /// .unwrap(); /// ``` - pub fn build(&mut self) -> Result { - self - .parts - .take() - .expect("cannot reuse Uri builder") - .and_then(|parts| parts.http_try_into()) + pub fn build(self) -> Result { + let parts = self.parts?; + Uri::from_parts(parts).map_err(Into::into) } - fn map(&mut self, f: F) -> &mut Self + // private + + fn map(self, func: F) -> Self where - F: FnOnce(&mut Parts) -> Result<()>, + F: FnOnce(Parts) -> Result, { - let res = if let Some(Ok(ref mut parts)) = self.parts { - f(parts) - } else { - return self; - }; - if let Err(err) = res { - self.parts = Some(Err(err)); + Builder { + parts: self.parts.and_then(func), } - - self } } @@ -149,8 +148,50 @@ #[inline] fn default() -> Builder { Builder { - parts: Some(Ok(Parts::default())), + parts: Ok(Parts::default()), } } } +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn build_from_str() { + let uri = Builder::new() + .scheme(Scheme::HTTP) + .authority("hyper.rs") + .path_and_query("/foo?a=1") + .build() + .unwrap(); + assert_eq!(uri.scheme_str(), Some("http")); + assert_eq!(uri.authority().unwrap().host(), "hyper.rs"); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some("a=1")); + } + + #[test] + fn build_from_string() { + for i in 1..10 { + let uri = Builder::new() + .path_and_query(format!("/foo?a={}", i)) + .build() + .unwrap(); + let expected_query = format!("a={}", i); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some(expected_query.as_str())); + } + } + + #[test] + fn build_from_string_ref() { + for i in 1..10 { + let p_a_q = format!("/foo?a={}", i); + let uri = Builder::new().path_and_query(&p_a_q).build().unwrap(); + let expected_query = format!("a={}", i); + assert_eq!(uri.path(), "/foo"); + assert_eq!(uri.query(), Some(expected_query.as_str())); + } + } +} diff -Nru rust-http-0.1.21/src/uri/mod.rs rust-http-0.2.7/src/uri/mod.rs --- rust-http-0.1.21/src/uri/mod.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/uri/mod.rs 1973-11-29 21:33:09.000000000 +0000 @@ -17,31 +17,28 @@ //! assert_eq!(uri.host(), None); //! //! let uri = "https://www.rust-lang.org/install.html".parse::().unwrap(); -//! assert_eq!(uri.scheme_part().map(|s| s.as_str()), Some("https")); +//! assert_eq!(uri.scheme_str(), Some("https")); //! assert_eq!(uri.host(), Some("www.rust-lang.org")); //! assert_eq!(uri.path(), "/install.html"); //! ``` -use HttpTryFrom; -use byte_str::ByteStr; +use crate::byte_str::ByteStr; +use std::convert::TryFrom; use bytes::Bytes; -use std::{fmt, u8, u16}; -// Deprecated in 1.26, needed until our minimum version is >=1.23. -#[allow(unused, deprecated)] -use std::ascii::AsciiExt; +use std::error::Error; use std::hash::{Hash, Hasher}; use std::str::{self, FromStr}; -use std::error::Error; +use std::{fmt, u16, u8}; use self::scheme::Scheme2; pub use self::authority::Authority; pub use self::builder::Builder; pub use self::path::PathAndQuery; -pub use self::scheme::Scheme; pub use self::port::Port; +pub use self::scheme::Scheme; mod authority; mod builder; @@ -91,7 +88,7 @@ /// assert_eq!(uri.host(), None); /// /// let uri = "https://www.rust-lang.org/install.html".parse::().unwrap(); -/// assert_eq!(uri.scheme_part().map(|s| s.as_str()), Some("https")); +/// assert_eq!(uri.scheme_str(), Some("https")); /// assert_eq!(uri.host(), Some("www.rust-lang.org")); /// assert_eq!(uri.path(), "/install.html"); /// ``` @@ -126,10 +123,6 @@ /// An error resulting from a failed attempt to construct a URI. #[derive(Debug)] -pub struct InvalidUriBytes(InvalidUri); - -/// An error resulting from a failed attempt to construct a URI. -#[derive(Debug)] pub struct InvalidUriParts(InvalidUri); #[derive(Debug, Eq, PartialEq)] @@ -150,6 +143,12 @@ // u16::MAX is reserved for None const MAX_LEN: usize = (u16::MAX - 1) as usize; +// URI_CHARS is a table of valid characters in a URI. An entry in the table is +// 0 for invalid characters. For valid characters the entry is itself (i.e. +// the entry for 33 is b'!' because b'!' == 33u8). An important characteristic +// of this table is that all entries above 127 are invalid. This makes all of the +// valid entries a valid single-byte UTF-8 code point. This means that a slice +// of such valid entries is valid UTF-8. const URI_CHARS: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x @@ -202,7 +201,40 @@ Builder::new() } - /// Attempt to convert a `Uri` from `Parts` + /// Attempt to convert a `Parts` into a `Uri`. + /// + /// # Examples + /// + /// Relative URI + /// + /// ``` + /// # use http::uri::*; + /// let mut parts = Parts::default(); + /// parts.path_and_query = Some("/foo".parse().unwrap()); + /// + /// let uri = Uri::from_parts(parts).unwrap(); + /// + /// assert_eq!(uri.path(), "/foo"); + /// + /// assert!(uri.scheme().is_none()); + /// assert!(uri.authority().is_none()); + /// ``` + /// + /// Absolute URI + /// + /// ``` + /// # use http::uri::*; + /// let mut parts = Parts::default(); + /// parts.scheme = Some("http".parse().unwrap()); + /// parts.authority = Some("foo.com".parse().unwrap()); + /// parts.path_and_query = Some("/foo".parse().unwrap()); + /// + /// let uri = Uri::from_parts(parts).unwrap(); + /// + /// assert_eq!(uri.scheme().unwrap().as_str(), "http"); + /// assert_eq!(uri.authority().unwrap(), "foo.com"); + /// assert_eq!(uri.path(), "/foo"); + /// ``` pub fn from_parts(src: Parts) -> Result { if src.scheme.is_some() { if src.authority.is_none() { @@ -220,7 +252,9 @@ let scheme = match src.scheme { Some(scheme) => scheme, - None => Scheme { inner: Scheme2::None }, + None => Scheme { + inner: Scheme2::None, + }, }; let authority = match src.authority { @@ -240,29 +274,23 @@ }) } - /// Attempt to convert a `Uri` from `Bytes` + /// Attempt to convert a `Bytes` buffer to a `Uri`. /// - /// This function will be replaced by a `TryFrom` implementation once the - /// trait lands in stable. - /// - /// # Examples - /// - /// ``` - /// # extern crate http; - /// # use http::uri::*; - /// extern crate bytes; - /// - /// use bytes::Bytes; - /// - /// # pub fn main() { - /// let bytes = Bytes::from("http://example.com/foo"); - /// let uri = Uri::from_shared(bytes).unwrap(); - /// - /// assert_eq!(uri.host().unwrap(), "example.com"); - /// assert_eq!(uri.path(), "/foo"); - /// # } - /// ``` - pub fn from_shared(s: Bytes) -> Result { + /// This will try to prevent a copy if the type passed is the type used + /// internally, and will copy the data if it is not. + pub fn from_maybe_shared(src: T) -> Result + where + T: AsRef<[u8]> + 'static, + { + if_downcast_into!(T, Bytes, src, { + return Uri::from_shared(src); + }); + + Uri::try_from(src.as_ref()) + } + + // Not public while `bytes` is unstable. + fn from_shared(s: Bytes) -> Result { use self::ErrorKind::*; if s.len() > MAX_LEN { @@ -273,33 +301,31 @@ 0 => { return Err(Empty.into()); } - 1 => { - match s[0] { - b'/' => { - return Ok(Uri { - scheme: Scheme::empty(), - authority: Authority::empty(), - path_and_query: PathAndQuery::slash(), - }); - } - b'*' => { - return Ok(Uri { - scheme: Scheme::empty(), - authority: Authority::empty(), - path_and_query: PathAndQuery::star(), - }); - } - _ => { - let authority = Authority::from_shared(s)?; - - return Ok(Uri { - scheme: Scheme::empty(), - authority: authority, - path_and_query: PathAndQuery::empty(), - }); - } + 1 => match s[0] { + b'/' => { + return Ok(Uri { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query: PathAndQuery::slash(), + }); } - } + b'*' => { + return Ok(Uri { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query: PathAndQuery::star(), + }); + } + _ => { + let authority = Authority::from_shared(s)?; + + return Ok(Uri { + scheme: Scheme::empty(), + authority: authority, + path_and_query: PathAndQuery::empty(), + }); + } + }, _ => {} } @@ -441,7 +467,7 @@ /// /// let uri: Uri = "http://example.org/hello/world".parse().unwrap(); /// - /// assert_eq!(uri.scheme_part(), Some(&Scheme::HTTP)); + /// assert_eq!(uri.scheme(), Some(&Scheme::HTTP)); /// ``` /// /// @@ -451,10 +477,10 @@ /// # use http::Uri; /// let uri: Uri = "/hello/world".parse().unwrap(); /// - /// assert!(uri.scheme_part().is_none()); + /// assert!(uri.scheme().is_none()); /// ``` #[inline] - pub fn scheme_part(&self) -> Option<&Scheme> { + pub fn scheme(&self) -> Option<&Scheme> { if self.scheme.inner.is_none() { None } else { @@ -462,13 +488,6 @@ } } - #[deprecated(since = "0.1.2", note = "use scheme_part or scheme_str instead")] - #[doc(hidden)] - #[inline] - pub fn scheme(&self) -> Option<&str> { - self.scheme_str() - } - /// Get the scheme of this `Uri` as a `&str`. /// /// # Example @@ -505,8 +524,6 @@ /// authority /// ``` /// - /// This function will be renamed to `authority` in the next semver release. - /// /// # Examples /// /// Absolute URI @@ -515,7 +532,7 @@ /// # use http::Uri; /// let uri: Uri = "http://example.org:80/hello/world".parse().unwrap(); /// - /// assert_eq!(uri.authority_part().map(|a| a.as_str()), Some("example.org:80")); + /// assert_eq!(uri.authority().map(|a| a.as_str()), Some("example.org:80")); /// ``` /// /// @@ -525,10 +542,10 @@ /// # use http::Uri; /// let uri: Uri = "/hello/world".parse().unwrap(); /// - /// assert!(uri.authority_part().is_none()); + /// assert!(uri.authority().is_none()); /// ``` #[inline] - pub fn authority_part(&self) -> Option<&Authority> { + pub fn authority(&self) -> Option<&Authority> { if self.authority.data.is_empty() { None } else { @@ -536,17 +553,6 @@ } } - #[deprecated(since = "0.1.1", note = "use authority_part instead")] - #[doc(hidden)] - #[inline] - pub fn authority(&self) -> Option<&str> { - if self.authority.data.is_empty() { - None - } else { - Some(self.authority.as_str()) - } - } - /// Get the host of this `Uri`. /// /// The host subcomponent of authority is identified by an IP literal @@ -582,13 +588,7 @@ /// ``` #[inline] pub fn host(&self) -> Option<&str> { - self.authority_part().map(|a| a.host()) - } - - #[deprecated(since="0.1.14", note="use `port_part` or `port_u16` instead")] - #[doc(hidden)] - pub fn port(&self) -> Option { - self.port_u16() + self.authority().map(|a| a.host()) } /// Get the port part of this `Uri`. @@ -613,7 +613,7 @@ /// # use http::Uri; /// let uri: Uri = "http://example.org:80/hello/world".parse().unwrap(); /// - /// let port = uri.port_part().unwrap(); + /// let port = uri.port().unwrap(); /// assert_eq!(port.as_u16(), 80); /// ``` /// @@ -623,7 +623,7 @@ /// # use http::Uri; /// let uri: Uri = "http://example.org/hello/world".parse().unwrap(); /// - /// assert!(uri.port_part().is_none()); + /// assert!(uri.port().is_none()); /// ``` /// /// Relative URI @@ -632,11 +632,10 @@ /// # use http::Uri; /// let uri: Uri = "/hello/world".parse().unwrap(); /// - /// assert!(uri.port_part().is_none()); + /// assert!(uri.port().is_none()); /// ``` - pub fn port_part(&self) -> Option> { - self.authority_part() - .and_then(|a| a.port_part()) + pub fn port(&self) -> Option> { + self.authority().and_then(|a| a.port()) } /// Get the port of this `Uri` as a `u16`. @@ -651,7 +650,7 @@ /// assert_eq!(uri.port_u16(), Some(80)); /// ``` pub fn port_u16(&self) -> Option { - self.port_part().and_then(|p| Some(p.as_u16())) + self.port().and_then(|p| Some(p.as_u16())) } /// Get the query string of this `Uri`, starting after the `?`. @@ -707,7 +706,16 @@ } } -impl<'a> HttpTryFrom<&'a str> for Uri { +impl<'a> TryFrom<&'a [u8]> for Uri { + type Error = InvalidUri; + + #[inline] + fn try_from(t: &'a [u8]) -> Result { + Uri::from_shared(Bytes::copy_from_slice(t)) + } +} + +impl<'a> TryFrom<&'a str> for Uri { type Error = InvalidUri; #[inline] @@ -716,7 +724,7 @@ } } -impl<'a> HttpTryFrom<&'a String> for Uri { +impl<'a> TryFrom<&'a String> for Uri { type Error = InvalidUri; #[inline] @@ -725,8 +733,8 @@ } } -impl HttpTryFrom for Uri { - type Error = InvalidUriBytes; +impl TryFrom for Uri { + type Error = InvalidUri; #[inline] fn try_from(t: String) -> Result { @@ -734,16 +742,16 @@ } } -impl HttpTryFrom for Uri { - type Error = InvalidUriBytes; +impl<'a> TryFrom> for Uri { + type Error = InvalidUri; #[inline] - fn try_from(t: Bytes) -> Result { - Uri::from_shared(t) + fn try_from(vec: Vec) -> Result { + Uri::from_shared(Bytes::from(vec)) } } -impl HttpTryFrom for Uri { +impl TryFrom for Uri { type Error = InvalidUriParts; #[inline] @@ -752,8 +760,8 @@ } } -impl<'a> HttpTryFrom<&'a Uri> for Uri { - type Error = ::Error; +impl<'a> TryFrom<&'a Uri> for Uri { + type Error = crate::Error; #[inline] fn try_from(src: &'a Uri) -> Result { @@ -761,40 +769,29 @@ } } -/// Convert a `Uri` from parts -/// -/// # Examples -/// -/// Relative URI -/// -/// ``` -/// # use http::uri::*; -/// let mut parts = Parts::default(); -/// parts.path_and_query = Some("/foo".parse().unwrap()); -/// -/// let uri = Uri::from_parts(parts).unwrap(); -/// -/// assert_eq!(uri.path(), "/foo"); -/// -/// assert!(uri.scheme_part().is_none()); -/// assert!(uri.authority().is_none()); -/// ``` -/// -/// Absolute URI -/// -/// ``` -/// # use http::uri::*; -/// let mut parts = Parts::default(); -/// parts.scheme = Some("http".parse().unwrap()); -/// parts.authority = Some("foo.com".parse().unwrap()); -/// parts.path_and_query = Some("/foo".parse().unwrap()); -/// -/// let uri = Uri::from_parts(parts).unwrap(); -/// -/// assert_eq!(uri.scheme_part().unwrap().as_str(), "http"); -/// assert_eq!(uri.authority().unwrap(), "foo.com"); -/// assert_eq!(uri.path(), "/foo"); -/// ``` +/// Convert an `Authority` into a `Uri`. +impl From for Uri { + fn from(authority: Authority) -> Self { + Self { + scheme: Scheme::empty(), + authority, + path_and_query: PathAndQuery::empty(), + } + } +} + +/// Convert a `PathAndQuery` into a `Uri`. +impl From for Uri { + fn from(path_and_query: PathAndQuery) -> Self { + Self { + scheme: Scheme::empty(), + authority: Authority::empty(), + path_and_query, + } + } +} + +/// Convert a `Uri` into `Parts` impl From for Parts { fn from(src: Uri) -> Self { let path_and_query = if src.has_path() { @@ -823,9 +820,9 @@ } } -fn parse_full(mut s: Bytes) -> Result { +fn parse_full(mut s: Bytes) -> Result { // Parse the scheme - let scheme = match Scheme2::parse(&s[..]).map_err(InvalidUriBytes)? { + let scheme = match Scheme2::parse(&s[..])? { Scheme2::None => Scheme2::None, Scheme2::Standard(p) => { // TODO: use truncate @@ -848,7 +845,7 @@ // Find the end of the authority. The scheme will already have been // extracted. - let authority_end = Authority::parse(&s[..]).map_err(InvalidUriBytes)?; + let authority_end = Authority::parse(&s[..])?; if scheme.is_none() { if authority_end != s.len() { @@ -888,17 +885,17 @@ #[inline] fn from_str(s: &str) -> Result { - Uri::from_shared(s.into()).map_err(|e| e.0) + Uri::try_from(s.as_bytes()) } } impl PartialEq for Uri { fn eq(&self, other: &Uri) -> bool { - if self.scheme_part() != other.scheme_part() { + if self.scheme() != other.scheme() { return false; } - if self.authority_part() != other.authority_part() { + if self.authority() != other.authority() { return false; } @@ -919,7 +916,7 @@ let mut other = other.as_bytes(); let mut absolute = false; - if let Some(scheme) = self.scheme_part() { + if let Some(scheme) = self.scheme() { let scheme = scheme.as_str().as_bytes(); absolute = true; @@ -940,7 +937,7 @@ other = &other[3..]; } - if let Some(auth) = self.authority_part() { + if let Some(auth) = self.authority() { let len = auth.data.len(); absolute = true; @@ -1000,7 +997,7 @@ } impl<'a> PartialEq<&'a str> for Uri { - fn eq(&self, other: & &'a str) -> bool { + fn eq(&self, other: &&'a str) -> bool { self == *other } } @@ -1026,12 +1023,12 @@ } impl fmt::Display for Uri { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(scheme) = self.scheme_part() { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if let Some(scheme) = self.scheme() { write!(f, "{}://", scheme)?; } - if let Some(authority) = self.authority_part() { + if let Some(authority) = self.authority() { write!(f, "{}", authority)?; } @@ -1046,7 +1043,7 @@ } impl fmt::Debug for Uri { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } @@ -1057,26 +1054,14 @@ } } -impl From for InvalidUriBytes { - fn from(src: ErrorKind) -> InvalidUriBytes { - InvalidUriBytes(src.into()) - } -} - impl From for InvalidUriParts { fn from(src: ErrorKind) -> InvalidUriParts { InvalidUriParts(src.into()) } } -impl fmt::Display for InvalidUri { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.description().fmt(f) - } -} - -impl Error for InvalidUri { - fn description(&self) -> &str { +impl InvalidUri { + fn s(&self) -> &str { match self.0 { ErrorKind::InvalidUriChar => "invalid uri character", ErrorKind::InvalidScheme => "invalid scheme", @@ -1093,38 +1078,33 @@ } } -impl fmt::Display for InvalidUriBytes { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - self.0.fmt(f) +impl fmt::Display for InvalidUri { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.s().fmt(f) } } +impl Error for InvalidUri {} + impl fmt::Display for InvalidUriParts { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } -impl Error for InvalidUriBytes { - fn description(&self) -> &str { - self.0.description() - } -} - -impl Error for InvalidUriParts { - fn description(&self) -> &str { - self.0.description() - } -} +impl Error for InvalidUriParts {} impl Hash for Uri { - fn hash(&self, state: &mut H) where H: Hasher { + fn hash(&self, state: &mut H) + where + H: Hasher, + { if !self.scheme.inner.is_none() { self.scheme.hash(state); state.write_u8(0xff); } - if let Some(auth) = self.authority_part() { + if let Some(auth) = self.authority() { auth.hash(state); } diff -Nru rust-http-0.1.21/src/uri/path.rs rust-http-0.2.7/src/uri/path.rs --- rust-http-0.1.21/src/uri/path.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/uri/path.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,11 +1,11 @@ -use std::{cmp, fmt, str}; +use std::convert::TryFrom; use std::str::FromStr; +use std::{cmp, fmt, str}; use bytes::Bytes; -use byte_str::ByteStr; -use convert::HttpTryFrom; -use super::{ErrorKind, InvalidUri, InvalidUriBytes}; +use super::{ErrorKind, InvalidUri}; +use crate::byte_str::ByteStr; /// Represents the path component of a URI #[derive(Clone)] @@ -17,37 +17,12 @@ const NONE: u16 = ::std::u16::MAX; impl PathAndQuery { - /// Attempt to convert a `PathAndQuery` from `Bytes`. - /// - /// This function will be replaced by a `TryFrom` implementation once the - /// trait lands in stable. - /// - /// # Examples - /// - /// ``` - /// # extern crate http; - /// # use http::uri::*; - /// extern crate bytes; - /// - /// use bytes::Bytes; - /// - /// # pub fn main() { - /// let bytes = Bytes::from("/hello?world"); - /// let path_and_query = PathAndQuery::from_shared(bytes).unwrap(); - /// - /// assert_eq!(path_and_query.path(), "/hello"); - /// assert_eq!(path_and_query.query(), Some("world")); - /// # } - /// ``` - pub fn from_shared(mut src: Bytes) -> Result { + // Not public while `bytes` is unstable. + pub(super) fn from_shared(mut src: Bytes) -> Result { let mut query = NONE; let mut fragment = None; // block for iterator borrow - // - // allow: `...` pattersn are now `..=`, but we cannot update yet - // because of minimum Rust version - #[allow(warnings)] { let mut iter = src.as_ref().iter().enumerate(); @@ -63,29 +38,37 @@ b'#' => { fragment = Some(i); break; - }, + } // This is the range of bytes that don't need to be // percent-encoded in the path. If it should have been // percent-encoded, then error. 0x21 | - 0x24...0x3B | + 0x24..=0x3B | 0x3D | - 0x40...0x5F | - 0x61...0x7A | + 0x40..=0x5F | + 0x61..=0x7A | 0x7C | 0x7E => {}, + // These are code points that are supposed to be + // percent-encoded in the path but there are clients + // out there sending them as is and httparse accepts + // to parse those requests, so they are allowed here + // for parity. + // + // For reference, those are code points that are used + // to send requests with JSON directly embedded in + // the URI path. Yes, those things happen for real. + b'"' | + b'{' | b'}' => {}, + _ => return Err(ErrorKind::InvalidUriChar.into()), } } // query ... if query != NONE { - - // allow: `...` pattersn are now `..=`, but we cannot update yet - // because of minimum Rust version - #[allow(warnings)] for (i, &b) in iter { match b { // While queries *should* be percent-encoded, most @@ -94,14 +77,14 @@ // // Allowed: 0x21 / 0x24 - 0x3B / 0x3D / 0x3F - 0x7E 0x21 | - 0x24...0x3B | + 0x24..=0x3B | 0x3D | - 0x3F...0x7E => {}, + 0x3F..=0x7E => {}, b'#' => { fragment = Some(i); break; - }, + } _ => return Err(ErrorKind::InvalidUriChar.into()), } @@ -141,8 +124,22 @@ pub fn from_static(src: &'static str) -> Self { let src = Bytes::from_static(src.as_bytes()); - PathAndQuery::from_shared(src) - .unwrap() + PathAndQuery::from_shared(src).unwrap() + } + + /// Attempt to convert a `Bytes` buffer to a `PathAndQuery`. + /// + /// This will try to prevent a copy if the type passed is the type used + /// internally, and will copy the data if it is not. + pub fn from_maybe_shared(src: T) -> Result + where + T: AsRef<[u8]> + 'static, + { + if_downcast_into!(T, Bytes, src, { + return PathAndQuery::from_shared(src); + }); + + PathAndQuery::try_from(src.as_ref()) } pub(super) fn empty() -> Self { @@ -276,60 +273,64 @@ } ret } +} - /// Converts this `PathAndQuery` back to a sequence of bytes +impl<'a> TryFrom<&'a [u8]> for PathAndQuery { + type Error = InvalidUri; #[inline] - pub fn into_bytes(self) -> Bytes { - self.into() + fn try_from(s: &'a [u8]) -> Result { + PathAndQuery::from_shared(Bytes::copy_from_slice(s)) } } -impl HttpTryFrom for PathAndQuery { - type Error = InvalidUriBytes; +impl<'a> TryFrom<&'a str> for PathAndQuery { + type Error = InvalidUri; #[inline] - fn try_from(bytes: Bytes) -> Result { - PathAndQuery::from_shared(bytes) + fn try_from(s: &'a str) -> Result { + TryFrom::try_from(s.as_bytes()) } } -impl<'a> HttpTryFrom<&'a [u8]> for PathAndQuery { +impl<'a> TryFrom> for PathAndQuery { type Error = InvalidUri; #[inline] - fn try_from(s: &'a [u8]) -> Result { - PathAndQuery::from_shared(s.into()).map_err(|e| e.0) + fn try_from(vec: Vec) -> Result { + PathAndQuery::from_shared(vec.into()) } } -impl<'a> HttpTryFrom<&'a str> for PathAndQuery { +impl TryFrom for PathAndQuery { type Error = InvalidUri; #[inline] - fn try_from(s: &'a str) -> Result { - HttpTryFrom::try_from(s.as_bytes()) + fn try_from(s: String) -> Result { + PathAndQuery::from_shared(s.into()) } } -impl FromStr for PathAndQuery { - type Err = InvalidUri; +impl TryFrom<&String> for PathAndQuery { + type Error = InvalidUri; #[inline] - fn from_str(s: &str) -> Result { - HttpTryFrom::try_from(s) + fn try_from(s: &String) -> Result { + TryFrom::try_from(s.as_bytes()) } } -impl From for Bytes { - fn from(src: PathAndQuery) -> Bytes { - src.data.into() +impl FromStr for PathAndQuery { + type Err = InvalidUri; + #[inline] + fn from_str(s: &str) -> Result { + TryFrom::try_from(s) } } impl fmt::Debug for PathAndQuery { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(self, f) } } impl fmt::Display for PathAndQuery { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { if !self.data.is_empty() { match self.data.as_bytes()[0] { b'/' | b'*' => write!(fmt, "{}", &self.data[..]), @@ -546,6 +547,11 @@ assert_eq!("qr=%3", pq("/a/b?qr=%3").query().unwrap()); } + #[test] + fn json_is_fine() { + assert_eq!(r#"/{"bread":"baguette"}"#, pq(r#"/{"bread":"baguette"}"#).path()); + } + fn pq(s: &str) -> PathAndQuery { s.parse().expect(&format!("parsing {}", s)) } diff -Nru rust-http-0.1.21/src/uri/port.rs rust-http-0.2.7/src/uri/port.rs --- rust-http-0.1.21/src/uri/port.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/uri/port.rs 1973-11-29 21:33:09.000000000 +0000 @@ -19,7 +19,7 @@ /// # use http::uri::Authority; /// let authority: Authority = "example.org:80".parse().unwrap(); /// - /// let port = authority.port_part().unwrap(); + /// let port = authority.port().unwrap(); /// assert_eq!(port.as_u16(), 80); /// ``` pub fn as_u16(&self) -> u16 { @@ -38,13 +38,8 @@ bytes .as_ref() .parse::() - .map(|port| Port { - port, - repr: bytes, - }) - .map_err(|_| { - ErrorKind::InvalidPort.into() - }) + .map(|port| Port { port, repr: bytes }) + .map_err(|_| ErrorKind::InvalidPort.into()) } /// Returns the port number as a `str`. @@ -57,7 +52,7 @@ /// # use http::uri::Authority; /// let authority: Authority = "example.org:80".parse().unwrap(); /// - /// let port = authority.port_part().unwrap(); + /// let port = authority.port().unwrap(); /// assert_eq!(port.as_str(), "80"); /// ``` pub fn as_str(&self) -> &str { @@ -69,15 +64,13 @@ where T: fmt::Debug, { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_tuple("Port") - .field(&self.port) - .finish() + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("Port").field(&self.port).finish() } } impl fmt::Display for Port { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { // Use `u16::fmt` so that it respects any formatting flags that // may have been set (like padding, align, etc). fmt::Display::fmt(&self.port, f) diff -Nru rust-http-0.1.21/src/uri/scheme.rs rust-http-0.2.7/src/uri/scheme.rs --- rust-http-0.1.21/src/uri/scheme.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/uri/scheme.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,15 +1,12 @@ -// Deprecated in 1.26, needed until our minimum version is >=1.23. -#[allow(unused, deprecated)] -use std::ascii::AsciiExt; +use std::convert::TryFrom; use std::fmt; use std::hash::{Hash, Hasher}; use std::str::FromStr; use bytes::Bytes; -use byte_str::ByteStr; -use convert::HttpTryFrom; -use super::{ErrorKind, InvalidUri, InvalidUriBytes}; +use super::{ErrorKind, InvalidUri}; +use crate::byte_str::ByteStr; /// Represents the scheme component of a URI #[derive(Clone)] @@ -41,40 +38,6 @@ inner: Scheme2::Standard(Protocol::Https), }; - /// Attempt to convert a `Scheme` from `Bytes` - /// - /// This function will be replaced by a `TryFrom` implementation once the - /// trait lands in stable. - /// - /// # Examples - /// - /// ``` - /// # extern crate http; - /// # use http::uri::*; - /// extern crate bytes; - /// - /// use bytes::Bytes; - /// - /// # pub fn main() { - /// let bytes = Bytes::from("http"); - /// let scheme = Scheme::from_shared(bytes).unwrap(); - /// - /// assert_eq!(scheme.as_str(), "http"); - /// # } - /// ``` - pub fn from_shared(s: Bytes) -> Result { - use self::Scheme2::*; - - match Scheme2::parse_exact(&s[..]).map_err(InvalidUriBytes)? { - None => Err(ErrorKind::InvalidScheme.into()), - Standard(p) => Ok(Standard(p).into()), - Other(_) => { - let b = unsafe { ByteStr::from_utf8_unchecked(s) }; - Ok(Other(Box::new(b)).into()) - } - } - } - pub(super) fn empty() -> Self { Scheme { inner: Scheme2::None, @@ -92,8 +55,8 @@ /// ``` #[inline] pub fn as_str(&self) -> &str { - use self::Scheme2::*; use self::Protocol::*; + use self::Scheme2::*; match self.inner { Standard(Http) => "http", @@ -102,23 +65,9 @@ None => unreachable!(), } } - - /// Converts this `Scheme` back to a sequence of bytes - #[inline] - pub fn into_bytes(self) -> Bytes { - self.into() - } -} - -impl HttpTryFrom for Scheme { - type Error = InvalidUriBytes; - #[inline] - fn try_from(bytes: Bytes) -> Result { - Scheme::from_shared(bytes) - } } -impl<'a> HttpTryFrom<&'a [u8]> for Scheme { +impl<'a> TryFrom<&'a [u8]> for Scheme { type Error = InvalidUri; #[inline] fn try_from(s: &'a [u8]) -> Result { @@ -128,20 +77,23 @@ None => Err(ErrorKind::InvalidScheme.into()), Standard(p) => Ok(Standard(p).into()), Other(_) => { - // Unsafe: parse_exact already checks for a strict subset of UTF-8 - Ok(Other(Box::new(unsafe { - ByteStr::from_utf8_unchecked(s.into()) - })).into()) + let bytes = Bytes::copy_from_slice(s); + + // Safety: postcondition on parse_exact() means that s and + // hence bytes are valid UTF-8. + let string = unsafe { ByteStr::from_utf8_unchecked(bytes) }; + + Ok(Other(Box::new(string)).into()) } } } } -impl<'a> HttpTryFrom<&'a str> for Scheme { +impl<'a> TryFrom<&'a str> for Scheme { type Error = InvalidUri; #[inline] fn try_from(s: &'a str) -> Result { - HttpTryFrom::try_from(s.as_bytes()) + TryFrom::try_from(s.as_bytes()) } } @@ -149,33 +101,18 @@ type Err = InvalidUri; fn from_str(s: &str) -> Result { - HttpTryFrom::try_from(s) - } -} - -impl From for Bytes { - #[inline] - fn from(src: Scheme) -> Self { - use self::Scheme2::*; - use self::Protocol::*; - - match src.inner { - None => Bytes::new(), - Standard(Http) => Bytes::from_static(b"http"), - Standard(Https) => Bytes::from_static(b"https"), - Other(v) => (*v).into(), - } + TryFrom::try_from(s) } } impl fmt::Debug for Scheme { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } } impl fmt::Display for Scheme { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.write_str(self.as_str()) } } @@ -228,7 +165,10 @@ /// Case-insensitive hashing impl Hash for Scheme { - fn hash(&self, state: &mut H) where H: Hasher { + fn hash(&self, state: &mut H) + where + H: Hasher, + { match self.inner { Scheme2::None => (), Scheme2::Standard(Protocol::Http) => state.write_u8(1), @@ -258,6 +198,12 @@ // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) // +// SCHEME_CHARS is a table of valid characters in the scheme part of a URI. An +// entry in the table is 0 for invalid characters. For valid characters the +// entry is itself (i.e. the entry for 43 is b'+' because b'+' == 43u8). An +// important characteristic of this table is that all entries above 127 are +// invalid. This makes all of the valid entries a valid single-byte UTF-8 code +// point. This means that a slice of such valid entries is valid UTF-8. const SCHEME_CHARS: [u8; 256] = [ // 0 1 2 3 4 5 6 7 8 9 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // x @@ -289,6 +235,7 @@ ]; impl Scheme2 { + // Postcondition: On all Ok() returns, s is valid UTF-8 fn parse_exact(s: &[u8]) -> Result, InvalidUri> { match s { b"http" => Ok(Protocol::Http.into()), @@ -298,6 +245,8 @@ return Err(ErrorKind::SchemeTooLong.into()); } + // check that each byte in s is a SCHEME_CHARS which implies + // that it is a valid single byte UTF-8 code point. for &b in s { match SCHEME_CHARS[b as usize] { b':' => { @@ -344,7 +293,7 @@ } // Not a scheme - if &s[i+1..i+3] != b"//" { + if &s[i + 1..i + 3] != b"//" { break; } @@ -387,3 +336,28 @@ Scheme { inner: src } } } + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn scheme_eq_to_str() { + assert_eq!(&scheme("http"), "http"); + assert_eq!(&scheme("https"), "https"); + assert_eq!(&scheme("ftp"), "ftp"); + assert_eq!(&scheme("my+funky+scheme"), "my+funky+scheme"); + } + + #[test] + fn invalid_scheme_is_error() { + Scheme::try_from("my_funky_scheme").expect_err("Unexpectly valid Scheme"); + + // Invalid UTF-8 + Scheme::try_from([0xC0].as_ref()).expect_err("Unexpectly valid Scheme"); + } + + fn scheme(s: &str) -> Scheme { + s.parse().expect(&format!("Invalid scheme: {}", s)) + } +} diff -Nru rust-http-0.1.21/src/uri/tests.rs rust-http-0.2.7/src/uri/tests.rs --- rust-http-0.1.21/src/uri/tests.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/uri/tests.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,6 +1,6 @@ use std::str::FromStr; -use super::{ErrorKind, InvalidUri, Uri, URI_CHARS, Port}; +use super::{ErrorKind, InvalidUri, Port, Uri, URI_CHARS}; #[test] fn test_char_table() { @@ -12,9 +12,9 @@ } macro_rules! part { - ($s:expr) => ( + ($s:expr) => { Some(&$s.parse().unwrap()) - ) + }; } macro_rules! test_parse { @@ -59,8 +59,8 @@ "/some/path/here?and=then&hello#and-bye", [], - scheme_part = None, - authority_part = None, + scheme = None, + authority = None, path = "/some/path/here", query = Some("and=then&hello"), host = None, @@ -71,12 +71,12 @@ "http://127.0.0.1:61761/chunks", [], - scheme_part = part!("http"), - authority_part = part!("127.0.0.1:61761"), + scheme = part!("http"), + authority = part!("127.0.0.1:61761"), path = "/chunks", query = None, host = Some("127.0.0.1"), - port_part = Port::from_str("61761").ok(), + port = Port::from_str("61761").ok(), } test_parse! { @@ -84,12 +84,12 @@ "https://127.0.0.1:61761", ["https://127.0.0.1:61761/"], - scheme_part = part!("https"), - authority_part = part!("127.0.0.1:61761"), + scheme = part!("https"), + authority = part!("127.0.0.1:61761"), path = "/", query = None, host = Some("127.0.0.1"), - port_part = Port::from_str("61761").ok(), + port = Port::from_str("61761").ok(), } test_parse! { @@ -97,8 +97,8 @@ "*", [], - scheme_part = None, - authority_part = None, + scheme = None, + authority = None, path = "*", query = None, host = None, @@ -109,11 +109,11 @@ "localhost", ["LOCALHOST", "LocaLHOSt"], - scheme_part = None, - authority_part = part!("localhost"), + scheme = None, + authority = part!("localhost"), path = "", query = None, - port_part = None, + port = None, host = Some("localhost"), } @@ -122,11 +122,11 @@ "S", [], - scheme_part = None, - authority_part = part!("S"), + scheme = None, + authority = part!("S"), path = "", query = None, - port_part = None, + port = None, host = Some("S"), } @@ -135,26 +135,25 @@ "localhost:3000", ["localhosT:3000"], - scheme_part = None, - authority_part = part!("localhost:3000"), + scheme = None, + authority = part!("localhost:3000"), path = "", query = None, host = Some("localhost"), - port_part = Port::from_str("3000").ok(), + port = Port::from_str("3000").ok(), } - test_parse! { test_uri_parse_absolute_with_default_port_http, "http://127.0.0.1:80", ["http://127.0.0.1:80/"], - scheme_part = part!("http"), - authority_part = part!("127.0.0.1:80"), + scheme = part!("http"), + authority = part!("127.0.0.1:80"), host = Some("127.0.0.1"), path = "/", query = None, - port_part = Port::from_str("80").ok(), + port = Port::from_str("80").ok(), } test_parse! { @@ -162,12 +161,12 @@ "https://127.0.0.1:443", ["https://127.0.0.1:443/"], - scheme_part = part!("https"), - authority_part = part!("127.0.0.1:443"), + scheme = part!("https"), + authority = part!("127.0.0.1:443"), host = Some("127.0.0.1"), path = "/", query = None, - port_part = Port::from_str("443").ok(), + port = Port::from_str("443").ok(), } test_parse! { @@ -175,12 +174,12 @@ "http://127.0.0.1/#?", [], - scheme_part = part!("http"), - authority_part = part!("127.0.0.1"), + scheme = part!("http"), + authority = part!("127.0.0.1"), host = Some("127.0.0.1"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -188,11 +187,11 @@ "http://127.0.0.1/path?", [], - scheme_part = part!("http"), - authority_part = part!("127.0.0.1"), + scheme = part!("http"), + authority = part!("127.0.0.1"), path = "/path", query = Some(""), - port_part = None, + port = None, } test_parse! { @@ -200,11 +199,11 @@ "http://127.0.0.1?foo=bar", [], - scheme_part = part!("http"), - authority_part = part!("127.0.0.1"), + scheme = part!("http"), + authority = part!("127.0.0.1"), path = "/", query = Some("foo=bar"), - port_part = None, + port = None, } test_parse! { @@ -212,11 +211,11 @@ "http://127.0.0.1#foo/bar", [], - scheme_part = part!("http"), - authority_part = part!("127.0.0.1"), + scheme = part!("http"), + authority = part!("127.0.0.1"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -224,11 +223,11 @@ "http://127.0.0.1#foo?bar", [], - scheme_part = part!("http"), - authority_part = part!("127.0.0.1"), + scheme = part!("http"), + authority = part!("127.0.0.1"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -236,11 +235,11 @@ "thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost", [], - scheme_part = None, - authority_part = part!("thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost"), + scheme = None, + authority = part!("thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost"), path = "", query = None, - port_part = None, + port = None, } test_parse! { @@ -248,11 +247,11 @@ "thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost:1234", [], - scheme_part = None, - authority_part = part!("thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost:1234"), + scheme = None, + authority = part!("thequickbrownfoxjumpedoverthelazydogtofindthelargedangerousdragon.localhost:1234"), path = "", query = None, - port_part = Port::from_str("1234").ok(), + port = Port::from_str("1234").ok(), } test_parse! { @@ -260,12 +259,12 @@ "http://a:b@127.0.0.1:1234/", [], - scheme_part = part!("http"), - authority_part = part!("a:b@127.0.0.1:1234"), + scheme = part!("http"), + authority = part!("a:b@127.0.0.1:1234"), host = Some("127.0.0.1"), path = "/", query = None, - port_part = Port::from_str("1234").ok(), + port = Port::from_str("1234").ok(), } test_parse! { @@ -273,12 +272,12 @@ "http://a:b@127.0.0.1/", [], - scheme_part = part!("http"), - authority_part = part!("a:b@127.0.0.1"), + scheme = part!("http"), + authority = part!("a:b@127.0.0.1"), host = Some("127.0.0.1"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -286,12 +285,12 @@ "http://a@127.0.0.1/", [], - scheme_part = part!("http"), - authority_part = part!("a@127.0.0.1"), + scheme = part!("http"), + authority = part!("a@127.0.0.1"), host = Some("127.0.0.1"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -299,12 +298,12 @@ "user@localhost:3000", [], - scheme_part = None, - authority_part = part!("user@localhost:3000"), + scheme = None, + authority = part!("user@localhost:3000"), path = "", query = None, host = Some("localhost"), - port_part = Port::from_str("3000").ok(), + port = Port::from_str("3000").ok(), } test_parse! { @@ -312,12 +311,12 @@ "user:pass@localhost:3000", [], - scheme_part = None, - authority_part = part!("user:pass@localhost:3000"), + scheme = None, + authority = part!("user:pass@localhost:3000"), path = "", query = None, host = Some("localhost"), - port_part = Port::from_str("3000").ok(), + port = Port::from_str("3000").ok(), } test_parse! { @@ -325,12 +324,12 @@ "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]/", [], - scheme_part = part!("http"), - authority_part = part!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), + scheme = part!("http"), + authority = part!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), host = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -338,12 +337,12 @@ "http://[::1]/", [], - scheme_part = part!("http"), - authority_part = part!("[::1]"), + scheme = part!("http"), + authority = part!("[::1]"), host = Some("[::1]"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -351,12 +350,12 @@ "http://[::]/", [], - scheme_part = part!("http"), - authority_part = part!("[::]"), + scheme = part!("http"), + authority = part!("[::]"), host = Some("[::]"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -364,12 +363,12 @@ "http://[2001:db8::2:1]/", [], - scheme_part = part!("http"), - authority_part = part!("[2001:db8::2:1]"), + scheme = part!("http"), + authority = part!("[2001:db8::2:1]"), host = Some("[2001:db8::2:1]"), path = "/", query = None, - port_part = None, + port = None, } test_parse! { @@ -377,12 +376,12 @@ "http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008/", [], - scheme_part = part!("http"), - authority_part = part!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008"), + scheme = part!("http"), + authority = part!("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:8008"), host = Some("[2001:0db8:85a3:0000:0000:8a2e:0370:7334]"), path = "/", query = None, - port_part = Port::from_str("8008").ok(), + port = Port::from_str("8008").ok(), } test_parse! { @@ -390,12 +389,12 @@ "/echo/abcdefgh_i-j%20/abcdefg_i-j%20478", [], - scheme_part = None, - authority_part = None, + scheme = None, + authority = None, host = None, path = "/echo/abcdefgh_i-j%20/abcdefg_i-j%20478", query = None, - port_part = None, + port = None, } test_parse! { diff -Nru rust-http-0.1.21/src/version.rs rust-http-0.2.7/src/version.rs --- rust-http-0.1.21/src/version.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/src/version.rs 1973-11-29 21:33:09.000000000 +0000 @@ -37,6 +37,9 @@ /// `HTTP/2.0` pub const HTTP_2: Version = Version(Http::H2); + + /// `HTTP/3.0` + pub const HTTP_3: Version = Version(Http::H3); } #[derive(PartialEq, PartialOrd, Copy, Clone, Eq, Ord, Hash)] @@ -45,6 +48,8 @@ Http10, Http11, H2, + H3, + __NonExhaustive, } impl Default for Version { @@ -55,14 +60,16 @@ } impl fmt::Debug for Version { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use self::Http::*; f.write_str(match self.0 { Http09 => "HTTP/0.9", Http10 => "HTTP/1.0", Http11 => "HTTP/1.1", - H2 => "HTTP/2.0", + H2 => "HTTP/2.0", + H3 => "HTTP/3.0", + __NonExhaustive => unreachable!(), }) } } diff -Nru rust-http-0.1.21/tests/header_map_fuzz.rs rust-http-0.2.7/tests/header_map_fuzz.rs --- rust-http-0.1.21/tests/header_map_fuzz.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/tests/header_map_fuzz.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,12 +1,10 @@ -extern crate http; -extern crate rand; -extern crate quickcheck; - -use http::*; use http::header::*; +use http::*; use quickcheck::{Arbitrary, Gen, QuickCheck, TestResult}; -use rand::{StdRng, SeedableRng, Rng}; +use rand::rngs::StdRng; +use rand::seq::SliceRandom; +use rand::{Rng, SeedableRng}; use std::collections::HashMap; @@ -17,14 +15,13 @@ TestResult::from_bool(true) } - QuickCheck::new() - .quickcheck(prop as fn(Fuzz) -> TestResult) + QuickCheck::new().quickcheck(prop as fn(Fuzz) -> TestResult) } #[derive(Debug, Clone)] struct Fuzz { // The magic seed that makes the test case reproducible - seed: [usize; 4], + seed: [u8; 32], // Actions to perform steps: Vec, @@ -49,8 +46,8 @@ #[derive(Debug, Clone)] enum Action { Insert { - name: HeaderName, // Name to insert - val: HeaderValue, // Value to insert + name: HeaderName, // Name to insert + val: HeaderValue, // Value to insert old: Option, // Old value }, Append { @@ -59,7 +56,7 @@ ret: bool, }, Remove { - name: HeaderName, // Name to remove + name: HeaderName, // Name to remove val: Option, // Value to get }, } @@ -71,9 +68,9 @@ } impl Fuzz { - fn new(seed: [usize; 4]) -> Fuzz { + fn new(seed: [u8; 32]) -> Fuzz { // Seed the RNG - let mut rng = StdRng::from_seed(&seed); + let mut rng = StdRng::from_seed(seed); let mut steps = vec![]; let mut expect = AltMap::default(); @@ -113,7 +110,7 @@ impl Arbitrary for Fuzz { fn arbitrary(g: &mut G) -> Self { - Fuzz::new(quickcheck::Rng::gen(g)) + Fuzz::new(Rng::gen(g)) } } @@ -129,9 +126,7 @@ /// 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 sum = weight.insert + weight.remove + weight.append; let mut num = rng.gen_range(0, sum); @@ -180,8 +175,7 @@ let name = self.gen_name(-5, rng); let val = gen_header_value(rng); - let vals = self.map.entry(name.clone()) - .or_insert(vec![]); + let vals = self.map.entry(name.clone()).or_insert(vec![]); let ret = !vals.is_empty(); vals.push(val.clone()); @@ -195,7 +189,7 @@ /// Negative numbers weigh finding an existing header higher fn gen_name(&self, weight: i32, rng: &mut StdRng) -> HeaderName { - let mut existing = rng.gen_weighted_bool(weight.abs() as u32); + let mut existing = rng.gen_ratio(1, weight.abs() as u32); if weight < 0 { existing = !existing; @@ -268,82 +262,90 @@ } fn gen_header_name(g: &mut StdRng) -> HeaderName { - if g.gen_weighted_bool(2) { - g.choose(&[ - 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::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::RETRY_AFTER, - header::SERVER, - header::SET_COOKIE, - header::STRICT_TRANSPORT_SECURITY, - header::TE, - header::TRAILER, - header::TRANSFER_ENCODING, - header::USER_AGENT, - header::UPGRADE, - header::UPGRADE_INSECURE_REQUESTS, - 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, - ]).unwrap().clone() + 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::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() @@ -356,10 +358,15 @@ } fn gen_string(g: &mut StdRng, min: usize, max: usize) -> String { - let bytes: Vec<_> = (min..max).map(|_| { - // Chars to pick from - g.choose(b"ABCDEFGHIJKLMNOPQRSTUVabcdefghilpqrstuvwxyz----").unwrap().clone() - }).collect(); + let bytes: Vec<_> = (min..max) + .map(|_| { + // Chars to pick from + b"ABCDEFGHIJKLMNOPQRSTUVabcdefghilpqrstuvwxyz----" + .choose(g) + .unwrap() + .clone() + }) + .collect(); String::from_utf8(bytes).unwrap() } diff -Nru rust-http-0.1.21/tests/header_map.rs rust-http-0.2.7/tests/header_map.rs --- rust-http-0.1.21/tests/header_map.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/tests/header_map.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,7 +1,5 @@ -extern crate http; - -use http::*; use http::header::*; +use http::*; #[test] fn smoke() { @@ -11,7 +9,7 @@ let name: HeaderName = "hello".parse().unwrap(); - match headers.entry(&name).unwrap() { + match headers.entry(&name) { Entry::Vacant(e) => { e.insert("world".parse().unwrap()); } @@ -20,7 +18,7 @@ assert!(headers.get("hello").is_some()); - match headers.entry(&name).unwrap() { + match headers.entry(&name) { Entry::Occupied(mut e) => { assert_eq!(e.get(), &"world"); @@ -46,6 +44,18 @@ } #[test] +fn with_capacity_max() { + // The largest capacity such that (cap + cap / 3) < MAX_SIZE. + HeaderMap::::with_capacity(24_576); +} + +#[test] +#[should_panic] +fn with_capacity_overflow() { + HeaderMap::::with_capacity(24_577); +} + +#[test] #[should_panic] fn reserve_overflow() { // See https://github.com/hyperium/http/issues/352 @@ -63,12 +73,10 @@ { let mut iter = headers.drain(); - let (name, values) = iter.next().unwrap(); - assert_eq!(name.as_str(), "hello"); + let (name, value) = iter.next().unwrap(); + assert_eq!(name.unwrap().as_str(), "hello"); - let values: Vec<_> = values.collect(); - assert_eq!(values.len(), 1); - assert_eq!(values[0], "world"); + assert_eq!(value, "world"); assert!(iter.next().is_none()); } @@ -76,27 +84,34 @@ assert!(headers.is_empty()); // Insert two sequential values - headers.insert("hello".parse::().unwrap(), "world".parse().unwrap()); - headers.insert("zomg".parse::().unwrap(), "bar".parse().unwrap()); - headers.append("hello".parse::().unwrap(), "world2".parse().unwrap()); + headers.insert( + "hello".parse::().unwrap(), + "world".parse().unwrap(), + ); + headers.insert( + "zomg".parse::().unwrap(), + "bar".parse().unwrap(), + ); + headers.append( + "hello".parse::().unwrap(), + "world2".parse().unwrap(), + ); // Drain... { let mut iter = headers.drain(); - let (name, values) = iter.next().unwrap(); - assert_eq!(name.as_str(), "hello"); - let values: Vec<_> = values.collect(); - assert_eq!(values.len(), 2); - assert_eq!(values[0], "world"); - assert_eq!(values[1], "world2"); - - let (name, values) = iter.next().unwrap(); - assert_eq!(name.as_str(), "zomg"); - - let values: Vec<_> = values.collect(); - assert_eq!(values.len(), 1); - assert_eq!(values[0], "bar"); + let (name, value) = iter.next().unwrap(); + assert_eq!(name.unwrap().as_str(), "hello"); + assert_eq!(value, "world"); + + let (name, value) = iter.next().unwrap(); + assert_eq!(name, None); + assert_eq!(value, "world2"); + + let (name, value) = iter.next().unwrap(); + assert_eq!(name.unwrap().as_str(), "zomg"); + assert_eq!(value, "bar"); assert!(iter.next().is_none()); } @@ -112,7 +127,7 @@ headers.append("hello", "world2".parse().unwrap()); let iter = headers.drain(); - assert_eq!(iter.size_hint(), (2, Some(2))); + assert_eq!(iter.size_hint(), (2, Some(3))); // not consuming `iter` } @@ -140,15 +155,31 @@ fn drain_entry() { let mut headers = HeaderMap::new(); - headers.insert("hello".parse::().unwrap(), "world".parse().unwrap()); - headers.insert("zomg".parse::().unwrap(), "foo".parse().unwrap()); - headers.append("hello".parse::().unwrap(), "world2".parse().unwrap()); - headers.insert("more".parse::().unwrap(), "words".parse().unwrap()); - headers.append("more".parse::().unwrap(), "insertions".parse().unwrap()); + headers.insert( + "hello".parse::().unwrap(), + "world".parse().unwrap(), + ); + headers.insert( + "zomg".parse::().unwrap(), + "foo".parse().unwrap(), + ); + headers.append( + "hello".parse::().unwrap(), + "world2".parse().unwrap(), + ); + headers.insert( + "more".parse::().unwrap(), + "words".parse().unwrap(), + ); + headers.append( + "more".parse::().unwrap(), + "insertions".parse().unwrap(), + ); + assert_eq!(5, headers.len()); - // Using insert + // Using insert_mult { - let mut e = match headers.entry("hello").unwrap() { + let mut e = match headers.entry("hello") { Entry::Occupied(e) => e, _ => panic!(), }; @@ -158,6 +189,8 @@ assert_eq!(vals[0], "world"); assert_eq!(vals[1], "world2"); } + + assert_eq!(5-2+1, headers.len()); } #[test] @@ -167,10 +200,16 @@ assert_eq!(a, b); - a.insert("hello".parse::().unwrap(), "world".parse().unwrap()); + a.insert( + "hello".parse::().unwrap(), + "world".parse().unwrap(), + ); assert_ne!(a, b); - b.insert("hello".parse::().unwrap(), "world".parse().unwrap()); + b.insert( + "hello".parse::().unwrap(), + "world".parse().unwrap(), + ); assert_eq!(a, b); a.insert("foo".parse::().unwrap(), "bar".parse().unwrap()); @@ -228,13 +267,18 @@ for (i, hdr) in STD.iter().enumerate() { m.insert(hdr.clone(), hdr.as_str().parse().unwrap()); - for j in 0..(i+1) { + for j in 0..(i + 1) { assert_eq!(m[&STD[j]], STD[j].as_str()); } if i != 0 { - for j in (i+1)..STD.len() { - assert!(m.get(&STD[j]).is_none(), "contained {}; j={}", STD[j].as_str(), j); + for j in (i + 1)..STD.len() { + assert!( + m.get(&STD[j]).is_none(), + "contained {}; j={}", + STD[j].as_str(), + j + ); } } } @@ -248,11 +292,11 @@ for (i, hdr) in hdrs.iter().enumerate() { h.insert(hdr.clone(), hdr.as_str().parse().unwrap()); - for j in 0..(i+1) { + for j in 0..(i + 1) { assert_eq!(h[&hdrs[j]], hdrs[j].as_str()); } - for j in (i+1)..hdrs.len() { + for j in (i + 1)..hdrs.len() { assert!(h.get(&hdrs[j]).is_none()); } } @@ -266,7 +310,8 @@ map.append(header::CONTENT_TYPE, "html".parse().unwrap()); map.append(header::CONTENT_TYPE, "xml".parse().unwrap()); - let vals = map.get_all(&header::CONTENT_TYPE) + let vals = map + .get_all(&header::CONTENT_TYPE) .iter() .collect::>(); @@ -274,10 +319,12 @@ } fn custom_std(n: usize) -> Vec { - (0..n).map(|i| { - let s = format!("{}-{}", STD[i % STD.len()].as_str(), i); - s.parse().unwrap() - }).collect() + (0..n) + .map(|i| { + let s = format!("{}-{}", STD[i % STD.len()].as_str(), i); + s.parse().unwrap() + }) + .collect() } const STD: &'static [HeaderName] = &[ @@ -377,3 +424,213 @@ HeaderValue::from_static("hello\tworld"); HeaderValue::from_str("hello\tworld").unwrap(); } + + +#[test] +fn remove_multiple_a() { + let mut headers = HeaderMap::new(); + headers.insert(VIA, "1.1 example.com".parse().unwrap()); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_2=value 2".parse().unwrap()); + headers.append(VIA, "1.1 other.com".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_3=value 3".parse().unwrap()); + headers.insert(VARY, "*".parse().unwrap()); + + assert_eq!(headers.len(), 6); + + let cookie = headers.remove(SET_COOKIE); + assert_eq!(cookie, Some("cookie_1=value 1".parse().unwrap())); + assert_eq!(headers.len(), 3); + + let via = headers.remove(VIA); + assert_eq!(via, Some("1.1 example.com".parse().unwrap())); + assert_eq!(headers.len(), 1); + + let vary = headers.remove(VARY); + assert_eq!(vary, Some("*".parse().unwrap())); + assert_eq!(headers.len(), 0); +} + +#[test] +fn remove_multiple_b() { + let mut headers = HeaderMap::new(); + headers.insert(VIA, "1.1 example.com".parse().unwrap()); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_2=value 2".parse().unwrap()); + headers.append(VIA, "1.1 other.com".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_3=value 3".parse().unwrap()); + headers.insert(VARY, "*".parse().unwrap()); + + assert_eq!(headers.len(), 6); + + let vary = headers.remove(VARY); + assert_eq!(vary, Some("*".parse().unwrap())); + assert_eq!(headers.len(), 5); + + let via = headers.remove(VIA); + assert_eq!(via, Some("1.1 example.com".parse().unwrap())); + assert_eq!(headers.len(), 3); + + let cookie = headers.remove(SET_COOKIE); + assert_eq!(cookie, Some("cookie_1=value 1".parse().unwrap())); + assert_eq!(headers.len(), 0); +} + +#[test] +fn remove_entry_multi_0() { + let mut headers = HeaderMap::new(); + let cookies = remove_all_values(&mut headers, SET_COOKIE); + assert_eq!(cookies.len(), 0); + assert_eq!(headers.len(), 0); +} + +#[test] +fn remove_entry_multi_0_others() { + let mut headers = HeaderMap::new(); + headers.insert(VIA, "1.1 example.com".parse().unwrap()); + headers.append(VIA, "1.1 other.com".parse().unwrap()); + + let cookies = remove_all_values(&mut headers, SET_COOKIE); + assert_eq!(cookies.len(), 0); + assert_eq!(headers.len(), 2); +} + +#[test] +fn remove_entry_multi_1() { + let mut headers = HeaderMap::new(); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + + let cookies = remove_all_values(&mut headers, SET_COOKIE); + assert_eq!(cookies.len(), 1); + assert_eq!(headers.len(), 0); +} + +#[test] +fn remove_entry_multi_1_other() { + let mut headers = HeaderMap::new(); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.insert(VIA, "1.1 example.com".parse().unwrap()); + + let cookies = remove_all_values(&mut headers, SET_COOKIE); + assert_eq!(cookies.len(), 1); + assert_eq!(headers.len(), 1); + + let vias = remove_all_values(&mut headers, VIA); + assert_eq!(vias.len(), 1); + assert_eq!(headers.len(), 0); +} + +// For issue hyperimum/http#446 +#[test] +fn remove_entry_multi_2() { + let mut headers = HeaderMap::new(); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_2=value 2".parse().unwrap()); + + let cookies = remove_all_values(&mut headers, SET_COOKIE); + assert_eq!(cookies.len(), 2); + assert_eq!(headers.len(), 0); +} + +#[test] +fn remove_entry_multi_3() { + let mut headers = HeaderMap::new(); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_2=value 2".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_3=value 3".parse().unwrap()); + + let cookies = remove_all_values(&mut headers, SET_COOKIE); + assert_eq!(cookies.len(), 3); + assert_eq!(headers.len(), 0); +} + +#[test] +fn remove_entry_multi_3_others() { + let mut headers = HeaderMap::new(); + headers.insert(VIA, "1.1 example.com".parse().unwrap()); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_2=value 2".parse().unwrap()); + headers.append(VIA, "1.1 other.com".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_3=value 3".parse().unwrap()); + headers.insert(VARY, "*".parse().unwrap()); + + let cookies = remove_all_values(&mut headers, SET_COOKIE); + assert_eq!(cookies.len(), 3); + assert_eq!(headers.len(), 3); + + let vias = remove_all_values(&mut headers, VIA); + assert_eq!(vias.len(), 2); + assert_eq!(headers.len(), 1); + + let varies = remove_all_values(&mut headers, VARY); + assert_eq!(varies.len(), 1); + assert_eq!(headers.len(), 0); +} + +fn remove_all_values(headers: &mut HeaderMap, key: K) -> Vec + where K: IntoHeaderName +{ + match headers.entry(key) { + Entry::Occupied(e) => e.remove_entry_mult().1.collect(), + Entry::Vacant(_) => vec![], + } +} + +#[test] +fn remove_entry_3_others_a() { + let mut headers = HeaderMap::new(); + headers.insert(VIA, "1.1 example.com".parse().unwrap()); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_2=value 2".parse().unwrap()); + headers.append(VIA, "1.1 other.com".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_3=value 3".parse().unwrap()); + headers.insert(VARY, "*".parse().unwrap()); + + assert_eq!(headers.len(), 6); + + let cookie = remove_values(&mut headers, SET_COOKIE); + assert_eq!(cookie, Some("cookie_1=value 1".parse().unwrap())); + assert_eq!(headers.len(), 3); + + let via = remove_values(&mut headers, VIA); + assert_eq!(via, Some("1.1 example.com".parse().unwrap())); + assert_eq!(headers.len(), 1); + + let vary = remove_values(&mut headers, VARY); + assert_eq!(vary, Some("*".parse().unwrap())); + assert_eq!(headers.len(), 0); +} + +#[test] +fn remove_entry_3_others_b() { + let mut headers = HeaderMap::new(); + headers.insert(VIA, "1.1 example.com".parse().unwrap()); + headers.insert(SET_COOKIE, "cookie_1=value 1".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_2=value 2".parse().unwrap()); + headers.append(VIA, "1.1 other.com".parse().unwrap()); + headers.append(SET_COOKIE, "cookie_3=value 3".parse().unwrap()); + headers.insert(VARY, "*".parse().unwrap()); + + assert_eq!(headers.len(), 6); + + let vary = remove_values(&mut headers, VARY); + assert_eq!(vary, Some("*".parse().unwrap())); + assert_eq!(headers.len(), 5); + + let via = remove_values(&mut headers, VIA); + assert_eq!(via, Some("1.1 example.com".parse().unwrap())); + assert_eq!(headers.len(), 3); + + let cookie = remove_values(&mut headers, SET_COOKIE); + assert_eq!(cookie, Some("cookie_1=value 1".parse().unwrap())); + assert_eq!(headers.len(), 0); +} + +fn remove_values(headers: &mut HeaderMap, key: K) -> Option + where K: IntoHeaderName +{ + match headers.entry(key) { + Entry::Occupied(e) => Some(e.remove_entry().1), + Entry::Vacant(_) => None, + } +} diff -Nru rust-http-0.1.21/tests/status_code.rs rust-http-0.2.7/tests/status_code.rs --- rust-http-0.1.21/tests/status_code.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/tests/status_code.rs 1973-11-29 21:33:09.000000000 +0000 @@ -1,14 +1,16 @@ -extern crate http; - use http::*; #[test] fn from_bytes() { - for ok in &["100", "101", "199", "200", "250", "299", "321", "399", "499", "599"] { + for ok in &[ + "100", "101", "199", "200", "250", "299", "321", "399", "499", "599", "600", "999" + ] { assert!(StatusCode::from_bytes(ok.as_bytes()).is_ok()); } - for not_ok in &["0", "00", "10", "40", "99", "000", "010", "099", "600", "610", "999"] { + for not_ok in &[ + "0", "00", "10", "40", "99", "000", "010", "099", "1000", "1999", + ] { assert!(StatusCode::from_bytes(not_ok.as_bytes()).is_err()); } } @@ -20,48 +22,61 @@ assert_eq!(status, 200u16); } -macro_rules! test_round_trip { - ($($num:expr,)+) => { - #[test] - fn roundtrip() { - $( - let status = StatusCode::from_bytes(stringify!($num).as_bytes()).unwrap(); - let expect = $num; - - assert_eq!(u16::from(status), expect); - )+ - } +#[test] +fn roundtrip() { + for s in 100..1000 { + let sstr = s.to_string(); + let status = StatusCode::from_bytes(sstr.as_bytes()).unwrap(); + assert_eq!(s, u16::from(status)); + assert_eq!(sstr, status.as_str()); } } -test_round_trip!( - 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, - 120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, - 140, 141, 142, 143, 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, - 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, - 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, - - 200, 201, 202, 203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, - 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, - 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 256, 257, 258, 259, - 260, 261, 262, 263, 264, 265, 266, 267, 268, 269, 270, 271, 272, 273, 274, 275, 276, 277, 278, 279, - 280, 281, 282, 283, 284, 285, 286, 287, 288, 289, 290, 291, 292, 293, 294, 295, 296, 297, 298, 299, - - 300, 301, 302, 303, 304, 305, 306, 307, 308, 309, 310, 311, 312, 313, 314, 315, 316, 317, 318, 319, - 320, 321, 322, 323, 324, 325, 326, 327, 328, 329, 330, 331, 332, 333, 334, 335, 336, 337, 338, 339, - 340, 341, 342, 343, 344, 345, 346, 347, 348, 349, 350, 351, 352, 353, 354, 355, 356, 357, 358, 359, - 360, 361, 362, 363, 364, 365, 366, 367, 368, 369, 370, 371, 372, 373, 374, 375, 376, 377, 378, 379, - 380, 381, 382, 383, 384, 385, 386, 387, 388, 389, 390, 391, 392, 393, 394, 395, 396, 397, 398, 399, - - 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 418, 419, - 420, 421, 422, 423, 424, 425, 426, 427, 428, 429, 430, 431, 432, 433, 434, 435, 436, 437, 438, 439, - 440, 441, 442, 443, 444, 445, 446, 447, 448, 449, 450, 451, 452, 453, 454, 455, 456, 457, 458, 459, - 460, 461, 462, 463, 464, 465, 466, 467, 468, 469, 470, 471, 472, 473, 474, 475, 476, 477, 478, 479, - 480, 481, 482, 483, 484, 485, 486, 487, 488, 489, 490, 491, 492, 493, 494, 495, 496, 497, 498, 499, - - 500, 501, 502, 503, 504, 505, 506, 507, 508, 509, 510, 511, 512, 513, 514, 515, 516, 517, 518, 519, - 520, 521, 522, 523, 524, 525, 526, 527, 528, 529, 530, 531, 532, 533, 534, 535, 536, 537, 538, 539, - 540, 541, 542, 543, 544, 545, 546, 547, 548, 549, 550, 551, 552, 553, 554, 555, 556, 557, 558, 559, - 560, 561, 562, 563, 564, 565, 566, 567, 568, 569, 570, 571, 572, 573, 574, 575, 576, 577, 578, 579, - 580, 581, 582, 583, 584, 585, 586, 587, 588, 589, 590, 591, 592, 593, 594, 595, 596, 597, 598, 599, - ); +#[test] +fn is_informational() { + assert!(status_code(100).is_informational()); + assert!(status_code(199).is_informational()); + + assert!(!status_code(200).is_informational()); +} + +#[test] +fn is_success() { + assert!(status_code(200).is_success()); + assert!(status_code(299).is_success()); + + assert!(!status_code(199).is_success()); + assert!(!status_code(300).is_success()); +} + +#[test] +fn is_redirection() { + assert!(status_code(300).is_redirection()); + assert!(status_code(399).is_redirection()); + + assert!(!status_code(299).is_redirection()); + assert!(!status_code(400).is_redirection()); +} + +#[test] +fn is_client_error() { + assert!(status_code(400).is_client_error()); + assert!(status_code(499).is_client_error()); + + assert!(!status_code(399).is_client_error()); + assert!(!status_code(500).is_client_error()); +} + +#[test] +fn is_server_error() { + assert!(status_code(500).is_server_error()); + assert!(status_code(599).is_server_error()); + + assert!(!status_code(499).is_server_error()); + assert!(!status_code(600).is_server_error()); +} + +/// Helper method for readability +fn status_code(status_code: u16) -> StatusCode { + StatusCode::from_u16(status_code).unwrap() +} diff -Nru rust-http-0.1.21/.travis.yml rust-http-0.2.7/.travis.yml --- rust-http-0.1.21/.travis.yml 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/.travis.yml 1970-01-01 00:00:00.000000000 +0000 @@ -1,26 +0,0 @@ -language: rust -sudo: false - -cache: cargo - -matrix: - include: - - rust: stable - - rust: beta - - rust: nightly - # ensure wasm always builds - - rust: stable - script: - - rustup target add wasm32-unknown-unknown - - cargo build --target=wasm32-unknown-unknown - # minimum rustc version - - rust: 1.20.0 - script: cargo build - -script: - - cargo test - - 'if [ "$TRAVIS_RUST_VERSION" == "nightly" ]; then cargo test --benches; fi' - -notifications: - email: - on_success: never diff -Nru rust-http-0.1.21/util/Cargo.toml rust-http-0.2.7/util/Cargo.toml --- rust-http-0.1.21/util/Cargo.toml 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/util/Cargo.toml 1970-01-01 00:00:00.000000000 +0000 @@ -1,4 +0,0 @@ -[package] -name = "gen" -version = "0.1.0" -authors = ["Carl Lerche "] diff -Nru rust-http-0.1.21/util/README.md rust-http-0.2.7/util/README.md --- rust-http-0.1.21/util/README.md 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/util/README.md 1970-01-01 00:00:00.000000000 +0000 @@ -1 +0,0 @@ -Generates standard header code diff -Nru rust-http-0.1.21/util/src/main.rs rust-http-0.2.7/util/src/main.rs --- rust-http-0.1.21/util/src/main.rs 2019-12-02 19:18:55.000000000 +0000 +++ rust-http-0.2.7/util/src/main.rs 1970-01-01 00:00:00.000000000 +0000 @@ -1,1040 +0,0 @@ -macro_rules! standard_headers { - ( - $( - $doc:expr, - $name:expr; - )+ - ) => { - const HEADERS: &[(&'static str, &'static str)] = &[ - $( - ($doc, $name), - )+ - ]; - } -} - -standard_headers! { - r#" - /// Advertises which content types the client is able to understand. - /// - /// The Accept request HTTP header advertises which content types, expressed - /// as MIME types, the client is able to understand. Using content - /// negotiation, the server then selects one of the proposals, uses it and - /// informs the client of its choice with the Content-Type response header. - /// Browsers set adequate values for this header depending of the context - /// where the request is done: when fetching a CSS stylesheet a different - /// value is set for the request than when fetching an image, video or a - /// script. - "#, - "accept"; - - r#" - /// Advertises which character set the client is able to understand. - /// - /// The Accept-Charset request HTTP header advertises which character set - /// the client is able to understand. Using content negotiation, the server - /// then selects one of the proposals, uses it and informs the client of its - /// choice within the Content-Type response header. Browsers usually don't - /// set this header as the default value for each content type is usually - /// correct and transmitting it would allow easier fingerprinting. - /// - /// If the server cannot serve any matching character set, it can - /// theoretically send back a 406 (Not Acceptable) error code. But, for a - /// better user experience, this is rarely done and the more common way is - /// to ignore the Accept-Charset header in this case. - "#, - "accept-charset"; - - r#" - /// Advertises which content encoding the client is able to understand. - /// - /// The Accept-Encoding request HTTP header advertises which content - /// encoding, usually a compression algorithm, the client is able to - /// understand. Using content negotiation, the server selects one of the - /// proposals, uses it and informs the client of its choice with the - /// Content-Encoding response header. - /// - /// Even if both the client and the server supports the same compression - /// algorithms, the server may choose not to compress the body of a - /// response, if the identity value is also acceptable. Two common cases - /// lead to this: - /// - /// * The data to be sent is already compressed and a second compression - /// won't lead to smaller data to be transmitted. This may the case with - /// some image formats; - /// - /// * The server is overloaded and cannot afford the computational overhead - /// induced by the compression requirement. Typically, Microsoft recommends - /// not to compress if a server use more than 80 % of its computational - /// power. - /// - /// As long as the identity value, meaning no encryption, is not explicitly - /// forbidden, by an identity;q=0 or a *;q=0 without another explicitly set - /// value for identity, the server must never send back a 406 Not Acceptable - /// error. - "#, - "accept-encoding"; - - r#" - /// Advertises which languages the client is able to understand. - /// - /// The Accept-Language request HTTP header advertises which languages the - /// client is able to understand, and which locale variant is preferred. - /// Using content negotiation, the server then selects one of the proposals, - /// uses it and informs the client of its choice with the Content-Language - /// response header. Browsers set adequate values for this header according - /// their user interface language and even if a user can change it, this - /// happens rarely (and is frown upon as it leads to fingerprinting). - /// - /// This header is a hint to be used when the server has no way of - /// determining the language via another way, like a specific URL, that is - /// controlled by an explicit user decision. It is recommended that the - /// server never overrides an explicit decision. The content of the - /// Accept-Language is often out of the control of the user (like when - /// traveling and using an Internet Cafe in a different country); the user - /// may also want to visit a page in another language than the locale of - /// their user interface. - /// - /// If the server cannot serve any matching language, it can theoretically - /// send back a 406 (Not Acceptable) error code. But, for a better user - /// experience, this is rarely done and more common way is to ignore the - /// Accept-Language header in this case. - "#, - "accept-language"; - - r#" - /// Advertises which patch formats the server is able to understand. - /// - /// Accept-Patch should appear in the OPTIONS response for any resource that - /// supports the use of the PATCH method. The presence of the - /// Accept-Patch header in response to any method is an implicit indication - /// that PATCH is allowed on the resource identified by the URI. The - /// presence of a specific patch document format in this header indicates - /// that that specific format is allowed on the resource identified by the - /// URI. - "#, - "accept-patch"; - - r#" - /// Marker used by the server to advertise partial request support. - /// - /// The Accept-Ranges response HTTP header is a marker used by the server to - /// advertise its support of partial requests. The value of this field - /// indicates the unit that can be used to define a range. - /// - /// In presence of an Accept-Ranges header, the browser may try to resume an - /// interrupted download, rather than to start it from the start again. - "#, - "accept-ranges"; - - r#" - /// Preflight response indicating if the response to the request can be - /// exposed to the page. - /// - /// The Access-Control-Allow-Credentials response header indicates whether - /// or not the response to the request can be exposed to the page. It can be - /// exposed when the true value is returned; it can't in other cases. - /// - /// Credentials are cookies, authorization headers or TLS client - /// certificates. - /// - /// When used as part of a response to a preflight request, this indicates - /// whether or not the actual request can be made using credentials. Note - /// that simple GET requests are not preflighted, and so if a request is - /// made for a resource with credentials, if this header is not returned - /// with the resource, the response is ignored by the browser and not - /// returned to web content. - /// - /// The Access-Control-Allow-Credentials header works in conjunction with - /// the XMLHttpRequest.withCredentials property or with the credentials - /// option in the Request() constructor of the Fetch API. Credentials must - /// be set on both sides (the Access-Control-Allow-Credentials header and in - /// the XHR or Fetch request) in order for the CORS request with credentials - /// to succeed. - "#, - "access-control-allow-credentials"; - - r#" - /// Preflight response indicating permitted HTTP headers. - /// - /// The Access-Control-Allow-Headers response header is used in response to - /// a preflight request to indicate which HTTP headers will be available via - /// Access-Control-Expose-Headers when making the actual request. - /// - /// The simple headers, Accept, Accept-Language, Content-Language, - /// Content-Type (but only with a MIME type of its parsed value (ignoring - /// parameters) of either application/x-www-form-urlencoded, - /// multipart/form-data, or text/plain), are always available and don't need - /// to be listed by this header. - /// - /// This header is required if the request has an - /// Access-Control-Request-Headers header. - "#, - "access-control-allow-headers"; - - r#" - /// Preflight header response indicating permitted access methods. - /// - /// The Access-Control-Allow-Methods response header specifies the method or - /// methods allowed when accessing the resource in response to a preflight - /// request. - "#, - "access-control-allow-methods"; - - - r#" - /// Indicates whether the response can be shared with resources with the - /// given origin. - "#, - "access-control-allow-origin"; - - r#" - /// Indicates which headers can be exposed as part of the response by - /// listing their names. - "#, - "access-control-expose-headers"; - - r#" - /// Indicates how long the results of a preflight request can be cached. - "#, - "access-control-max-age"; - - r#" - /// Informs the server which HTTP headers will be used when an actual - /// request is made. - "#, - "access-control-request-headers"; - - r#" - /// Informs the server know which HTTP method will be used when the actual - /// request is made. - "#, - "access-control-request-method"; - - r#" - /// Indicates the time in seconds the object has been in a proxy cache. - /// - /// The Age header is usually close to zero. If it is Age: 0, it was - /// probably just fetched from the origin server; otherwise It is usually - /// calculated as a difference between the proxy's current date and the Date - /// general header included in the HTTP response. - "#, - "age"; - - r#" - /// Lists the set of methods support by a resource. - /// - /// This header must be sent if the server responds with a 405 Method Not - /// Allowed status code to indicate which request methods can be used. An - /// empty Allow header indicates that the resource allows no request - /// methods, which might occur temporarily for a given resource, for - /// example. - "#, - "allow"; - - r#" - /// Advertises the availability of alternate services to clients. - "#, - "alt-svc"; - - r#" - /// Contains the credentials to authenticate a user agent with a server. - /// - /// Usually this header is included after the server has responded with a - /// 401 Unauthorized status and the WWW-Authenticate header. - "#, - "authorization"; - - r#" - /// Specifies directives for caching mechanisms in both requests and - /// responses. - /// - /// Caching directives are unidirectional, meaning that a given directive in - /// a request is not implying that the same directive is to be given in the - /// response. - "#, - "cache-control"; - - r#" - /// Controls whether or not the network connection stays open after the - /// current transaction finishes. - /// - /// If the value sent is keep-alive, the connection is persistent and not - /// closed, allowing for subsequent requests to the same server to be done. - /// - /// Except for the standard hop-by-hop headers (Keep-Alive, - /// Transfer-Encoding, TE, Connection, Trailer, Upgrade, Proxy-Authorization - /// and Proxy-Authenticate), any hop-by-hop headers used by the message must - /// be listed in the Connection header, so that the first proxy knows he has - /// to consume them and not to forward them further. Standard hop-by-hop - /// headers can be listed too (it is often the case of Keep-Alive, but this - /// is not mandatory. - "#, - "connection"; - - r#" - /// Indicates if the content is expected to be displayed inline. - /// - /// In a regular HTTP response, the Content-Disposition response header is a - /// header indicating if the content is expected to be displayed inline in - /// the browser, that is, as a Web page or as part of a Web page, or as an - /// attachment, that is downloaded and saved locally. - /// - /// In a multipart/form-data body, the HTTP Content-Disposition general - /// header is a header that can be used on the subpart of a multipart body - /// to give information about the field it applies to. The subpart is - /// delimited by the boundary defined in the Content-Type header. Used on - /// the body itself, Content-Disposition has no effect. - /// - /// The Content-Disposition header is defined in the larger context of MIME - /// messages for e-mail, but only a subset of the possible parameters apply - /// to HTTP forms and POST requests. Only the value form-data, as well as - /// the optional directive name and filename, can be used in the HTTP - /// context. - "#, - "content-disposition"; - - r#" - /// Used to compress the media-type. - /// - /// When present, its value indicates what additional content encoding has - /// been applied to the entity-body. It lets the client know, how to decode - /// in order to obtain the media-type referenced by the Content-Type header. - /// - /// It is recommended to compress data as much as possible and therefore to - /// use this field, but some types of resources, like jpeg images, are - /// already compressed. Sometimes using additional compression doesn't - /// reduce payload size and can even make the payload longer. - "#, - "content-encoding"; - - r#" - /// Used to describe the languages intended for the audience. - /// - /// This header allows a user to differentiate according to the users' own - /// preferred language. For example, if "Content-Language: de-DE" is set, it - /// says that the document is intended for German language speakers - /// (however, it doesn't indicate the document is written in German. For - /// example, it might be written in English as part of a language course for - /// German speakers). - /// - /// If no Content-Language is specified, the default is that the content is - /// intended for all language audiences. Multiple language tags are also - /// possible, as well as applying the Content-Language header to various - /// media types and not only to textual documents. - "#, - "content-language"; - - r#" - /// Indicates the size fo the entity-body. - /// - /// The header value must be a decimal indicating the number of octets sent - /// to the recipient. - "#, - "content-length"; - - r#" - /// Indicates an alternate location for the returned data. - /// - /// The principal use case is to indicate the URL of the resource - /// transmitted as the result of content negotiation. - /// - /// Location and Content-Location are different: Location indicates the - /// target of a redirection (or the URL of a newly created document), while - /// Content-Location indicates the direct URL to use to access the resource, - /// without the need of further content negotiation. Location is a header - /// associated with the response, while Content-Location is associated with - /// the entity returned. - "#, - "content-location"; - - r#" - /// Contains the MD5 digest of the entity-body. - /// - /// The Content-MD5 entity-header field, as defined in RFC 1864 [23], is an - /// MD5 digest of the entity-body for the purpose of providing an end-to-end - /// message integrity check (MIC) of the entity-body. (Note: a MIC is good - /// for detecting accidental modification of the entity-body in transit, but - /// is not proof against malicious attacks.) - "#, - "content-md5"; - - r#" - /// Indicates where in a full body message a partial message belongs. - "#, - "content-range"; - - r#" - /// Allows controlling resources the user agent is allowed to load for a - /// given page. - /// - /// With a few exceptions, policies mostly involve specifying server origins - /// and script endpoints. This helps guard against cross-site scripting - /// attacks (XSS). - "#, - "content-security-policy"; - - r#" - /// Allows experimenting with policies by monitoring their effects. - /// - /// The HTTP Content-Security-Policy-Report-Only response header allows web - /// developers to experiment with policies by monitoring (but not enforcing) - /// their effects. These violation reports consist of JSON documents sent - /// via an HTTP POST request to the specified URI. - "#, - "content-security-policy-report-only"; - - r#" - /// Used to indicate the media type of the resource. - /// - /// In responses, a Content-Type header tells the client what the content - /// type of the returned content actually is. Browsers will do MIME sniffing - /// in some cases and will not necessarily follow the value of this header; - /// to prevent this behavior, the header X-Content-Type-Options can be set - /// to nosniff. - /// - /// In requests, (such as POST or PUT), the client tells the server what - /// type of data is actually sent. - "#, - "content-type"; - - r#" - /// Contains stored HTTP cookies previously sent by the server with the - /// Set-Cookie header. - /// - /// The Cookie header might be omitted entirely, if the privacy setting of - /// the browser are set to block them, for example. - "#, - "cookie"; - - r#" - /// Indicates the client's tracking preference. - /// - /// This header lets users indicate whether they would prefer privacy rather - /// than personalized content. - "#, - "dnt"; - - r#" - /// Contains the date and time at which the message was originated. - "#, - "date"; - - r#" - /// Identifier for a specific version of a resource. - /// - /// This header allows caches to be more efficient, and saves bandwidth, as - /// a web server does not need to send a full response if the content has - /// not changed. On the other side, if the content has changed, etags are - /// useful to help prevent simultaneous updates of a resource from - /// overwriting each other ("mid-air collisions"). - /// - /// If the resource at a given URL changes, a new Etag value must be - /// generated. Etags are therefore similar to fingerprints and might also be - /// used for tracking purposes by some servers. A comparison of them allows - /// to quickly determine whether two representations of a resource are the - /// same, but they might also be set to persist indefinitely by a tracking - /// server. - "#, - "etag"; - - r#" - /// Indicates expectations that need to be fulfilled by the server in order - /// to properly handle the request. - /// - /// The only expectation defined in the specification is Expect: - /// 100-continue, to which the server shall respond with: - /// - /// * 100 if the information contained in the header is sufficient to cause - /// an immediate success, - /// - /// * 417 (Expectation Failed) if it cannot meet the expectation; or any - /// other 4xx status otherwise. - /// - /// For example, the server may reject a request if its Content-Length is - /// too large. - /// - /// No common browsers send the Expect header, but some other clients such - /// as cURL do so by default. - "#, - "expect"; - - r#" - /// Contains the date/time after which the response is considered stale. - /// - /// Invalid dates, like the value 0, represent a date in the past and mean - /// that the resource is already expired. - /// - /// If there is a Cache-Control header with the "max-age" or "s-max-age" - /// directive in the response, the Expires header is ignored. - "#, - "expires"; - - r#" - /// Contains information from the client-facing side of proxy servers that - /// is altered or lost when a proxy is involved in the path of the request. - /// - /// The alternative and de-facto standard versions of this header are the - /// X-Forwarded-For, X-Forwarded-Host and X-Forwarded-Proto headers. - /// - /// This header is used for debugging, statistics, and generating - /// location-dependent content and by design it exposes privacy sensitive - /// information, such as the IP address of the client. Therefore the user's - /// privacy must be kept in mind when deploying this header. - "#, - "forwarded"; - - r#" - /// Contains an Internet email address for a human user who controls the - /// requesting user agent. - /// - /// If you are running a robotic user agent (e.g. a crawler), the From - /// header should be sent, so you can be contacted if problems occur on - /// servers, such as if the robot is sending excessive, unwanted, or invalid - /// requests. - "#, - "from"; - - r#" - /// Specifies the domain name of the server and (optionally) the TCP port - /// number on which the server is listening. - /// - /// If no port is given, the default port for the service requested (e.g., - /// "80" for an HTTP URL) is implied. - /// - /// A Host header field must be sent in all HTTP/1.1 request messages. A 400 - /// (Bad Request) status code will be sent to any HTTP/1.1 request message - /// that lacks a Host header field or contains more than one. - "#, - "host"; - - r#" - /// Makes a request conditional based on the E-Tag. - /// - /// For GET and HEAD methods, the server will send back the requested - /// resource only if it matches one of the listed ETags. For PUT and other - /// non-safe methods, it will only upload the resource in this case. - /// - /// The comparison with the stored ETag uses the strong comparison - /// algorithm, meaning two files are considered identical byte to byte only. - /// This is weakened when the W/ prefix is used in front of the ETag. - /// - /// There are two common use cases: - /// - /// * For GET and HEAD methods, used in combination with an Range header, it - /// can guarantee that the new ranges requested comes from the same resource - /// than the previous one. If it doesn't match, then a 416 (Range Not - /// Satisfiable) response is returned. - /// - /// * For other methods, and in particular for PUT, If-Match can be used to - /// prevent the lost update problem. It can check if the modification of a - /// resource that the user wants to upload will not override another change - /// that has been done since the original resource was fetched. If the - /// request cannot be fulfilled, the 412 (Precondition Failed) response is - /// returned. - "#, - "if-match"; - - r#" - /// Makes a request conditional based on the modification date. - /// - /// The If-Modified-Since request HTTP header makes the request conditional: - /// the server will send back the requested resource, with a 200 status, - /// only if it has been last modified after the given date. If the request - /// has not been modified since, the response will be a 304 without any - /// body; the Last-Modified header will contain the date of last - /// modification. Unlike If-Unmodified-Since, If-Modified-Since can only be - /// used with a GET or HEAD. - /// - /// When used in combination with If-None-Match, it is ignored, unless the - /// server doesn't support If-None-Match. - /// - /// The most common use case is to update a cached entity that has no - /// associated ETag. - "#, - "if-modified-since"; - - r#" - /// Makes a request conditional based on the E-Tag. - /// - /// The If-None-Match HTTP request header makes the request conditional. For - /// GET and HEAD methods, the server will send back the requested resource, - /// with a 200 status, only if it doesn't have an ETag matching the given - /// ones. For other methods, the request will be processed only if the - /// eventually existing resource's ETag doesn't match any of the values - /// listed. - /// - /// When the condition fails for GET and HEAD methods, then the server must - /// return HTTP status code 304 (Not Modified). For methods that apply - /// server-side changes, the status code 412 (Precondition Failed) is used. - /// Note that the server generating a 304 response MUST generate any of the - /// following header fields that would have been sent in a 200 (OK) response - /// to the same request: Cache-Control, Content-Location, Date, ETag, - /// Expires, and Vary. - /// - /// The comparison with the stored ETag uses the weak comparison algorithm, - /// meaning two files are considered identical not only if they are - /// identical byte to byte, but if the content is equivalent. For example, - /// two pages that would differ only by the date of generation in the footer - /// would be considered as identical. - /// - /// When used in combination with If-Modified-Since, it has precedence (if - /// the server supports it). - /// - /// There are two common use cases: - /// - /// * For `GET` and `HEAD` methods, to update a cached entity that has an associated ETag. - /// * For other methods, and in particular for `PUT`, `If-None-Match` used with - /// the `*` value can be used to save a file not known to exist, - /// guaranteeing that another upload didn't happen before, losing the data - /// of the previous put; this problems is the variation of the lost update - /// problem. - "#, - "if-none-match"; - - r#" - /// Makes a request conditional based on range. - /// - /// The If-Range HTTP request header makes a range request conditional: if - /// the condition is fulfilled, the range request will be issued and the - /// server sends back a 206 Partial Content answer with the appropriate - /// body. If the condition is not fulfilled, the full resource is sent back, - /// with a 200 OK status. - /// - /// This header can be used either with a Last-Modified validator, or with - /// an ETag, but not with both. - /// - /// The most common use case is to resume a download, to guarantee that the - /// stored resource has not been modified since the last fragment has been - /// received. - "#, - "if-range"; - - r#" - /// Makes the request conditional based on the last modification date. - /// - /// The If-Unmodified-Since request HTTP header makes the request - /// conditional: the server will send back the requested resource, or accept - /// it in the case of a POST or another non-safe method, only if it has not - /// been last modified after the given date. If the request has been - /// modified after the given date, the response will be a 412 (Precondition - /// Failed) error. - /// - /// There are two common use cases: - /// - /// * In conjunction non-safe methods, like POST, it can be used to - /// implement an optimistic concurrency control, like done by some wikis: - /// editions are rejected if the stored document has been modified since the - /// original has been retrieved. - /// - /// * In conjunction with a range request with a If-Range header, it can be - /// used to ensure that the new fragment requested comes from an unmodified - /// document. - "#, - "if-unmodified-since"; - - r#" - /// Content-Types that are acceptable for the response. - "#, - "last-modified"; - - r#" - /// Hint about how the connection and may be used to set a timeout and a - /// maximum amount of requests. - "#, - "keep-alive"; - - r#" - /// Allows the server to point an interested client to another resource - /// containing metadata about the requested resource. - "#, - "link"; - - r#" - /// Indicates the URL to redirect a page to. - /// - /// The Location response header indicates the URL to redirect a page to. It - /// only provides a meaning when served with a 3xx status response. - /// - /// The HTTP method used to make the new request to fetch the page pointed - /// to by Location depends of the original method and of the kind of - /// redirection: - /// - /// * If 303 (See Also) responses always lead to the use of a GET method, - /// 307 (Temporary Redirect) and 308 (Permanent Redirect) don't change the - /// method used in the original request; - /// - /// * 301 (Permanent Redirect) and 302 (Found) doesn't change the method - /// most of the time, though older user-agents may (so you basically don't - /// know). - /// - /// All responses with one of these status codes send a Location header. - /// - /// Beside redirect response, messages with 201 (Created) status also - /// include the Location header. It indicates the URL to the newly created - /// resource. - /// - /// Location and Content-Location are different: Location indicates the - /// target of a redirection (or the URL of a newly created resource), while - /// Content-Location indicates the direct URL to use to access the resource - /// when content negotiation happened, without the need of further content - /// negotiation. Location is a header associated with the response, while - /// Content-Location is associated with the entity returned. - "#, - "location"; - - r#" - /// Indicates the max number of intermediaries the request should be sent - /// through. - "#, - "max-forwards"; - - r#" - /// Indicates where a fetch originates from. - /// - /// It doesn't include any path information, but only the server name. It is - /// sent with CORS requests, as well as with POST requests. It is similar to - /// the Referer header, but, unlike this header, it doesn't disclose the - /// whole path. - "#, - "origin"; - - r#" - /// HTTP/1.0 header usually used for backwards compatibility. - /// - /// The Pragma HTTP/1.0 general header is an implementation-specific header - /// that may have various effects along the request-response chain. It is - /// used for backwards compatibility with HTTP/1.0 caches where the - /// Cache-Control HTTP/1.1 header is not yet present. - "#, - "pragma"; - - r#" - /// Defines the authentication method that should be used to gain access to - /// a proxy. - /// - /// Unlike `www-authenticate`, the `proxy-authenticate` header field applies - /// only to the next outbound client on the response chain. This is because - /// only the client that chose a given proxy is likely to have the - /// credentials necessary for authentication. However, when multiple proxies - /// are used within the same administrative domain, such as office and - /// regional caching proxies within a large corporate network, it is common - /// for credentials to be generated by the user agent and passed through the - /// hierarchy until consumed. Hence, in such a configuration, it will appear - /// as if Proxy-Authenticate is being forwarded because each proxy will send - /// the same challenge set. - /// - /// The `proxy-authenticate` header is sent along with a `407 Proxy - /// Authentication Required`. - "#, - "proxy-authenticate"; - - r#" - /// Contains the credentials to authenticate a user agent to a proxy server. - /// - /// This header is usually included after the server has responded with a - /// 407 Proxy Authentication Required status and the Proxy-Authenticate - /// header. - "#, - "proxy-authorization"; - - r#" - /// Associates a specific cryptographic public key with a certain server. - /// - /// This decreases the risk of MITM attacks with forged certificates. If one - /// or several keys are pinned and none of them are used by the server, the - /// browser will not accept the response as legitimate, and will not display - /// it. - "#, - "public-key-pins"; - - r#" - /// Sends reports of pinning violation to the report-uri specified in the - /// header. - /// - /// Unlike `Public-Key-Pins`, this header still allows browsers to connect - /// to the server if the pinning is violated. - "#, - "public-key-pins-report-only"; - - r#" - /// Indicates the part of a document that the server should return. - /// - /// Several parts can be requested with one Range header at once, and the - /// server may send back these ranges in a multipart document. If the server - /// sends back ranges, it uses the 206 Partial Content for the response. If - /// the ranges are invalid, the server returns the 416 Range Not Satisfiable - /// error. The server can also ignore the Range header and return the whole - /// document with a 200 status code. - "#, - "range"; - - r#" - /// Contains the address of the previous web page from which a link to the - /// currently requested page was followed. - /// - /// The Referer header allows servers to identify where people are visiting - /// them from and may use that data for analytics, logging, or optimized - /// caching, for example. - "#, - "referer"; - - r#" - /// Governs which referrer information should be included with requests - /// made. - "#, - "referrer-policy"; - - r#" - /// Informs the web browser that the current page or frame should be - /// refreshed. - "#, - "refresh"; - - r#" - /// The Retry-After response HTTP header indicates how long the user agent - /// should wait before making a follow-up request. There are two main cases - /// this header is used: - /// - /// * When sent with a 503 (Service Unavailable) response, it indicates how - /// long the service is expected to be unavailable. - /// - /// * When sent with a redirect response, such as 301 (Moved Permanently), - /// it indicates the minimum time that the user agent is asked to wait - /// before issuing the redirected request. - "#, - "retry-after"; - - r#" - /// Contains information about the software used by the origin server to - /// handle the request. - /// - /// Overly long and detailed Server values should be avoided as they - /// potentially reveal internal implementation details that might make it - /// (slightly) easier for attackers to find and exploit known security - /// holes. - "#, - "server"; - - r#" - /// Used to send cookies from the server to the user agent. - "#, - "set-cookie"; - - r#" - /// Tells the client to communicate with HTTPS instead of using HTTP. - "#, - "strict-transport-security"; - - r#" - /// Informs the server of transfer encodings willing to be accepted as part - /// of the response. - /// - /// See also the Transfer-Encoding response header for more details on - /// transfer encodings. Note that chunked is always acceptable for HTTP/1.1 - /// recipients and you that don't have to specify "chunked" using the TE - /// header. However, it is useful for setting if the client is accepting - /// trailer fields in a chunked transfer coding using the "trailers" value. - "#, - "te"; - - r#" - /// Indicates the tracking status that applied to the corresponding request. - "#, - "tk"; - - r#" - /// Allows the sender to include additional fields at the end of chunked - /// messages. - "#, - "trailer"; - - r#" - /// Specifies the form of encoding used to safely transfer the entity to the - /// client. - /// - /// `transfer-encoding` is a hop-by-hop header, that is applying to a - /// message between two nodes, not to a resource itself. Each segment of a - /// multi-node connection can use different `transfer-encoding` values. If - /// you want to compress data over the whole connection, use the end-to-end - /// header `content-encoding` header instead. - /// - /// When present on a response to a `HEAD` request that has no body, it - /// indicates the value that would have applied to the corresponding `GET` - /// message. - "#, - "transfer-encoding"; - - r#" - /// A response to the client's tracking preference. - /// - /// A tracking status value (TSV) is a single character response to the - /// user's tracking preference with regard to data collected via the - /// designated resource. For a site-wide tracking status resource, the - /// designated resource is any resource on the same origin server. For a Tk - /// response header field, the target resource of the corresponding request - /// is the designated resource, and remains so for any subsequent - /// request-specific tracking status resource referred to by the Tk field - /// value. - "#, - "tsv"; - - r#" - /// Contains a string that allows identifying the requesting client's - /// software. - "#, - "user-agent"; - - r#" - /// Used as part of the exchange to upgrade the protocol. - "#, - "upgrade"; - - r#" - /// Sends a signal to the server expressing the client’s preference for an - /// encrypted and authenticated response. - "#, - "upgrade-insecure-requests"; - - r#" - /// Determines how to match future requests with cached responses. - /// - /// The `vary` HTTP response header determines how to match future request - /// headers to decide whether a cached response can be used rather than - /// requesting a fresh one from the origin server. It is used by the server - /// to indicate which headers it used when selecting a representation of a - /// resource in a content negotiation algorithm. - /// - /// The `vary` header should be set on a 304 Not Modified response exactly - /// like it would have been set on an equivalent 200 OK response. - "#, - "vary"; - - r#" - /// Added by proxies to track routing. - /// - /// The `via` general header is added by proxies, both forward and reverse - /// proxies, and can appear in the request headers and the response headers. - /// It is used for tracking message forwards, avoiding request loops, and - /// identifying the protocol capabilities of senders along the - /// request/response chain. - "#, - "via"; - - r#" - /// General HTTP header contains information about possible problems with - /// the status of the message. - /// - /// More than one `warning` header may appear in a response. Warning header - /// fields can in general be applied to any message, however some warn-codes - /// are specific to caches and can only be applied to response messages. - "#, - "warning"; - - r#" - /// Defines the authentication method that should be used to gain access to - /// a resource. - "#, - "www-authenticate"; - - r#" - /// Marker used by the server to indicate that the MIME types advertised in - /// the `content-type` headers should not be changed and be followed. - /// - /// This allows to opt-out of MIME type sniffing, or, in other words, it is - /// a way to say that the webmasters knew what they were doing. - /// - /// This header was introduced by Microsoft in IE 8 as a way for webmasters - /// to block content sniffing that was happening and could transform - /// non-executable MIME types into executable MIME types. Since then, other - /// browsers have introduced it, even if their MIME sniffing algorithms were - /// less aggressive. - /// - /// Site security testers usually expect this header to be set. - "#, - "x-content-type-options"; - - r#" - /// Controls DNS prefetching. - /// - /// The `x-dns-prefetch-control` HTTP response header controls DNS - /// prefetching, a feature by which browsers proactively perform domain name - /// resolution on both links that the user may choose to follow as well as - /// URLs for items referenced by the document, including images, CSS, - /// JavaScript, and so forth. - /// - /// This prefetching is performed in the background, so that the DNS is - /// likely to have been resolved by the time the referenced items are - /// needed. This reduces latency when the user clicks a link. - "#, - "x-dns-prefetch-control"; - - r#" - /// Indicates whether or not a browser should be allowed to render a page in - /// a frame. - /// - /// Sites can use this to avoid clickjacking attacks, by ensuring that their - /// content is not embedded into other sites. - /// - /// The added security is only provided if the user accessing the document - /// is using a browser supporting `x-frame-options`. - "#, - "x-frame-options"; - - r#" - /// Stop pages from loading when an XSS attack is detected. - /// - /// The HTTP X-XSS-Protection response header is a feature of Internet - /// Explorer, Chrome and Safari that stops pages from loading when they - /// detect reflected cross-site scripting (XSS) attacks. Although these - /// protections are largely unnecessary in modern browsers when sites - /// implement a strong Content-Security-Policy that disables the use of - /// inline JavaScript ('unsafe-inline'), they can still provide protections - /// for users of older web browsers that don't yet support CSP. - "#, - "x-xss-protection"; -} - -fn constantize(s: &str) -> String { - let parts = s.split("-").map(|s| { - s.chars().enumerate().map(|(n, c)| { - if n == 0 { - c.to_uppercase().to_string() - } else { - c.to_string() - } - }) - }); - - let mut res = String::new(); - - for part in parts { - res.extend(part); - } - - res -} - -fn upcase(s: &str) -> String { - let mut ret = String::new(); - - for ch in s.chars() { - if ch == '-' { - ret.push('_'); - } else { - for ch in ch.to_uppercase() { - ret.push(ch); - } - } - } - - ret -} - -pub fn main() { - for &(doc, string) in HEADERS.iter() { - println!("{}", &doc[1..doc.len()-5]); - println!(" ({}, {}, {:?});", constantize(string), upcase(string), string); - println!(""); - } -}