headers-0.3.9/.cargo_vcs_info.json0000644000000001360000000000100124600ustar { "git": { "sha1": "2b9fc5be92f0346482aa6d09917a434a56ade3f3" }, "path_in_vcs": "" }headers-0.3.9/.github/workflows/ci.yml000064400000000000000000000016401046102023000157640ustar 00000000000000name: CI on: [push, pull_request] env: minrust: 1.56.0 jobs: test: name: Test runs-on: ubuntu-latest strategy: matrix: rust: [stable, beta, nightly] steps: - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} components: rustfmt - run: cargo test --workspace - if: matrix.rust == 'nightly' run: cargo test --benches - name: Check minimal versions if: matrix.rust == 'nightly' run: | cargo clean cargo update -Z minimal-versions cargo check - run: cargo fmt --all --check MSRV: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Install rust ${{ env.minrust }} uses: dtolnay/rust-toolchain@master with: toolchain: ${{ env.minrust }} - run: cargo build headers-0.3.9/.gitignore000064400000000000000000000000221046102023000132320ustar 00000000000000target Cargo.lock headers-0.3.9/Cargo.toml0000644000000021720000000000100104600ustar # 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] rust-version = "1.56" name = "headers" version = "0.3.9" authors = ["Sean McArthur "] description = "typed HTTP headers" homepage = "https://hyper.rs" readme = "README.md" keywords = [ "http", "headers", "hyper", "hyperium", ] categories = ["web-programming"] license = "MIT" repository = "https://github.com/hyperium/headers" [dependencies.base64] version = "0.21.3" [dependencies.bytes] version = "1" [dependencies.headers-core] version = "0.2" [dependencies.http] version = "0.2.0" [dependencies.httpdate] version = "1" [dependencies.mime] version = "0.3.14" [dependencies.sha1] version = "0.10" [features] nightly = [] headers-0.3.9/Cargo.toml.orig000064400000000000000000000012031046102023000141330ustar 00000000000000[package] name = "headers" version = "0.3.9" # don't forget to update html_root_url description = "typed HTTP headers" license = "MIT" readme = "README.md" homepage = "https://hyper.rs" repository = "https://github.com/hyperium/headers" authors = ["Sean McArthur "] keywords = ["http", "headers", "hyper", "hyperium"] categories = ["web-programming"] rust-version = "1.56" [workspace] members = [ "./", "headers-core", ] [dependencies] http = "0.2.0" headers-core = { version = "0.2", path = "./headers-core" } base64 = "0.21.3" bytes = "1" mime = "0.3.14" sha1 = "0.10" httpdate = "1" [features] nightly = [] headers-0.3.9/LICENSE000064400000000000000000000020471046102023000122600ustar 00000000000000Copyright (c) 2014-2019 Sean McArthur Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. headers-0.3.9/README.md000064400000000000000000000002711046102023000125270ustar 00000000000000# rust http headers [![Build Status](https://github.com/hyperium/headers/workflows/CI/badge.svg)](https://github.com/hyperium/headers/actions?query=workflow%3ACI) Typed HTTP headers. headers-0.3.9/src/common/accept_ranges.rs000064400000000000000000000017251046102023000165000ustar 00000000000000use util::FlatCsv; /// `Accept-Ranges` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-2.3) /// /// The `Accept-Ranges` header field allows a server to indicate that it /// supports range requests for the target resource. /// /// # ABNF /// /// ```text /// Accept-Ranges = acceptable-ranges /// acceptable-ranges = 1#range-unit / \"none\" /// /// # Example values /// * `bytes` /// * `none` /// * `unknown-unit` /// ``` /// /// # Examples /// /// ``` /// use headers::{AcceptRanges, HeaderMap, HeaderMapExt}; /// /// let mut headers = HeaderMap::new(); /// /// headers.typed_insert(AcceptRanges::bytes()); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct AcceptRanges(FlatCsv); derive_header! { AcceptRanges(_), name: ACCEPT_RANGES } impl AcceptRanges { /// A constructor to easily create the common `Accept-Ranges: bytes` header. pub fn bytes() -> Self { AcceptRanges(::HeaderValue::from_static("bytes").into()) } } headers-0.3.9/src/common/access_control_allow_credentials.rs000064400000000000000000000043441046102023000224560ustar 00000000000000use {Header, HeaderName, HeaderValue}; /// `Access-Control-Allow-Credentials` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) /// /// > The Access-Control-Allow-Credentials HTTP response header indicates whether the /// > response to request can be exposed when the credentials flag is true. When part /// > of the response to an preflight request it indicates that the actual request can /// > be made with credentials. The Access-Control-Allow-Credentials HTTP header must /// > match the following ABNF: /// /// # ABNF /// /// ```text /// Access-Control-Allow-Credentials: "Access-Control-Allow-Credentials" ":" "true" /// ``` /// /// Since there is only one acceptable field value, the header struct does not accept /// any values at all. Setting an empty `AccessControlAllowCredentials` header is /// sufficient. See the examples below. /// /// # Example values /// * "true" /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::AccessControlAllowCredentials; /// /// let allow_creds = AccessControlAllowCredentials; /// ``` #[derive(Clone, PartialEq, Eq, Debug)] pub struct AccessControlAllowCredentials; impl Header for AccessControlAllowCredentials { fn name() -> &'static HeaderName { &::http::header::ACCESS_CONTROL_ALLOW_CREDENTIALS } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .and_then(|value| { if value == "true" { Some(AccessControlAllowCredentials) } else { None } }) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { values.extend(::std::iter::once(HeaderValue::from_static("true"))); } } #[cfg(test)] mod tests { use super::super::test_decode; use super::*; #[test] fn allow_credentials_is_case_sensitive() { let allow_header = test_decode::(&["true"]); assert!(allow_header.is_some()); let allow_header = test_decode::(&["True"]); assert!(allow_header.is_none()); } } headers-0.3.9/src/common/access_control_allow_headers.rs000064400000000000000000000051661046102023000215770ustar 00000000000000use std::iter::FromIterator; use util::FlatCsv; use {HeaderName, HeaderValue}; /// `Access-Control-Allow-Headers` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) /// /// The `Access-Control-Allow-Headers` header indicates, as part of the /// response to a preflight request, which header field names can be used /// during the actual request. /// /// # ABNF /// /// ```text /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name /// ``` /// /// # Example values /// * `accept-language, date` /// /// # Examples /// /// ``` /// # extern crate headers; /// extern crate http; /// use http::header::{CACHE_CONTROL, CONTENT_TYPE}; /// use headers::AccessControlAllowHeaders; /// /// let allow_headers = vec![CACHE_CONTROL, CONTENT_TYPE] /// .into_iter() /// .collect::(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct AccessControlAllowHeaders(FlatCsv); derive_header! { AccessControlAllowHeaders(_), name: ACCESS_CONTROL_ALLOW_HEADERS } impl AccessControlAllowHeaders { /// Returns an iterator over `HeaderName`s contained within. pub fn iter<'a>(&'a self) -> impl Iterator + 'a { self.0 .iter() .map(|s| s.parse().ok()) .take_while(|val| val.is_some()) .filter_map(|val| val) } } impl FromIterator for AccessControlAllowHeaders { fn from_iter(iter: I) -> Self where I: IntoIterator, { let flat = iter.into_iter().map(HeaderValue::from).collect(); AccessControlAllowHeaders(flat) } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn iter() { let allow_headers = test_decode::(&["foo, bar"]).unwrap(); let as_vec = allow_headers.iter().collect::>(); assert_eq!(as_vec.len(), 2); assert_eq!(as_vec[0], "foo"); assert_eq!(as_vec[1], "bar"); } #[test] fn from_iter() { let allow: AccessControlAllowHeaders = vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] .into_iter() .collect(); let headers = test_encode(allow); assert_eq!( headers["access-control-allow-headers"], "cache-control, if-range" ); } #[test] fn test_with_invalid() { let allow_headers = test_decode::(&["foo foo, bar"]).unwrap(); assert!(allow_headers.iter().collect::>().is_empty()); } } headers-0.3.9/src/common/access_control_allow_methods.rs000064400000000000000000000045031046102023000216210ustar 00000000000000use std::iter::FromIterator; use http::Method; use util::FlatCsv; /// `Access-Control-Allow-Methods` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-methods-response-header) /// /// The `Access-Control-Allow-Methods` header indicates, as part of the /// response to a preflight request, which methods can be used during the /// actual request. /// /// # ABNF /// /// ```text /// Access-Control-Allow-Methods: "Access-Control-Allow-Methods" ":" #Method /// ``` /// /// # Example values /// * `PUT, DELETE, XMODIFY` /// /// # Examples /// /// ``` /// # extern crate headers; /// extern crate http; /// use http::Method; /// use headers::AccessControlAllowMethods; /// /// let allow_methods = vec![Method::GET, Method::PUT] /// .into_iter() /// .collect::(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct AccessControlAllowMethods(FlatCsv); derive_header! { AccessControlAllowMethods(_), name: ACCESS_CONTROL_ALLOW_METHODS } impl AccessControlAllowMethods { /// Returns an iterator over `Method`s contained within. pub fn iter<'a>(&'a self) -> impl Iterator + 'a { self.0.iter().filter_map(|s| s.parse().ok()) } } impl FromIterator for AccessControlAllowMethods { fn from_iter(iter: I) -> Self where I: IntoIterator, { let methods = iter .into_iter() .map(|method| { method .as_str() .parse::<::HeaderValue>() .expect("Method is a valid HeaderValue") }) .collect(); AccessControlAllowMethods(methods) } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn iter() { let allowed = test_decode::(&["GET, PUT"]).unwrap(); let as_vec = allowed.iter().collect::>(); assert_eq!(as_vec.len(), 2); assert_eq!(as_vec[0], Method::GET); assert_eq!(as_vec[1], Method::PUT); } #[test] fn from_iter() { let allow: AccessControlAllowMethods = vec![Method::GET, Method::PUT].into_iter().collect(); let headers = test_encode(allow); assert_eq!(headers["access-control-allow-methods"], "GET, PUT"); } } headers-0.3.9/src/common/access_control_allow_origin.rs000064400000000000000000000115211046102023000214430ustar 00000000000000use std::convert::TryFrom; use super::origin::Origin; use util::{IterExt, TryFromValues}; use HeaderValue; /// The `Access-Control-Allow-Origin` response header, /// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header) /// /// The `Access-Control-Allow-Origin` header indicates whether a resource /// can be shared based by returning the value of the Origin request header, /// `*`, or `null` in the response. /// /// ## ABNF /// /// ```text /// Access-Control-Allow-Origin = "Access-Control-Allow-Origin" ":" origin-list-or-null | "*" /// ``` /// /// ## Example values /// * `null` /// * `*` /// * `http://google.com/` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::AccessControlAllowOrigin; /// use std::convert::TryFrom; /// /// let any_origin = AccessControlAllowOrigin::ANY; /// let null_origin = AccessControlAllowOrigin::NULL; /// let origin = AccessControlAllowOrigin::try_from("http://web-platform.test:8000"); /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AccessControlAllowOrigin(OriginOrAny); derive_header! { AccessControlAllowOrigin(_), name: ACCESS_CONTROL_ALLOW_ORIGIN } #[derive(Clone, Debug, PartialEq, Eq, Hash)] enum OriginOrAny { Origin(Origin), /// Allow all origins Any, } impl AccessControlAllowOrigin { /// `Access-Control-Allow-Origin: *` pub const ANY: AccessControlAllowOrigin = AccessControlAllowOrigin(OriginOrAny::Any); /// `Access-Control-Allow-Origin: null` pub const NULL: AccessControlAllowOrigin = AccessControlAllowOrigin(OriginOrAny::Origin(Origin::NULL)); /// Returns the origin if there's one specified. pub fn origin(&self) -> Option<&Origin> { match self.0 { OriginOrAny::Origin(ref origin) => Some(origin), _ => None, } } } impl TryFrom<&str> for AccessControlAllowOrigin { type Error = ::Error; fn try_from(s: &str) -> Result { let header_value = HeaderValue::from_str(s).map_err(|_| ::Error::invalid())?; let origin = OriginOrAny::try_from(&header_value)?; Ok(Self(origin)) } } impl TryFrom<&HeaderValue> for OriginOrAny { type Error = ::Error; fn try_from(header_value: &HeaderValue) -> Result { Origin::try_from_value(header_value) .map(OriginOrAny::Origin) .ok_or_else(::Error::invalid) } } impl TryFromValues for OriginOrAny { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .just_one() .and_then(|value| { if value == "*" { return Some(OriginOrAny::Any); } Origin::try_from_value(value).map(OriginOrAny::Origin) }) .ok_or_else(::Error::invalid) } } impl<'a> From<&'a OriginOrAny> for HeaderValue { fn from(origin: &'a OriginOrAny) -> HeaderValue { match origin { OriginOrAny::Origin(ref origin) => origin.into_value(), OriginOrAny::Any => HeaderValue::from_static("*"), } } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn origin() { let s = "http://web-platform.test:8000"; let allow_origin = test_decode::(&[s]).unwrap(); { let origin = allow_origin.origin().unwrap(); assert_eq!(origin.scheme(), "http"); assert_eq!(origin.hostname(), "web-platform.test"); assert_eq!(origin.port(), Some(8000)); } let headers = test_encode(allow_origin); assert_eq!(headers["access-control-allow-origin"], s); } #[test] fn try_from_origin() { let s = "http://web-platform.test:8000"; let allow_origin = AccessControlAllowOrigin::try_from(s).unwrap(); { let origin = allow_origin.origin().unwrap(); assert_eq!(origin.scheme(), "http"); assert_eq!(origin.hostname(), "web-platform.test"); assert_eq!(origin.port(), Some(8000)); } let headers = test_encode(allow_origin); assert_eq!(headers["access-control-allow-origin"], s); } #[test] fn any() { let allow_origin = test_decode::(&["*"]).unwrap(); assert_eq!(allow_origin, AccessControlAllowOrigin::ANY); let headers = test_encode(allow_origin); assert_eq!(headers["access-control-allow-origin"], "*"); } #[test] fn null() { let allow_origin = test_decode::(&["null"]).unwrap(); assert_eq!(allow_origin, AccessControlAllowOrigin::NULL); let headers = test_encode(allow_origin); assert_eq!(headers["access-control-allow-origin"], "null"); } } headers-0.3.9/src/common/access_control_expose_headers.rs000064400000000000000000000044321046102023000217570ustar 00000000000000use std::iter::FromIterator; use util::FlatCsv; use {HeaderName, HeaderValue}; /// `Access-Control-Expose-Headers` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-expose-headers-response-header) /// /// The Access-Control-Expose-Headers header indicates which headers are safe to expose to the /// API of a CORS API specification. /// /// # ABNF /// /// ```text /// Access-Control-Expose-Headers = "Access-Control-Expose-Headers" ":" #field-name /// ``` /// /// # Example values /// * `ETag, Content-Length` /// /// # Examples /// /// ``` /// # extern crate headers; /// extern crate http; /// # fn main() { /// use http::header::{CONTENT_LENGTH, ETAG}; /// use headers::AccessControlExposeHeaders; /// /// let expose = vec![CONTENT_LENGTH, ETAG] /// .into_iter() /// .collect::(); /// # } /// ``` #[derive(Clone, Debug)] pub struct AccessControlExposeHeaders(FlatCsv); derive_header! { AccessControlExposeHeaders(_), name: ACCESS_CONTROL_EXPOSE_HEADERS } impl AccessControlExposeHeaders { /// Returns an iterator over `HeaderName`s contained within. pub fn iter<'a>(&'a self) -> impl Iterator + 'a { self.0.iter().filter_map(|s| s.parse().ok()) } } impl FromIterator for AccessControlExposeHeaders { fn from_iter(iter: I) -> Self where I: IntoIterator, { let flat = iter.into_iter().map(HeaderValue::from).collect(); AccessControlExposeHeaders(flat) } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn iter() { let expose_headers = test_decode::(&["foo, bar"]).unwrap(); let as_vec = expose_headers.iter().collect::>(); assert_eq!(as_vec.len(), 2); assert_eq!(as_vec[0], "foo"); assert_eq!(as_vec[1], "bar"); } #[test] fn from_iter() { let expose: AccessControlExposeHeaders = vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] .into_iter() .collect(); let headers = test_encode(expose); assert_eq!( headers["access-control-expose-headers"], "cache-control, if-range" ); } } headers-0.3.9/src/common/access_control_max_age.rs000064400000000000000000000021641046102023000203620ustar 00000000000000use std::time::Duration; use util::Seconds; /// `Access-Control-Max-Age` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-max-age-response-header) /// /// The `Access-Control-Max-Age` header indicates how long the results of a /// preflight request can be cached in a preflight result cache. /// /// # ABNF /// /// ```text /// Access-Control-Max-Age = \"Access-Control-Max-Age\" \":\" delta-seconds /// ``` /// /// # Example values /// /// * `531` /// /// # Examples /// /// ``` /// # extern crate headers; /// use std::time::Duration; /// use headers::AccessControlMaxAge; /// /// let max_age = AccessControlMaxAge::from(Duration::from_secs(531)); /// ``` #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct AccessControlMaxAge(Seconds); derive_header! { AccessControlMaxAge(_), name: ACCESS_CONTROL_MAX_AGE } impl From for AccessControlMaxAge { fn from(dur: Duration) -> AccessControlMaxAge { AccessControlMaxAge(dur.into()) } } impl From for Duration { fn from(acma: AccessControlMaxAge) -> Duration { acma.0.into() } } headers-0.3.9/src/common/access_control_request_headers.rs000064400000000000000000000045331046102023000221460ustar 00000000000000use std::iter::FromIterator; use util::FlatCsv; use {HeaderName, HeaderValue}; /// `Access-Control-Request-Headers` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-request-headers-request-header) /// /// The `Access-Control-Request-Headers` header indicates which headers will /// be used in the actual request as part of the preflight request. /// during the actual request. /// /// # ABNF /// /// ```text /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name /// ``` /// /// # Example values /// * `accept-language, date` /// /// # Examples /// /// ``` /// # extern crate headers; /// extern crate http; /// # fn main() { /// use http::header::{ACCEPT_LANGUAGE, DATE}; /// use headers::AccessControlRequestHeaders; /// /// let req_headers = vec![ACCEPT_LANGUAGE, DATE] /// .into_iter() /// .collect::(); /// # } /// ``` #[derive(Clone, Debug)] pub struct AccessControlRequestHeaders(FlatCsv); derive_header! { AccessControlRequestHeaders(_), name: ACCESS_CONTROL_REQUEST_HEADERS } impl AccessControlRequestHeaders { /// Returns an iterator over `HeaderName`s contained within. pub fn iter<'a>(&'a self) -> impl Iterator + 'a { self.0.iter().filter_map(|s| s.parse().ok()) } } impl FromIterator for AccessControlRequestHeaders { fn from_iter(iter: I) -> Self where I: IntoIterator, { let flat = iter.into_iter().map(HeaderValue::from).collect(); AccessControlRequestHeaders(flat) } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn iter() { let req_headers = test_decode::(&["foo, bar"]).unwrap(); let as_vec = req_headers.iter().collect::>(); assert_eq!(as_vec.len(), 2); assert_eq!(as_vec[0], "foo"); assert_eq!(as_vec[1], "bar"); } #[test] fn from_iter() { let req_headers: AccessControlRequestHeaders = vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] .into_iter() .collect(); let headers = test_encode(req_headers); assert_eq!( headers["access-control-request-headers"], "cache-control, if-range" ); } } headers-0.3.9/src/common/access_control_request_method.rs000064400000000000000000000042371046102023000220140ustar 00000000000000use http::Method; use {Header, HeaderName, HeaderValue}; /// `Access-Control-Request-Method` header, part of /// [CORS](http://www.w3.org/TR/cors/#access-control-request-method-request-header) /// /// The `Access-Control-Request-Method` header indicates which method will be /// used in the actual request as part of the preflight request. /// # ABNF /// /// ```text /// Access-Control-Request-Method: \"Access-Control-Request-Method\" \":\" Method /// ``` /// /// # Example values /// * `GET` /// /// # Examples /// /// ``` /// # extern crate headers; /// extern crate http; /// use headers::AccessControlRequestMethod; /// use http::Method; /// /// let req_method = AccessControlRequestMethod::from(Method::GET); /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct AccessControlRequestMethod(Method); impl Header for AccessControlRequestMethod { fn name() -> &'static HeaderName { &::http::header::ACCESS_CONTROL_REQUEST_METHOD } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .and_then(|value| Method::from_bytes(value.as_bytes()).ok()) .map(AccessControlRequestMethod) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { // For the more common methods, try to use a static string. let s = match self.0 { Method::GET => "GET", Method::POST => "POST", Method::PUT => "PUT", Method::DELETE => "DELETE", _ => { let val = HeaderValue::from_str(self.0.as_ref()) .expect("Methods are also valid HeaderValues"); values.extend(::std::iter::once(val)); return; } }; values.extend(::std::iter::once(HeaderValue::from_static(s))); } } impl From for AccessControlRequestMethod { fn from(method: Method) -> AccessControlRequestMethod { AccessControlRequestMethod(method) } } impl From for Method { fn from(method: AccessControlRequestMethod) -> Method { method.0 } } headers-0.3.9/src/common/age.rs000064400000000000000000000033741046102023000144400ustar 00000000000000use std::time::Duration; use util::Seconds; /// `Age` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.1) /// /// The "Age" header field conveys the sender's estimate of the amount of /// time since the response was generated or successfully validated at /// the origin server. Age values are calculated as specified in /// [Section 4.2.3](https://tools.ietf.org/html/rfc7234#section-4.2.3). /// /// ## ABNF /// /// ```text /// Age = delta-seconds /// ``` /// /// The Age field-value is a non-negative integer, representing time in /// seconds (see [Section 1.2.1](https://tools.ietf.org/html/rfc7234#section-1.2.1)). /// /// The presence of an Age header field implies that the response was not /// generated or validated by the origin server for this request. /// However, lack of an Age header field does not imply the origin was /// contacted, since the response might have been received from an /// HTTP/1.0 cache that does not implement Age. /// /// ## Example values /// /// * `3600` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::Age; /// /// let len = Age::from_secs(60); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Age(Seconds); derive_header! { Age(_), name: AGE } impl Age { /// Creates a new `Age` header from the specified number of whole seconds. pub fn from_secs(secs: u64) -> Self { Self(Seconds::from_secs(secs)) } /// Returns the number of seconds for this `Age` header. pub fn as_secs(&self) -> u64 { self.0.as_u64() } } impl From for Age { fn from(dur: Duration) -> Self { Age(Seconds::from(dur)) } } impl From for Duration { fn from(age: Age) -> Self { age.0.into() } } headers-0.3.9/src/common/allow.rs000064400000000000000000000030541046102023000150150ustar 00000000000000use std::iter::FromIterator; use http::Method; use util::FlatCsv; /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) /// /// The `Allow` header field lists the set of methods advertised as /// supported by the target resource. The purpose of this field is /// strictly to inform the recipient of valid request methods associated /// with the resource. /// /// # ABNF /// /// ```text /// Allow = #method /// ``` /// /// # Example values /// * `GET, HEAD, PUT` /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` /// * `` /// /// # Examples /// /// ``` /// # extern crate headers; /// extern crate http; /// use headers::Allow; /// use http::Method; /// /// let allow = vec![Method::GET, Method::POST] /// .into_iter() /// .collect::(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct Allow(FlatCsv); derive_header! { Allow(_), name: ALLOW } impl Allow { /// Returns an iterator over `Method`s contained within. pub fn iter<'a>(&'a self) -> impl Iterator + 'a { self.0.iter().filter_map(|s| s.parse().ok()) } } impl FromIterator for Allow { fn from_iter(iter: I) -> Self where I: IntoIterator, { let flat = iter .into_iter() .map(|method| { method .as_str() .parse::<::HeaderValue>() .expect("Method is a valid HeaderValue") }) .collect(); Allow(flat) } } headers-0.3.9/src/common/authorization.rs000064400000000000000000000200241046102023000165730ustar 00000000000000//! Authorization header and types. use base64::engine::general_purpose::STANDARD as ENGINE; use base64::Engine; use bytes::Bytes; use util::HeaderValueString; use HeaderValue; /// `Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.2) /// /// The `Authorization` header field allows a user agent to authenticate /// itself with an origin server -- usually, but not necessarily, after /// receiving a 401 (Unauthorized) response. Its value consists of /// credentials containing the authentication information of the user /// agent for the realm of the resource being requested. /// /// # ABNF /// /// ```text /// Authorization = credentials /// ``` /// /// # Example values /// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` /// * `Bearer fpKL54jvWmEGVoRdCNjG` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::Authorization; /// /// let basic = Authorization::basic("Aladdin", "open sesame"); /// let bearer = Authorization::bearer("some-opaque-token").unwrap(); /// ``` /// #[derive(Clone, PartialEq, Debug)] pub struct Authorization(pub C); impl Authorization { /// Create a `Basic` authorization header. pub fn basic(username: &str, password: &str) -> Self { let colon_pos = username.len(); let decoded = format!("{}:{}", username, password); Authorization(Basic { decoded, colon_pos }) } /// View the decoded username. pub fn username(&self) -> &str { self.0.username() } /// View the decoded password. pub fn password(&self) -> &str { self.0.password() } } impl Authorization { /// Try to create a `Bearer` authorization header. pub fn bearer(token: &str) -> Result { HeaderValueString::from_string(format!("Bearer {}", token)) .map(|val| Authorization(Bearer(val))) .ok_or_else(|| InvalidBearerToken { _inner: () }) } /// View the token part as a `&str`. pub fn token(&self) -> &str { self.0.token() } } impl ::Header for Authorization { fn name() -> &'static ::HeaderName { &::http::header::AUTHORIZATION } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .and_then(|val| { let slice = val.as_bytes(); if slice.starts_with(C::SCHEME.as_bytes()) && slice.len() > C::SCHEME.len() && slice[C::SCHEME.len()] == b' ' { C::decode(val).map(Authorization) } else { None } }) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { let mut value = self.0.encode(); value.set_sensitive(true); debug_assert!( value.as_bytes().starts_with(C::SCHEME.as_bytes()), "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}", C::SCHEME, value, ); values.extend(::std::iter::once(value)); } } /// Credentials to be used in the `Authorization` header. pub trait Credentials: Sized { /// The scheme identify the format of these credentials. /// /// This is the static string that always prefixes the actual credentials, /// like `"Basic"` in basic authorization. const SCHEME: &'static str; /// Try to decode the credentials from the `HeaderValue`. /// /// The `SCHEME` will be the first part of the `value`. fn decode(value: &HeaderValue) -> Option; /// Encode the credentials to a `HeaderValue`. /// /// The `SCHEME` must be the first part of the `value`. fn encode(&self) -> HeaderValue; } /// Credential holder for Basic Authentication #[derive(Clone, PartialEq, Debug)] pub struct Basic { decoded: String, colon_pos: usize, } impl Basic { /// View the decoded username. pub fn username(&self) -> &str { &self.decoded[..self.colon_pos] } /// View the decoded password. pub fn password(&self) -> &str { &self.decoded[self.colon_pos + 1..] } } impl Credentials for Basic { const SCHEME: &'static str = "Basic"; fn decode(value: &HeaderValue) -> Option { debug_assert!( value.as_bytes().starts_with(b"Basic "), "HeaderValue to decode should start with \"Basic ..\", received = {:?}", value, ); let bytes = &value.as_bytes()["Basic ".len()..]; let non_space_pos = bytes.iter().position(|b| *b != b' ')?; let bytes = &bytes[non_space_pos..]; let bytes = ENGINE.decode(bytes).ok()?; let decoded = String::from_utf8(bytes).ok()?; let colon_pos = decoded.find(':')?; Some(Basic { decoded, colon_pos }) } fn encode(&self) -> HeaderValue { let mut encoded = String::from("Basic "); ENGINE.encode_string(&self.decoded, &mut encoded); let bytes = Bytes::from(encoded); HeaderValue::from_maybe_shared(bytes) .expect("base64 encoding is always a valid HeaderValue") } } #[derive(Clone, PartialEq, Debug)] /// Token holder for Bearer Authentication, most often seen with oauth pub struct Bearer(HeaderValueString); impl Bearer { /// View the token part as a `&str`. pub fn token(&self) -> &str { &self.0.as_str()["Bearer ".len()..] } } impl Credentials for Bearer { const SCHEME: &'static str = "Bearer"; fn decode(value: &HeaderValue) -> Option { debug_assert!( value.as_bytes().starts_with(b"Bearer "), "HeaderValue to decode should start with \"Bearer ..\", received = {:?}", value, ); HeaderValueString::from_val(value).ok().map(Bearer) } fn encode(&self) -> HeaderValue { (&self.0).into() } } error_type!(InvalidBearerToken); #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::{Authorization, Basic, Bearer}; use http::header::HeaderMap; use HeaderMapExt; #[test] fn basic_encode() { let auth = Authorization::basic("Aladdin", "open sesame"); let headers = test_encode(auth); assert_eq!( headers["authorization"], "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==", ); } #[test] fn basic_roundtrip() { let auth = Authorization::basic("Aladdin", "open sesame"); let mut h = HeaderMap::new(); h.typed_insert(auth.clone()); assert_eq!(h.typed_get(), Some(auth)); } #[test] fn basic_encode_no_password() { let auth = Authorization::basic("Aladdin", ""); let headers = test_encode(auth); assert_eq!(headers["authorization"], "Basic QWxhZGRpbjo=",); } #[test] fn basic_decode() { let auth: Authorization = test_decode(&["Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=="]).unwrap(); assert_eq!(auth.0.username(), "Aladdin"); assert_eq!(auth.0.password(), "open sesame"); } #[test] fn basic_decode_no_password() { let auth: Authorization = test_decode(&["Basic QWxhZGRpbjo="]).unwrap(); assert_eq!(auth.0.username(), "Aladdin"); assert_eq!(auth.0.password(), ""); } #[test] fn bearer_encode() { let auth = Authorization::bearer("fpKL54jvWmEGVoRdCNjG").unwrap(); let headers = test_encode(auth); assert_eq!(headers["authorization"], "Bearer fpKL54jvWmEGVoRdCNjG",); } #[test] fn bearer_decode() { let auth: Authorization = test_decode(&["Bearer fpKL54jvWmEGVoRdCNjG"]).unwrap(); assert_eq!(auth.0.token().as_bytes(), b"fpKL54jvWmEGVoRdCNjG"); } } //bench_header!(raw, Authorization, { vec![b"foo bar baz".to_vec()] }); //bench_header!(basic, Authorization, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] }); //bench_header!(bearer, Authorization, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] }); headers-0.3.9/src/common/cache_control.rs000064400000000000000000000360031046102023000165020ustar 00000000000000use std::fmt; use std::iter::FromIterator; use std::str::FromStr; use std::time::Duration; use util::{self, csv, Seconds}; use HeaderValue; /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) /// with extensions in [RFC8246](https://www.rfc-editor.org/rfc/rfc8246) /// /// The `Cache-Control` header field is used to specify directives for /// caches along the request/response chain. Such cache directives are /// unidirectional in that the presence of a directive in a request does /// not imply that the same directive is to be given in the response. /// /// ## ABNF /// /// ```text /// Cache-Control = 1#cache-directive /// cache-directive = token [ "=" ( token / quoted-string ) ] /// ``` /// /// ## Example values /// /// * `no-cache` /// * `private, community="UCI"` /// * `max-age=30` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::CacheControl; /// /// let cc = CacheControl::new(); /// ``` #[derive(PartialEq, Clone, Debug)] pub struct CacheControl { flags: Flags, max_age: Option, max_stale: Option, min_fresh: Option, s_max_age: Option, } #[derive(Debug, Clone, PartialEq)] struct Flags { bits: u64, } impl Flags { const NO_CACHE: Self = Self { bits: 0b000000001 }; const NO_STORE: Self = Self { bits: 0b000000010 }; const NO_TRANSFORM: Self = Self { bits: 0b000000100 }; const ONLY_IF_CACHED: Self = Self { bits: 0b000001000 }; const MUST_REVALIDATE: Self = Self { bits: 0b000010000 }; const PUBLIC: Self = Self { bits: 0b000100000 }; const PRIVATE: Self = Self { bits: 0b001000000 }; const PROXY_REVALIDATE: Self = Self { bits: 0b010000000 }; const IMMUTABLE: Self = Self { bits: 0b100000000 }; fn empty() -> Self { Self { bits: 0 } } fn contains(&self, flag: Self) -> bool { (self.bits & flag.bits) != 0 } fn insert(&mut self, flag: Self) { self.bits |= flag.bits; } } impl CacheControl { /// Construct a new empty `CacheControl` header. pub fn new() -> Self { CacheControl { flags: Flags::empty(), max_age: None, max_stale: None, min_fresh: None, s_max_age: None, } } // getters /// Check if the `no-cache` directive is set. pub fn no_cache(&self) -> bool { self.flags.contains(Flags::NO_CACHE) } /// Check if the `no-store` directive is set. pub fn no_store(&self) -> bool { self.flags.contains(Flags::NO_STORE) } /// Check if the `no-transform` directive is set. pub fn no_transform(&self) -> bool { self.flags.contains(Flags::NO_TRANSFORM) } /// Check if the `only-if-cached` directive is set. pub fn only_if_cached(&self) -> bool { self.flags.contains(Flags::ONLY_IF_CACHED) } /// Check if the `public` directive is set. pub fn public(&self) -> bool { self.flags.contains(Flags::PUBLIC) } /// Check if the `private` directive is set. pub fn private(&self) -> bool { self.flags.contains(Flags::PRIVATE) } /// Check if the `immutable` directive is set. pub fn immutable(&self) -> bool { self.flags.contains(Flags::IMMUTABLE) } /// Get the value of the `max-age` directive if set. pub fn max_age(&self) -> Option { self.max_age.map(Into::into) } /// Get the value of the `max-stale` directive if set. pub fn max_stale(&self) -> Option { self.max_stale.map(Into::into) } /// Get the value of the `min-fresh` directive if set. pub fn min_fresh(&self) -> Option { self.min_fresh.map(Into::into) } /// Get the value of the `s-maxage` directive if set. pub fn s_max_age(&self) -> Option { self.s_max_age.map(Into::into) } // setters /// Set the `no-cache` directive. pub fn with_no_cache(mut self) -> Self { self.flags.insert(Flags::NO_CACHE); self } /// Set the `no-store` directive. pub fn with_no_store(mut self) -> Self { self.flags.insert(Flags::NO_STORE); self } /// Set the `no-transform` directive. pub fn with_no_transform(mut self) -> Self { self.flags.insert(Flags::NO_TRANSFORM); self } /// Set the `only-if-cached` directive. pub fn with_only_if_cached(mut self) -> Self { self.flags.insert(Flags::ONLY_IF_CACHED); self } /// Set the `private` directive. pub fn with_private(mut self) -> Self { self.flags.insert(Flags::PRIVATE); self } /// Set the `public` directive. pub fn with_public(mut self) -> Self { self.flags.insert(Flags::PUBLIC); self } /// Set the `immutable` directive. pub fn with_immutable(mut self) -> Self { self.flags.insert(Flags::IMMUTABLE); self } /// Set the `max-age` directive. pub fn with_max_age(mut self, duration: Duration) -> Self { self.max_age = Some(duration.into()); self } /// Set the `max-stale` directive. pub fn with_max_stale(mut self, duration: Duration) -> Self { self.max_stale = Some(duration.into()); self } /// Set the `min-fresh` directive. pub fn with_min_fresh(mut self, duration: Duration) -> Self { self.min_fresh = Some(duration.into()); self } /// Set the `s-maxage` directive. pub fn with_s_max_age(mut self, duration: Duration) -> Self { self.s_max_age = Some(duration.into()); self } } impl ::Header for CacheControl { fn name() -> &'static ::HeaderName { &::http::header::CACHE_CONTROL } fn decode<'i, I: Iterator>(values: &mut I) -> Result { csv::from_comma_delimited(values).map(|FromIter(cc)| cc) } fn encode>(&self, values: &mut E) { values.extend(::std::iter::once(util::fmt(Fmt(self)))); } } // Adapter to be used in Header::decode struct FromIter(CacheControl); impl FromIterator for FromIter { fn from_iter(iter: I) -> Self where I: IntoIterator, { let mut cc = CacheControl::new(); // ignore all unknown directives let iter = iter.into_iter().filter_map(|dir| match dir { KnownDirective::Known(dir) => Some(dir), KnownDirective::Unknown => None, }); for directive in iter { match directive { Directive::NoCache => { cc.flags.insert(Flags::NO_CACHE); } Directive::NoStore => { cc.flags.insert(Flags::NO_STORE); } Directive::NoTransform => { cc.flags.insert(Flags::NO_TRANSFORM); } Directive::OnlyIfCached => { cc.flags.insert(Flags::ONLY_IF_CACHED); } Directive::MustRevalidate => { cc.flags.insert(Flags::MUST_REVALIDATE); } Directive::Public => { cc.flags.insert(Flags::PUBLIC); } Directive::Private => { cc.flags.insert(Flags::PRIVATE); } Directive::Immutable => { cc.flags.insert(Flags::IMMUTABLE); } Directive::ProxyRevalidate => { cc.flags.insert(Flags::PROXY_REVALIDATE); } Directive::MaxAge(secs) => { cc.max_age = Some(Duration::from_secs(secs.into()).into()); } Directive::MaxStale(secs) => { cc.max_stale = Some(Duration::from_secs(secs.into()).into()); } Directive::MinFresh(secs) => { cc.min_fresh = Some(Duration::from_secs(secs.into()).into()); } Directive::SMaxAge(secs) => { cc.s_max_age = Some(Duration::from_secs(secs.into()).into()); } } } FromIter(cc) } } struct Fmt<'a>(&'a CacheControl); impl<'a> fmt::Display for Fmt<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let if_flag = |f: Flags, dir: Directive| { if self.0.flags.contains(f) { Some(dir) } else { None } }; let slice = &[ if_flag(Flags::NO_CACHE, Directive::NoCache), if_flag(Flags::NO_STORE, Directive::NoStore), if_flag(Flags::NO_TRANSFORM, Directive::NoTransform), if_flag(Flags::ONLY_IF_CACHED, Directive::OnlyIfCached), if_flag(Flags::MUST_REVALIDATE, Directive::MustRevalidate), if_flag(Flags::PUBLIC, Directive::Public), if_flag(Flags::PRIVATE, Directive::Private), if_flag(Flags::IMMUTABLE, Directive::Immutable), if_flag(Flags::PROXY_REVALIDATE, Directive::ProxyRevalidate), self.0 .max_age .as_ref() .map(|s| Directive::MaxAge(s.as_u64())), self.0 .max_stale .as_ref() .map(|s| Directive::MaxStale(s.as_u64())), self.0 .min_fresh .as_ref() .map(|s| Directive::MinFresh(s.as_u64())), self.0 .s_max_age .as_ref() .map(|s| Directive::SMaxAge(s.as_u64())), ]; let iter = slice.iter().filter_map(|o| *o); csv::fmt_comma_delimited(f, iter) } } #[derive(Clone, Copy)] enum KnownDirective { Known(Directive), Unknown, } #[derive(Clone, Copy)] enum Directive { NoCache, NoStore, NoTransform, OnlyIfCached, // request directives MaxAge(u64), MaxStale(u64), MinFresh(u64), // response directives MustRevalidate, Public, Private, Immutable, ProxyRevalidate, SMaxAge(u64), } impl fmt::Display for Directive { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt( match *self { Directive::NoCache => "no-cache", Directive::NoStore => "no-store", Directive::NoTransform => "no-transform", Directive::OnlyIfCached => "only-if-cached", Directive::MaxAge(secs) => return write!(f, "max-age={}", secs), Directive::MaxStale(secs) => return write!(f, "max-stale={}", secs), Directive::MinFresh(secs) => return write!(f, "min-fresh={}", secs), Directive::MustRevalidate => "must-revalidate", Directive::Public => "public", Directive::Private => "private", Directive::Immutable => "immutable", Directive::ProxyRevalidate => "proxy-revalidate", Directive::SMaxAge(secs) => return write!(f, "s-maxage={}", secs), }, f, ) } } impl FromStr for KnownDirective { type Err = (); fn from_str(s: &str) -> Result { Ok(KnownDirective::Known(match s { "no-cache" => Directive::NoCache, "no-store" => Directive::NoStore, "no-transform" => Directive::NoTransform, "only-if-cached" => Directive::OnlyIfCached, "must-revalidate" => Directive::MustRevalidate, "public" => Directive::Public, "private" => Directive::Private, "immutable" => Directive::Immutable, "proxy-revalidate" => Directive::ProxyRevalidate, "" => return Err(()), _ => match s.find('=') { Some(idx) if idx + 1 < s.len() => { match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { ("max-age", secs) => secs.parse().map(Directive::MaxAge).map_err(|_| ())?, ("max-stale", secs) => { secs.parse().map(Directive::MaxStale).map_err(|_| ())? } ("min-fresh", secs) => { secs.parse().map(Directive::MinFresh).map_err(|_| ())? } ("s-maxage", secs) => { secs.parse().map(Directive::SMaxAge).map_err(|_| ())? } _unknown => return Ok(KnownDirective::Unknown), } } Some(_) | None => return Ok(KnownDirective::Unknown), }, })) } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn test_parse_multiple_headers() { assert_eq!( test_decode::(&["no-cache", "private"]).unwrap(), CacheControl::new().with_no_cache().with_private(), ); } #[test] fn test_parse_argument() { assert_eq!( test_decode::(&["max-age=100, private"]).unwrap(), CacheControl::new() .with_max_age(Duration::from_secs(100)) .with_private(), ); } #[test] fn test_parse_quote_form() { assert_eq!( test_decode::(&["max-age=\"200\""]).unwrap(), CacheControl::new().with_max_age(Duration::from_secs(200)), ); } #[test] fn test_parse_extension() { assert_eq!( test_decode::(&["foo, no-cache, bar=baz"]).unwrap(), CacheControl::new().with_no_cache(), "unknown extensions are ignored but shouldn't fail parsing", ); } #[test] fn test_immutable() { let cc = CacheControl::new().with_immutable(); let headers = test_encode(cc.clone()); assert_eq!(headers["cache-control"], "immutable"); assert_eq!(test_decode::(&["immutable"]).unwrap(), cc); assert!(cc.immutable()); } #[test] fn test_parse_bad_syntax() { assert_eq!(test_decode::(&["max-age=lolz"]), None); } #[test] fn encode_one_flag_directive() { let cc = CacheControl::new().with_no_cache(); let headers = test_encode(cc); assert_eq!(headers["cache-control"], "no-cache"); } #[test] fn encode_one_param_directive() { let cc = CacheControl::new().with_max_age(Duration::from_secs(300)); let headers = test_encode(cc); assert_eq!(headers["cache-control"], "max-age=300"); } #[test] fn encode_two_directive() { let headers = test_encode(CacheControl::new().with_no_cache().with_private()); assert_eq!(headers["cache-control"], "no-cache, private"); let headers = test_encode( CacheControl::new() .with_no_cache() .with_max_age(Duration::from_secs(100)), ); assert_eq!(headers["cache-control"], "no-cache, max-age=100"); } } headers-0.3.9/src/common/connection.rs000064400000000000000000000065221046102023000160410ustar 00000000000000use std::iter::FromIterator; use self::sealed::AsConnectionOption; use util::FlatCsv; use {HeaderName, HeaderValue}; /// `Connection` header, defined in /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.1) /// /// The `Connection` header field allows the sender to indicate desired /// control options for the current connection. In order to avoid /// confusing downstream recipients, a proxy or gateway MUST remove or /// replace any received connection options before forwarding the /// message. /// /// # ABNF /// /// ```text /// Connection = 1#connection-option /// connection-option = token /// /// # Example values /// * `close` /// * `keep-alive` /// * `upgrade` /// ``` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::Connection; /// /// let keep_alive = Connection::keep_alive(); /// ``` // This is frequently just 1 or 2 values, so optimize for that case. #[derive(Clone, Debug)] pub struct Connection(FlatCsv); derive_header! { Connection(_), name: CONNECTION } impl Connection { /// A constructor to easily create a `Connection: close` header. #[inline] pub fn close() -> Connection { Connection(HeaderValue::from_static("close").into()) } /// A constructor to easily create a `Connection: keep-alive` header. #[inline] pub fn keep_alive() -> Connection { Connection(HeaderValue::from_static("keep-alive").into()) } /// A constructor to easily create a `Connection: Upgrade` header. #[inline] pub fn upgrade() -> Connection { Connection(HeaderValue::from_static("upgrade").into()) } /// Check if this header contains a given "connection option". /// /// This can be used with various argument types: /// /// - `&str` /// - `&HeaderName` /// - `HeaderName` /// /// # Example /// /// ``` /// # extern crate headers; /// extern crate http; /// /// use http::header::UPGRADE; /// use headers::Connection; /// /// let conn = Connection::keep_alive(); /// /// assert!(!conn.contains("close")); /// assert!(!conn.contains(UPGRADE)); /// assert!(conn.contains("keep-alive")); /// assert!(conn.contains("Keep-Alive")); /// ``` pub fn contains(&self, name: impl AsConnectionOption) -> bool { let s = name.as_connection_option(); self.0 .iter() .find(|&opt| opt.eq_ignore_ascii_case(s)) .is_some() } } impl FromIterator for Connection { fn from_iter(iter: I) -> Self where I: IntoIterator, { let flat = iter.into_iter().map(HeaderValue::from).collect(); Connection(flat) } } mod sealed { pub trait AsConnectionOption: Sealed { fn as_connection_option(&self) -> &str; } pub trait Sealed {} impl<'a> AsConnectionOption for &'a str { fn as_connection_option(&self) -> &str { *self } } impl<'a> Sealed for &'a str {} impl<'a> AsConnectionOption for &'a ::HeaderName { fn as_connection_option(&self) -> &str { self.as_ref() } } impl<'a> Sealed for &'a ::HeaderName {} impl AsConnectionOption for ::HeaderName { fn as_connection_option(&self) -> &str { self.as_ref() } } impl Sealed for ::HeaderName {} } headers-0.3.9/src/common/content_disposition.rs000064400000000000000000000267021046102023000200020ustar 00000000000000// # References // // "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt // "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt // "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc2388.txt // Browser conformance tests at: http://greenbytes.de/tech/tc2231/ // IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml /// A `Content-Disposition` header, (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266). /// /// The Content-Disposition response header field is used to convey /// additional information about how to process the response payload, and /// also can be used to attach additional metadata, such as the filename /// to use when saving the response payload locally. /// /// # ABNF /// ```text /// content-disposition = "Content-Disposition" ":" /// disposition-type *( ";" disposition-parm ) /// /// disposition-type = "inline" | "attachment" | disp-ext-type /// ; case-insensitive /// /// disp-ext-type = token /// /// disposition-parm = filename-parm | disp-ext-parm /// /// filename-parm = "filename" "=" value /// | "filename*" "=" ext-value /// /// disp-ext-parm = token "=" value /// | ext-token "=" ext-value /// /// ext-token = /// ``` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::ContentDisposition; /// /// let cd = ContentDisposition::inline(); /// ``` #[derive(Clone, Debug)] pub struct ContentDisposition(::HeaderValue); impl ContentDisposition { /// Construct a `Content-Disposition: inline` header. pub fn inline() -> ContentDisposition { ContentDisposition(::HeaderValue::from_static("inline")) } /* pub fn attachment(filename: &str) -> ContentDisposition { let full = Bytes::from(format!("attachment; filename={}", filename)); match ::HeaderValue::from_maybe_shared(full) { Ok(val) => ContentDisposition(val), Err(_) => { unimplemented!("filename that isn't ASCII"); } } } */ /// Check if the disposition-type is `inline`. pub fn is_inline(&self) -> bool { self.get_type() == "inline" } /// Check if the disposition-type is `attachment`. pub fn is_attachment(&self) -> bool { self.get_type() == "attachment" } /// Check if the disposition-type is `form-data`. pub fn is_form_data(&self) -> bool { self.get_type() == "form-data" } fn get_type(&self) -> &str { self.0 .to_str() .unwrap_or("") .split(';') .next() .expect("split always has at least 1 item") } } impl ::Header for ContentDisposition { fn name() -> &'static ::HeaderName { &::http::header::CONTENT_DISPOSITION } fn decode<'i, I: Iterator>(values: &mut I) -> Result { //TODO: parse harder values .next() .cloned() .map(ContentDisposition) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { values.extend(::std::iter::once(self.0.clone())); } } /* use language_tags::LanguageTag; use std::fmt; use unicase; use {Header, Raw, parsing}; use parsing::{parse_extended_value, http_percent_encode}; use shared::Charset; /// The implied disposition of the content of the HTTP body. #[derive(Clone, Debug, PartialEq)] pub enum DispositionType { /// Inline implies default processing Inline, /// Attachment implies that the recipient should prompt the user to save the response locally, /// rather than process it normally (as per its media type). Attachment, /// Extension type. Should be handled by recipients the same way as Attachment Ext(String) } /// A parameter to the disposition type. #[derive(Clone, Debug, PartialEq)] pub enum DispositionParam { /// A Filename consisting of a Charset, an optional LanguageTag, and finally a sequence of /// bytes representing the filename Filename(Charset, Option, Vec), /// Extension type consisting of token and value. Recipients should ignore unrecognized /// parameters. Ext(String, String) } #[derive(Clone, Debug, PartialEq)] pub struct ContentDisposition { /// The disposition pub disposition: DispositionType, /// Disposition parameters pub parameters: Vec, } impl Header for ContentDisposition { fn header_name() -> &'static str { static NAME: &'static str = "Content-Disposition"; NAME } fn parse_header(raw: &Raw) -> ::Result { parsing::from_one_raw_str(raw).and_then(|s: String| { let mut sections = s.split(';'); let disposition = match sections.next() { Some(s) => s.trim(), None => return Err(::Error::Header), }; let mut cd = ContentDisposition { disposition: if unicase::eq_ascii(&*disposition, "inline") { DispositionType::Inline } else if unicase::eq_ascii(&*disposition, "attachment") { DispositionType::Attachment } else { DispositionType::Ext(disposition.to_owned()) }, parameters: Vec::new(), }; for section in sections { let mut parts = section.splitn(2, '='); let key = if let Some(key) = parts.next() { key.trim() } else { return Err(::Error::Header); }; let val = if let Some(val) = parts.next() { val.trim() } else { return Err(::Error::Header); }; cd.parameters.push( if unicase::eq_ascii(&*key, "filename") { DispositionParam::Filename( Charset::Ext("UTF-8".to_owned()), None, val.trim_matches('"').as_bytes().to_owned()) } else if unicase::eq_ascii(&*key, "filename*") { let extended_value = try!(parse_extended_value(val)); DispositionParam::Filename(extended_value.charset, extended_value.language_tag, extended_value.value) } else { DispositionParam::Ext(key.to_owned(), val.trim_matches('"').to_owned()) } ); } Ok(cd) }) } #[inline] fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { f.fmt_line(self) } } impl fmt::Display for ContentDisposition { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.disposition { DispositionType::Inline => try!(write!(f, "inline")), DispositionType::Attachment => try!(write!(f, "attachment")), DispositionType::Ext(ref s) => try!(write!(f, "{}", s)), } for param in &self.parameters { match *param { DispositionParam::Filename(ref charset, ref opt_lang, ref bytes) => { let mut use_simple_format: bool = false; if opt_lang.is_none() { if let Charset::Ext(ref ext) = *charset { if unicase::eq_ascii(&**ext, "utf-8") { use_simple_format = true; } } } if use_simple_format { try!(write!(f, "; filename=\"{}\"", match String::from_utf8(bytes.clone()) { Ok(s) => s, Err(_) => return Err(fmt::Error), })); } else { try!(write!(f, "; filename*={}'", charset)); if let Some(ref lang) = *opt_lang { try!(write!(f, "{}", lang)); }; try!(write!(f, "'")); try!(http_percent_encode(f, bytes)) } }, DispositionParam::Ext(ref k, ref v) => try!(write!(f, "; {}=\"{}\"", k, v)), } } Ok(()) } } #[cfg(test)] mod tests { use super::{ContentDisposition,DispositionType,DispositionParam}; use ::Header; use ::shared::Charset; #[test] fn test_parse_header() { assert!(ContentDisposition::parse_header(&"".into()).is_err()); let a = "form-data; dummy=3; name=upload;\r\n filename=\"sample.png\"".into(); let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Ext("form-data".to_owned()), parameters: vec![ DispositionParam::Ext("dummy".to_owned(), "3".to_owned()), DispositionParam::Ext("name".to_owned(), "upload".to_owned()), DispositionParam::Filename( Charset::Ext("UTF-8".to_owned()), None, "sample.png".bytes().collect()) ] }; assert_eq!(a, b); let a = "attachment; filename=\"image.jpg\"".into(); let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ DispositionParam::Filename( Charset::Ext("UTF-8".to_owned()), None, "image.jpg".bytes().collect()) ] }; assert_eq!(a, b); let a = "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates".into(); let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let b = ContentDisposition { disposition: DispositionType::Attachment, parameters: vec![ DispositionParam::Filename( Charset::Ext("UTF-8".to_owned()), None, vec![0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a', b't', b'e', b's']) ] }; assert_eq!(a, b); } #[test] fn test_display() { let as_string = "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; let a = as_string.into(); let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!(as_string, display_rendered); let a = "attachment; filename*=UTF-8''black%20and%20white.csv".into(); let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"black and white.csv\"".to_owned(), display_rendered); let a = "attachment; filename=colourful.csv".into(); let a: ContentDisposition = ContentDisposition::parse_header(&a).unwrap(); let display_rendered = format!("{}",a); assert_eq!("attachment; filename=\"colourful.csv\"".to_owned(), display_rendered); } } */ headers-0.3.9/src/common/content_encoding.rs000064400000000000000000000041031046102023000172130ustar 00000000000000use self::sealed::AsCoding; use util::FlatCsv; use HeaderValue; /// `Content-Encoding` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.2.2) /// /// The `Content-Encoding` header field indicates what content codings /// have been applied to the representation, beyond those inherent in the /// media type, and thus what decoding mechanisms have to be applied in /// order to obtain data in the media type referenced by the Content-Type /// header field. Content-Encoding is primarily used to allow a /// representation's data to be compressed without losing the identity of /// its underlying media type. /// /// # ABNF /// /// ```text /// Content-Encoding = 1#content-coding /// ``` /// /// # Example values /// /// * `gzip` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::ContentEncoding; /// /// let content_enc = ContentEncoding::gzip(); /// ``` #[derive(Clone, Debug)] pub struct ContentEncoding(FlatCsv); derive_header! { ContentEncoding(_), name: CONTENT_ENCODING } impl ContentEncoding { /// A constructor to easily create a `Content-Encoding: gzip` header. #[inline] pub fn gzip() -> ContentEncoding { ContentEncoding(HeaderValue::from_static("gzip").into()) } /// Check if this header contains a given "coding". /// /// This can be used with these argument types: /// /// - `&str` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::ContentEncoding; /// /// let content_enc = ContentEncoding::gzip(); /// /// assert!(content_enc.contains("gzip")); /// assert!(!content_enc.contains("br")); /// ``` pub fn contains(&self, coding: impl AsCoding) -> bool { let s = coding.as_coding(); self.0.iter().find(|&opt| opt == s).is_some() } } mod sealed { pub trait AsCoding: Sealed {} pub trait Sealed { fn as_coding(&self) -> &str; } impl<'a> AsCoding for &'a str {} impl<'a> Sealed for &'a str { fn as_coding(&self) -> &str { *self } } } headers-0.3.9/src/common/content_length.rs000064400000000000000000000057211046102023000167150ustar 00000000000000use {Header, HeaderValue}; /// `Content-Length` header, defined in /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2) /// /// When a message does not have a `Transfer-Encoding` header field, a /// Content-Length header field can provide the anticipated size, as a /// decimal number of octets, for a potential payload body. For messages /// that do include a payload body, the Content-Length field-value /// provides the framing information necessary for determining where the /// body (and message) ends. For messages that do not include a payload /// body, the Content-Length indicates the size of the selected /// representation. /// /// Note that setting this header will *remove* any previously set /// `Transfer-Encoding` header, in accordance with /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2): /// /// > A sender MUST NOT send a Content-Length header field in any message /// > that contains a Transfer-Encoding header field. /// /// ## ABNF /// /// ```text /// Content-Length = 1*DIGIT /// ``` /// /// ## Example values /// /// * `3495` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::ContentLength; /// /// let len = ContentLength(1_000); /// ``` #[derive(Clone, Copy, Debug, PartialEq)] pub struct ContentLength(pub u64); impl Header for ContentLength { fn name() -> &'static ::http::header::HeaderName { &::http::header::CONTENT_LENGTH } fn decode<'i, I: Iterator>(values: &mut I) -> Result { // If multiple Content-Length headers were sent, everything can still // be alright if they all contain the same value, and all parse // correctly. If not, then it's an error. let mut len = None; for value in values { let parsed = value .to_str() .map_err(|_| ::Error::invalid())? .parse::() .map_err(|_| ::Error::invalid())?; if let Some(prev) = len { if prev != parsed { return Err(::Error::invalid()); } } else { len = Some(parsed); } } len.map(ContentLength).ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { values.extend(::std::iter::once(self.0.into())); } } /* __hyper__tm!(ContentLength, tests { // Testcase from RFC test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); test_header!(test_invalid, vec![b"34v95"], None); // Can't use the test_header macro because "5, 5" gets cleaned to "5". #[test] fn test_duplicates() { let parsed = HeaderField::parse_header(&vec![b"5".to_vec(), b"5".to_vec()].into()).unwrap(); assert_eq!(parsed, HeaderField(5)); assert_eq!(format!("{}", parsed), "5"); } test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None); }); */ headers-0.3.9/src/common/content_location.rs000064400000000000000000000026261046102023000172450ustar 00000000000000use HeaderValue; /// `Content-Location` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.4.2) /// /// The header can be used by both the client in requests and the server /// in responses with different semantics. Client sets `Content-Location` /// to refer to the URI where original representation of the body was /// obtained. /// /// In responses `Content-Location` represents URI for the representation /// that was content negotiated, created or for the response payload. /// /// # ABNF /// /// ```text /// Content-Location = absolute-URI / partial-URI /// ``` /// /// # Example values /// /// * `/hypertext/Overview.html` /// * `http://www.example.org/hypertext/Overview.html` /// /// # Examples /// #[derive(Clone, Debug, PartialEq)] pub struct ContentLocation(HeaderValue); derive_header! { ContentLocation(_), name: CONTENT_LOCATION } #[cfg(test)] mod tests { use super::super::test_decode; use super::*; #[test] fn absolute_uri() { let s = "http://www.example.net/index.html"; let loc = test_decode::(&[s]).unwrap(); assert_eq!(loc, ContentLocation(HeaderValue::from_static(s))); } #[test] fn relative_uri_with_fragment() { let s = "/People.html#tim"; let loc = test_decode::(&[s]).unwrap(); assert_eq!(loc, ContentLocation(HeaderValue::from_static(s))); } } headers-0.3.9/src/common/content_range.rs000064400000000000000000000155101046102023000165250ustar 00000000000000use std::fmt; use std::ops::{Bound, RangeBounds}; use {util, HeaderValue}; /// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) /// /// # ABNF /// /// ```text /// Content-Range = byte-content-range /// / other-content-range /// /// byte-content-range = bytes-unit SP /// ( byte-range-resp / unsatisfied-range ) /// /// byte-range-resp = byte-range "/" ( complete-length / "*" ) /// byte-range = first-byte-pos "-" last-byte-pos /// unsatisfied-range = "*/" complete-length /// /// complete-length = 1*DIGIT /// /// other-content-range = other-range-unit SP other-range-resp /// other-range-resp = *CHAR /// ``` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::ContentRange; /// /// // 100 bytes (included byte 199), with a full length of 3,400 /// let cr = ContentRange::bytes(100..200, 3400).unwrap(); /// ``` //NOTE: only supporting bytes-content-range, YAGNI the extension #[derive(Clone, Debug, PartialEq)] pub struct ContentRange { /// First and last bytes of the range, omitted if request could not be /// satisfied range: Option<(u64, u64)>, /// Total length of the instance, can be omitted if unknown complete_length: Option, } error_type!(InvalidContentRange); impl ContentRange { /// Construct a new `Content-Range: bytes ..` header. pub fn bytes( range: impl RangeBounds, complete_length: impl Into>, ) -> Result { let complete_length = complete_length.into(); let start = match range.start_bound() { Bound::Included(&s) => s, Bound::Excluded(&s) => s + 1, Bound::Unbounded => 0, }; let end = match range.end_bound() { Bound::Included(&e) => e, Bound::Excluded(&e) => e - 1, Bound::Unbounded => match complete_length { Some(max) => max - 1, None => return Err(InvalidContentRange { _inner: () }), }, }; Ok(ContentRange { range: Some((start, end)), complete_length, }) } /// Create a new `ContentRange` stating the range could not be satisfied. /// /// The passed argument is the complete length of the entity. pub fn unsatisfied_bytes(complete_length: u64) -> Self { ContentRange { range: None, complete_length: Some(complete_length), } } /// Get the byte range if satisified. /// /// Note that these byte ranges are inclusive on both ends. pub fn bytes_range(&self) -> Option<(u64, u64)> { self.range } /// Get the bytes complete length if available. pub fn bytes_len(&self) -> Option { self.complete_length } } impl ::Header for ContentRange { fn name() -> &'static ::HeaderName { &::http::header::CONTENT_RANGE } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .and_then(|v| v.to_str().ok()) .and_then(|s| split_in_two(s, ' ')) .and_then(|(unit, spec)| { if unit != "bytes" { // For now, this only supports bytes-content-range. nani? return None; } let (range, complete_length) = split_in_two(spec, '/')?; let complete_length = if complete_length == "*" { None } else { Some(complete_length.parse().ok()?) }; let range = if range == "*" { None } else { let (first_byte, last_byte) = split_in_two(range, '-')?; let first_byte = first_byte.parse().ok()?; let last_byte = last_byte.parse().ok()?; if last_byte < first_byte { return None; } Some((first_byte, last_byte)) }; Some(ContentRange { range, complete_length, }) }) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { struct Adapter<'a>(&'a ContentRange); impl<'a> fmt::Display for Adapter<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("bytes ")?; if let Some((first_byte, last_byte)) = self.0.range { write!(f, "{}-{}", first_byte, last_byte)?; } else { f.write_str("*")?; } f.write_str("/")?; if let Some(v) = self.0.complete_length { write!(f, "{}", v) } else { f.write_str("*") } } } values.extend(::std::iter::once(util::fmt(Adapter(self)))); } } fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { let mut iter = s.splitn(2, separator); match (iter.next(), iter.next()) { (Some(a), Some(b)) => Some((a, b)), _ => None, } } /* test_header!(test_bytes, vec![b"bytes 0-499/500"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), complete_length: Some(500) }))); test_header!(test_bytes_unknown_len, vec![b"bytes 0-499/*"], Some(ContentRange(ContentRangeSpec::Bytes { range: Some((0, 499)), complete_length: None }))); test_header!(test_bytes_unknown_range, vec![b"bytes */ 500"], Some(ContentRange(ContentRangeSpec::Bytes { range: None, complete_length: Some(500) }))); test_header!(test_unregistered, vec![b"seconds 1-2"], Some(ContentRange(ContentRangeSpec::Unregistered { unit: "seconds".to_owned(), resp: "1-2".to_owned() }))); test_header!(test_no_len, vec![b"bytes 0-499"], None::); test_header!(test_only_unit, vec![b"bytes"], None::); test_header!(test_end_less_than_start, vec![b"bytes 499-0/500"], None::); test_header!(test_blank, vec![b""], None::); test_header!(test_bytes_many_spaces, vec![b"bytes 1-2/500 3"], None::); test_header!(test_bytes_many_slashes, vec![b"bytes 1-2/500/600"], None::); test_header!(test_bytes_many_dashes, vec![b"bytes 1-2-3/500"], None::); */ headers-0.3.9/src/common/content_type.rs000064400000000000000000000115101046102023000164060ustar 00000000000000use std::fmt; use mime::{self, Mime}; /// `Content-Type` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) /// /// The `Content-Type` header field indicates the media type of the /// associated representation: either the representation enclosed in the /// message payload or the selected representation, as determined by the /// message semantics. The indicated media type defines both the data /// format and how that data is intended to be processed by a recipient, /// within the scope of the received message semantics, after any content /// codings indicated by Content-Encoding are decoded. /// /// Although the `mime` crate allows the mime options to be any slice, this crate /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If /// this is an issue, it's possible to implement `Header` on a custom struct. /// /// # ABNF /// /// ```text /// Content-Type = media-type /// ``` /// /// # Example values /// /// * `text/html; charset=utf-8` /// * `application/json` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::ContentType; /// /// let ct = ContentType::json(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct ContentType(Mime); impl ContentType { /// A constructor to easily create a `Content-Type: application/json` header. #[inline] pub fn json() -> ContentType { ContentType(mime::APPLICATION_JSON) } /// A constructor to easily create a `Content-Type: text/plain` header. #[inline] pub fn text() -> ContentType { ContentType(mime::TEXT_PLAIN) } /// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header. #[inline] pub fn text_utf8() -> ContentType { ContentType(mime::TEXT_PLAIN_UTF_8) } /// A constructor to easily create a `Content-Type: text/html` header. #[inline] pub fn html() -> ContentType { ContentType(mime::TEXT_HTML) } /// A constructor to easily create a `Content-Type: text/xml` header. #[inline] pub fn xml() -> ContentType { ContentType(mime::TEXT_XML) } /// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header. #[inline] pub fn form_url_encoded() -> ContentType { ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) } /// A constructor to easily create a `Content-Type: image/jpeg` header. #[inline] pub fn jpeg() -> ContentType { ContentType(mime::IMAGE_JPEG) } /// A constructor to easily create a `Content-Type: image/png` header. #[inline] pub fn png() -> ContentType { ContentType(mime::IMAGE_PNG) } /// A constructor to easily create a `Content-Type: application/octet-stream` header. #[inline] pub fn octet_stream() -> ContentType { ContentType(mime::APPLICATION_OCTET_STREAM) } } impl ::Header for ContentType { fn name() -> &'static ::HeaderName { &::http::header::CONTENT_TYPE } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .and_then(|v| v.to_str().ok()?.parse().ok()) .map(ContentType) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { let value = self .0 .as_ref() .parse() .expect("Mime is always a valid HeaderValue"); values.extend(::std::iter::once(value)); } } impl From for ContentType { fn from(m: mime::Mime) -> ContentType { ContentType(m) } } impl From for mime::Mime { fn from(ct: ContentType) -> mime::Mime { ct.0 } } impl fmt::Display for ContentType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl std::str::FromStr for ContentType { type Err = ::Error; fn from_str(s: &str) -> Result { s.parse::() .map(|m| m.into()) .map_err(|_| ::Error::invalid()) } } #[cfg(test)] mod tests { use super::super::test_decode; use super::ContentType; #[test] fn json() { assert_eq!( test_decode::(&["application/json"]), Some(ContentType::json()), ); } #[test] fn from_str() { assert_eq!( "application/json".parse::().unwrap(), ContentType::json(), ); assert!("invalid-mimetype".parse::().is_err()); } bench_header!(bench_plain, ContentType, "text/plain"); bench_header!(bench_json, ContentType, "application/json"); bench_header!( bench_formdata, ContentType, "multipart/form-data; boundary=---------------abcd" ); } headers-0.3.9/src/common/cookie.rs000064400000000000000000000135511046102023000151530ustar 00000000000000use util::{FlatCsv, SemiColon}; /// `Cookie` header, defined in [RFC6265](http://tools.ietf.org/html/rfc6265#section-5.4) /// /// If the user agent does attach a Cookie header field to an HTTP /// request, the user agent must send the cookie-string /// as the value of the header field. /// /// When the user agent generates an HTTP request, the user agent MUST NOT /// attach more than one Cookie header field. /// /// # Example values /// * `SID=31d4d96e407aad42` /// * `SID=31d4d96e407aad42; lang=en-US` /// #[derive(Clone, Debug)] pub struct Cookie(FlatCsv); derive_header! { Cookie(_), name: COOKIE } impl Cookie { /// Lookup a value for a cookie name. /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::{Cookie, HeaderMap, HeaderMapExt, HeaderValue}; /// /// // Setup the header map with strings... /// let mut headers = HeaderMap::new(); /// headers.insert("cookie", HeaderValue::from_static("lang=en-US")); /// /// // Parse a `Cookie` so we can play with it... /// let cookie = headers /// .typed_get::() /// .expect("we just inserted a valid Cookie"); /// /// assert_eq!(cookie.get("lang"), Some("en-US")); /// assert_eq!(cookie.get("SID"), None); /// ``` pub fn get(&self, name: &str) -> Option<&str> { self.iter() .find(|&(key, _)| key == name) .map(|(_, val)| val) } /// Get the number of key-value pairs this `Cookie` contains. pub fn len(&self) -> usize { self.iter().count() } /// Iterator the key-value pairs of this `Cookie` header. pub fn iter(&self) -> impl Iterator { self.0.iter().filter_map(|kv| { let mut iter = kv.splitn(2, '='); let key = iter.next()?.trim(); let val = iter.next()?.trim(); Some((key, val)) }) } } /* impl PartialEq for Cookie { fn eq(&self, other: &Cookie) -> bool { if self.0.len() == other.0.len() { for &(ref k, ref v) in self.0.iter() { if other.get(k) != Some(v) { return false; } } true } else { false } } } */ #[cfg(test)] mod tests { use super::super::test_decode; use super::Cookie; #[test] fn test_parse() { let cookie = test_decode::(&["foo=bar"]).unwrap(); assert_eq!(cookie.get("foo"), Some("bar")); assert_eq!(cookie.get("bar"), None); } #[test] fn test_multipe_same_name() { let cookie = test_decode::(&["foo=bar; foo=baz"]).unwrap(); assert_eq!(cookie.get("foo"), Some("bar")); } #[test] fn test_multipe_lines() { let cookie = test_decode::(&["foo=bar", "lol = cat"]).unwrap(); assert_eq!(cookie.get("foo"), Some("bar")); assert_eq!(cookie.get("lol"), Some("cat")); } /* #[test] fn test_set_and_get() { let mut cookie = Cookie::new(); cookie.append("foo", "bar"); cookie.append(String::from("dyn"), String::from("amic")); assert_eq!(cookie.get("foo"), Some("bar")); assert_eq!(cookie.get("dyn"), Some("amic")); assert!(cookie.get("nope").is_none()); cookie.append("foo", "notbar"); assert_eq!(cookie.get("foo"), Some("bar")); cookie.set("foo", "hi"); assert_eq!(cookie.get("foo"), Some("hi")); assert_eq!(cookie.get("dyn"), Some("amic")); } #[test] fn test_eq() { let mut cookie = Cookie::new(); let mut cookie2 = Cookie::new(); // empty is equal assert_eq!(cookie, cookie2); // left has more params cookie.append("foo", "bar"); assert_ne!(cookie, cookie2); // same len, different params cookie2.append("bar", "foo"); assert_ne!(cookie, cookie2); // right has more params, and matching KV cookie2.append("foo", "bar"); assert_ne!(cookie, cookie2); // same params, different order cookie.append("bar", "foo"); assert_eq!(cookie, cookie2); } #[test] fn test_parse() { let mut cookie = Cookie::new(); let parsed = Cookie::parse_header(&b"foo=bar".to_vec().into()).unwrap(); cookie.append("foo", "bar"); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&b"foo=bar;".to_vec().into()).unwrap(); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&b"foo=bar; baz=quux".to_vec().into()).unwrap(); cookie.append("baz", "quux"); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&b"foo=bar;; baz=quux".to_vec().into()).unwrap(); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&b"foo=bar; invalid ; bad; ;; baz=quux".to_vec().into()) .unwrap(); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&b" foo = bar;baz= quux ".to_vec().into()).unwrap(); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&vec![b"foo = bar".to_vec(), b"baz= quux ".to_vec()].into()) .unwrap(); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&b"foo=bar; baz=quux ; empty=".to_vec().into()).unwrap(); cookie.append("empty", ""); assert_eq!(cookie, parsed); let mut cookie = Cookie::new(); let parsed = Cookie::parse_header(&b"middle=equals=in=the=middle".to_vec().into()).unwrap(); cookie.append("middle", "equals=in=the=middle"); assert_eq!(cookie, parsed); let parsed = Cookie::parse_header(&b"middle=equals=in=the=middle; double==2".to_vec().into()) .unwrap(); cookie.append("double", "=2"); assert_eq!(cookie, parsed); } */ } headers-0.3.9/src/common/date.rs000064400000000000000000000016001046102023000146070ustar 00000000000000use std::time::SystemTime; use util::HttpDate; /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) /// /// The `Date` header field represents the date and time at which the /// message was originated. /// /// ## ABNF /// /// ```text /// Date = HTTP-date /// ``` /// /// ## Example values /// /// * `Tue, 15 Nov 1994 08:12:31 GMT` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::Date; /// use std::time::SystemTime; /// /// let date = Date::from(SystemTime::now()); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Date(HttpDate); derive_header! { Date(_), name: DATE } impl From for Date { fn from(time: SystemTime) -> Date { Date(time.into()) } } impl From for SystemTime { fn from(date: Date) -> SystemTime { date.0.into() } } headers-0.3.9/src/common/etag.rs000064400000000000000000000060731046102023000146230ustar 00000000000000use std::str::FromStr; use util::EntityTag; /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) /// /// The `ETag` header field in a response provides the current entity-tag /// for the selected representation, as determined at the conclusion of /// handling the request. An entity-tag is an opaque validator for /// differentiating between multiple representations of the same /// resource, regardless of whether those multiple representations are /// due to resource state changes over time, content negotiation /// resulting in multiple representations being valid at the same time, /// or both. An entity-tag consists of an opaque quoted string, possibly /// prefixed by a weakness indicator. /// /// # ABNF /// /// ```text /// ETag = entity-tag /// ``` /// /// # Example values /// /// * `"xyzzy"` /// * `W/"xyzzy"` /// * `""` /// /// # Examples /// /// ``` /// let etag = "\"xyzzy\"".parse::().unwrap(); /// ``` #[derive(Clone, Debug, PartialEq, Eq)] pub struct ETag(pub(super) EntityTag); derive_header! { ETag(_), name: ETAG } impl ETag { #[cfg(test)] pub(crate) fn from_static(src: &'static str) -> ETag { ETag(EntityTag::from_static(src)) } } error_type!(InvalidETag); impl FromStr for ETag { type Err = InvalidETag; fn from_str(src: &str) -> Result { let val = src.parse().map_err(|_| InvalidETag { _inner: () })?; EntityTag::from_owned(val) .map(ETag) .ok_or_else(|| InvalidETag { _inner: () }) } } /* test_etag { // From the RFC test_header!(test1, vec![b"\"xyzzy\""], Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); test_header!(test2, vec![b"W/\"xyzzy\""], Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); test_header!(test3, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); // Own tests test_header!(test4, vec![b"\"foobar\""], Some(ETag(EntityTag::new(false, "foobar".to_owned())))); test_header!(test5, vec![b"\"\""], Some(ETag(EntityTag::new(false, "".to_owned())))); test_header!(test6, vec![b"W/\"weak-etag\""], Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); test_header!(test7, vec![b"W/\"\x65\x62\""], Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); test_header!(test8, vec![b"W/\"\""], Some(ETag(EntityTag::new(true, "".to_owned())))); test_header!(test9, vec![b"no-dquotes"], None::); test_header!(test10, vec![b"w/\"the-first-w-is-case-sensitive\""], None::); test_header!(test11, vec![b""], None::); test_header!(test12, vec![b"\"unmatched-dquotes1"], None::); test_header!(test13, vec![b"unmatched-dquotes2\""], None::); test_header!(test14, vec![b"matched-\"dquotes\""], None::); test_header!(test15, vec![b"\""], None::); } */ headers-0.3.9/src/common/expect.rs000064400000000000000000000037371046102023000151770ustar 00000000000000use std::fmt; use util::IterExt; /// The `Expect` header. /// /// > The "Expect" header field in a request indicates a certain set of /// > behaviors (expectations) that need to be supported by the server in /// > order to properly handle this request. The only such expectation /// > defined by this specification is 100-continue. /// > /// > Expect = "100-continue" /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::Expect; /// /// let expect = Expect::CONTINUE; /// ``` #[derive(Clone, PartialEq)] pub struct Expect(()); impl Expect { /// "100-continue" pub const CONTINUE: Expect = Expect(()); } impl ::Header for Expect { fn name() -> &'static ::HeaderName { &::http::header::EXPECT } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .just_one() .and_then(|value| { if value == "100-continue" { Some(Expect::CONTINUE) } else { None } }) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { values.extend(::std::iter::once(::HeaderValue::from_static( "100-continue", ))); } } impl fmt::Debug for Expect { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Expect").field(&"100-continue").finish() } } #[cfg(test)] mod tests { use super::super::test_decode; use super::Expect; #[test] fn expect_continue() { assert_eq!( test_decode::(&["100-continue"]), Some(Expect::CONTINUE), ); } #[test] fn expectation_failed() { assert_eq!(test_decode::(&["sandwich"]), None,); } #[test] fn too_many_values() { assert_eq!( test_decode::(&["100-continue", "100-continue"]), None, ); } } headers-0.3.9/src/common/expires.rs000064400000000000000000000021761046102023000153620ustar 00000000000000use std::time::SystemTime; use util::HttpDate; /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) /// /// The `Expires` header field gives the date/time after which the /// response is considered stale. /// /// The presence of an Expires field does not imply that the original /// resource will change or cease to exist at, before, or after that /// time. /// /// # ABNF /// /// ```text /// Expires = HTTP-date /// ``` /// /// # Example values /// * `Thu, 01 Dec 1994 16:00:00 GMT` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::Expires; /// use std::time::{SystemTime, Duration}; /// /// let time = SystemTime::now() + Duration::from_secs(60 * 60 * 24); /// let expires = Expires::from(time); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Expires(HttpDate); derive_header! { Expires(_), name: EXPIRES } impl From for Expires { fn from(time: SystemTime) -> Expires { Expires(time.into()) } } impl From for SystemTime { fn from(date: Expires) -> SystemTime { date.0.into() } } headers-0.3.9/src/common/host.rs000064400000000000000000000024471046102023000146610ustar 00000000000000use std::convert::TryFrom; use std::fmt; use http::uri::Authority; /// The `Host` header. #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd)] pub struct Host(Authority); impl Host { /// Get the hostname, such as example.domain. pub fn hostname(&self) -> &str { self.0.host() } /// Get the optional port number. pub fn port(&self) -> Option { self.0.port_u16() } } impl ::Header for Host { fn name() -> &'static ::HeaderName { &::http::header::HOST } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .cloned() .and_then(|val| Authority::try_from(val.as_bytes()).ok()) .map(Host) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { let bytes = self.0.as_str().as_bytes(); let val = ::HeaderValue::from_bytes(bytes).expect("Authority is a valid HeaderValue"); values.extend(::std::iter::once(val)); } } impl From for Host { fn from(auth: Authority) -> Host { Host(auth) } } impl fmt::Display for Host { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } headers-0.3.9/src/common/if_match.rs000064400000000000000000000054041046102023000154520ustar 00000000000000use super::ETag; use util::EntityTagRange; use HeaderValue; /// `If-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) /// /// The `If-Match` header field makes the request method conditional on /// the recipient origin server either having at least one current /// representation of the target resource, when the field-value is "*", /// or having a current representation of the target resource that has an /// entity-tag matching a member of the list of entity-tags provided in /// the field-value. /// /// An origin server MUST use the strong comparison function when /// comparing entity-tags for `If-Match`, since the client /// intends this precondition to prevent the method from being applied if /// there have been any changes to the representation data. /// /// # ABNF /// /// ```text /// If-Match = "*" / 1#entity-tag /// ``` /// /// # Example values /// /// * `"xyzzy"` /// * "xyzzy", "r2d2xxxx", "c3piozzzz" /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::IfMatch; /// /// let if_match = IfMatch::any(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct IfMatch(EntityTagRange); derive_header! { IfMatch(_), name: IF_MATCH } impl IfMatch { /// Create a new `If-Match: *` header. pub fn any() -> IfMatch { IfMatch(EntityTagRange::Any) } /// Returns whether this is `If-Match: *`, matching any entity tag. pub fn is_any(&self) -> bool { match self.0 { EntityTagRange::Any => true, EntityTagRange::Tags(..) => false, } } /// Checks whether the `ETag` strongly matches. pub fn precondition_passes(&self, etag: &ETag) -> bool { self.0.matches_strong(&etag.0) } } impl From for IfMatch { fn from(etag: ETag) -> IfMatch { IfMatch(EntityTagRange::Tags(HeaderValue::from(etag.0).into())) } } #[cfg(test)] mod tests { use super::*; #[test] fn is_any() { assert!(IfMatch::any().is_any()); assert!(!IfMatch::from(ETag::from_static("\"yolo\"")).is_any()); } #[test] fn precondition_fails() { let if_match = IfMatch::from(ETag::from_static("\"foo\"")); let bar = ETag::from_static("\"bar\""); let weak_foo = ETag::from_static("W/\"foo\""); assert!(!if_match.precondition_passes(&bar)); assert!(!if_match.precondition_passes(&weak_foo)); } #[test] fn precondition_passes() { let foo = ETag::from_static("\"foo\""); let if_match = IfMatch::from(foo.clone()); assert!(if_match.precondition_passes(&foo)); } #[test] fn precondition_any() { let foo = ETag::from_static("\"foo\""); let if_match = IfMatch::any(); assert!(if_match.precondition_passes(&foo)); } } headers-0.3.9/src/common/if_modified_since.rs000064400000000000000000000036511046102023000173210ustar 00000000000000use std::time::SystemTime; use util::HttpDate; /// `If-Modified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) /// /// The `If-Modified-Since` header field makes a GET or HEAD request /// method conditional on the selected representation's modification date /// being more recent than the date provided in the field-value. /// Transfer of the selected representation's data is avoided if that /// data has not changed. /// /// # ABNF /// /// ```text /// If-Modified-Since = HTTP-date /// ``` /// /// # Example values /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::IfModifiedSince; /// use std::time::{Duration, SystemTime}; /// /// let time = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let if_mod = IfModifiedSince::from(time); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct IfModifiedSince(HttpDate); derive_header! { IfModifiedSince(_), name: IF_MODIFIED_SINCE } impl IfModifiedSince { /// Check if the supplied time means the resource has been modified. pub fn is_modified(&self, last_modified: SystemTime) -> bool { self.0 < last_modified.into() } } impl From for IfModifiedSince { fn from(time: SystemTime) -> IfModifiedSince { IfModifiedSince(time.into()) } } impl From for SystemTime { fn from(date: IfModifiedSince) -> SystemTime { date.0.into() } } #[cfg(test)] mod tests { use super::*; use std::time::Duration; #[test] fn is_modified() { let newer = SystemTime::now(); let exact = newer - Duration::from_secs(2); let older = newer - Duration::from_secs(4); let if_mod = IfModifiedSince::from(exact); assert!(if_mod.is_modified(newer)); assert!(!if_mod.is_modified(exact)); assert!(!if_mod.is_modified(older)); } } headers-0.3.9/src/common/if_none_match.rs000064400000000000000000000056171046102023000164770ustar 00000000000000use super::ETag; use util::EntityTagRange; use HeaderValue; /// `If-None-Match` header, defined in /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) /// /// The `If-None-Match` header field makes the request method conditional /// on a recipient cache or origin server either not having any current /// representation of the target resource, when the field-value is "*", /// or having a selected representation with an entity-tag that does not /// match any of those listed in the field-value. /// /// A recipient MUST use the weak comparison function when comparing /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags /// can be used for cache validation even if there have been changes to /// the representation data. /// /// # ABNF /// /// ```text /// If-None-Match = "*" / 1#entity-tag /// ``` /// /// # Example values /// /// * `"xyzzy"` /// * `W/"xyzzy"` /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` /// * `*` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::IfNoneMatch; /// /// let if_none_match = IfNoneMatch::any(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct IfNoneMatch(EntityTagRange); derive_header! { IfNoneMatch(_), name: IF_NONE_MATCH } impl IfNoneMatch { /// Create a new `If-None-Match: *` header. pub fn any() -> IfNoneMatch { IfNoneMatch(EntityTagRange::Any) } /// Checks whether the ETag passes this precondition. pub fn precondition_passes(&self, etag: &ETag) -> bool { !self.0.matches_weak(&etag.0) } } impl From for IfNoneMatch { fn from(etag: ETag) -> IfNoneMatch { IfNoneMatch(EntityTagRange::Tags(HeaderValue::from(etag.0).into())) } } /* test_if_none_match { test_header!(test1, vec![b"\"xyzzy\""]); test_header!(test2, vec![b"W/\"xyzzy\""]); test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); test_header!(test5, vec![b"*"]); } */ #[cfg(test)] mod tests { use super::*; #[test] fn precondition_fails() { let foo = ETag::from_static("\"foo\""); let weak_foo = ETag::from_static("W/\"foo\""); let if_none = IfNoneMatch::from(foo.clone()); assert!(!if_none.precondition_passes(&foo)); assert!(!if_none.precondition_passes(&weak_foo)); } #[test] fn precondition_passes() { let if_none = IfNoneMatch::from(ETag::from_static("\"foo\"")); let bar = ETag::from_static("\"bar\""); let weak_bar = ETag::from_static("W/\"bar\""); assert!(if_none.precondition_passes(&bar)); assert!(if_none.precondition_passes(&weak_bar)); } #[test] fn precondition_any() { let foo = ETag::from_static("\"foo\""); let if_none = IfNoneMatch::any(); assert!(!if_none.precondition_passes(&foo)); } } headers-0.3.9/src/common/if_range.rs000064400000000000000000000075071046102023000154600ustar 00000000000000use std::time::SystemTime; use super::{ETag, LastModified}; use util::{EntityTag, HttpDate}; use HeaderValue; /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) /// /// If a client has a partial copy of a representation and wishes to have /// an up-to-date copy of the entire representation, it could use the /// Range header field with a conditional GET (using either or both of /// If-Unmodified-Since and If-Match.) However, if the precondition /// fails because the representation has been modified, the client would /// then have to make a second request to obtain the entire current /// representation. /// /// The `If-Range` header field allows a client to \"short-circuit\" the /// second request. Informally, its meaning is as follows: if the /// representation is unchanged, send me the part(s) that I am requesting /// in Range; otherwise, send me the entire representation. /// /// # ABNF /// /// ```text /// If-Range = entity-tag / HTTP-date /// ``` /// /// # Example values /// /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// * `\"xyzzy\"` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::IfRange; /// use std::time::{SystemTime, Duration}; /// /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let if_range = IfRange::date(fetched); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct IfRange(IfRange_); derive_header! { IfRange(_), name: IF_RANGE } impl IfRange { /// Create an `IfRange` header with an entity tag. pub fn etag(tag: ETag) -> IfRange { IfRange(IfRange_::EntityTag(tag.0)) } /// Create an `IfRange` header with a date value. pub fn date(time: SystemTime) -> IfRange { IfRange(IfRange_::Date(time.into())) } /// Checks if the resource has been modified, or if the range request /// can be served. pub fn is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool { match self.0 { IfRange_::Date(since) => last_modified.map(|time| since < time.0).unwrap_or(true), IfRange_::EntityTag(ref entity) => { etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true) } } } } #[derive(Clone, Debug, PartialEq)] enum IfRange_ { /// The entity-tag the client has of the resource EntityTag(EntityTag), /// The date when the client retrieved the resource Date(HttpDate), } impl ::util::TryFromValues for IfRange_ { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .next() .and_then(|val| { if let Some(tag) = EntityTag::from_val(val) { return Some(IfRange_::EntityTag(tag)); } let date = HttpDate::from_val(val)?; Some(IfRange_::Date(date)) }) .ok_or_else(::Error::invalid) } } impl<'a> From<&'a IfRange_> for HeaderValue { fn from(if_range: &'a IfRange_) -> HeaderValue { match *if_range { IfRange_::EntityTag(ref tag) => tag.into(), IfRange_::Date(ref date) => date.into(), } } } /* #[cfg(test)] mod tests { use std::str; use *; use super::IfRange as HeaderField; test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); test_header!(test2, vec![b"\"xyzzy\""]); test_header!(test3, vec![b"this-is-invalid"], None::); } */ #[cfg(test)] mod tests { use super::*; #[test] fn test_is_modified_etag() { let etag = ETag::from_static("\"xyzzy\""); let if_range = IfRange::etag(etag.clone()); assert!(!if_range.is_modified(Some(&etag), None)); let etag = ETag::from_static("W/\"xyzzy\""); assert!(if_range.is_modified(Some(&etag), None)); } } headers-0.3.9/src/common/if_unmodified_since.rs000064400000000000000000000040421046102023000176570ustar 00000000000000use std::time::SystemTime; use util::HttpDate; /// `If-Unmodified-Since` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) /// /// The `If-Unmodified-Since` header field makes the request method /// conditional on the selected representation's last modification date /// being earlier than or equal to the date provided in the field-value. /// This field accomplishes the same purpose as If-Match for cases where /// the user agent does not have an entity-tag for the representation. /// /// # ABNF /// /// ```text /// If-Unmodified-Since = HTTP-date /// ``` /// /// # Example values /// /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::IfUnmodifiedSince; /// use std::time::{SystemTime, Duration}; /// /// let time = SystemTime::now() - Duration::from_secs(60 * 60 * 24); /// let if_unmod = IfUnmodifiedSince::from(time); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct IfUnmodifiedSince(HttpDate); derive_header! { IfUnmodifiedSince(_), name: IF_UNMODIFIED_SINCE } impl IfUnmodifiedSince { /// Check if the supplied time passes the precondtion. pub fn precondition_passes(&self, last_modified: SystemTime) -> bool { self.0 >= last_modified.into() } } impl From for IfUnmodifiedSince { fn from(time: SystemTime) -> IfUnmodifiedSince { IfUnmodifiedSince(time.into()) } } impl From for SystemTime { fn from(date: IfUnmodifiedSince) -> SystemTime { date.0.into() } } #[cfg(test)] mod tests { use super::*; use std::time::Duration; #[test] fn precondition_passes() { let newer = SystemTime::now(); let exact = newer - Duration::from_secs(2); let older = newer - Duration::from_secs(4); let if_unmod = IfUnmodifiedSince::from(exact); assert!(!if_unmod.precondition_passes(newer)); assert!(if_unmod.precondition_passes(exact)); assert!(if_unmod.precondition_passes(older)); } } headers-0.3.9/src/common/last_modified.rs000064400000000000000000000023031046102023000164760ustar 00000000000000use std::time::SystemTime; use util::HttpDate; /// `Last-Modified` header, defined in /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) /// /// The `Last-Modified` header field in a response provides a timestamp /// indicating the date and time at which the origin server believes the /// selected representation was last modified, as determined at the /// conclusion of handling the request. /// /// # ABNF /// /// ```text /// Expires = HTTP-date /// ``` /// /// # Example values /// /// * `Sat, 29 Oct 1994 19:43:31 GMT` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::LastModified; /// use std::time::{Duration, SystemTime}; /// /// let modified = LastModified::from( /// SystemTime::now() - Duration::from_secs(60 * 60 * 24) /// ); /// ``` #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct LastModified(pub(super) HttpDate); derive_header! { LastModified(_), name: LAST_MODIFIED } impl From for LastModified { fn from(time: SystemTime) -> LastModified { LastModified(time.into()) } } impl From for SystemTime { fn from(date: LastModified) -> SystemTime { date.0.into() } } headers-0.3.9/src/common/location.rs000064400000000000000000000022301046102023000155020ustar 00000000000000use HeaderValue; /// `Location` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.2) /// /// The `Location` header field is used in some responses to refer to a /// specific resource in relation to the response. The type of /// relationship is defined by the combination of request method and /// status code semantics. /// /// # ABNF /// /// ```text /// Location = URI-reference /// ``` /// /// # Example values /// * `/People.html#tim` /// * `http://www.example.net/index.html` /// /// # Examples /// #[derive(Clone, Debug, PartialEq)] pub struct Location(HeaderValue); derive_header! { Location(_), name: LOCATION } #[cfg(test)] mod tests { use super::super::test_decode; use super::*; #[test] fn absolute_uri() { let s = "http://www.example.net/index.html"; let loc = test_decode::(&[s]).unwrap(); assert_eq!(loc, Location(HeaderValue::from_static(s))); } #[test] fn relative_uri_with_fragment() { let s = "/People.html#tim"; let loc = test_decode::(&[s]).unwrap(); assert_eq!(loc, Location(HeaderValue::from_static(s))); } } headers-0.3.9/src/common/mod.rs000064400000000000000000000134421046102023000144600ustar 00000000000000//! A Collection of Header implementations for common HTTP Headers. //! //! ## Mime //! //! Several header fields use MIME values for their contents. Keeping with the //! strongly-typed theme, the [mime](https://docs.rs/mime) crate //! is used, such as `ContentType(pub Mime)`. //pub use self::accept_charset::AcceptCharset; //pub use self::accept_encoding::AcceptEncoding; //pub use self::accept_language::AcceptLanguage; pub use self::accept_ranges::AcceptRanges; //pub use self::accept::Accept; pub use self::access_control_allow_credentials::AccessControlAllowCredentials; pub use self::access_control_allow_headers::AccessControlAllowHeaders; pub use self::access_control_allow_methods::AccessControlAllowMethods; pub use self::access_control_allow_origin::AccessControlAllowOrigin; pub use self::access_control_expose_headers::AccessControlExposeHeaders; pub use self::access_control_max_age::AccessControlMaxAge; pub use self::access_control_request_headers::AccessControlRequestHeaders; pub use self::access_control_request_method::AccessControlRequestMethod; pub use self::age::Age; pub use self::allow::Allow; pub use self::authorization::Authorization; pub use self::cache_control::CacheControl; pub use self::connection::Connection; pub use self::content_disposition::ContentDisposition; pub use self::content_encoding::ContentEncoding; //pub use self::content_language::ContentLanguage; pub use self::content_length::ContentLength; pub use self::content_location::ContentLocation; pub use self::content_range::ContentRange; pub use self::content_type::ContentType; pub use self::cookie::Cookie; pub use self::date::Date; pub use self::etag::ETag; pub use self::expect::Expect; pub use self::expires::Expires; //pub use self::from::From; pub use self::host::Host; pub use self::if_match::IfMatch; pub use self::if_modified_since::IfModifiedSince; pub use self::if_none_match::IfNoneMatch; pub use self::if_range::IfRange; pub use self::if_unmodified_since::IfUnmodifiedSince; //pub use self::last_event_id::LastEventId; pub use self::last_modified::LastModified; //pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; pub use self::location::Location; pub use self::origin::Origin; pub use self::pragma::Pragma; //pub use self::prefer::{Prefer, Preference}; //pub use self::preference_applied::PreferenceApplied; pub use self::proxy_authorization::ProxyAuthorization; pub use self::range::Range; pub use self::referer::Referer; pub use self::referrer_policy::ReferrerPolicy; pub use self::retry_after::RetryAfter; pub use self::sec_websocket_accept::SecWebsocketAccept; pub use self::sec_websocket_key::SecWebsocketKey; pub use self::sec_websocket_version::SecWebsocketVersion; pub use self::server::Server; pub use self::set_cookie::SetCookie; pub use self::strict_transport_security::StrictTransportSecurity; pub use self::te::Te; pub use self::transfer_encoding::TransferEncoding; pub use self::upgrade::Upgrade; pub use self::user_agent::UserAgent; pub use self::vary::Vary; //pub use self::warning::Warning; #[cfg(test)] fn test_decode(values: &[&str]) -> Option { use HeaderMapExt; let mut map = ::http::HeaderMap::new(); for val in values { map.append(T::name(), val.parse().unwrap()); } map.typed_get() } #[cfg(test)] fn test_encode(header: T) -> ::http::HeaderMap { use HeaderMapExt; let mut map = ::http::HeaderMap::new(); map.typed_insert(header); map } #[cfg(test)] macro_rules! bench_header { ($mod:ident, $ty:ident, $value:expr) => { #[cfg(feature = "nightly")] mod $mod { use super::$ty; use HeaderMapExt; #[bench] fn bench_decode(b: &mut ::test::Bencher) { let mut map = ::http::HeaderMap::new(); map.append( <$ty as ::Header>::name(), $value.parse().expect("HeaderValue::from_str($value)"), ); b.bytes = $value.len() as u64; b.iter(|| { map.typed_get::<$ty>().unwrap(); }); } #[bench] fn bench_encode(b: &mut ::test::Bencher) { let mut map = ::http::HeaderMap::new(); map.append( <$ty as ::Header>::name(), $value.parse().expect("HeaderValue::from_str($value)"), ); let typed = map.typed_get::<$ty>().unwrap(); b.bytes = $value.len() as u64; b.iter(|| { map.typed_insert(typed.clone()); map.clear(); }); } } }; } //mod accept; //mod accept_charset; //mod accept_encoding; //mod accept_language; mod accept_ranges; mod access_control_allow_credentials; mod access_control_allow_headers; mod access_control_allow_methods; mod access_control_allow_origin; mod access_control_expose_headers; mod access_control_max_age; mod access_control_request_headers; mod access_control_request_method; mod age; mod allow; pub mod authorization; mod cache_control; mod connection; mod content_disposition; mod content_encoding; //mod content_language; mod content_length; mod content_location; mod content_range; mod content_type; mod cookie; mod date; mod etag; mod expect; mod expires; //mod from; mod host; mod if_match; mod if_modified_since; mod if_none_match; mod if_range; mod if_unmodified_since; //mod last_event_id; mod last_modified; //mod link; mod location; mod origin; mod pragma; //mod prefer; //mod preference_applied; mod proxy_authorization; mod range; mod referer; mod referrer_policy; mod retry_after; mod sec_websocket_accept; mod sec_websocket_key; mod sec_websocket_version; mod server; mod set_cookie; mod strict_transport_security; mod te; mod transfer_encoding; mod upgrade; mod user_agent; mod vary; //mod warning; headers-0.3.9/src/common/origin.rs000064400000000000000000000135621046102023000151730ustar 00000000000000use std::convert::TryFrom; use std::fmt; use bytes::Bytes; use http::uri::{self, Authority, Scheme, Uri}; use util::{IterExt, TryFromValues}; use HeaderValue; /// The `Origin` header. /// /// The `Origin` header is a version of the `Referer` header that is used for all HTTP fetches and `POST`s whose CORS flag is set. /// This header is often used to inform recipients of the security context of where the request was initiated. /// /// Following the spec, [https://fetch.spec.whatwg.org/#origin-header][url], the value of this header is composed of /// a String (scheme), Host (host/port) /// /// [url]: https://fetch.spec.whatwg.org/#origin-header /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::Origin; /// /// let origin = Origin::NULL; /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Origin(OriginOrNull); derive_header! { Origin(_), name: ORIGIN } #[derive(Clone, Debug, PartialEq, Eq, Hash)] enum OriginOrNull { Origin(Scheme, Authority), Null, } impl Origin { /// The literal `null` Origin header. pub const NULL: Origin = Origin(OriginOrNull::Null); /// Checks if `Origin` is `null`. #[inline] pub fn is_null(&self) -> bool { match self.0 { OriginOrNull::Null => true, _ => false, } } /// Get the "scheme" part of this origin. #[inline] pub fn scheme(&self) -> &str { match self.0 { OriginOrNull::Origin(ref scheme, _) => scheme.as_str(), OriginOrNull::Null => "", } } /// Get the "hostname" part of this origin. #[inline] pub fn hostname(&self) -> &str { match self.0 { OriginOrNull::Origin(_, ref auth) => auth.host(), OriginOrNull::Null => "", } } /// Get the "port" part of this origin. #[inline] pub fn port(&self) -> Option { match self.0 { OriginOrNull::Origin(_, ref auth) => auth.port_u16(), OriginOrNull::Null => None, } } /// Tries to build a `Origin` from three parts, the scheme, the host and an optional port. pub fn try_from_parts( scheme: &str, host: &str, port: impl Into>, ) -> Result { struct MaybePort(Option); impl fmt::Display for MaybePort { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(port) = self.0 { write!(f, ":{}", port) } else { Ok(()) } } } let bytes = Bytes::from(format!("{}://{}{}", scheme, host, MaybePort(port.into()))); HeaderValue::from_maybe_shared(bytes) .ok() .and_then(|val| Self::try_from_value(&val)) .ok_or_else(|| InvalidOrigin { _inner: () }) } // Used in AccessControlAllowOrigin pub(super) fn try_from_value(value: &HeaderValue) -> Option { OriginOrNull::try_from_value(value).map(Origin) } pub(super) fn into_value(&self) -> HeaderValue { (&self.0).into() } } impl fmt::Display for Origin { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.0 { OriginOrNull::Origin(ref scheme, ref auth) => write!(f, "{}://{}", scheme, auth), OriginOrNull::Null => f.write_str("null"), } } } error_type!(InvalidOrigin); impl OriginOrNull { fn try_from_value(value: &HeaderValue) -> Option { if value == "null" { return Some(OriginOrNull::Null); } let uri = Uri::try_from(value.as_bytes()).ok()?; let (scheme, auth) = match uri.into_parts() { uri::Parts { scheme: Some(scheme), authority: Some(auth), path_and_query: None, .. } => (scheme, auth), uri::Parts { scheme: Some(ref scheme), authority: Some(ref auth), path_and_query: Some(ref p), .. } if p == "/" => (scheme.clone(), auth.clone()), _ => { return None; } }; Some(OriginOrNull::Origin(scheme, auth)) } } impl TryFromValues for OriginOrNull { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .just_one() .and_then(OriginOrNull::try_from_value) .ok_or_else(::Error::invalid) } } impl<'a> From<&'a OriginOrNull> for HeaderValue { fn from(origin: &'a OriginOrNull) -> HeaderValue { match origin { OriginOrNull::Origin(ref scheme, ref auth) => { let s = format!("{}://{}", scheme, auth); let bytes = Bytes::from(s); HeaderValue::from_maybe_shared(bytes) .expect("Scheme and Authority are valid header values") } // Serialized as "null" per ASCII serialization of an origin // https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin OriginOrNull::Null => HeaderValue::from_static("null"), } } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn origin() { let s = "http://web-platform.test:8000"; let origin = test_decode::(&[s]).unwrap(); assert_eq!(origin.scheme(), "http"); assert_eq!(origin.hostname(), "web-platform.test"); assert_eq!(origin.port(), Some(8000)); let headers = test_encode(origin); assert_eq!(headers["origin"], s); } #[test] fn null() { assert_eq!(test_decode::(&["null"]), Some(Origin::NULL),); let headers = test_encode(Origin::NULL); assert_eq!(headers["origin"], "null"); } } headers-0.3.9/src/common/pragma.rs000064400000000000000000000031261046102023000151460ustar 00000000000000use HeaderValue; /// The `Pragma` header defined by HTTP/1.0. /// /// > The "Pragma" header field allows backwards compatibility with /// > HTTP/1.0 caches, so that clients can specify a "no-cache" request /// > that they will understand (as Cache-Control was not defined until /// > HTTP/1.1). When the Cache-Control header field is also present and /// > understood in a request, Pragma is ignored. /// > In HTTP/1.0, Pragma was defined as an extensible field for /// > implementation-specified directives for recipients. This /// > specification deprecates such extensions to improve interoperability. /// /// Spec: [https://tools.ietf.org/html/rfc7234#section-5.4][url] /// /// [url]: https://tools.ietf.org/html/rfc7234#section-5.4 /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::Pragma; /// /// let pragma = Pragma::no_cache(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct Pragma(HeaderValue); derive_header! { Pragma(_), name: PRAGMA } impl Pragma { /// Construct the literal `no-cache` Pragma header. pub fn no_cache() -> Pragma { Pragma(HeaderValue::from_static("no-cache")) } /// Return whether this pragma is `no-cache`. pub fn is_no_cache(&self) -> bool { self.0 == "no-cache" } } #[cfg(test)] mod tests { use super::super::test_decode; use super::Pragma; #[test] fn no_cache_is_no_cache() { assert!(Pragma::no_cache().is_no_cache()); } #[test] fn etc_is_not_no_cache() { let ext = test_decode::(&["dexter"]).unwrap(); assert!(!ext.is_no_cache()); } } headers-0.3.9/src/common/proxy_authorization.rs000064400000000000000000000030611046102023000200360ustar 00000000000000use super::authorization::{Authorization, Credentials}; /// `Proxy-Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.4) /// /// The `Proxy-Authorization` header field allows a user agent to authenticate /// itself with an HTTP proxy -- usually, but not necessarily, after /// receiving a 407 (Proxy Authentication Required) response and the /// `Proxy-Authenticate` header. Its value consists of credentials containing /// the authentication information of the user agent for the realm of the /// resource being requested. /// /// # ABNF /// /// ```text /// Proxy-Authorization = credentials /// ``` /// /// # Example values /// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` /// * `Bearer fpKL54jvWmEGVoRdCNjG` /// /// # Examples /// #[derive(Clone, PartialEq, Debug)] pub struct ProxyAuthorization(pub C); impl ::Header for ProxyAuthorization { fn name() -> &'static ::HeaderName { &::http::header::PROXY_AUTHORIZATION } fn decode<'i, I: Iterator>(values: &mut I) -> Result { Authorization::decode(values).map(|auth| ProxyAuthorization(auth.0)) } fn encode>(&self, values: &mut E) { let value = self.0.encode(); debug_assert!( value.as_bytes().starts_with(C::SCHEME.as_bytes()), "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}", C::SCHEME, value, ); values.extend(::std::iter::once(value)); } } headers-0.3.9/src/common/range.rs000064400000000000000000000336141046102023000150000ustar 00000000000000use std::ops::{Bound, RangeBounds}; /// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) /// /// The "Range" header field on a GET request modifies the method /// semantics to request transfer of only one or more subranges of the /// selected representation data, rather than the entire selected /// representation data. /// /// # ABNF /// /// ```text /// Range = byte-ranges-specifier / other-ranges-specifier /// other-ranges-specifier = other-range-unit "=" other-range-set /// other-range-set = 1*VCHAR /// /// bytes-unit = "bytes" /// /// byte-ranges-specifier = bytes-unit "=" byte-range-set /// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) /// byte-range-spec = first-byte-pos "-" [last-byte-pos] /// first-byte-pos = 1*DIGIT /// last-byte-pos = 1*DIGIT /// ``` /// /// # Example values /// /// * `bytes=1000-` /// * `bytes=-2000` /// * `bytes=0-1,30-40` /// * `bytes=0-10,20-90,-100` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::Range; /// /// /// let range = Range::bytes(0..1234).unwrap(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct Range(::HeaderValue); error_type!(InvalidRange); impl Range { /// Creates a `Range` header from bounds. pub fn bytes(bounds: impl RangeBounds) -> Result { let v = match (bounds.start_bound(), bounds.end_bound()) { (Bound::Unbounded, Bound::Included(end)) => format!("bytes=-{}", end), (Bound::Unbounded, Bound::Excluded(&end)) => format!("bytes=-{}", end - 1), (Bound::Included(start), Bound::Included(end)) => format!("bytes={}-{}", start, end), (Bound::Included(start), Bound::Excluded(&end)) => { format!("bytes={}-{}", start, end - 1) } (Bound::Included(start), Bound::Unbounded) => format!("bytes={}-", start), _ => return Err(InvalidRange { _inner: () }), }; Ok(Range(::HeaderValue::from_str(&v).unwrap())) } /// Iterate the range sets as a tuple of bounds. pub fn iter<'a>(&'a self) -> impl Iterator, Bound)> + 'a { let s = self .0 .to_str() .expect("valid string checked in Header::decode()"); s["bytes=".len()..].split(',').filter_map(|spec| { let mut iter = spec.trim().splitn(2, '-'); Some((parse_bound(iter.next()?)?, parse_bound(iter.next()?)?)) }) } } fn parse_bound(s: &str) -> Option> { if s.is_empty() { return Some(Bound::Unbounded); } s.parse().ok().map(Bound::Included) } impl ::Header for Range { fn name() -> &'static ::HeaderName { &::http::header::RANGE } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .and_then(|val| { if val.to_str().ok()?.starts_with("bytes=") { Some(Range(val.clone())) } else { None } }) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { values.extend(::std::iter::once(self.0.clone())); } } /* impl ByteRangeSpec { /// Given the full length of the entity, attempt to normalize the byte range /// into an satisfiable end-inclusive (from, to) range. /// /// The resulting range is guaranteed to be a satisfiable range within the bounds /// of `0 <= from <= to < full_length`. /// /// If the byte range is deemed unsatisfiable, `None` is returned. /// An unsatisfiable range is generally cause for a server to either reject /// the client request with a `416 Range Not Satisfiable` status code, or to /// simply ignore the range header and serve the full entity using a `200 OK` /// status code. /// /// This function closely follows [RFC 7233][1] section 2.1. /// As such, it considers ranges to be satisfiable if they meet the following /// conditions: /// /// > If a valid byte-range-set includes at least one byte-range-spec with /// a first-byte-pos that is less than the current length of the /// representation, or at least one suffix-byte-range-spec with a /// non-zero suffix-length, then the byte-range-set is satisfiable. /// Otherwise, the byte-range-set is unsatisfiable. /// /// The function also computes remainder ranges based on the RFC: /// /// > If the last-byte-pos value is /// absent, or if the value is greater than or equal to the current /// length of the representation data, the byte range is interpreted as /// the remainder of the representation (i.e., the server replaces the /// value of last-byte-pos with a value that is one less than the current /// length of the selected representation). /// /// [1]: https://tools.ietf.org/html/rfc7233 pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { // If the full length is zero, there is no satisfiable end-inclusive range. if full_length == 0 { return None; } match self { &ByteRangeSpec::FromTo(from, to) => { if from < full_length && from <= to { Some((from, ::std::cmp::min(to, full_length - 1))) } else { None } }, &ByteRangeSpec::AllFrom(from) => { if from < full_length { Some((from, full_length - 1)) } else { None } }, &ByteRangeSpec::Last(last) => { if last > 0 { // From the RFC: If the selected representation is shorter // than the specified suffix-length, // the entire representation is used. if last > full_length { Some((0, full_length - 1)) } else { Some((full_length - last, full_length - 1)) } } else { None } } } } } impl Range { /// Get the most common byte range header ("bytes=from-to") pub fn bytes(from: u64, to: u64) -> Range { Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) } /// Get byte range header with multiple subranges /// ("bytes=from1-to1,from2-to2,fromX-toX") pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { Range::Bytes(ranges.iter().map(|r| ByteRangeSpec::FromTo(r.0, r.1)).collect()) } } impl fmt::Display for ByteRangeSpec { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), } } } impl fmt::Display for Range { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { Range::Bytes(ref ranges) => { try!(write!(f, "bytes=")); for (i, range) in ranges.iter().enumerate() { if i != 0 { try!(f.write_str(",")); } try!(Display::fmt(range, f)); } Ok(()) }, Range::Unregistered(ref unit, ref range_str) => { write!(f, "{}={}", unit, range_str) }, } } } impl FromStr for Range { type Err = ::Error; fn from_str(s: &str) -> ::Result { let mut iter = s.splitn(2, '='); match (iter.next(), iter.next()) { (Some("bytes"), Some(ranges)) => { let ranges = from_comma_delimited(ranges); if ranges.is_empty() { return Err(::Error::Header); } Ok(Range::Bytes(ranges)) } (Some(unit), Some(range_str)) if unit != "" && range_str != "" => { Ok(Range::Unregistered(unit.to_owned(), range_str.to_owned())) }, _ => Err(::Error::Header) } } } impl FromStr for ByteRangeSpec { type Err = ::Error; fn from_str(s: &str) -> ::Result { let mut parts = s.splitn(2, '-'); match (parts.next(), parts.next()) { (Some(""), Some(end)) => { end.parse().or(Err(::Error::Header)).map(ByteRangeSpec::Last) }, (Some(start), Some("")) => { start.parse().or(Err(::Error::Header)).map(ByteRangeSpec::AllFrom) }, (Some(start), Some(end)) => { match (start.parse(), end.parse()) { (Ok(start), Ok(end)) if start <= end => Ok(ByteRangeSpec::FromTo(start, end)), _ => Err(::Error::Header) } }, _ => Err(::Error::Header) } } } fn from_comma_delimited(s: &str) -> Vec { s.split(',') .filter_map(|x| match x.trim() { "" => None, y => Some(y) }) .filter_map(|x| x.parse().ok()) .collect() } impl Header for Range { fn header_name() -> &'static str { static NAME: &'static str = "Range"; NAME } fn parse_header(raw: &Raw) -> ::Result { from_one_raw_str(raw) } fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { f.fmt_line(self) } } #[test] fn test_parse_bytes_range_valid() { let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); let r3 = Range::bytes(1, 100); assert_eq!(r, r2); assert_eq!(r2, r3); let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); let r2: Range = Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); let r3 = Range::Bytes( vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] ); assert_eq!(r, r2); assert_eq!(r2, r3); let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); let r3 = Range::Bytes( vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::Last(100)] ); assert_eq!(r, r2); assert_eq!(r2, r3); let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_unregistered_range_valid() { let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); assert_eq!(r, r2); let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); assert_eq!(r, r2); let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); assert_eq!(r, r2); } #[test] fn test_parse_invalid() { let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); assert_eq!(r.ok(), None); let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); assert_eq!(r.ok(), None); let r: ::Result = Header::parse_header(&"abc".into()); assert_eq!(r.ok(), None); let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); assert_eq!(r.ok(), None); let r: ::Result = Header::parse_header(&"bytes=".into()); assert_eq!(r.ok(), None); let r: ::Result = Header::parse_header(&"custom=".into()); assert_eq!(r.ok(), None); let r: ::Result = Header::parse_header(&"=1-100".into()); assert_eq!(r.ok(), None); } #[test] fn test_fmt() { use Headers; let mut headers = Headers::new(); headers.set( Range::Bytes( vec![ByteRangeSpec::FromTo(0, 1000), ByteRangeSpec::AllFrom(2000)] )); assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); headers.clear(); headers.set(Range::Bytes(vec![])); assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); headers.clear(); headers.set(Range::Unregistered("custom".to_owned(), "1-xxx".to_owned())); assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); } #[test] fn test_byte_range_spec_to_satisfiable_range() { assert_eq!(Some((0, 0)), ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3)); assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3)); assert_eq!(Some((1, 2)), ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0)); assert_eq!(Some((0, 2)), ByteRangeSpec::AllFrom(0).to_satisfiable_range(3)); assert_eq!(Some((2, 2)), ByteRangeSpec::AllFrom(2).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::AllFrom(3).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::AllFrom(5).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::AllFrom(0).to_satisfiable_range(0)); assert_eq!(Some((1, 2)), ByteRangeSpec::Last(2).to_satisfiable_range(3)); assert_eq!(Some((2, 2)), ByteRangeSpec::Last(1).to_satisfiable_range(3)); assert_eq!(Some((0, 2)), ByteRangeSpec::Last(5).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); } bench_header!(bytes_multi, Range, { vec![b"bytes=1-1001,2001-3001,10001-".to_vec()]}); bench_header!(custom_unit, Range, { vec![b"other=0-100000".to_vec()]}); */ headers-0.3.9/src/common/referer.rs000064400000000000000000000027171046102023000153360ustar 00000000000000use std::str::FromStr; use http::header::HeaderValue; /// `Referer` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.2) /// /// The `Referer` \[sic\] header field allows the user agent to specify a /// URI reference for the resource from which the target URI was obtained /// (i.e., the "referrer", though the field name is misspelled). A user /// agent MUST NOT include the fragment and userinfo components of the /// URI reference, if any, when generating the Referer field value. /// /// ## ABNF /// /// ```text /// Referer = absolute-URI / partial-URI /// ``` /// /// ## Example values /// /// * `http://www.example.org/hypertext/Overview.html` /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::Referer; /// /// let r = Referer::from_static("/People.html#tim"); /// ``` #[derive(Debug, Clone, PartialEq)] pub struct Referer(HeaderValue); derive_header! { Referer(_), name: REFERER } impl Referer { /// Create a `Referer` with a static string. /// /// # Panic /// /// Panics if the string is not a legal header value. pub fn from_static(s: &'static str) -> Referer { Referer(HeaderValue::from_static(s)) } } error_type!(InvalidReferer); impl FromStr for Referer { type Err = InvalidReferer; fn from_str(src: &str) -> Result { HeaderValue::from_str(src) .map(Referer) .map_err(|_| InvalidReferer { _inner: () }) } } headers-0.3.9/src/common/referrer_policy.rs000064400000000000000000000127101046102023000170710ustar 00000000000000use HeaderValue; /// `Referrer-Policy` header, part of /// [Referrer Policy](https://www.w3.org/TR/referrer-policy/#referrer-policy-header) /// /// The `Referrer-Policy` HTTP header specifies the referrer /// policy that the user agent applies when determining what /// referrer information should be included with requests made, /// and with browsing contexts created from the context of the /// protected resource. /// /// # ABNF /// /// ```text /// Referrer-Policy: 1#policy-token /// policy-token = "no-referrer" / "no-referrer-when-downgrade" /// / "same-origin" / "origin" /// / "origin-when-cross-origin" / "unsafe-url" /// ``` /// /// # Example values /// /// * `no-referrer` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::ReferrerPolicy; /// /// let rp = ReferrerPolicy::NO_REFERRER; /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct ReferrerPolicy(Policy); derive_header! { ReferrerPolicy(_), name: REFERRER_POLICY } #[derive(Clone, Debug, PartialEq, Eq, Hash)] enum Policy { NoReferrer, NoReferrerWhenDowngrade, SameOrigin, Origin, OriginWhenCrossOrigin, UnsafeUrl, StrictOrigin, StrictOriginWhenCrossOrigin, } impl ReferrerPolicy { /// `no-referrer` pub const NO_REFERRER: Self = ReferrerPolicy(Policy::NoReferrer); /// `no-referrer-when-downgrade` pub const NO_REFERRER_WHEN_DOWNGRADE: Self = ReferrerPolicy(Policy::NoReferrerWhenDowngrade); /// `same-origin` pub const SAME_ORIGIN: Self = ReferrerPolicy(Policy::SameOrigin); /// `origin` pub const ORIGIN: Self = ReferrerPolicy(Policy::Origin); /// `origin-when-cross-origin` pub const ORIGIN_WHEN_CROSS_ORIGIN: Self = ReferrerPolicy(Policy::OriginWhenCrossOrigin); /// `unsafe-url` pub const UNSAFE_URL: Self = ReferrerPolicy(Policy::UnsafeUrl); /// `strict-origin` pub const STRICT_ORIGIN: Self = ReferrerPolicy(Policy::StrictOrigin); ///`strict-origin-when-cross-origin` pub const STRICT_ORIGIN_WHEN_CROSS_ORIGIN: Self = ReferrerPolicy(Policy::StrictOriginWhenCrossOrigin); } impl ::util::TryFromValues for Policy { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { // See https://www.w3.org/TR/referrer-policy/#determine-policy-for-token // tl;dr - Pick *last* known policy in the list let mut known = None; for s in csv(values) { known = Some(match s { "no-referrer" | "never" => Policy::NoReferrer, "no-referrer-when-downgrade" | "default" => Policy::NoReferrerWhenDowngrade, "same-origin" => Policy::SameOrigin, "origin" => Policy::Origin, "origin-when-cross-origin" => Policy::OriginWhenCrossOrigin, "strict-origin" => Policy::StrictOrigin, "strict-origin-when-cross-origin" => Policy::StrictOriginWhenCrossOrigin, "unsafe-url" | "always" => Policy::UnsafeUrl, _ => continue, }); } known.ok_or_else(::Error::invalid) } } impl<'a> From<&'a Policy> for HeaderValue { fn from(policy: &'a Policy) -> HeaderValue { HeaderValue::from_static(match *policy { Policy::NoReferrer => "no-referrer", Policy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade", Policy::SameOrigin => "same-origin", Policy::Origin => "origin", Policy::OriginWhenCrossOrigin => "origin-when-cross-origin", Policy::StrictOrigin => "strict-origin", Policy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin", Policy::UnsafeUrl => "unsafe-url", }) } } fn csv<'i, I>(values: I) -> impl Iterator where I: Iterator, { values.flat_map(|value| { value.to_str().into_iter().flat_map(|string| { string.split(',').filter_map(|x| match x.trim() { "" => None, y => Some(y), }) }) }) } #[cfg(test)] mod tests { use super::super::test_decode; use super::ReferrerPolicy; #[test] fn decode_as_last_policy() { assert_eq!( test_decode::(&["same-origin, origin"]), Some(ReferrerPolicy::ORIGIN), ); assert_eq!( test_decode::(&["origin", "same-origin"]), Some(ReferrerPolicy::SAME_ORIGIN), ); } #[test] fn decode_as_last_known() { assert_eq!( test_decode::(&["origin, nope, nope, nope"]), Some(ReferrerPolicy::ORIGIN), ); assert_eq!( test_decode::(&["nope, origin, nope, nope"]), Some(ReferrerPolicy::ORIGIN), ); assert_eq!( test_decode::(&["nope, origin", "nope, nope"]), Some(ReferrerPolicy::ORIGIN), ); assert_eq!( test_decode::(&["nope", "origin", "nope, nope"]), Some(ReferrerPolicy::ORIGIN), ); } #[test] fn decode_unknown() { assert_eq!(test_decode::(&["nope"]), None,); } #[test] fn matching() { let rp = ReferrerPolicy::ORIGIN; match rp { ReferrerPolicy::ORIGIN => (), _ => panic!("matched wrong"), } } } headers-0.3.9/src/common/retry_after.rs000064400000000000000000000064361046102023000162340ustar 00000000000000use std::time::{Duration, SystemTime}; use util::{HttpDate, Seconds, TryFromValues}; use HeaderValue; /// The `Retry-After` header. /// /// The `Retry-After` response-header field can be used with a 503 (Service /// Unavailable) response to indicate how long the service is expected to be /// unavailable to the requesting client. This field MAY also be used with any /// 3xx (Redirection) response to indicate the minimum time the user-agent is /// asked wait before issuing the redirected request. The value of this field /// can be either an HTTP-date or an integer number of seconds (in decimal) /// after the time of the response. /// /// # Examples /// ``` /// # extern crate headers; /// use std::time::{Duration, SystemTime}; /// use headers::RetryAfter; /// /// let delay = RetryAfter::delay(Duration::from_secs(300)); /// let date = RetryAfter::date(SystemTime::now()); /// ``` /// Retry-After header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.3) #[derive(Debug, Clone, PartialEq, Eq)] pub struct RetryAfter(After); derive_header! { RetryAfter(_), name: RETRY_AFTER } #[derive(Debug, Clone, PartialEq, Eq)] enum After { /// Retry after the given DateTime DateTime(HttpDate), /// Retry after this duration has elapsed Delay(Seconds), } impl RetryAfter { /// Create an `RetryAfter` header with a date value. pub fn date(time: SystemTime) -> RetryAfter { RetryAfter(After::DateTime(time.into())) } /// Create an `RetryAfter` header with a date value. pub fn delay(dur: Duration) -> RetryAfter { RetryAfter(After::Delay(dur.into())) } } impl TryFromValues for After { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .next() .and_then(|val| { if let Some(delay) = Seconds::from_val(val) { return Some(After::Delay(delay)); } let date = HttpDate::from_val(val)?; Some(After::DateTime(date)) }) .ok_or_else(::Error::invalid) } } impl<'a> From<&'a After> for HeaderValue { fn from(after: &'a After) -> HeaderValue { match *after { After::Delay(ref delay) => delay.into(), After::DateTime(ref date) => date.into(), } } } #[cfg(test)] mod tests { use super::super::test_decode; use super::RetryAfter; use std::time::Duration; use util::HttpDate; #[test] fn delay_decode() { let r: RetryAfter = test_decode(&["1234"]).unwrap(); assert_eq!(r, RetryAfter::delay(Duration::from_secs(1234)),); } macro_rules! test_retry_after_datetime { ($name:ident, $s:expr) => { #[test] fn $name() { let r: RetryAfter = test_decode(&[$s]).unwrap(); let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::().unwrap(); assert_eq!(r, RetryAfter(super::After::DateTime(dt))); } }; } test_retry_after_datetime!(date_decode_rfc1123, "Sun, 06 Nov 1994 08:49:37 GMT"); test_retry_after_datetime!(date_decode_rfc850, "Sunday, 06-Nov-94 08:49:37 GMT"); test_retry_after_datetime!(date_decode_asctime, "Sun Nov 6 08:49:37 1994"); } headers-0.3.9/src/common/sec_websocket_accept.rs000064400000000000000000000034411046102023000200360ustar 00000000000000use base64::engine::general_purpose::STANDARD as ENGINE; use base64::Engine; use bytes::Bytes; use sha1::{Digest, Sha1}; use super::SecWebsocketKey; /// The `Sec-Websocket-Accept` header. /// /// This header is used in the Websocket handshake, sent back by the /// server indicating a successful handshake. It is a signature /// of the `Sec-Websocket-Key` header. /// /// # Example /// /// ```no_run /// # extern crate headers; /// use headers::{SecWebsocketAccept, SecWebsocketKey}; /// /// let sec_key: SecWebsocketKey = /* from request headers */ /// # unimplemented!(); /// /// let sec_accept = SecWebsocketAccept::from(sec_key); /// ``` #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SecWebsocketAccept(::HeaderValue); derive_header! { SecWebsocketAccept(_), name: SEC_WEBSOCKET_ACCEPT } impl From for SecWebsocketAccept { fn from(key: SecWebsocketKey) -> SecWebsocketAccept { sign(key.0.as_bytes()) } } fn sign(key: &[u8]) -> SecWebsocketAccept { let mut sha1 = Sha1::default(); sha1.update(key); sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]); let b64 = Bytes::from(ENGINE.encode(&sha1.finalize())); let val = ::HeaderValue::from_maybe_shared(b64).expect("base64 is a valid value"); SecWebsocketAccept(val) } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn key_to_accept() { // From https://tools.ietf.org/html/rfc6455#section-1.2 let key = test_decode::(&["dGhlIHNhbXBsZSBub25jZQ=="]).expect("key"); let accept = SecWebsocketAccept::from(key); let headers = test_encode(accept); assert_eq!( headers["sec-websocket-accept"], "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" ); } } headers-0.3.9/src/common/sec_websocket_key.rs000064400000000000000000000003171046102023000173660ustar 00000000000000/// The `Sec-Websocket-Key` header. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct SecWebsocketKey(pub(super) ::HeaderValue); derive_header! { SecWebsocketKey(_), name: SEC_WEBSOCKET_KEY } headers-0.3.9/src/common/sec_websocket_version.rs000064400000000000000000000027721046102023000202720ustar 00000000000000/// The `Sec-Websocket-Version` header. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct SecWebsocketVersion(u8); impl SecWebsocketVersion { /// `Sec-Websocket-Version: 13` pub const V13: SecWebsocketVersion = SecWebsocketVersion(13); } impl ::Header for SecWebsocketVersion { fn name() -> &'static ::HeaderName { &::http::header::SEC_WEBSOCKET_VERSION } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .next() .and_then(|value| { if value == "13" { Some(SecWebsocketVersion::V13) } else { None } }) .ok_or_else(::Error::invalid) } fn encode>(&self, values: &mut E) { debug_assert_eq!(self.0, 13); values.extend(::std::iter::once(::HeaderValue::from_static("13"))); } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::SecWebsocketVersion; #[test] fn decode_v13() { assert_eq!( test_decode::(&["13"]), Some(SecWebsocketVersion::V13), ); } #[test] fn decode_fail() { assert_eq!(test_decode::(&["1"]), None,); } #[test] fn encode_v13() { let headers = test_encode(SecWebsocketVersion::V13); assert_eq!(headers["sec-websocket-version"], "13"); } } headers-0.3.9/src/common/server.rs000064400000000000000000000034531046102023000152100ustar 00000000000000use std::fmt; use std::str::FromStr; use util::HeaderValueString; /// `Server` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.2) /// /// The `Server` header field contains information about the software /// used by the origin server to handle the request, which is often used /// by clients to help identify the scope of reported interoperability /// problems, to work around or tailor requests to avoid particular /// server limitations, and for analytics regarding server or operating /// system use. An origin server MAY generate a Server field in its /// responses. /// /// # ABNF /// /// ```text /// Server = product *( RWS ( product / comment ) ) /// ``` /// /// # Example values /// * `CERN/3.0 libwww/2.17` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::Server; /// /// let server = Server::from_static("hyper/0.12.2"); /// ``` #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Server(HeaderValueString); derive_header! { Server(_), name: SERVER } impl Server { /// Construct a `Server` from a static string. /// /// # Panic /// /// Panics if the static string is not a legal header value. pub fn from_static(s: &'static str) -> Server { Server(HeaderValueString::from_static(s)) } /// View this `Server` as a `&str`. pub fn as_str(&self) -> &str { self.0.as_str() } } error_type!(InvalidServer); impl FromStr for Server { type Err = InvalidServer; fn from_str(src: &str) -> Result { HeaderValueString::from_str(src) .map(Server) .map_err(|_| InvalidServer { _inner: () }) } } impl fmt::Display for Server { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } headers-0.3.9/src/common/set_cookie.rs000064400000000000000000000071751046102023000160330ustar 00000000000000/// `Set-Cookie` header, defined [RFC6265](http://tools.ietf.org/html/rfc6265#section-4.1) /// /// The Set-Cookie HTTP response header is used to send cookies from the /// server to the user agent. /// /// Informally, the Set-Cookie response header contains the header name /// "Set-Cookie" followed by a ":" and a cookie. Each cookie begins with /// a name-value-pair, followed by zero or more attribute-value pairs. /// /// # ABNF /// /// ```text /// set-cookie-header = "Set-Cookie:" SP set-cookie-string /// set-cookie-string = cookie-pair *( ";" SP cookie-av ) /// cookie-pair = cookie-name "=" cookie-value /// cookie-name = token /// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) /// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E /// ; US-ASCII characters excluding CTLs, /// ; whitespace DQUOTE, comma, semicolon, /// ; and backslash /// token = /// /// cookie-av = expires-av / max-age-av / domain-av / /// path-av / secure-av / httponly-av / /// extension-av /// expires-av = "Expires=" sane-cookie-date /// sane-cookie-date = /// max-age-av = "Max-Age=" non-zero-digit *DIGIT /// ; In practice, both expires-av and max-age-av /// ; are limited to dates representable by the /// ; user agent. /// non-zero-digit = %x31-39 /// ; digits 1 through 9 /// domain-av = "Domain=" domain-value /// domain-value = /// ; defined in [RFC1034], Section 3.5, as /// ; enhanced by [RFC1123], Section 2.1 /// path-av = "Path=" path-value /// path-value = /// secure-av = "Secure" /// httponly-av = "HttpOnly" /// extension-av = /// ``` /// /// # Example values /// /// * `SID=31d4d96e407aad42` /// * `lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT` /// * `lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT` /// * `lang=en-US; Path=/; Domain=example.com` /// /// # Example #[derive(Clone, Debug)] pub struct SetCookie(Vec<::HeaderValue>); impl ::Header for SetCookie { fn name() -> &'static ::HeaderName { &::http::header::SET_COOKIE } fn decode<'i, I: Iterator>(values: &mut I) -> Result { let vec = values.cloned().collect::>(); if !vec.is_empty() { Ok(SetCookie(vec)) } else { Err(::Error::invalid()) } } fn encode>(&self, values: &mut E) { values.extend(self.0.iter().cloned()); } } #[cfg(test)] mod tests { use super::super::{test_decode, test_encode}; use super::*; #[test] fn decode() { let set_cookie = test_decode::(&["foo=bar", "baz=quux"]).unwrap(); assert_eq!(set_cookie.0.len(), 2); assert_eq!(set_cookie.0[0], "foo=bar"); assert_eq!(set_cookie.0[1], "baz=quux"); } #[test] fn encode() { let set_cookie = SetCookie(vec![ ::HeaderValue::from_static("foo=bar"), ::HeaderValue::from_static("baz=quux"), ]); let headers = test_encode(set_cookie); let mut vals = headers.get_all("set-cookie").into_iter(); assert_eq!(vals.next().unwrap(), "foo=bar"); assert_eq!(vals.next().unwrap(), "baz=quux"); assert_eq!(vals.next(), None); } } headers-0.3.9/src/common/strict_transport_security.rs000064400000000000000000000170531046102023000212560ustar 00000000000000use std::fmt; use std::time::Duration; use util::{self, IterExt, Seconds}; /// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797) /// /// This specification defines a mechanism enabling web sites to declare /// themselves accessible only via secure connections and/or for users to be /// able to direct their user agent(s) to interact with given sites only over /// secure connections. This overall policy is referred to as HTTP Strict /// Transport Security (HSTS). The policy is declared by web sites via the /// Strict-Transport-Security HTTP response header field and/or by other means, /// such as user agent configuration, for example. /// /// # ABNF /// /// ```text /// [ directive ] *( ";" [ directive ] ) /// /// directive = directive-name [ "=" directive-value ] /// directive-name = token /// directive-value = token | quoted-string /// /// ``` /// /// # Example values /// /// * `max-age=31536000` /// * `max-age=15768000 ; includeSubdomains` /// /// # Example /// /// ``` /// # extern crate headers; /// use std::time::Duration; /// use headers::StrictTransportSecurity; /// /// let sts = StrictTransportSecurity::including_subdomains(Duration::from_secs(31_536_000)); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct StrictTransportSecurity { /// Signals the UA that the HSTS Policy applies to this HSTS Host as well as /// any subdomains of the host's domain name. include_subdomains: bool, /// Specifies the number of seconds, after the reception of the STS header /// field, during which the UA regards the host (from whom the message was /// received) as a Known HSTS Host. max_age: Seconds, } impl StrictTransportSecurity { // NOTE: The two constructors exist to make a user *have* to decide if // subdomains can be included or not, instead of forgetting due to an // incorrect assumption about a default. /// Create an STS header that includes subdomains pub fn including_subdomains(max_age: Duration) -> StrictTransportSecurity { StrictTransportSecurity { max_age: max_age.into(), include_subdomains: true, } } /// Create an STS header that excludes subdomains pub fn excluding_subdomains(max_age: Duration) -> StrictTransportSecurity { StrictTransportSecurity { max_age: max_age.into(), include_subdomains: false, } } // getters /// Get whether this should include subdomains. pub fn include_subdomains(&self) -> bool { self.include_subdomains } /// Get the max-age. pub fn max_age(&self) -> Duration { self.max_age.into() } } enum Directive { MaxAge(u64), IncludeSubdomains, Unknown, } fn from_str(s: &str) -> Result { s.split(';') .map(str::trim) .map(|sub| { if sub.eq_ignore_ascii_case("includeSubdomains") { Some(Directive::IncludeSubdomains) } else { let mut sub = sub.splitn(2, '='); match (sub.next(), sub.next()) { (Some(left), Some(right)) if left.trim().eq_ignore_ascii_case("max-age") => { right .trim() .trim_matches('"') .parse() .ok() .map(Directive::MaxAge) } _ => Some(Directive::Unknown), } } }) .fold(Some((None, None)), |res, dir| match (res, dir) { (Some((None, sub)), Some(Directive::MaxAge(age))) => Some((Some(age), sub)), (Some((age, None)), Some(Directive::IncludeSubdomains)) => Some((age, Some(()))), (Some((Some(_), _)), Some(Directive::MaxAge(_))) | (Some((_, Some(_))), Some(Directive::IncludeSubdomains)) | (_, None) => None, (res, _) => res, }) .and_then(|res| match res { (Some(age), sub) => Some(StrictTransportSecurity { max_age: Duration::from_secs(age).into(), include_subdomains: sub.is_some(), }), _ => None, }) .ok_or_else(::Error::invalid) } impl ::Header for StrictTransportSecurity { fn name() -> &'static ::HeaderName { &::http::header::STRICT_TRANSPORT_SECURITY } fn decode<'i, I: Iterator>(values: &mut I) -> Result { values .just_one() .and_then(|v| v.to_str().ok()) .map(from_str) .unwrap_or_else(|| Err(::Error::invalid())) } fn encode>(&self, values: &mut E) { struct Adapter<'a>(&'a StrictTransportSecurity); impl<'a> fmt::Display for Adapter<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if self.0.include_subdomains { write!(f, "max-age={}; includeSubdomains", self.0.max_age) } else { write!(f, "max-age={}", self.0.max_age) } } } values.extend(::std::iter::once(util::fmt(Adapter(self)))); } } #[cfg(test)] mod tests { use super::super::test_decode; use super::StrictTransportSecurity; use std::time::Duration; #[test] fn test_parse_max_age() { let h = test_decode::(&["max-age=31536000"]).unwrap(); assert_eq!( h, StrictTransportSecurity { include_subdomains: false, max_age: Duration::from_secs(31536000).into(), } ); } #[test] fn test_parse_max_age_no_value() { assert_eq!(test_decode::(&["max-age"]), None,); } #[test] fn test_parse_quoted_max_age() { let h = test_decode::(&["max-age=\"31536000\""]).unwrap(); assert_eq!( h, StrictTransportSecurity { include_subdomains: false, max_age: Duration::from_secs(31536000).into(), } ); } #[test] fn test_parse_spaces_max_age() { let h = test_decode::(&["max-age = 31536000"]).unwrap(); assert_eq!( h, StrictTransportSecurity { include_subdomains: false, max_age: Duration::from_secs(31536000).into(), } ); } #[test] fn test_parse_include_subdomains() { let h = test_decode::(&["max-age=15768000 ; includeSubDomains"]) .unwrap(); assert_eq!( h, StrictTransportSecurity { include_subdomains: true, max_age: Duration::from_secs(15768000).into(), } ); } #[test] fn test_parse_no_max_age() { assert_eq!( test_decode::(&["includeSubdomains"]), None, ); } #[test] fn test_parse_max_age_nan() { assert_eq!( test_decode::(&["max-age = izzy"]), None, ); } #[test] fn test_parse_duplicate_directives() { assert_eq!( test_decode::(&["max-age=1; max-age=2"]), None, ); } } //bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] }); headers-0.3.9/src/common/te.rs000064400000000000000000000020021046102023000142770ustar 00000000000000use util::FlatCsv; /// `TE` header, defined in /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-4.3) /// /// As RFC7230 states, "The "TE" header field in a request indicates what transfer codings, /// besides chunked, the client is willing to accept in response, and /// whether or not the client is willing to accept trailer fields in a /// chunked transfer coding." /// /// For HTTP/1.1 compliant clients `chunked` transfer codings are assumed to be acceptable and /// so should never appear in this header. /// /// # ABNF /// /// ```text /// TE = "TE" ":" #( t-codings ) /// t-codings = "trailers" | ( transfer-extension [ accept-params ] ) /// ``` /// /// # Example values /// * `trailers` /// * `trailers, deflate;q=0.5` /// * `` /// /// # Examples /// #[derive(Clone, Debug, PartialEq)] pub struct Te(FlatCsv); derive_header! { Te(_), name: TE } impl Te { /// Create a `TE: trailers` header. pub fn trailers() -> Self { Te(::HeaderValue::from_static("trailers").into()) } } headers-0.3.9/src/common/transfer_encoding.rs000064400000000000000000000056021046102023000173720ustar 00000000000000use util::FlatCsv; use HeaderValue; /// `Transfer-Encoding` header, defined in /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.1) /// /// The `Transfer-Encoding` header field lists the transfer coding names /// corresponding to the sequence of transfer codings that have been (or /// will be) applied to the payload body in order to form the message /// body. /// /// Note that setting this header will *remove* any previously set /// `Content-Length` header, in accordance with /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2): /// /// > A sender MUST NOT send a Content-Length header field in any message /// > that contains a Transfer-Encoding header field. /// /// # ABNF /// /// ```text /// Transfer-Encoding = 1#transfer-coding /// ``` /// /// # Example values /// /// * `chunked` /// * `gzip, chunked` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::TransferEncoding; /// /// let transfer = TransferEncoding::chunked(); /// ``` // This currently is just a `HeaderValue`, instead of a `Vec`, since // the most common by far instance is simply the string `chunked`. It'd be a // waste to need to allocate just for that. #[derive(Clone, Debug)] pub struct TransferEncoding(FlatCsv); derive_header! { TransferEncoding(_), name: TRANSFER_ENCODING } impl TransferEncoding { /// Constructor for the most common Transfer-Encoding, `chunked`. pub fn chunked() -> TransferEncoding { TransferEncoding(HeaderValue::from_static("chunked").into()) } /// Returns whether this ends with the `chunked` encoding. pub fn is_chunked(&self) -> bool { self.0 .value //TODO(perf): use split and trim (not an actual method) on &[u8] .to_str() .map(|s| { s.split(',') .next_back() .map(|encoding| encoding.trim() == "chunked") .expect("split always has at least 1 item") }) .unwrap_or(false) } } #[cfg(test)] mod tests { use super::super::test_decode; use super::TransferEncoding; #[test] fn chunked_is_chunked() { assert!(TransferEncoding::chunked().is_chunked()); } #[test] fn decode_gzip_chunked_is_chunked() { let te = test_decode::(&["gzip, chunked"]).unwrap(); assert!(te.is_chunked()); } #[test] fn decode_chunked_gzip_is_not_chunked() { let te = test_decode::(&["chunked, gzip"]).unwrap(); assert!(!te.is_chunked()); } #[test] fn decode_notchunked_is_not_chunked() { let te = test_decode::(&["notchunked"]).unwrap(); assert!(!te.is_chunked()); } #[test] fn decode_multiple_is_chunked() { let te = test_decode::(&["gzip", "chunked"]).unwrap(); assert!(te.is_chunked()); } } headers-0.3.9/src/common/upgrade.rs000064400000000000000000000030101046102023000153160ustar 00000000000000use HeaderValue; /// `Upgrade` header, defined in [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.7) /// /// The `Upgrade` header field is intended to provide a simple mechanism /// for transitioning from HTTP/1.1 to some other protocol on the same /// connection. A client MAY send a list of protocols in the Upgrade /// header field of a request to invite the server to switch to one or /// more of those protocols, in order of descending preference, before /// sending the final response. A server MAY ignore a received Upgrade /// header field if it wishes to continue using the current protocol on /// that connection. Upgrade cannot be used to insist on a protocol /// change. /// /// ## ABNF /// /// ```text /// Upgrade = 1#protocol /// /// protocol = protocol-name ["/" protocol-version] /// protocol-name = token /// protocol-version = token /// ``` /// /// ## Example values /// /// * `HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11` /// /// # Note /// /// In practice, the `Upgrade` header is never that complicated. In most cases, /// it is only ever a single value, such as `"websocket"`. /// /// # Examples /// /// ``` /// # extern crate headers; /// use headers::Upgrade; /// /// let ws = Upgrade::websocket(); /// ``` #[derive(Clone, Debug, PartialEq)] pub struct Upgrade(HeaderValue); derive_header! { Upgrade(_), name: UPGRADE } impl Upgrade { /// Constructs an `Upgrade: websocket` header. pub fn websocket() -> Upgrade { Upgrade(HeaderValue::from_static("websocket")) } } headers-0.3.9/src/common/user_agent.rs000064400000000000000000000040571046102023000160370ustar 00000000000000use std::fmt; use std::str::FromStr; use util::HeaderValueString; /// `User-Agent` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.3) /// /// The `User-Agent` header field contains information about the user /// agent originating the request, which is often used by servers to help /// identify the scope of reported interoperability problems, to work /// around or tailor responses to avoid particular user agent /// limitations, and for analytics regarding browser or operating system /// use. A user agent SHOULD send a User-Agent field in each request /// unless specifically configured not to do so. /// /// # ABNF /// /// ```text /// User-Agent = product *( RWS ( product / comment ) ) /// product = token ["/" product-version] /// product-version = token /// ``` /// /// # Example values /// /// * `CERN-LineMode/2.15 libwww/2.17b3` /// * `Bunnies` /// /// # Notes /// /// * The parser does not split the value /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::UserAgent; /// /// let ua = UserAgent::from_static("hyper/0.12.2"); /// ``` #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct UserAgent(HeaderValueString); derive_header! { UserAgent(_), name: USER_AGENT } impl UserAgent { /// Create a `UserAgent` from a static string. /// /// # Panic /// /// Panics if the static string is not a legal header value. pub fn from_static(src: &'static str) -> UserAgent { UserAgent(HeaderValueString::from_static(src)) } /// View this `UserAgent` as a `&str`. pub fn as_str(&self) -> &str { self.0.as_str() } } error_type!(InvalidUserAgent); impl FromStr for UserAgent { type Err = InvalidUserAgent; fn from_str(src: &str) -> Result { HeaderValueString::from_str(src) .map(UserAgent) .map_err(|_| InvalidUserAgent { _inner: () }) } } impl fmt::Display for UserAgent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } headers-0.3.9/src/common/vary.rs000064400000000000000000000036451046102023000146660ustar 00000000000000use util::FlatCsv; use HeaderValue; /// `Vary` header, defined in [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.4) /// /// The "Vary" header field in a response describes what parts of a /// request message, aside from the method, Host header field, and /// request target, might influence the origin server's process for /// selecting and representing this response. The value consists of /// either a single asterisk ("*") or a list of header field names /// (case-insensitive). /// /// # ABNF /// /// ```text /// Vary = "*" / 1#field-name /// ``` /// /// # Example values /// /// * `accept-encoding, accept-language` /// /// # Example /// /// ``` /// # extern crate headers; /// use headers::Vary; /// /// let vary = Vary::any(); /// ``` #[derive(Debug, Clone, PartialEq)] pub struct Vary(FlatCsv); derive_header! { Vary(_), name: VARY } impl Vary { /// Create a new `Very: *` header. pub fn any() -> Vary { Vary(HeaderValue::from_static("*").into()) } /// Check if this includes `*`. pub fn is_any(&self) -> bool { self.0.iter().any(|val| val == "*") } /// Iterate the header names of this `Vary`. pub fn iter_strs(&self) -> impl Iterator { self.0.iter() } } /* test_vary { test_header!(test1, vec![b"accept-encoding, accept-language"]); #[test] fn test2() { let mut vary: ::Result; vary = Header::parse_header(&"*".into()); assert_eq!(vary.ok(), Some(Vary::Any)); vary = Header::parse_header(&"etag,cookie,allow".into()); assert_eq!(vary.ok(), Some(Vary::Items(vec!["eTag".parse().unwrap(), "cookIE".parse().unwrap(), "AlLOw".parse().unwrap(),]))); } } */ #[cfg(test)] mod tests { use super::*; #[test] fn any_is_any() { assert!(Vary::any().is_any()); } } headers-0.3.9/src/disabled/accept.rs000064400000000000000000000105401046102023000154130ustar 00000000000000use mime::{self, Mime}; use {QualityItem, qitem}; header! { /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) /// /// The `Accept` header field can be used by user agents to specify /// response media types that are acceptable. Accept header fields can /// be used to indicate that the request is specifically limited to a /// small set of desired types, as in the case of a request for an /// in-line image /// /// # ABNF /// /// ```text /// Accept = #( media-range [ accept-params ] ) /// /// media-range = ( "*/*" /// / ( type "/" "*" ) /// / ( type "/" subtype ) /// ) *( OWS ";" OWS parameter ) /// accept-params = weight *( accept-ext ) /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] /// ``` /// /// # Example values /// * `audio/*; q=0.2, audio/basic` /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` /// /// # Examples /// ``` /// # extern crate headers; /// extern crate mime; /// use headers::{Headers, Accept, qitem}; /// /// let mut headers = Headers::new(); /// /// headers.set( /// Accept(vec![ /// qitem(mime::TEXT_HTML), /// ]) /// ); /// ``` /// /// ``` /// # extern crate headers; /// extern crate mime; /// use headers::{Headers, Accept, qitem}; /// /// let mut headers = Headers::new(); /// headers.set( /// Accept(vec![ /// qitem(mime::APPLICATION_JSON), /// ]) /// ); /// ``` /// ``` /// # extern crate headers; /// extern crate mime; /// use headers::{Headers, Accept, QualityItem, q, qitem}; /// /// let mut headers = Headers::new(); /// /// headers.set( /// Accept(vec![ /// qitem(mime::TEXT_HTML), /// qitem("application/xhtml+xml".parse().unwrap()), /// QualityItem::new( /// mime::TEXT_XML, /// q(900) /// ), /// qitem("image/webp".parse().unwrap()), /// QualityItem::new( /// mime::STAR_STAR, /// q(800) /// ), /// ]) /// ); /// ``` (Accept, ACCEPT) => (QualityItem)+ test_accept { // Tests from the RFC test_header!( test1, vec![b"audio/*; q=0.2, audio/basic"], Some(HeaderField(vec![ QualityItem::new("audio/*".parse().unwrap(), q(200)), qitem("audio/basic".parse().unwrap()), ]))); test_header!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], Some(HeaderField(vec![ QualityItem::new(TEXT_PLAIN, q(500)), qitem(TEXT_HTML), QualityItem::new( "text/x-dvi".parse().unwrap(), q(800)), qitem("text/x-c".parse().unwrap()), ]))); // Custom tests test_header!( test3, vec![b"text/plain; charset=utf-8"], Some(Accept(vec![ qitem(TEXT_PLAIN_UTF_8), ]))); test_header!( test4, vec![b"text/plain; charset=utf-8; q=0.5"], Some(Accept(vec![ QualityItem::new(TEXT_PLAIN_UTF_8, q(500)), ]))); #[test] fn test_fuzzing1() { let raw: Raw = "chunk#;e".into(); let header = Accept::parse_header(&raw); assert!(header.is_ok()); } } } impl Accept { /// A constructor to easily create `Accept: */*`. pub fn star() -> Accept { Accept(vec![qitem(mime::STAR_STAR)]) } /// A constructor to easily create `Accept: application/json`. pub fn json() -> Accept { Accept(vec![qitem(mime::APPLICATION_JSON)]) } /// A constructor to easily create `Accept: text/*`. pub fn text() -> Accept { Accept(vec![qitem(mime::TEXT_STAR)]) } /// A constructor to easily create `Accept: image/*`. pub fn image() -> Accept { Accept(vec![qitem(mime::IMAGE_STAR)]) } } bench_header!(bench, Accept, { vec![b"text/plain; q=0.5, text/html".to_vec()] }); headers-0.3.9/src/disabled/accept_charset.rs000064400000000000000000000033731046102023000171320ustar 00000000000000use {Charset, QualityItem}; header! { /// `Accept-Charset` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) /// /// The `Accept-Charset` header field can be sent by a user agent to /// indicate what charsets are acceptable in textual response content. /// This field allows user agents capable of understanding more /// comprehensive or special-purpose charsets to signal that capability /// to an origin server that is capable of representing information in /// those charsets. /// /// # ABNF /// /// ```text /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) /// ``` /// /// # Example values /// * `iso-8859-5, unicode-1-1;q=0.8` /// /// # Examples /// ``` /// use headers::{Headers, AcceptCharset, Charset, qitem}; /// /// let mut headers = Headers::new(); /// headers.set( /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// ); /// ``` /// ``` /// use headers::{Headers, AcceptCharset, Charset, q, QualityItem}; /// /// let mut headers = Headers::new(); /// headers.set( /// AcceptCharset(vec![ /// QualityItem::new(Charset::Us_Ascii, q(900)), /// QualityItem::new(Charset::Iso_8859_10, q(200)), /// ]) /// ); /// ``` /// ``` /// use headers::{Headers, AcceptCharset, Charset, qitem}; /// /// let mut headers = Headers::new(); /// headers.set( /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// ); /// ``` (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ test_accept_charset { /// Testcase from RFC test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); } } headers-0.3.9/src/disabled/accept_encoding.rs000064400000000000000000000044151046102023000172650ustar 00000000000000use {Encoding, QualityItem}; header! { /// `Accept-Encoding` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) /// /// The `Accept-Encoding` header field can be used by user agents to /// indicate what response content-codings are /// acceptable in the response. An `identity` token is used as a synonym /// for "no encoding" in order to communicate when no encoding is /// preferred. /// /// # ABNF /// /// ```text /// Accept-Encoding = #( codings [ weight ] ) /// codings = content-coding / "identity" / "*" /// ``` /// /// # Example values /// * `compress, gzip` /// * `` /// * `*` /// * `compress;q=0.5, gzip;q=1` /// * `gzip;q=1.0, identity; q=0.5, *;q=0` /// /// # Examples /// ``` /// use headers::{Headers, AcceptEncoding, Encoding, qitem}; /// /// let mut headers = Headers::new(); /// headers.set( /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) /// ); /// ``` /// ``` /// use headers::{Headers, AcceptEncoding, Encoding, qitem}; /// /// let mut headers = Headers::new(); /// headers.set( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// qitem(Encoding::Gzip), /// qitem(Encoding::Deflate), /// ]) /// ); /// ``` /// ``` /// use headers::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; /// /// let mut headers = Headers::new(); /// headers.set( /// AcceptEncoding(vec![ /// qitem(Encoding::Chunked), /// QualityItem::new(Encoding::Gzip, q(600)), /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), /// ]) /// ); /// ``` (AcceptEncoding, ACCEPT_ENCODING) => (QualityItem)* test_accept_encoding { // From the RFC test_header!(test1, vec![b"compress, gzip"]); test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); test_header!(test3, vec![b"*"]); // Note: Removed quality 1 from gzip test_header!(test4, vec![b"compress;q=0.5, gzip"]); // Note: Removed quality 1 from gzip test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); } } headers-0.3.9/src/disabled/accept_language.rs000064400000000000000000000042231046102023000172570ustar 00000000000000use language_tags::LanguageTag; use QualityItem; header! { /// `Accept-Language` header, defined in /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) /// /// The `Accept-Language` header field can be used by user agents to /// indicate the set of natural languages that are preferred in the /// response. /// /// # ABNF /// /// ```text /// Accept-Language = 1#( language-range [ weight ] ) /// language-range = /// ``` /// /// # Example values /// * `da, en-gb;q=0.8, en;q=0.7` /// * `en-us;q=1.0, en;q=0.5, fr` /// /// # Examples /// /// ``` /// use headers::{Headers, AcceptLanguage, LanguageTag, qitem}; /// /// let mut headers = Headers::new(); /// let mut langtag: LanguageTag = Default::default(); /// langtag.language = Some("en".to_owned()); /// langtag.region = Some("US".to_owned()); /// headers.set( /// AcceptLanguage(vec![ /// qitem(langtag), /// ]) /// ); /// ``` /// /// ``` /// # extern crate headers; /// # #[macro_use] extern crate language_tags; /// # use headers::{Headers, AcceptLanguage, QualityItem, q, qitem}; /// # /// # fn main() { /// let mut headers = Headers::new(); /// headers.set( /// AcceptLanguage(vec![ /// qitem(langtag!(da)), /// QualityItem::new(langtag!(en;;;GB), q(800)), /// QualityItem::new(langtag!(en), q(700)), /// ]) /// ); /// # } /// ``` (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ test_accept_language { // From the RFC test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); // Own test test_header!( test2, vec![b"en-US, en; q=0.5, fr"], Some(AcceptLanguage(vec![ qitem("en-US".parse().unwrap()), QualityItem::new("en".parse().unwrap(), q(500)), qitem("fr".parse().unwrap()), ]))); } } bench_header!(bench, AcceptLanguage, { vec![b"en-us;q=1.0, en;q=0.5, fr".to_vec()] }); headers-0.3.9/src/disabled/content_language.rs000064400000000000000000000014501046102023000174710ustar 00000000000000use util::FlatCsv; /// `Content-Language` header, defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) /// /// The `Content-Language` header field describes the natural language(s) /// of the intended audience for the representation. Note that this /// might not be equivalent to all the languages used within the /// representation. /// /// # ABNF /// /// ```text /// Content-Language = 1#language-tag /// ``` /// /// # Example values /// /// * `da` /// * `mi, en` /// /// # Examples /// /// ``` /// # extern crate headers; /// #[macro_use] extern crate language_tags; /// use headers::ContentLanguage; /// # /// # fn main() { /// let con_lang = ContentLanguage::new([langtag!(en)]) /// # } /// ``` #[derive(Clone, Debug, PartialEq, Header)] pub struct ContentLanguage(FlatCsv); headers-0.3.9/src/disabled/from.rs000064400000000000000000000014271046102023000151230ustar 00000000000000header! { /// `From` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.1) /// /// The `From` header field contains an Internet email address for a /// human user who controls the requesting user agent. The address ought /// to be machine-usable. /// /// # ABNF /// /// ```text /// From = mailbox /// mailbox = /// ``` /// /// # Example /// /// ``` /// use headers::{Headers, From}; /// /// let mut headers = Headers::new(); /// headers.set(From("webmaster@example.org".to_owned())); /// ``` // FIXME: Maybe use mailbox? (From, FROM) => [String] test_from { test_header!(test1, vec![b"webmaster@example.org"]); } } headers-0.3.9/src/disabled/last_event_id.rs000064400000000000000000000024571046102023000170040ustar 00000000000000use std::fmt; use util::HeaderValueString; /// `Last-Event-ID` header, defined in /// [RFC3864](https://html.spec.whatwg.org/multipage/references.html#refsRFC3864) /// /// The `Last-Event-ID` header contains information about /// the last event in an http interaction so that it's easier to /// track of event state. This is helpful when working /// with [Server-Sent-Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/). If the connection were to be dropped, for example, it'd /// be useful to let the server know what the last event you /// received was. /// /// The spec is a String with the id of the last event, it can be /// an empty string which acts a sort of "reset". // NOTE: This module is disabled since there is no const LAST_EVENT_ID to be // used for the `impl Header`. It should be possible to enable this module // when `HeaderName::from_static` can become a `const fn`. #[derive(Clone, Debug, PartialEq, Header)] pub struct LastEventId(HeaderValueString); impl fmt::Display for LastEventId { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } #[cfg(test)] mod tests { /* // Initial state test_header!(test1, vec![b""]); // Own testcase test_header!(test2, vec![b"1"], Some(LastEventId("1".to_owned()))); */ } headers-0.3.9/src/disabled/link.rs000064400000000000000000001150311046102023000151120ustar 00000000000000use std::fmt; use std::borrow::Cow; use std::str::FromStr; #[allow(unused, deprecated)] use std::ascii::AsciiExt; use mime::Mime; use language_tags::LanguageTag; use parsing; use {Header, Raw}; /// The `Link` header, defined in /// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) /// /// # ABNF /// /// ```text /// Link = "Link" ":" #link-value /// link-value = "<" URI-Reference ">" *( ";" link-param ) /// link-param = ( ( "rel" "=" relation-types ) /// | ( "anchor" "=" <"> URI-Reference <"> ) /// | ( "rev" "=" relation-types ) /// | ( "hreflang" "=" Language-Tag ) /// | ( "media" "=" ( MediaDesc | ( <"> MediaDesc <"> ) ) ) /// | ( "title" "=" quoted-string ) /// | ( "title*" "=" ext-value ) /// | ( "type" "=" ( media-type | quoted-mt ) ) /// | ( link-extension ) ) /// link-extension = ( parmname [ "=" ( ptoken | quoted-string ) ] ) /// | ( ext-name-star "=" ext-value ) /// ext-name-star = parmname "*" ; reserved for RFC2231-profiled /// ; extensions. Whitespace NOT /// ; allowed in between. /// ptoken = 1*ptokenchar /// ptokenchar = "!" | "#" | "$" | "%" | "&" | "'" | "(" /// | ")" | "*" | "+" | "-" | "." | "/" | DIGIT /// | ":" | "<" | "=" | ">" | "?" | "@" | ALPHA /// | "[" | "]" | "^" | "_" | "`" | "{" | "|" /// | "}" | "~" /// media-type = type-name "/" subtype-name /// quoted-mt = <"> media-type <"> /// relation-types = relation-type /// | <"> relation-type *( 1*SP relation-type ) <"> /// relation-type = reg-rel-type | ext-rel-type /// reg-rel-type = LOALPHA *( LOALPHA | DIGIT | "." | "-" ) /// ext-rel-type = URI /// ``` /// /// # Example values /// /// `Link: ; rel="previous"; /// title="previous chapter"` /// /// `Link: ; rel="previous"; title*=UTF-8'de'letztes%20Kapitel, /// ; rel="next"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel` /// /// # Examples /// /// ``` /// use headers::{Headers, Link, LinkValue, RelationType}; /// /// let link_value = LinkValue::new("http://example.com/TheBook/chapter2") /// .push_rel(RelationType::Previous) /// .set_title("previous chapter"); /// /// let mut headers = Headers::new(); /// headers.set( /// Link::new(vec![link_value]) /// ); /// ``` #[derive(Clone, PartialEq, Debug)] pub struct Link { /// A list of the `link-value`s of the Link entity-header. values: Vec } /// A single `link-value` of a `Link` header, based on: /// [RFC5988](http://tools.ietf.org/html/rfc5988#section-5) #[derive(Clone, PartialEq, Debug)] pub struct LinkValue { /// Target IRI: `link-value`. link: Cow<'static, str>, /// Forward Relation Types: `rel`. rel: Option>, /// Context IRI: `anchor`. anchor: Option, /// Reverse Relation Types: `rev`. rev: Option>, /// Hint on the language of the result of dereferencing /// the link: `hreflang`. href_lang: Option>, /// Destination medium or media: `media`. media_desc: Option>, /// Label of the destination of a Link: `title`. title: Option, /// The `title` encoded in a different charset: `title*`. title_star: Option, /// Hint on the media type of the result of dereferencing /// the link: `type`. media_type: Option, } /// A Media Descriptors Enum based on: /// [https://www.w3.org/TR/html401/types.html#h-6.13][url] /// /// [url]: https://www.w3.org/TR/html401/types.html#h-6.13 #[derive(Clone, PartialEq, Debug)] pub enum MediaDesc { /// screen. Screen, /// tty. Tty, /// tv. Tv, /// projection. Projection, /// handheld. Handheld, /// print. Print, /// braille. Braille, /// aural. Aural, /// all. All, /// Unrecognized media descriptor extension. Extension(String) } /// A Link Relation Type Enum based on: /// [RFC5988](https://tools.ietf.org/html/rfc5988#section-6.2.2) #[derive(Clone, PartialEq, Debug)] pub enum RelationType { /// alternate. Alternate, /// appendix. Appendix, /// bookmark. Bookmark, /// chapter. Chapter, /// contents. Contents, /// copyright. Copyright, /// current. Current, /// describedby. DescribedBy, /// edit. Edit, /// edit-media. EditMedia, /// enclosure. Enclosure, /// first. First, /// glossary. Glossary, /// help. Help, /// hub. Hub, /// index. Index, /// last. Last, /// latest-version. LatestVersion, /// license. License, /// next. Next, /// next-archive. NextArchive, /// payment. Payment, /// prev. Prev, /// predecessor-version. PredecessorVersion, /// previous. Previous, /// prev-archive. PrevArchive, /// related. Related, /// replies. Replies, /// section. Section, /// self. RelationTypeSelf, /// service. Service, /// start. Start, /// stylesheet. Stylesheet, /// subsection. Subsection, /// successor-version. SuccessorVersion, /// up. Up, /// versionHistory. VersionHistory, /// via. Via, /// working-copy. WorkingCopy, /// working-copy-of. WorkingCopyOf, /// ext-rel-type. ExtRelType(String) } //////////////////////////////////////////////////////////////////////////////// // Struct methods //////////////////////////////////////////////////////////////////////////////// impl Link { /// Create `Link` from a `Vec`. pub fn new(link_values: Vec) -> Link { Link { values: link_values } } /// Get the `Link` header's `LinkValue`s. pub fn values(&self) -> &[LinkValue] { self.values.as_ref() } /// Add a `LinkValue` instance to the `Link` header's values. pub fn push_value(&mut self, link_value: LinkValue) { self.values.push(link_value); } } impl LinkValue { /// Create `LinkValue` from URI-Reference. pub fn new(uri: T) -> LinkValue where T: Into> { LinkValue { link: uri.into(), rel: None, anchor: None, rev: None, href_lang: None, media_desc: None, title: None, title_star: None, media_type: None, } } /// Get the `LinkValue`'s value. pub fn link(&self) -> &str { self.link.as_ref() } /// Get the `LinkValue`'s `rel` parameter(s). pub fn rel(&self) -> Option<&[RelationType]> { self.rel.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `anchor` parameter. pub fn anchor(&self) -> Option<&str> { self.anchor.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `rev` parameter(s). pub fn rev(&self) -> Option<&[RelationType]> { self.rev.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `hreflang` parameter(s). pub fn href_lang(&self) -> Option<&[LanguageTag]> { self.href_lang.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `media` parameter(s). pub fn media_desc(&self) -> Option<&[MediaDesc]> { self.media_desc.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `title` parameter. pub fn title(&self) -> Option<&str> { self.title.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `title*` parameter. pub fn title_star(&self) -> Option<&str> { self.title_star.as_ref().map(AsRef::as_ref) } /// Get the `LinkValue`'s `type` parameter. pub fn media_type(&self) -> Option<&Mime> { self.media_type.as_ref() } /// Add a `RelationType` to the `LinkValue`'s `rel` parameter. pub fn push_rel(mut self, rel: RelationType) -> LinkValue { let mut v = self.rel.take().unwrap_or(Vec::new()); v.push(rel); self.rel = Some(v); self } /// Set `LinkValue`'s `anchor` parameter. pub fn set_anchor>(mut self, anchor: T) -> LinkValue { self.anchor = Some(anchor.into()); self } /// Add a `RelationType` to the `LinkValue`'s `rev` parameter. pub fn push_rev(mut self, rev: RelationType) -> LinkValue { let mut v = self.rev.take().unwrap_or(Vec::new()); v.push(rev); self.rev = Some(v); self } /// Add a `LanguageTag` to the `LinkValue`'s `hreflang` parameter. pub fn push_href_lang(mut self, language_tag: LanguageTag) -> LinkValue { let mut v = self.href_lang.take().unwrap_or(Vec::new()); v.push(language_tag); self.href_lang = Some(v); self } /// Add a `MediaDesc` to the `LinkValue`'s `media_desc` parameter. pub fn push_media_desc(mut self, media_desc: MediaDesc) -> LinkValue { let mut v = self.media_desc.take().unwrap_or(Vec::new()); v.push(media_desc); self.media_desc = Some(v); self } /// Set `LinkValue`'s `title` parameter. pub fn set_title>(mut self, title: T) -> LinkValue { self.title = Some(title.into()); self } /// Set `LinkValue`'s `title*` parameter. pub fn set_title_star>(mut self, title_star: T) -> LinkValue { self.title_star = Some(title_star.into()); self } /// Set `LinkValue`'s `type` parameter. pub fn set_media_type(mut self, media_type: Mime) -> LinkValue { self.media_type = Some(media_type); self } } //////////////////////////////////////////////////////////////////////////////// // Trait implementations //////////////////////////////////////////////////////////////////////////////// impl Header for Link { fn header_name() -> &'static str { static NAME: &'static str = "Link"; NAME } fn parse_header(raw: &Raw) -> ::Result { // If more that one `Link` headers are present in a request's // headers they are combined in a single `Link` header containing // all the `link-value`s present in each of those `Link` headers. raw.iter() .map(parsing::from_raw_str::) .fold(None, |p, c| { match (p, c) { (None, c) => Some(c), (e @ Some(Err(_)), _) => e, (Some(Ok(mut p)), Ok(c)) => { p.values.extend(c.values); Some(Ok(p)) }, _ => Some(Err(::Error::Header)), } }) .unwrap_or(Err(::Error::Header)) } fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { f.fmt_line(self) } } impl fmt::Display for Link { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_delimited(f, self.values.as_slice(), ", ", ("", "")) } } impl fmt::Display for LinkValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { try!(write!(f, "<{}>", self.link)); if let Some(ref rel) = self.rel { try!(fmt_delimited(f, rel.as_slice(), " ", ("; rel=\"", "\""))); } if let Some(ref anchor) = self.anchor { try!(write!(f, "; anchor=\"{}\"", anchor)); } if let Some(ref rev) = self.rev { try!(fmt_delimited(f, rev.as_slice(), " ", ("; rev=\"", "\""))); } if let Some(ref href_lang) = self.href_lang { for tag in href_lang { try!(write!(f, "; hreflang={}", tag)); } } if let Some(ref media_desc) = self.media_desc { try!(fmt_delimited(f, media_desc.as_slice(), ", ", ("; media=\"", "\""))); } if let Some(ref title) = self.title { try!(write!(f, "; title=\"{}\"", title)); } if let Some(ref title_star) = self.title_star { try!(write!(f, "; title*={}", title_star)); } if let Some(ref media_type) = self.media_type { try!(write!(f, "; type=\"{}\"", media_type)); } Ok(()) } } impl FromStr for Link { type Err = ::Error; fn from_str(s: &str) -> ::Result { // Create a split iterator with delimiters: `;`, `,` let link_split = SplitAsciiUnquoted::new(s, ";,"); let mut link_values: Vec = Vec::new(); // Loop over the splits parsing the Link header into // a `Vec` for segment in link_split { // Parse the `Target IRI` // https://tools.ietf.org/html/rfc5988#section-5.1 if segment.trim().starts_with('<') { link_values.push( match verify_and_trim(segment.trim(), (b'<', b'>')) { Err(_) => return Err(::Error::Header), Ok(s) => { LinkValue { link: s.to_owned().into(), rel: None, anchor: None, rev: None, href_lang: None, media_desc: None, title: None, title_star: None, media_type: None, } }, } ); } else { // Parse the current link-value's parameters let mut link_param_split = segment.splitn(2, '='); let link_param_name = match link_param_split.next() { None => return Err(::Error::Header), Some(p) => p.trim(), }; let link_header = match link_values.last_mut() { None => return Err(::Error::Header), Some(l) => l, }; if "rel".eq_ignore_ascii_case(link_param_name) { // Parse relation type: `rel`. // https://tools.ietf.org/html/rfc5988#section-5.3 if link_header.rel.is_none() { link_header.rel = match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => { s.trim_matches(|c: char| c == '"' || c.is_whitespace()) .split(' ') .map(|t| t.trim().parse()) .collect::, _>>() .or_else(|_| Err(::Error::Header)) .ok() }, }; } } else if "anchor".eq_ignore_ascii_case(link_param_name) { // Parse the `Context IRI`. // https://tools.ietf.org/html/rfc5988#section-5.2 link_header.anchor = match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { Err(_) => return Err(::Error::Header), Ok(a) => Some(String::from(a)), }, }; } else if "rev".eq_ignore_ascii_case(link_param_name) { // Parse relation type: `rev`. // https://tools.ietf.org/html/rfc5988#section-5.3 if link_header.rev.is_none() { link_header.rev = match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => { s.trim_matches(|c: char| c == '"' || c.is_whitespace()) .split(' ') .map(|t| t.trim().parse()) .collect::, _>>() .or_else(|_| Err(::Error::Header)) .ok() }, } } } else if "hreflang".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `hreflang`. // https://tools.ietf.org/html/rfc5988#section-5.4 let mut v = link_header.href_lang.take().unwrap_or(Vec::new()); v.push( match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => match s.trim().parse() { Err(_) => return Err(::Error::Header), Ok(t) => t, }, } ); link_header.href_lang = Some(v); } else if "media".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `media`. // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.media_desc.is_none() { link_header.media_desc = match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => { s.trim_matches(|c: char| c == '"' || c.is_whitespace()) .split(',') .map(|t| t.trim().parse()) .collect::, _>>() .or_else(|_| Err(::Error::Header)) .ok() }, }; } } else if "title".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `title`. // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.title.is_none() { link_header.title = match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { Err(_) => return Err(::Error::Header), Ok(t) => Some(String::from(t)), }, }; } } else if "title*".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `title*`. // https://tools.ietf.org/html/rfc5988#section-5.4 // // Definition of `ext-value`: // https://tools.ietf.org/html/rfc5987#section-3.2.1 if link_header.title_star.is_none() { link_header.title_star = match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => Some(String::from(s.trim())), }; } } else if "type".eq_ignore_ascii_case(link_param_name) { // Parse target attribute: `type`. // https://tools.ietf.org/html/rfc5988#section-5.4 if link_header.media_type.is_none() { link_header.media_type = match link_param_split.next() { None | Some("") => return Err(::Error::Header), Some(s) => match verify_and_trim(s.trim(), (b'"', b'"')) { Err(_) => return Err(::Error::Header), Ok(t) => match t.parse() { Err(_) => return Err(::Error::Header), Ok(m) => Some(m), }, }, }; } } else { return Err(::Error::Header); } } } Ok(Link::new(link_values)) } } impl fmt::Display for MediaDesc { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { MediaDesc::Screen => write!(f, "screen"), MediaDesc::Tty => write!(f, "tty"), MediaDesc::Tv => write!(f, "tv"), MediaDesc::Projection => write!(f, "projection"), MediaDesc::Handheld => write!(f, "handheld"), MediaDesc::Print => write!(f, "print"), MediaDesc::Braille => write!(f, "braille"), MediaDesc::Aural => write!(f, "aural"), MediaDesc::All => write!(f, "all"), MediaDesc::Extension(ref other) => write!(f, "{}", other), } } } impl FromStr for MediaDesc { type Err = ::Error; fn from_str(s: &str) -> ::Result { match s { "screen" => Ok(MediaDesc::Screen), "tty" => Ok(MediaDesc::Tty), "tv" => Ok(MediaDesc::Tv), "projection" => Ok(MediaDesc::Projection), "handheld" => Ok(MediaDesc::Handheld), "print" => Ok(MediaDesc::Print), "braille" => Ok(MediaDesc::Braille), "aural" => Ok(MediaDesc::Aural), "all" => Ok(MediaDesc::All), _ => Ok(MediaDesc::Extension(String::from(s))), } } } impl fmt::Display for RelationType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { RelationType::Alternate => write!(f, "alternate"), RelationType::Appendix => write!(f, "appendix"), RelationType::Bookmark => write!(f, "bookmark"), RelationType::Chapter => write!(f, "chapter"), RelationType::Contents => write!(f, "contents"), RelationType::Copyright => write!(f, "copyright"), RelationType::Current => write!(f, "current"), RelationType::DescribedBy => write!(f, "describedby"), RelationType::Edit => write!(f, "edit"), RelationType::EditMedia => write!(f, "edit-media"), RelationType::Enclosure => write!(f, "enclosure"), RelationType::First => write!(f, "first"), RelationType::Glossary => write!(f, "glossary"), RelationType::Help => write!(f, "help"), RelationType::Hub => write!(f, "hub"), RelationType::Index => write!(f, "index"), RelationType::Last => write!(f, "last"), RelationType::LatestVersion => write!(f, "latest-version"), RelationType::License => write!(f, "license"), RelationType::Next => write!(f, "next"), RelationType::NextArchive => write!(f, "next-archive"), RelationType::Payment => write!(f, "payment"), RelationType::Prev => write!(f, "prev"), RelationType::PredecessorVersion => write!(f, "predecessor-version"), RelationType::Previous => write!(f, "previous"), RelationType::PrevArchive => write!(f, "prev-archive"), RelationType::Related => write!(f, "related"), RelationType::Replies => write!(f, "replies"), RelationType::Section => write!(f, "section"), RelationType::RelationTypeSelf => write!(f, "self"), RelationType::Service => write!(f, "service"), RelationType::Start => write!(f, "start"), RelationType::Stylesheet => write!(f, "stylesheet"), RelationType::Subsection => write!(f, "subsection"), RelationType::SuccessorVersion => write!(f, "successor-version"), RelationType::Up => write!(f, "up"), RelationType::VersionHistory => write!(f, "version-history"), RelationType::Via => write!(f, "via"), RelationType::WorkingCopy => write!(f, "working-copy"), RelationType::WorkingCopyOf => write!(f, "working-copy-of"), RelationType::ExtRelType(ref uri) => write!(f, "{}", uri), } } } impl FromStr for RelationType { type Err = ::Error; fn from_str(s: &str) -> ::Result { if "alternate".eq_ignore_ascii_case(s) { Ok(RelationType::Alternate) } else if "appendix".eq_ignore_ascii_case(s) { Ok(RelationType::Appendix) } else if "bookmark".eq_ignore_ascii_case(s) { Ok(RelationType::Bookmark) } else if "chapter".eq_ignore_ascii_case(s) { Ok(RelationType::Chapter) } else if "contents".eq_ignore_ascii_case(s) { Ok(RelationType::Contents) } else if "copyright".eq_ignore_ascii_case(s) { Ok(RelationType::Copyright) } else if "current".eq_ignore_ascii_case(s) { Ok(RelationType::Current) } else if "describedby".eq_ignore_ascii_case(s) { Ok(RelationType::DescribedBy) } else if "edit".eq_ignore_ascii_case(s) { Ok(RelationType::Edit) } else if "edit-media".eq_ignore_ascii_case(s) { Ok(RelationType::EditMedia) } else if "enclosure".eq_ignore_ascii_case(s) { Ok(RelationType::Enclosure) } else if "first".eq_ignore_ascii_case(s) { Ok(RelationType::First) } else if "glossary".eq_ignore_ascii_case(s) { Ok(RelationType::Glossary) } else if "help".eq_ignore_ascii_case(s) { Ok(RelationType::Help) } else if "hub".eq_ignore_ascii_case(s) { Ok(RelationType::Hub) } else if "index".eq_ignore_ascii_case(s) { Ok(RelationType::Index) } else if "last".eq_ignore_ascii_case(s) { Ok(RelationType::Last) } else if "latest-version".eq_ignore_ascii_case(s) { Ok(RelationType::LatestVersion) } else if "license".eq_ignore_ascii_case(s) { Ok(RelationType::License) } else if "next".eq_ignore_ascii_case(s) { Ok(RelationType::Next) } else if "next-archive".eq_ignore_ascii_case(s) { Ok(RelationType::NextArchive) } else if "payment".eq_ignore_ascii_case(s) { Ok(RelationType::Payment) } else if "prev".eq_ignore_ascii_case(s) { Ok(RelationType::Prev) } else if "predecessor-version".eq_ignore_ascii_case(s) { Ok(RelationType::PredecessorVersion) } else if "previous".eq_ignore_ascii_case(s) { Ok(RelationType::Previous) } else if "prev-archive".eq_ignore_ascii_case(s) { Ok(RelationType::PrevArchive) } else if "related".eq_ignore_ascii_case(s) { Ok(RelationType::Related) } else if "replies".eq_ignore_ascii_case(s) { Ok(RelationType::Replies) } else if "section".eq_ignore_ascii_case(s) { Ok(RelationType::Section) } else if "self".eq_ignore_ascii_case(s) { Ok(RelationType::RelationTypeSelf) } else if "service".eq_ignore_ascii_case(s) { Ok(RelationType::Service) } else if "start".eq_ignore_ascii_case(s) { Ok(RelationType::Start) } else if "stylesheet".eq_ignore_ascii_case(s) { Ok(RelationType::Stylesheet) } else if "subsection".eq_ignore_ascii_case(s) { Ok(RelationType::Subsection) } else if "successor-version".eq_ignore_ascii_case(s) { Ok(RelationType::SuccessorVersion) } else if "up".eq_ignore_ascii_case(s) { Ok(RelationType::Up) } else if "version-history".eq_ignore_ascii_case(s) { Ok(RelationType::VersionHistory) } else if "via".eq_ignore_ascii_case(s) { Ok(RelationType::Via) } else if "working-copy".eq_ignore_ascii_case(s) { Ok(RelationType::WorkingCopy) } else if "working-copy-of".eq_ignore_ascii_case(s) { Ok(RelationType::WorkingCopyOf) } else { Ok(RelationType::ExtRelType(String::from(s))) } } } //////////////////////////////////////////////////////////////////////////////// // Utilities //////////////////////////////////////////////////////////////////////////////// struct SplitAsciiUnquoted<'a> { src: &'a str, pos: usize, del: &'a str } impl<'a> SplitAsciiUnquoted<'a> { fn new(s: &'a str, d: &'a str) -> SplitAsciiUnquoted<'a> { SplitAsciiUnquoted{ src: s, pos: 0, del: d, } } } impl<'a> Iterator for SplitAsciiUnquoted<'a> { type Item = &'a str; fn next(&mut self) -> Option<&'a str> { if self.pos < self.src.len() { let prev_pos = self.pos; let mut pos = self.pos; let mut in_quotes = false; for c in self.src[prev_pos..].as_bytes().iter() { in_quotes ^= *c == b'"'; // Ignore `c` if we're `in_quotes`. if !in_quotes && self.del.as_bytes().contains(c) { break; } pos += 1; } self.pos = pos + 1; Some(&self.src[prev_pos..pos]) } else { None } } } fn fmt_delimited(f: &mut fmt::Formatter, p: &[T], d: &str, b: (&str, &str)) -> fmt::Result { if p.len() != 0 { // Write a starting string `b.0` before the first element try!(write!(f, "{}{}", b.0, p[0])); for i in &p[1..] { // Write the next element preceded by the delimiter `d` try!(write!(f, "{}{}", d, i)); } // Write a ending string `b.1` before the first element try!(write!(f, "{}", b.1)); } Ok(()) } fn verify_and_trim(s: &str, b: (u8, u8)) -> ::Result<&str> { let length = s.len(); let byte_array = s.as_bytes(); // Verify that `s` starts with `b.0` and ends with `b.1` and return // the contained substring after trimming whitespace. if length > 1 && b.0 == byte_array[0] && b.1 == byte_array[length - 1] { Ok(s.trim_matches( |c: char| c == b.0 as char || c == b.1 as char || c.is_whitespace()) ) } else { Err(::Error::Header) } } //////////////////////////////////////////////////////////////////////////////// // Tests //////////////////////////////////////////////////////////////////////////////// #[cfg(test)] mod tests { use std::fmt; use std::fmt::Write; use super::{Link, LinkValue, MediaDesc, RelationType, SplitAsciiUnquoted}; use super::{fmt_delimited, verify_and_trim}; use Header; // use proto::ServerTransaction; use bytes::BytesMut; use mime; #[test] fn test_link() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") .push_rel(RelationType::Previous) .push_rev(RelationType::Next) .set_title("previous chapter"); let link_header = b"; \ rel=\"previous\"; rev=next; title=\"previous chapter\""; let expected_link = Link::new(vec![link_value]); let link = Header::parse_header(&vec![link_header.to_vec()].into()); assert_eq!(link.ok(), Some(expected_link)); } #[test] fn test_link_multiple_values() { let first_link = LinkValue::new("/TheBook/chapter2") .push_rel(RelationType::Previous) .set_title_star("UTF-8'de'letztes%20Kapitel"); let second_link = LinkValue::new("/TheBook/chapter4") .push_rel(RelationType::Next) .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); let link_header = b"; \ rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ ; \ rel=\"next\"; title*=UTF-8'de'n%c3%a4chstes%20Kapitel"; let expected_link = Link::new(vec![first_link, second_link]); let link = Header::parse_header(&vec![link_header.to_vec()].into()); assert_eq!(link.ok(), Some(expected_link)); } #[test] fn test_link_all_attributes() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") .push_rel(RelationType::Previous) .set_anchor("../anchor/example/") .push_rev(RelationType::Next) .push_href_lang("de".parse().unwrap()) .push_media_desc(MediaDesc::Screen) .set_title("previous chapter") .set_title_star("title* unparsed") .set_media_type(mime::TEXT_PLAIN); let link_header = b"; \ rel=\"previous\"; anchor=\"../anchor/example/\"; \ rev=\"next\"; hreflang=de; media=\"screen\"; \ title=\"previous chapter\"; title*=title* unparsed; \ type=\"text/plain\""; let expected_link = Link::new(vec![link_value]); let link = Header::parse_header(&vec![link_header.to_vec()].into()); assert_eq!(link.ok(), Some(expected_link)); } // TODO // #[test] // fn test_link_multiple_link_headers() { // let first_link = LinkValue::new("/TheBook/chapter2") // .push_rel(RelationType::Previous) // .set_title_star("UTF-8'de'letztes%20Kapitel"); // let second_link = LinkValue::new("/TheBook/chapter4") // .push_rel(RelationType::Next) // .set_title_star("UTF-8'de'n%c3%a4chstes%20Kapitel"); // let third_link = LinkValue::new("http://example.com/TheBook/chapter2") // .push_rel(RelationType::Previous) // .push_rev(RelationType::Next) // .set_title("previous chapter"); // let expected_link = Link::new(vec![first_link, second_link, third_link]); // let mut raw = BytesMut::from(b"GET /super_short_uri/and_whatever HTTP/1.1\r\nHost: \ // hyper.rs\r\nAccept: a lot of things\r\nAccept-Charset: \ // utf8\r\nAccept-Encoding: *\r\nLink: ; \ // rel=\"previous\"; title*=UTF-8'de'letztes%20Kapitel, \ // ; rel=\"next\"; title*=\ // UTF-8'de'n%c3%a4chstes%20Kapitel\r\n\ // Access-Control-Allow-Credentials: None\r\nLink: \ // ; \ // rel=\"previous\"; rev=next; title=\"previous chapter\"\ // \r\n\r\n".to_vec()); // let (mut res, _) = ServerTransaction::parse(&mut raw).unwrap().unwrap(); // let link = res.headers.remove::().unwrap(); // assert_eq!(link, expected_link); // } #[test] fn test_link_display() { let link_value = LinkValue::new("http://example.com/TheBook/chapter2") .push_rel(RelationType::Previous) .set_anchor("/anchor/example/") .push_rev(RelationType::Next) .push_href_lang("de".parse().unwrap()) .push_media_desc(MediaDesc::Screen) .set_title("previous chapter") .set_title_star("title* unparsed") .set_media_type(mime::TEXT_PLAIN); let link = Link::new(vec![link_value]); let mut link_header = String::new(); write!(&mut link_header, "{}", link).unwrap(); let expected_link_header = "; \ rel=\"previous\"; anchor=\"/anchor/example/\"; \ rev=\"next\"; hreflang=de; media=\"screen\"; \ title=\"previous chapter\"; title*=title* unparsed; \ type=\"text/plain\""; assert_eq!(link_header, expected_link_header); } #[test] fn test_link_parsing_errors() { let link_a = b"http://example.com/TheBook/chapter2; \ rel=\"previous\"; rev=next; title=\"previous chapter\""; let mut err: Result = Header::parse_header(&vec![link_a.to_vec()].into()); assert_eq!(err.is_err(), true); let link_b = b"; \ =\"previous\"; rev=next; title=\"previous chapter\""; err = Header::parse_header(&vec![link_b.to_vec()].into()); assert_eq!(err.is_err(), true); let link_c = b"; \ rel=; rev=next; title=\"previous chapter\""; err = Header::parse_header(&vec![link_c.to_vec()].into()); assert_eq!(err.is_err(), true); let link_d = b"; \ rel=\"previous\"; rev=next; title="; err = Header::parse_header(&vec![link_d.to_vec()].into()); assert_eq!(err.is_err(), true); let link_e = b"; \ rel=\"previous\"; rev=next; attr=unknown"; err = Header::parse_header(&vec![link_e.to_vec()].into()); assert_eq!(err.is_err(), true); } #[test] fn test_link_split_ascii_unquoted_iterator() { let string = "some, text; \"and, more; in quotes\", or not"; let mut string_split = SplitAsciiUnquoted::new(string, ";,"); assert_eq!(Some("some"), string_split.next()); assert_eq!(Some(" text"), string_split.next()); assert_eq!(Some(" \"and, more; in quotes\""), string_split.next()); assert_eq!(Some(" or not"), string_split.next()); assert_eq!(None, string_split.next()); } #[test] fn test_link_fmt_delimited() { struct TestFormatterStruct<'a> { v: Vec<&'a str> }; impl<'a> fmt::Display for TestFormatterStruct<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_delimited(f, self.v.as_slice(), ", ", (">>", "<<")) } } let test_formatter = TestFormatterStruct { v: vec!["first", "second"] }; let mut string = String::new(); write!(&mut string, "{}", test_formatter).unwrap(); let expected_string = ">>first, second<<"; assert_eq!(string, expected_string); } #[test] fn test_link_verify_and_trim() { let string = verify_and_trim("> some string <", (b'>', b'<')); assert_eq!(string.ok(), Some("some string")); let err = verify_and_trim(" > some string <", (b'>', b'<')); assert_eq!(err.is_err(), true); } } bench_header!(bench_link, Link, { vec![b"; rel=\"previous\"; rev=next; title=\"previous chapter\"; type=\"text/html\"; media=\"screen, tty\"".to_vec()] }); headers-0.3.9/src/disabled/prefer.rs000064400000000000000000000165011046102023000154420ustar 00000000000000use std::fmt; use std::str::FromStr; use {Header, Raw}; use parsing::{from_comma_delimited, fmt_comma_delimited}; /// `Prefer` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) /// /// The `Prefer` header field can be used by a client to request that certain /// behaviors be employed by a server while processing a request. /// /// # ABNF /// /// ```text /// Prefer = "Prefer" ":" 1#preference /// preference = token [ BWS "=" BWS word ] /// *( OWS ";" [ OWS parameter ] ) /// parameter = token [ BWS "=" BWS word ] /// ``` /// /// # Example values /// * `respond-async` /// * `return=minimal` /// * `wait=30` /// /// # Examples /// /// ``` /// use headers::{Headers, Prefer, Preference}; /// /// let mut headers = Headers::new(); /// headers.set( /// Prefer(vec![Preference::RespondAsync]) /// ); /// ``` /// /// ``` /// use headers::{Headers, Prefer, Preference}; /// /// let mut headers = Headers::new(); /// headers.set( /// Prefer(vec![ /// Preference::RespondAsync, /// Preference::ReturnRepresentation, /// Preference::Wait(10u32), /// Preference::Extension("foo".to_owned(), /// "bar".to_owned(), /// vec![]), /// ]) /// ); /// ``` #[derive(PartialEq, Clone, Debug)] pub struct Prefer(pub Vec); __hyper__deref!(Prefer => Vec); impl Header for Prefer { fn header_name() -> &'static str { static NAME: &'static str = "Prefer"; NAME } fn parse_header(raw: &Raw) -> ::Result { let preferences = try!(from_comma_delimited(raw)); if !preferences.is_empty() { Ok(Prefer(preferences)) } else { Err(::Error::Header) } } fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { f.fmt_line(self) } } impl fmt::Display for Prefer { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt_comma_delimited(f, &self[..]) } } /// Prefer contains a list of these preferences. #[derive(PartialEq, Clone, Debug)] pub enum Preference { /// "respond-async" RespondAsync, /// "return=representation" ReturnRepresentation, /// "return=minimal" ReturnMinimal, /// "handling=strict" HandlingStrict, /// "handling=lenient" HandlingLenient, /// "wait=delta" Wait(u32), /// Extension preferences. Always has a value, if none is specified it is /// just "". A preference can also have a list of parameters. Extension(String, String, Vec<(String, String)>) } impl fmt::Display for Preference { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use self::Preference::*; fmt::Display::fmt(match *self { RespondAsync => "respond-async", ReturnRepresentation => "return=representation", ReturnMinimal => "return=minimal", HandlingStrict => "handling=strict", HandlingLenient => "handling=lenient", Wait(secs) => return write!(f, "wait={}", secs), Extension(ref name, ref value, ref params) => { try!(write!(f, "{}", name)); if value != "" { try!(write!(f, "={}", value)); } if !params.is_empty() { for &(ref name, ref value) in params { try!(write!(f, "; {}", name)); if value != "" { try!(write!(f, "={}", value)); } } } return Ok(()); } }, f) } } impl FromStr for Preference { type Err = Option<::Err>; fn from_str(s: &str) -> Result::Err>> { use self::Preference::*; let mut params = s.split(';').map(|p| { let mut param = p.splitn(2, '='); match (param.next(), param.next()) { (Some(name), Some(value)) => (name.trim(), value.trim().trim_matches('"')), (Some(name), None) => (name.trim(), ""), // This can safely be unreachable because the [`splitn`][1] // function (used above) will always have at least one value. // // [1]: http://doc.rust-lang.org/std/primitive.str.html#method.splitn _ => { unreachable!(); } } }); match params.nth(0) { Some(param) => { let rest: Vec<(String, String)> = params.map(|(l, r)| (l.to_owned(), r.to_owned())).collect(); match param { ("respond-async", "") => if rest.is_empty() { Ok(RespondAsync) } else { Err(None) }, ("return", "representation") => if rest.is_empty() { Ok(ReturnRepresentation) } else { Err(None) }, ("return", "minimal") => if rest.is_empty() { Ok(ReturnMinimal) } else { Err(None) }, ("handling", "strict") => if rest.is_empty() { Ok(HandlingStrict) } else { Err(None) }, ("handling", "lenient") => if rest.is_empty() { Ok(HandlingLenient) } else { Err(None) }, ("wait", secs) => if rest.is_empty() { secs.parse().map(Wait).map_err(Some) } else { Err(None) }, (left, right) => Ok(Extension(left.to_owned(), right.to_owned(), rest)) } }, None => Err(None) } } } #[cfg(test)] mod tests { use Header; use super::*; #[test] fn test_parse_multiple_headers() { let prefer = Header::parse_header(&"respond-async, return=representation".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::RespondAsync, Preference::ReturnRepresentation]))) } #[test] fn test_parse_argument() { let prefer = Header::parse_header(&"wait=100, handling=lenient, respond-async".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(100), Preference::HandlingLenient, Preference::RespondAsync]))) } #[test] fn test_parse_quote_form() { let prefer = Header::parse_header(&"wait=\"200\", handling=\"strict\"".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![Preference::Wait(200), Preference::HandlingStrict]))) } #[test] fn test_parse_extension() { let prefer = Header::parse_header(&"foo, bar=baz, baz; foo; bar=baz, bux=\"\"; foo=\"\", buz=\"some parameter\"".into()); assert_eq!(prefer.ok(), Some(Prefer(vec![ Preference::Extension("foo".to_owned(), "".to_owned(), vec![]), Preference::Extension("bar".to_owned(), "baz".to_owned(), vec![]), Preference::Extension("baz".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned()), ("bar".to_owned(), "baz".to_owned())]), Preference::Extension("bux".to_owned(), "".to_owned(), vec![("foo".to_owned(), "".to_owned())]), Preference::Extension("buz".to_owned(), "some parameter".to_owned(), vec![])]))) } #[test] fn test_fail_with_args() { let prefer: ::Result = Header::parse_header(&"respond-async; foo=bar".into()); assert_eq!(prefer.ok(), None); } } bench_header!(normal, Prefer, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); headers-0.3.9/src/disabled/preference_applied.rs000064400000000000000000000061671046102023000200020ustar 00000000000000use std::fmt; use {Header, Raw, Preference}; use parsing::{from_comma_delimited, fmt_comma_delimited}; /// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/html/rfc7240) /// /// The `Preference-Applied` response header may be included within a /// response message as an indication as to which `Prefer` header tokens were /// honored by the server and applied to the processing of a request. /// /// # ABNF /// /// ```text /// Preference-Applied = "Preference-Applied" ":" 1#applied-pref /// applied-pref = token [ BWS "=" BWS word ] /// ``` /// /// # Example values /// /// * `respond-async` /// * `return=minimal` /// * `wait=30` /// /// # Examples /// /// ``` /// use headers::{Headers, PreferenceApplied, Preference}; /// /// let mut headers = Headers::new(); /// headers.set( /// PreferenceApplied(vec![Preference::RespondAsync]) /// ); /// ``` /// /// ``` /// use headers::{Headers, PreferenceApplied, Preference}; /// /// let mut headers = Headers::new(); /// headers.set( /// PreferenceApplied(vec![ /// Preference::RespondAsync, /// Preference::ReturnRepresentation, /// Preference::Wait(10u32), /// Preference::Extension("foo".to_owned(), /// "bar".to_owned(), /// vec![]), /// ]) /// ); /// ``` #[derive(PartialEq, Clone, Debug)] pub struct PreferenceApplied(pub Vec); __hyper__deref!(PreferenceApplied => Vec); impl Header for PreferenceApplied { fn header_name() -> &'static str { static NAME: &'static str = "Preference-Applied"; NAME } fn parse_header(raw: &Raw) -> ::Result { let preferences = try!(from_comma_delimited(raw)); if !preferences.is_empty() { Ok(PreferenceApplied(preferences)) } else { Err(::Error::Header) } } fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { f.fmt_line(self) } } impl fmt::Display for PreferenceApplied { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { //TODO: format this without allocating a Vec and cloning contents let preferences: Vec<_> = self.0.iter().map(|pref| match pref { // The spec ignores parameters in `Preferences-Applied` &Preference::Extension(ref name, ref value, _) => Preference::Extension( name.to_owned(), value.to_owned(), vec![] ), preference => preference.clone() }).collect(); fmt_comma_delimited(f, &preferences) } } #[cfg(test)] mod tests { use Preference; use super::*; #[test] fn test_format_ignore_parameters() { assert_eq!( format!("{}", PreferenceApplied(vec![Preference::Extension( "foo".to_owned(), "bar".to_owned(), vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())] )])), "foo=bar".to_owned() ); } } bench_header!(normal, PreferenceApplied, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); headers-0.3.9/src/disabled/util/charset.rs000064400000000000000000000151051046102023000165640ustar 00000000000000use std::fmt; use std::str::FromStr; /// A Mime charset. /// /// The string representation is normalised to upper case. /// /// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. /// /// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml #[derive(Clone, PartialEq)] pub struct Charset(Charset_); impl Charset { /// US ASCII pub const US_ASCII: Charset = Charset(Charset_::Us_Ascii); /// ISO-8859-1 pub const ISO_8859_1: Charset = Charset(Charset_::Iso_8859_1); /// ISO-8859-2 pub const ISO_8859_2: Charset = Charset(Charset_::Iso_8859_2); /// ISO-8859-3 pub const ISO_8859_3: Charset = Charset(Charset_::Iso_8859_3); /// ISO-8859-4 pub const ISO_8859_4: Charset = Charset(Charset_::Iso_8859_4); /// ISO-8859-5 pub const ISO_8859_5: Charset = Charset(Charset_::Iso_8859_5); /// ISO-8859-6 pub const ISO_8859_6: Charset = Charset(Charset_::Iso_8859_6); /// ISO-8859-7 pub const ISO_8859_7: Charset = Charset(Charset_::Iso_8859_7); /// ISO-8859-8 pub const ISO_8859_8: Charset = Charset(Charset_::Iso_8859_8); /// ISO-8859-9 pub const ISO_8859_9: Charset = Charset(Charset_::Iso_8859_9); /// ISO-8859-10 pub const ISO_8859_10: Charset = Charset(Charset_::Iso_8859_10); /// Shift_JIS pub const SHIFT_JIS: Charset = Charset(Charset_::Shift_Jis); /// EUC-JP pub const EUC_JP: Charset = Charset(Charset_::Euc_Jp); /// ISO-2022-KR pub const ISO_2022_KR: Charset = Charset(Charset_::Iso_2022_Kr); /// EUC-KR pub const EUC_KR: Charset: Charset(Charset_::Euc_Kr); /// ISO-2022-JP pub const ISO_2022_JP: Charset = Charset(Charset_::Iso_2022_Jp); /// ISO-2022-JP-2 pub const ISO_2022_JP_2: Charset = Charset(Charset_::Iso_2022_Jp_2); /// ISO-8859-6-E pub const ISO_8859_6_E: Charset = Charset(Charset_::Iso_8859_6_E); /// ISO-8859-6-I pub const ISO_8859_6_I: Charset = Charset(Charset_::Iso_8859_6_I); /// ISO-8859-8-E pub const ISO_8859_8_E: Charset = Charset(Charset_::Iso_8859_8_E); /// ISO-8859-8-I pub const ISO_8859_8_I: Charset = Charset(Charset_::Iso_8859_8_I); /// GB2312 pub const GB_2312: Charset = Charset(Charset_::Gb2312); /// Big5 pub const BIG_5: Charset = Charset(Charset_::Big5); /// KOI8-R pub const KOI8_R: Charset = Charset(Charset_::Koi8_R); } #[derive(Clone, Debug, PartialEq)] #[allow(non_camel_case_types)] enum Charset_ { /// US ASCII Us_Ascii, /// ISO-8859-1 Iso_8859_1, /// ISO-8859-2 Iso_8859_2, /// ISO-8859-3 Iso_8859_3, /// ISO-8859-4 Iso_8859_4, /// ISO-8859-5 Iso_8859_5, /// ISO-8859-6 Iso_8859_6, /// ISO-8859-7 Iso_8859_7, /// ISO-8859-8 Iso_8859_8, /// ISO-8859-9 Iso_8859_9, /// ISO-8859-10 Iso_8859_10, /// Shift_JIS Shift_Jis, /// EUC-JP Euc_Jp, /// ISO-2022-KR Iso_2022_Kr, /// EUC-KR Euc_Kr, /// ISO-2022-JP Iso_2022_Jp, /// ISO-2022-JP-2 Iso_2022_Jp_2, /// ISO-8859-6-E Iso_8859_6_E, /// ISO-8859-6-I Iso_8859_6_I, /// ISO-8859-8-E Iso_8859_8_E, /// ISO-8859-8-I Iso_8859_8_I, /// GB2312 Gb2312, /// Big5 Big5, /// KOI8-R Koi8_R, _Unknown, } impl Charset { fn name(&self) -> &'static str { match self.0 { Charset_::Us_Ascii => "US-ASCII", Charset_::Iso_8859_1 => "ISO-8859-1", Charset_::Iso_8859_2 => "ISO-8859-2", Charset_::Iso_8859_3 => "ISO-8859-3", Charset_::Iso_8859_4 => "ISO-8859-4", Charset_::Iso_8859_5 => "ISO-8859-5", Charset_::Iso_8859_6 => "ISO-8859-6", Charset_::Iso_8859_7 => "ISO-8859-7", Charset_::Iso_8859_8 => "ISO-8859-8", Charset_::Iso_8859_9 => "ISO-8859-9", Charset_::Iso_8859_10 => "ISO-8859-10", Charset_::Shift_Jis => "Shift-JIS", Charset_::Euc_Jp => "EUC-JP", Charset_::Iso_2022_Kr => "ISO-2022-KR", Charset_::Euc_Kr => "EUC-KR", Charset_::Iso_2022_Jp => "ISO-2022-JP", Charset_::Iso_2022_Jp_2 => "ISO-2022-JP-2", Charset_::Iso_8859_6_E => "ISO-8859-6-E", Charset_::Iso_8859_6_I => "ISO-8859-6-I", Charset_::Iso_8859_8_E => "ISO-8859-8-E", Charset_::Iso_8859_8_I => "ISO-8859-8-I", Charset_::Gb2312 => "GB2312", Charset_::Big5 => "5", Charset_::Koi8_R => "KOI8-R", Charset_::_Unknown => unreachable!("Charset::_Unknown"), } } } impl fmt::Display for Charset { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(self.name()) } } #[derive(Debug)] pub struct CharsetFromStrError(()); impl FromStr for Charset { type Err = CharsetFromStrError; fn from_str(s: &str) -> Result { Ok(Charset(match s.to_ascii_uppercase().as_ref() { "US-ASCII" => Charset_::Us_Ascii, "ISO-8859-1" => Charset_::Iso_8859_1, "ISO-8859-2" => Charset_::Iso_8859_2, "ISO-8859-3" => Charset_::Iso_8859_3, "ISO-8859-4" => Charset_::Iso_8859_4, "ISO-8859-5" => Charset_::Iso_8859_5, "ISO-8859-6" => Charset_::Iso_8859_6, "ISO-8859-7" => Charset_::Iso_8859_7, "ISO-8859-8" => Charset_::Iso_8859_8, "ISO-8859-9" => Charset_::Iso_8859_9, "ISO-8859-10" => Charset_::Iso_8859_10, "SHIFT-JIS" => Charset_::Shift_Jis, "EUC-JP" => Charset_::Euc_Jp, "ISO-2022-KR" => Charset_::Iso_2022_Kr, "EUC-KR" => Charset_::Euc_Kr, "ISO-2022-JP" => Charset_::Iso_2022_Jp, "ISO-2022-JP-2" => Charset_::Iso_2022_Jp_2, "ISO-8859-6-E" => Charset_::Iso_8859_6_E, "ISO-8859-6-I" => Charset_::Iso_8859_6_I, "ISO-8859-8-E" => Charset_::Iso_8859_8_E, "ISO-8859-8-I" => Charset_::Iso_8859_8_I, "GB2312" => Charset_::Gb2312, "5" => Charset_::Big5, "KOI8-R" => Charset_::Koi8_R, _unknown => return Err(CharsetFromStrError(())), })) } } #[test] fn test_parse() { assert_eq!(Charset::US_ASCII,"us-ascii".parse().unwrap()); assert_eq!(Charset::US_ASCII,"US-Ascii".parse().unwrap()); assert_eq!(Charset::US_ASCII,"US-ASCII".parse().unwrap()); assert_eq!(Charset::SHIFT_JIS,"Shift-JIS".parse().unwrap()); assert!("abcd".parse(::().is_err()); } #[test] fn test_display() { assert_eq!("US-ASCII", format!("{}", Charset::US_ASCII)); } headers-0.3.9/src/disabled/util/encoding.rs000064400000000000000000000030301046102023000167130ustar 00000000000000use std::fmt; use std::str; pub use self::Encoding::{Chunked, Brotli, Gzip, Deflate, Compress, Identity, EncodingExt, Trailers}; /// A value to represent an encoding used in `Transfer-Encoding` /// or `Accept-Encoding` header. #[derive(Clone, PartialEq, Debug)] pub enum Encoding { /// The `chunked` encoding. Chunked, /// The `br` encoding. Brotli, /// The `gzip` encoding. Gzip, /// The `deflate` encoding. Deflate, /// The `compress` encoding. Compress, /// The `identity` encoding. Identity, /// The `trailers` encoding. Trailers, /// Some other encoding that is less common, can be any String. EncodingExt(String) } impl fmt::Display for Encoding { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(match *self { Chunked => "chunked", Brotli => "br", Gzip => "gzip", Deflate => "deflate", Compress => "compress", Identity => "identity", Trailers => "trailers", EncodingExt(ref s) => s.as_ref() }) } } impl str::FromStr for Encoding { type Err = ::Error; fn from_str(s: &str) -> ::Result { match s { "chunked" => Ok(Chunked), "br" => Ok(Brotli), "deflate" => Ok(Deflate), "gzip" => Ok(Gzip), "compress" => Ok(Compress), "identity" => Ok(Identity), "trailers" => Ok(Trailers), _ => Ok(EncodingExt(s.to_owned())) } } } headers-0.3.9/src/disabled/util/extended_value.rs000064400000000000000000000162061046102023000201320ustar 00000000000000/// An extended header parameter value (i.e., tagged with a character set and optionally, /// a language), as defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). #[derive(Clone, Debug, PartialEq)] pub struct ExtendedValue { /// The character set that is used to encode the `value` to a string. pub charset: Charset, /// The human language details of the `value`, if available. pub language_tag: Option, /// The parameter value, as expressed in octets. pub value: Vec, } /// Parses extended header parameter values (`ext-value`), as defined in /// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). /// /// Extended values are denoted by parameter names that end with `*`. /// /// ## ABNF /// /// ```text /// ext-value = charset "'" [ language ] "'" value-chars /// ; like RFC 2231's /// ; (see [RFC2231], Section 7) /// /// charset = "UTF-8" / "ISO-8859-1" / mime-charset /// /// mime-charset = 1*mime-charsetc /// mime-charsetc = ALPHA / DIGIT /// / "!" / "#" / "$" / "%" / "&" /// / "+" / "-" / "^" / "_" / "`" /// / "{" / "}" / "~" /// ; as in Section 2.3 of [RFC2978] /// ; except that the single quote is not included /// ; SHOULD be registered in the IANA charset registry /// /// language = /// /// value-chars = *( pct-encoded / attr-char ) /// /// pct-encoded = "%" HEXDIG HEXDIG /// ; see [RFC3986], Section 2.1 /// /// attr-char = ALPHA / DIGIT /// / "!" / "#" / "$" / "&" / "+" / "-" / "." /// / "^" / "_" / "`" / "|" / "~" /// ; token except ( "*" / "'" / "%" ) /// ``` pub fn parse_extended_value(val: &str) -> ::Result { // Break into three pieces separated by the single-quote character let mut parts = val.splitn(3,'\''); // Interpret the first piece as a Charset let charset: Charset = match parts.next() { None => return Err(::Error::Header), Some(n) => try!(FromStr::from_str(n)), }; // Interpret the second piece as a language tag let lang: Option = match parts.next() { None => return Err(::Error::Header), Some("") => None, Some(s) => match s.parse() { Ok(lt) => Some(lt), Err(_) => return Err(::Error::Header), } }; // Interpret the third piece as a sequence of value characters let value: Vec = match parts.next() { None => return Err(::Error::Header), Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), }; Ok(ExtendedValue { charset: charset, language_tag: lang, value: value, }) } impl Display for ExtendedValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let encoded_value = percent_encoding::percent_encode(&self.value[..], self::percent_encoding_http::HTTP_VALUE); if let Some(ref lang) = self.language_tag { write!(f, "{}'{}'{}", self.charset, lang, encoded_value) } else { write!(f, "{}''{}", self.charset, encoded_value) } } } /// Percent encode a sequence of bytes with a character set defined in /// [https://tools.ietf.org/html/rfc5987#section-3.2][url] /// /// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { let encoded = percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); fmt::Display::fmt(&encoded, f) } mod percent_encoding_http { use percent_encoding; // internal module because macro is hard-coded to make a public item // but we don't want to public export this item define_encode_set! { // This encode set is used for HTTP header values and is defined at // https://tools.ietf.org/html/rfc5987#section-3.2 pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', '[', '\\', ']', '{', '}' } } } #[cfg(test)] mod tests { use shared::Charset; use super::{ExtendedValue, parse_extended_value}; use language_tags::LanguageTag; #[test] fn test_parse_extended_value_with_encoding_and_language_tag() { let expected_language_tag = "en".parse::().unwrap(); // RFC 5987, Section 3.2.2 // Extended notation, using the Unicode character U+00A3 (POUND SIGN) let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); assert!(result.is_ok()); let extended_value = result.unwrap(); assert_eq!(Charset::Iso_8859_1, extended_value.charset); assert!(extended_value.language_tag.is_some()); assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); assert_eq!(vec![163, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); } #[test] fn test_parse_extended_value_with_encoding() { // RFC 5987, Section 3.2.2 // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) // and U+20AC (EURO SIGN) let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); assert!(result.is_ok()); let extended_value = result.unwrap(); assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); assert!(extended_value.language_tag.is_none()); assert_eq!(vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't', b'e', b's'], extended_value.value); } #[test] fn test_parse_extended_value_missing_language_tag_and_encoding() { // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 let result = parse_extended_value("foo%20bar.html"); assert!(result.is_err()); } #[test] fn test_parse_extended_value_partially_formatted() { let result = parse_extended_value("UTF-8'missing third part"); assert!(result.is_err()); } #[test] fn test_parse_extended_value_partially_formatted_blank() { let result = parse_extended_value("blank second part'"); assert!(result.is_err()); } #[test] fn test_fmt_extended_value_with_encoding_and_language_tag() { let extended_value = ExtendedValue { charset: Charset::Iso_8859_1, language_tag: Some("en".parse().expect("Could not parse language tag")), value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], }; assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); } #[test] fn test_fmt_extended_value_with_encoding() { let extended_value = ExtendedValue { charset: Charset::Ext("UTF-8".to_string()), language_tag: None, value: vec![194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't', b'e', b's'], }; assert_eq!("UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", format!("{}", extended_value)); } } headers-0.3.9/src/disabled/util/quality_value.rs000064400000000000000000000177071046102023000200310ustar 00000000000000#[allow(unused, deprecated)] use std::ascii::AsciiExt; use std::cmp; use std::default::Default; use std::fmt; use std::str; #[cfg(test)] use self::internal::IntoQuality; /// Represents a quality used in quality values. /// /// Can be created with the `q` function. /// /// # Implementation notes /// /// The quality value is defined as a number between 0 and 1 with three decimal places. This means /// there are 1001 possible values. Since floating point numbers are not exact and the smallest /// floating point data type (`f32`) consumes four bytes, hyper uses an `u16` value to store the /// quality internally. For performance reasons you may set quality directly to a value between /// 0 and 1000 e.g. `Quality(532)` matches the quality `q=0.532`. /// /// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) /// gives more information on quality values in HTTP header fields. #[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] pub struct Quality(u16); impl Default for Quality { fn default() -> Quality { Quality(1000) } } /// Represents an item with a quality value as defined in /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). #[derive(Clone, PartialEq, Debug)] pub struct QualityValue { /// The actual contents of the field. value: T, /// The quality (client or server preference) for the value. quality: Quality, } impl QualityValue { /// Creates a new `QualityValue` from an item and a quality. pub fn new(value: T, quality: Quality) -> QualityValue { QualityValue { value, quality, } } /* /// Convenience function to set a `Quality` from a float or integer. /// /// Implemented for `u16` and `f32`. /// /// # Panic /// /// Panics if value is out of range. pub fn with_q(mut self, q: Q) -> QualityValue { self.quality = q.into_quality(); self } */ } impl From for QualityValue { fn from(value: T) -> QualityValue { QualityValue { value, quality: Quality::default(), } } } impl cmp::PartialOrd for QualityValue { fn partial_cmp(&self, other: &QualityValue) -> Option { self.quality.partial_cmp(&other.quality) } } impl fmt::Display for QualityValue { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.value, f)?; match self.quality.0 { 1000 => Ok(()), 0 => f.write_str("; q=0"), x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')) } } } impl str::FromStr for QualityValue { type Err = ::Error; fn from_str(s: &str) -> ::Result> { // Set defaults used if parsing fails. let mut raw_item = s; let mut quality = 1f32; let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); if parts.len() == 2 { if parts[0].len() < 2 { return Err(::Error::invalid()); } if parts[0].starts_with("q=") || parts[0].starts_with("Q=") { let q_part = &parts[0][2..parts[0].len()]; if q_part.len() > 5 { return Err(::Error::invalid()); } match q_part.parse::() { Ok(q_value) => { if 0f32 <= q_value && q_value <= 1f32 { quality = q_value; raw_item = parts[1]; } else { return Err(::Error::invalid()); } }, Err(_) => { return Err(::Error::invalid()) }, } } } match raw_item.parse::() { // we already checked above that the quality is within range Ok(item) => Ok(QualityValue::new(item, from_f32(quality))), Err(_) => { Err(::Error::invalid()) }, } } } #[inline] fn from_f32(f: f32) -> Quality { // this function is only used internally. A check that `f` is within range // should be done before calling this method. Just in case, this // debug_assert should catch if we were forgetful debug_assert!(f >= 0f32 && f <= 1f32, "q value must be between 0.0 and 1.0"); Quality((f * 1000f32) as u16) } #[cfg(test)] fn q(val: T) -> Quality { val.into_quality() } mod internal { use super::Quality; // TryFrom is probably better, but it's not stable. For now, we want to // keep the functionality of the `q` function, while allowing it to be // generic over `f32` and `u16`. // // `q` would panic before, so keep that behavior. `TryFrom` can be // introduced later for a non-panicking conversion. pub trait IntoQuality: Sealed + Sized { fn into_quality(self) -> Quality; } impl IntoQuality for f32 { fn into_quality(self) -> Quality { assert!(self >= 0f32 && self <= 1f32, "float must be between 0.0 and 1.0"); super::from_f32(self) } } impl IntoQuality for u16 { fn into_quality(self) -> Quality { assert!(self <= 1000, "u16 must be between 0 and 1000"); Quality(self) } } pub trait Sealed {} impl Sealed for u16 {} impl Sealed for f32 {} } #[cfg(test)] mod tests { use super::*; #[test] fn test_quality_item_fmt_q_1() { let x = QualityValue::from("foo"); assert_eq!(format!("{}", x), "foo"); } #[test] fn test_quality_item_fmt_q_0001() { let x = QualityValue::new("foo", Quality(1)); assert_eq!(format!("{}", x), "foo; q=0.001"); } #[test] fn test_quality_item_fmt_q_05() { let x = QualityValue::new("foo", Quality(500)); assert_eq!(format!("{}", x), "foo; q=0.5"); } #[test] fn test_quality_item_fmt_q_0() { let x = QualityValue::new("foo", Quality(0)); assert_eq!(x.to_string(), "foo; q=0"); } #[test] fn test_quality_item_from_str1() { let x: QualityValue = "chunked".parse().unwrap(); assert_eq!(x, QualityValue { value: "chunked".to_owned(), quality: Quality(1000), }); } #[test] fn test_quality_item_from_str2() { let x: QualityValue = "chunked; q=1".parse().unwrap(); assert_eq!(x, QualityValue { value: "chunked".to_owned(), quality: Quality(1000), }); } #[test] fn test_quality_item_from_str3() { let x: QualityValue = "gzip; q=0.5".parse().unwrap(); assert_eq!(x, QualityValue { value: "gzip".to_owned(), quality: Quality(500), }); } #[test] fn test_quality_item_from_str4() { let x: QualityValue = "gzip; q=0.273".parse().unwrap(); assert_eq!(x, QualityValue { value: "gzip".to_owned(), quality: Quality(273), }); } #[test] fn test_quality_item_from_str5() { assert!("gzip; q=0.2739999".parse::>().is_err()); } #[test] fn test_quality_item_from_str6() { assert!("gzip; q=2".parse::>().is_err()); } #[test] fn test_quality_item_ordering() { let x: QualityValue = "gzip; q=0.5".parse().unwrap(); let y: QualityValue = "gzip; q=0.273".parse().unwrap(); assert!(x > y) } #[test] fn test_quality() { assert_eq!(q(0.5), Quality(500)); } #[test] #[should_panic] fn test_quality_invalid() { q(-1.0); } #[test] #[should_panic] fn test_quality_invalid2() { q(2.0); } #[test] fn test_fuzzing_bugs() { assert!("99999;".parse::>().is_err()); assert!("\x0d;;;=\u{d6aa}==".parse::>().is_ok()) } } headers-0.3.9/src/disabled/warning.rs000064400000000000000000000131601046102023000156220ustar 00000000000000use std::fmt; use std::str::{FromStr}; use {Header, HttpDate, Raw}; use parsing::from_one_raw_str; /// `Warning` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.5) /// /// The `Warning` header field can be be used to carry additional information /// about the status or transformation of a message that might not be reflected /// in the status code. This header is sometimes used as backwards /// compatible way to notify of a deprecated API. /// /// # ABNF /// /// ```text /// Warning = 1#warning-value /// warning-value = warn-code SP warn-agent SP warn-text /// [ SP warn-date ] /// warn-code = 3DIGIT /// warn-agent = ( uri-host [ ":" port ] ) / pseudonym /// ; the name or pseudonym of the server adding /// ; the Warning header field, for use in debugging /// ; a single "-" is recommended when agent unknown /// warn-text = quoted-string /// warn-date = DQUOTE HTTP-date DQUOTE /// ``` /// /// # Example values /// /// * `Warning: 112 - "network down" "Sat, 25 Aug 2012 23:34:45 GMT"` /// * `Warning: 299 - "Deprecated API " "Tue, 15 Nov 1994 08:12:31 GMT"` /// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead."` /// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead." "Tue, 15 Nov 1994 08:12:31 GMT"` /// /// # Examples /// /// ``` /// use headers::{Headers, Warning}; /// /// let mut headers = Headers::new(); /// headers.set( /// Warning{ /// code: 299, /// agent: "api.hyper.rs".to_owned(), /// text: "Deprecated".to_owned(), /// date: None /// } /// ); /// ``` /// /// ``` /// use headers::{Headers, HttpDate, Warning}; /// /// let mut headers = Headers::new(); /// headers.set( /// Warning{ /// code: 299, /// agent: "api.hyper.rs".to_owned(), /// text: "Deprecated".to_owned(), /// date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() /// } /// ); /// ``` /// /// ``` /// use std::time::SystemTime; /// use headers::{Headers, Warning}; /// /// let mut headers = Headers::new(); /// headers.set( /// Warning{ /// code: 199, /// agent: "api.hyper.rs".to_owned(), /// text: "Deprecated".to_owned(), /// date: Some(SystemTime::now().into()) /// } /// ); /// ``` #[derive(PartialEq, Clone, Debug)] pub struct Warning { /// The 3 digit warn code. pub code: u16, /// The name or pseudonym of the server adding this header. pub agent: String, /// The warning message describing the error. pub text: String, /// An optional warning date. pub date: Option } impl Header for Warning { fn header_name() -> &'static str { static NAME: &'static str = "Warning"; NAME } fn parse_header(raw: &Raw) -> ::Result { from_one_raw_str(raw) } fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { f.fmt_line(self) } } impl fmt::Display for Warning { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.date { Some(date) => write!(f, "{:03} {} \"{}\" \"{}\"", self.code, self.agent, self.text, date), None => write!(f, "{:03} {} \"{}\"", self.code, self.agent, self.text) } } } impl FromStr for Warning { type Err = ::Error; fn from_str(s: &str) -> ::Result { let mut warning_split = s.split_whitespace(); let code = match warning_split.next() { Some(c) => match c.parse::() { Ok(c) => c, Err(..) => return Err(::Error::Header) }, None => return Err(::Error::Header) }; let agent = match warning_split.next() { Some(a) => a.to_string(), None => return Err(::Error::Header) }; let mut warning_split = s.split('"').skip(1); let text = match warning_split.next() { Some(t) => t.to_string(), None => return Err(::Error::Header) }; let date = match warning_split.skip(1).next() { Some(d) => d.parse::().ok(), None => None // Optional }; Ok(Warning { code: code, agent: agent, text: text, date: date }) } } #[cfg(test)] mod tests { use super::Warning; use {Header, HttpDate}; #[test] fn test_parsing() { let warning = Header::parse_header(&vec![b"112 - \"network down\" \"Sat, 25 Aug 2012 23:34:45 GMT\"".to_vec()].into()); assert_eq!(warning.ok(), Some(Warning { code: 112, agent: "-".to_owned(), text: "network down".to_owned(), date: "Sat, 25 Aug 2012 23:34:45 GMT".parse::().ok() })); let warning = Header::parse_header(&vec![b"299 api.hyper.rs:8080 \"Deprecated API : use newapi.hyper.rs instead.\"".to_vec()].into()); assert_eq!(warning.ok(), Some(Warning { code: 299, agent: "api.hyper.rs:8080".to_owned(), text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), date: None })); let warning = Header::parse_header(&vec![b"299 api.hyper.rs:8080 \"Deprecated API : use newapi.hyper.rs instead.\" \"Tue, 15 Nov 1994 08:12:31 GMT\"".to_vec()].into()); assert_eq!(warning.ok(), Some(Warning { code: 299, agent: "api.hyper.rs:8080".to_owned(), text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() })); } } headers-0.3.9/src/lib.rs000064400000000000000000000047651046102023000131670ustar 00000000000000#![deny(missing_docs)] #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] #![doc(html_root_url = "https://docs.rs/headers/0.3.9")] //! # Typed HTTP Headers //! //! hyper has the opinion that headers should be strongly-typed, because that's //! why we're using Rust in the first place. To set or get any header, an object //! must implement the `Header` trait from this module. Several common headers //! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. //! //! # Why Typed? //! //! Or, why not stringly-typed? Types give the following advantages: //! //! - More difficult to typo, since typos in types should be caught by the compiler //! - Parsing to a proper type by default //! //! # Defining Custom Headers //! //! ## Implementing the `Header` trait //! //! Consider a Do Not Track header. It can be true or false, but it represents //! that via the numerals `1` and `0`. //! //! ``` //! extern crate http; //! extern crate headers; //! //! use headers::{Header, HeaderName, HeaderValue}; //! //! struct Dnt(bool); //! //! impl Header for Dnt { //! fn name() -> &'static HeaderName { //! &http::header::DNT //! } //! //! fn decode<'i, I>(values: &mut I) -> Result //! where //! I: Iterator, //! { //! let value = values //! .next() //! .ok_or_else(headers::Error::invalid)?; //! //! if value == "0" { //! Ok(Dnt(false)) //! } else if value == "1" { //! Ok(Dnt(true)) //! } else { //! Err(headers::Error::invalid()) //! } //! } //! //! fn encode(&self, values: &mut E) //! where //! E: Extend, //! { //! let s = if self.0 { //! "1" //! } else { //! "0" //! }; //! //! let value = HeaderValue::from_static(s); //! //! values.extend(std::iter::once(value)); //! } //! } //! ``` extern crate base64; extern crate bytes; extern crate headers_core; extern crate http; extern crate httpdate; extern crate mime; extern crate sha1; #[cfg(all(test, feature = "nightly"))] extern crate test; pub use headers_core::{Error, Header}; #[doc(hidden)] pub use http::HeaderMap; #[doc(hidden)] pub use http::header::{HeaderName, HeaderValue}; #[macro_use] mod util; mod common; mod map_ext; pub use self::common::*; pub use self::map_ext::HeaderMapExt; headers-0.3.9/src/map_ext.rs000064400000000000000000000044561046102023000140530ustar 00000000000000use super::{Error, Header, HeaderValue}; use http; /// An extension trait adding "typed" methods to `http::HeaderMap`. pub trait HeaderMapExt: self::sealed::Sealed { /// Inserts the typed `Header` into this `HeaderMap`. fn typed_insert(&mut self, header: H) where H: Header; /// Tries to find the header by name, and then decode it into `H`. fn typed_get(&self) -> Option where H: Header; /// Tries to find the header by name, and then decode it into `H`. fn typed_try_get(&self) -> Result, Error> where H: Header; } impl HeaderMapExt for http::HeaderMap { fn typed_insert(&mut self, header: H) where H: Header, { let entry = self.entry(H::name()); let mut values = ToValues { state: State::First(entry), }; header.encode(&mut values); } fn typed_get(&self) -> Option where H: Header, { HeaderMapExt::typed_try_get(self).unwrap_or(None) } fn typed_try_get(&self) -> Result, Error> where H: Header, { let mut values = self.get_all(H::name()).iter(); if values.size_hint() == (0, Some(0)) { Ok(None) } else { H::decode(&mut values).map(Some) } } } struct ToValues<'a> { state: State<'a>, } #[derive(Debug)] enum State<'a> { First(http::header::Entry<'a, HeaderValue>), Latter(http::header::OccupiedEntry<'a, HeaderValue>), Tmp, } impl<'a> Extend for ToValues<'a> { fn extend>(&mut self, iter: T) { for value in iter { let entry = match ::std::mem::replace(&mut self.state, State::Tmp) { State::First(http::header::Entry::Occupied(mut e)) => { e.insert(value); e } State::First(http::header::Entry::Vacant(e)) => e.insert_entry(value), State::Latter(mut e) => { e.append(value); e } State::Tmp => unreachable!("ToValues State::Tmp"), }; self.state = State::Latter(entry); } } } mod sealed { pub trait Sealed {} impl Sealed for ::http::HeaderMap {} } headers-0.3.9/src/util/csv.rs000064400000000000000000000020561046102023000141600ustar 00000000000000use std::fmt; /// Reads a comma-delimited raw header into a Vec. pub(crate) fn from_comma_delimited<'i, I, T, E>(values: &mut I) -> Result where I: Iterator, T: ::std::str::FromStr, E: ::std::iter::FromIterator, { values .flat_map(|value| { value.to_str().into_iter().flat_map(|string| { string .split(',') .filter_map(|x| match x.trim() { "" => None, y => Some(y), }) .map(|x| x.parse().map_err(|_| ::Error::invalid())) }) }) .collect() } /// Format an array into a comma-delimited string. pub(crate) fn fmt_comma_delimited( f: &mut fmt::Formatter, mut iter: impl Iterator, ) -> fmt::Result { if let Some(part) = iter.next() { fmt::Display::fmt(&part, f)?; } for part in iter { f.write_str(", ")?; fmt::Display::fmt(&part, f)?; } Ok(()) } headers-0.3.9/src/util/entity.rs000064400000000000000000000250231046102023000147000ustar 00000000000000use std::fmt; use super::{FlatCsv, IterExt}; use HeaderValue; /// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) /// /// An entity tag consists of a string enclosed by two literal double quotes. /// Preceding the first double quote is an optional weakness indicator, /// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and `W/"xyzzy"`. /// /// # ABNF /// /// ```text /// entity-tag = [ weak ] opaque-tag /// weak = %x57.2F ; "W/", case-sensitive /// opaque-tag = DQUOTE *etagc DQUOTE /// etagc = %x21 / %x23-7E / obs-text /// ; VCHAR except double quotes, plus obs-text /// ``` /// /// # Comparison /// To check if two entity tags are equivalent in an application always use the `strong_eq` or /// `weak_eq` methods based on the context of the Tag. Only use `==` to check if two tags are /// identical. /// /// The example below shows the results for a set of entity-tag pairs and /// both the weak and strong comparison function results: /// /// | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | /// |---------|---------|-------------------|-----------------| /// | `W/"1"` | `W/"1"` | no match | match | /// | `W/"1"` | `W/"2"` | no match | no match | /// | `W/"1"` | `"1"` | no match | match | /// | `"1"` | `"1"` | match | match | #[derive(Clone, Eq, PartialEq)] pub(crate) struct EntityTag(T); #[derive(Clone, Debug, PartialEq)] pub(crate) enum EntityTagRange { Any, Tags(FlatCsv), } // ===== impl EntityTag ===== impl> EntityTag { /// Get the tag. pub(crate) fn tag(&self) -> &[u8] { let bytes = self.0.as_ref(); let end = bytes.len() - 1; if bytes[0] == b'W' { // W/"" &bytes[3..end] } else { // "" &bytes[1..end] } } /// Return if this is a "weak" tag. pub(crate) fn is_weak(&self) -> bool { self.0.as_ref()[0] == b'W' } /// For strong comparison two entity-tags are equivalent if both are not weak and their /// opaque-tags match character-by-character. pub(crate) fn strong_eq(&self, other: &EntityTag) -> bool where R: AsRef<[u8]>, { !self.is_weak() && !other.is_weak() && self.tag() == other.tag() } /// For weak comparison two entity-tags are equivalent if their /// opaque-tags match character-by-character, regardless of either or /// both being tagged as "weak". pub(crate) fn weak_eq(&self, other: &EntityTag) -> bool where R: AsRef<[u8]>, { self.tag() == other.tag() } /// The inverse of `EntityTag.strong_eq()`. #[cfg(test)] pub(crate) fn strong_ne(&self, other: &EntityTag) -> bool { !self.strong_eq(other) } /// The inverse of `EntityTag.weak_eq()`. #[cfg(test)] pub(crate) fn weak_ne(&self, other: &EntityTag) -> bool { !self.weak_eq(other) } pub(crate) fn parse(src: T) -> Option { let slice = src.as_ref(); let length = slice.len(); // Early exits if it doesn't terminate in a DQUOTE. if length < 2 || slice[length - 1] != b'"' { return None; } let start = match slice[0] { // "" b'"' => 1, // W/"" b'W' => { if length >= 4 && slice[1] == b'/' && slice[2] == b'"' { 3 } else { return None; } } _ => return None, }; if check_slice_validity(&slice[start..length - 1]) { Some(EntityTag(src)) } else { None } } } impl EntityTag { /* /// Constructs a new EntityTag. /// # Panics /// If the tag contains invalid characters. pub fn new(weak: bool, tag: String) -> EntityTag { assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); EntityTag { weak: weak, tag: tag } } /// Constructs a new weak EntityTag. /// # Panics /// If the tag contains invalid characters. pub fn weak(tag: String) -> EntityTag { EntityTag::new(true, tag) } /// Constructs a new strong EntityTag. /// # Panics /// If the tag contains invalid characters. pub fn strong(tag: String) -> EntityTag { EntityTag::new(false, tag) } */ #[cfg(test)] pub fn from_static(bytes: &'static str) -> EntityTag { let val = HeaderValue::from_static(bytes); match EntityTag::from_val(&val) { Some(tag) => tag, None => { panic!("invalid static string for EntityTag: {:?}", bytes); } } } pub(crate) fn from_owned(val: HeaderValue) -> Option { EntityTag::parse(val.as_bytes())?; Some(EntityTag(val)) } pub(crate) fn from_val(val: &HeaderValue) -> Option { EntityTag::parse(val.as_bytes()).map(|_entity| EntityTag(val.clone())) } } impl fmt::Debug for EntityTag { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } impl super::TryFromValues for EntityTag { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .just_one() .and_then(EntityTag::from_val) .ok_or_else(::Error::invalid) } } impl From for HeaderValue { fn from(tag: EntityTag) -> HeaderValue { tag.0 } } impl<'a> From<&'a EntityTag> for HeaderValue { fn from(tag: &'a EntityTag) -> HeaderValue { tag.0.clone() } } /// check that each char in the slice is either: /// 1. `%x21`, or /// 2. in the range `%x23` to `%x7E`, or /// 3. above `%x80` fn check_slice_validity(slice: &[u8]) -> bool { slice.iter().all(|&c| { // HeaderValue already validates that this doesnt contain control // characters, so we only need to look for DQUOTE (`"`). // // The debug_assert is just in case we use check_slice_validity in // some new context that didnt come from a HeaderValue. debug_assert!( (c >= b'\x21' && c <= b'\x7e') | (c >= b'\x80'), "EntityTag expects HeaderValue to have check for control characters" ); c != b'"' }) } // ===== impl EntityTagRange ===== impl EntityTagRange { pub(crate) fn matches_strong(&self, entity: &EntityTag) -> bool { self.matches_if(entity, |a, b| a.strong_eq(b)) } pub(crate) fn matches_weak(&self, entity: &EntityTag) -> bool { self.matches_if(entity, |a, b| a.weak_eq(b)) } fn matches_if(&self, entity: &EntityTag, func: F) -> bool where F: Fn(&EntityTag<&str>, &EntityTag) -> bool, { match *self { EntityTagRange::Any => true, EntityTagRange::Tags(ref tags) => tags .iter() .flat_map(EntityTag::<&str>::parse) .any(|tag| func(&tag, entity)), } } } impl super::TryFromValues for EntityTagRange { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { let flat = FlatCsv::try_from_values(values)?; if flat.value == "*" { Ok(EntityTagRange::Any) } else { Ok(EntityTagRange::Tags(flat)) } } } impl<'a> From<&'a EntityTagRange> for HeaderValue { fn from(tag: &'a EntityTagRange) -> HeaderValue { match *tag { EntityTagRange::Any => HeaderValue::from_static("*"), EntityTagRange::Tags(ref tags) => tags.into(), } } } #[cfg(test)] mod tests { use super::*; fn parse(slice: &[u8]) -> Option { let val = HeaderValue::from_bytes(slice).ok()?; EntityTag::from_val(&val) } #[test] fn test_etag_parse_success() { // Expected success let tag = parse(b"\"foobar\"").unwrap(); assert!(!tag.is_weak()); assert_eq!(tag.tag(), b"foobar"); let weak = parse(b"W/\"weaktag\"").unwrap(); assert!(weak.is_weak()); assert_eq!(weak.tag(), b"weaktag"); } #[test] fn test_etag_parse_failures() { // Expected failures macro_rules! fails { ($slice:expr) => { assert_eq!(parse($slice), None); }; } fails!(b"no-dquote"); fails!(b"w/\"the-first-w-is-case sensitive\""); fails!(b"W/\""); fails!(b""); fails!(b"\"unmatched-dquotes1"); fails!(b"unmatched-dquotes2\""); fails!(b"\"inner\"quotes\""); } /* #[test] fn test_etag_fmt() { assert_eq!(format!("{}", EntityTag::strong("foobar".to_owned())), "\"foobar\""); assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); assert_eq!(format!("{}", EntityTag::weak("weak-etag".to_owned())), "W/\"weak-etag\""); assert_eq!(format!("{}", EntityTag::weak("\u{0065}".to_owned())), "W/\"\x65\""); assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); } */ #[test] fn test_cmp() { // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | // |---------|---------|-------------------|-----------------| // | `W/"1"` | `W/"1"` | no match | match | // | `W/"1"` | `W/"2"` | no match | no match | // | `W/"1"` | `"1"` | no match | match | // | `"1"` | `"1"` | match | match | let mut etag1 = EntityTag::from_static("W/\"1\""); let mut etag2 = etag1.clone(); assert!(!etag1.strong_eq(&etag2)); assert!(etag1.weak_eq(&etag2)); assert!(etag1.strong_ne(&etag2)); assert!(!etag1.weak_ne(&etag2)); etag2 = EntityTag::from_static("W/\"2\""); assert!(!etag1.strong_eq(&etag2)); assert!(!etag1.weak_eq(&etag2)); assert!(etag1.strong_ne(&etag2)); assert!(etag1.weak_ne(&etag2)); etag2 = EntityTag::from_static("\"1\""); assert!(!etag1.strong_eq(&etag2)); assert!(etag1.weak_eq(&etag2)); assert!(etag1.strong_ne(&etag2)); assert!(!etag1.weak_ne(&etag2)); etag1 = EntityTag::from_static("\"1\""); assert!(etag1.strong_eq(&etag2)); assert!(etag1.weak_eq(&etag2)); assert!(!etag1.strong_ne(&etag2)); assert!(!etag1.weak_ne(&etag2)); } } headers-0.3.9/src/util/flat_csv.rs000064400000000000000000000127371046102023000151750ustar 00000000000000use std::fmt; use std::iter::FromIterator; use std::marker::PhantomData; use bytes::BytesMut; use util::TryFromValues; use HeaderValue; // A single `HeaderValue` that can flatten multiple values with commas. #[derive(Clone, PartialEq, Eq, Hash)] pub(crate) struct FlatCsv { pub(crate) value: HeaderValue, _marker: PhantomData, } pub(crate) trait Separator { const BYTE: u8; const CHAR: char; } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum Comma {} impl Separator for Comma { const BYTE: u8 = b','; const CHAR: char = ','; } #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub(crate) enum SemiColon {} impl Separator for SemiColon { const BYTE: u8 = b';'; const CHAR: char = ';'; } impl FlatCsv { pub(crate) fn iter(&self) -> impl Iterator { self.value.to_str().ok().into_iter().flat_map(|value_str| { let mut in_quotes = false; value_str .split(move |c| { if in_quotes { if c == '"' { in_quotes = false; } false // dont split } else { if c == Sep::CHAR { true // split } else { if c == '"' { in_quotes = true; } false // dont split } } }) .map(|item| item.trim()) }) } } impl TryFromValues for FlatCsv { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { let flat = values.collect(); Ok(flat) } } impl From for FlatCsv { fn from(value: HeaderValue) -> Self { FlatCsv { value, _marker: PhantomData, } } } impl<'a, Sep> From<&'a FlatCsv> for HeaderValue { fn from(flat: &'a FlatCsv) -> HeaderValue { flat.value.clone() } } impl fmt::Debug for FlatCsv { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(&self.value, f) } } impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv { fn from_iter(iter: I) -> Self where I: IntoIterator, { let mut values = iter.into_iter(); // Common case is there is only 1 value, optimize for that if let (1, Some(1)) = values.size_hint() { return values .next() .expect("size_hint claimed 1 item") .clone() .into(); } // Otherwise, there are multiple, so this should merge them into 1. let mut buf = values .next() .cloned() .map(|val| BytesMut::from(val.as_bytes())) .unwrap_or_else(|| BytesMut::new()); for val in values { buf.extend_from_slice(&[Sep::BYTE, b' ']); buf.extend_from_slice(val.as_bytes()); } let val = HeaderValue::from_maybe_shared(buf.freeze()) .expect("comma separated HeaderValues are valid"); val.into() } } // TODO: would be great if there was a way to de-dupe these with above impl FromIterator for FlatCsv { fn from_iter(iter: I) -> Self where I: IntoIterator, { let mut values = iter.into_iter(); // Common case is there is only 1 value, optimize for that if let (1, Some(1)) = values.size_hint() { return values.next().expect("size_hint claimed 1 item").into(); } // Otherwise, there are multiple, so this should merge them into 1. let mut buf = values .next() .map(|val| BytesMut::from(val.as_bytes())) .unwrap_or_else(|| BytesMut::new()); for val in values { buf.extend_from_slice(&[Sep::BYTE, b' ']); buf.extend_from_slice(val.as_bytes()); } let val = HeaderValue::from_maybe_shared(buf.freeze()) .expect("comma separated HeaderValues are valid"); val.into() } } #[cfg(test)] mod tests { use super::*; #[test] fn comma() { let val = HeaderValue::from_static("aaa, b; bb, ccc"); let csv = FlatCsv::::from(val); let mut values = csv.iter(); assert_eq!(values.next(), Some("aaa")); assert_eq!(values.next(), Some("b; bb")); assert_eq!(values.next(), Some("ccc")); assert_eq!(values.next(), None); } #[test] fn semicolon() { let val = HeaderValue::from_static("aaa; b, bb; ccc"); let csv = FlatCsv::::from(val); let mut values = csv.iter(); assert_eq!(values.next(), Some("aaa")); assert_eq!(values.next(), Some("b, bb")); assert_eq!(values.next(), Some("ccc")); assert_eq!(values.next(), None); } #[test] fn quoted_text() { let val = HeaderValue::from_static("foo=\"bar,baz\", sherlock=holmes"); let csv = FlatCsv::::from(val); let mut values = csv.iter(); assert_eq!(values.next(), Some("foo=\"bar,baz\"")); assert_eq!(values.next(), Some("sherlock=holmes")); assert_eq!(values.next(), None); } } headers-0.3.9/src/util/fmt.rs000064400000000000000000000004451046102023000141530ustar 00000000000000use std::fmt::Display; use HeaderValue; pub(crate) fn fmt(fmt: T) -> HeaderValue { let s = fmt.to_string(); match HeaderValue::from_maybe_shared(s) { Ok(val) => val, Err(err) => panic!("illegal HeaderValue; error = {:?}, fmt = \"{}\"", err, fmt), } } headers-0.3.9/src/util/http_date.rs000064400000000000000000000077161046102023000153510ustar 00000000000000use std::fmt; use std::str::FromStr; use std::time::SystemTime; use bytes::Bytes; use http::header::HeaderValue; use httpdate; use super::IterExt; /// A timestamp with HTTP formatting and parsing // Prior to 1995, there were three different formats commonly used by // servers to communicate timestamps. For compatibility with old // implementations, all three are defined here. The preferred format is // a fixed-length and single-zone subset of the date and time // specification used by the Internet Message Format [RFC5322]. // // HTTP-date = IMF-fixdate / obs-date // // An example of the preferred format is // // Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate // // Examples of the two obsolete formats are // // Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format // // A recipient that parses a timestamp value in an HTTP header field // MUST accept all three HTTP-date formats. When a sender generates a // header field that contains one or more timestamps defined as // HTTP-date, the sender MUST generate those timestamps in the // IMF-fixdate format. #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct HttpDate(httpdate::HttpDate); impl HttpDate { pub(crate) fn from_val(val: &HeaderValue) -> Option { val.to_str().ok()?.parse().ok() } } // TODO: remove this and FromStr? #[derive(Debug)] pub struct Error(()); impl super::TryFromValues for HttpDate { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .just_one() .and_then(HttpDate::from_val) .ok_or_else(::Error::invalid) } } impl From for HeaderValue { fn from(date: HttpDate) -> HeaderValue { (&date).into() } } impl<'a> From<&'a HttpDate> for HeaderValue { fn from(date: &'a HttpDate) -> HeaderValue { // TODO: could be just BytesMut instead of String let s = date.to_string(); let bytes = Bytes::from(s); HeaderValue::from_maybe_shared(bytes).expect("HttpDate always is a valid value") } } impl FromStr for HttpDate { type Err = Error; fn from_str(s: &str) -> Result { Ok(HttpDate(s.parse().map_err(|_| Error(()))?)) } } impl fmt::Debug for HttpDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl fmt::Display for HttpDate { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } } impl From for HttpDate { fn from(sys: SystemTime) -> HttpDate { HttpDate(sys.into()) } } impl From for SystemTime { fn from(date: HttpDate) -> SystemTime { SystemTime::from(date.0) } } #[cfg(test)] mod tests { use super::HttpDate; use std::time::{Duration, UNIX_EPOCH}; // The old tests had Sunday, but 1994-11-07 is a Monday. // See https://github.com/pyfisch/httpdate/pull/6#issuecomment-846881001 fn nov_07() -> HttpDate { HttpDate((UNIX_EPOCH + Duration::new(784198117, 0)).into()) } #[test] fn test_display_is_imf_fixdate() { assert_eq!("Mon, 07 Nov 1994 08:48:37 GMT", &nov_07().to_string()); } #[test] fn test_imf_fixdate() { assert_eq!( "Mon, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), nov_07() ); } #[test] fn test_rfc_850() { assert_eq!( "Monday, 07-Nov-94 08:48:37 GMT" .parse::() .unwrap(), nov_07() ); } #[test] fn test_asctime() { assert_eq!( "Mon Nov 7 08:48:37 1994".parse::().unwrap(), nov_07() ); } #[test] fn test_no_date() { assert!("this-is-no-date".parse::().is_err()); } } headers-0.3.9/src/util/iter.rs000064400000000000000000000003771046102023000143340ustar 00000000000000pub trait IterExt: Iterator { fn just_one(&mut self) -> Option { let one = self.next()?; match self.next() { Some(_) => None, None => Some(one), } } } impl IterExt for T {} headers-0.3.9/src/util/mod.rs000064400000000000000000000046471046102023000141540ustar 00000000000000use HeaderValue; //pub use self::charset::Charset; //pub use self::encoding::Encoding; pub(crate) use self::entity::{EntityTag, EntityTagRange}; pub(crate) use self::flat_csv::{FlatCsv, SemiColon}; pub(crate) use self::fmt::fmt; pub(crate) use self::http_date::HttpDate; pub(crate) use self::iter::IterExt; //pub use language_tags::LanguageTag; //pub use self::quality_value::{Quality, QualityValue}; pub(crate) use self::seconds::Seconds; pub(crate) use self::value_string::HeaderValueString; //mod charset; pub(crate) mod csv; //mod encoding; mod entity; mod flat_csv; mod fmt; mod http_date; mod iter; //mod quality_value; mod seconds; mod value_string; macro_rules! error_type { ($name:ident) => { #[doc(hidden)] pub struct $name { _inner: (), } impl ::std::fmt::Debug for $name { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { f.debug_struct(stringify!($name)).finish() } } impl ::std::fmt::Display for $name { fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { f.write_str(stringify!($name)) } } impl ::std::error::Error for $name {} }; } macro_rules! derive_header { ($type:ident(_), name: $name:ident) => { impl crate::Header for $type { fn name() -> &'static ::http::header::HeaderName { &::http::header::$name } fn decode<'i, I>(values: &mut I) -> Result where I: Iterator, { ::util::TryFromValues::try_from_values(values).map($type) } fn encode>(&self, values: &mut E) { values.extend(::std::iter::once((&self.0).into())); } } }; } /// A helper trait for use when deriving `Header`. pub(crate) trait TryFromValues: Sized { /// Try to convert from the values into an instance of `Self`. fn try_from_values<'i, I>(values: &mut I) -> Result where Self: Sized, I: Iterator; } impl TryFromValues for HeaderValue { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values.next().cloned().ok_or_else(|| ::Error::invalid()) } } headers-0.3.9/src/util/seconds.rs000064400000000000000000000027411046102023000150240ustar 00000000000000use std::fmt; use std::time::Duration; use util::IterExt; use HeaderValue; #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct Seconds(Duration); impl Seconds { pub(crate) fn from_val(val: &HeaderValue) -> Option { let secs = val.to_str().ok()?.parse().ok()?; Some(Self::from_secs(secs)) } pub(crate) fn from_secs(secs: u64) -> Self { Self::from(Duration::from_secs(secs)) } pub(crate) fn as_u64(&self) -> u64 { self.0.as_secs() } } impl super::TryFromValues for Seconds { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .just_one() .and_then(Seconds::from_val) .ok_or_else(::Error::invalid) } } impl<'a> From<&'a Seconds> for HeaderValue { fn from(secs: &'a Seconds) -> HeaderValue { secs.0.as_secs().into() } } impl From for Seconds { fn from(dur: Duration) -> Seconds { debug_assert!(dur.subsec_nanos() == 0); Seconds(dur) } } impl From for Duration { fn from(secs: Seconds) -> Duration { secs.0 } } impl fmt::Debug for Seconds { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}s", self.0.as_secs()) } } impl fmt::Display for Seconds { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0.as_secs(), f) } } headers-0.3.9/src/util/value_string.rs000064400000000000000000000050131046102023000160630ustar 00000000000000use std::{ fmt, str::{self, FromStr}, }; use bytes::Bytes; use http::header::HeaderValue; use super::IterExt; /// A value that is both a valid `HeaderValue` and `String`. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub(crate) struct HeaderValueString { /// Care must be taken to only set this value when it is also /// a valid `String`, since `as_str` will convert to a `&str` /// in an unchecked manner. value: HeaderValue, } impl HeaderValueString { pub(crate) fn from_val(val: &HeaderValue) -> Result { if val.to_str().is_ok() { Ok(HeaderValueString { value: val.clone() }) } else { Err(::Error::invalid()) } } pub(crate) fn from_string(src: String) -> Option { // A valid `str` (the argument)... let bytes = Bytes::from(src); HeaderValue::from_maybe_shared(bytes) .ok() .map(|value| HeaderValueString { value }) } pub(crate) fn from_static(src: &'static str) -> HeaderValueString { // A valid `str` (the argument)... HeaderValueString { value: HeaderValue::from_static(src), } } pub(crate) fn as_str(&self) -> &str { // HeaderValueString is only created from HeaderValues // that have validated they are also UTF-8 strings. unsafe { str::from_utf8_unchecked(self.value.as_bytes()) } } } impl fmt::Debug for HeaderValueString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Debug::fmt(self.as_str(), f) } } impl fmt::Display for HeaderValueString { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self.as_str(), f) } } impl super::TryFromValues for HeaderValueString { fn try_from_values<'i, I>(values: &mut I) -> Result where I: Iterator, { values .just_one() .map(HeaderValueString::from_val) .unwrap_or_else(|| Err(::Error::invalid())) } } impl<'a> From<&'a HeaderValueString> for HeaderValue { fn from(src: &'a HeaderValueString) -> HeaderValue { src.value.clone() } } #[derive(Debug)] pub(crate) struct FromStrError(()); impl FromStr for HeaderValueString { type Err = FromStrError; fn from_str(src: &str) -> Result { // A valid `str` (the argument)... src.parse() .map(|value| HeaderValueString { value }) .map_err(|_| FromStrError(())) } }