├── headers-core ├── LICENSE ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── headers-derive ├── LICENSE ├── README.md ├── Cargo.toml └── src │ └── lib.rs ├── .gitignore ├── src ├── util │ ├── iter.rs │ ├── fmt.rs │ ├── seconds.rs │ ├── csv.rs │ ├── mod.rs │ ├── value_string.rs │ ├── http_date.rs │ └── flat_csv.rs ├── common │ ├── sec_websocket_key.rs │ ├── date.rs │ ├── te.rs │ ├── access_control_max_age.rs │ ├── expires.rs │ ├── location.rs │ ├── last_modified.rs │ ├── host.rs │ ├── content_location.rs │ ├── upgrade.rs │ ├── pragma.rs │ ├── proxy_authorization.rs │ ├── allow.rs │ ├── sec_websocket_version.rs │ ├── referer.rs │ ├── age.rs │ ├── sec_websocket_accept.rs │ ├── server.rs │ ├── if_modified_since.rs │ ├── expect.rs │ ├── if_unmodified_since.rs │ ├── user_agent.rs │ ├── vary.rs │ ├── access_control_request_method.rs │ ├── access_control_allow_credentials.rs │ ├── access_control_expose_headers.rs │ ├── access_control_allow_methods.rs │ ├── access_control_request_headers.rs │ ├── content_encoding.rs │ ├── access_control_allow_headers.rs │ ├── accept_ranges.rs │ ├── if_match.rs │ ├── content_length.rs │ ├── transfer_encoding.rs │ ├── if_none_match.rs │ ├── etag.rs │ ├── retry_after.rs │ ├── connection.rs │ ├── set_cookie.rs │ ├── if_range.rs │ ├── access_control_allow_origin.rs │ ├── content_type.rs │ ├── referrer_policy.rs │ ├── mod.rs │ ├── origin.rs │ ├── cookie.rs │ └── content_range.rs ├── disabled │ ├── from.rs │ ├── content_language.rs │ ├── last_event_id.rs │ ├── util │ │ ├── encoding.rs │ │ └── charset.rs │ ├── accept_charset.rs │ ├── accept_language.rs │ ├── accept_encoding.rs │ ├── preference_applied.rs │ ├── accept.rs │ └── warning.rs ├── map_ext.rs └── lib.rs ├── README.md ├── Cargo.toml ├── LICENSE └── .github └── workflows └── ci.yml /headers-core/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /headers-derive/LICENSE: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /headers-core/README.md: -------------------------------------------------------------------------------- 1 | # Typed HTTP Headers: core `Header` trait 2 | 3 | WIP 4 | -------------------------------------------------------------------------------- /headers-derive/README.md: -------------------------------------------------------------------------------- 1 | # Internal derive(Header) macro for `headers` crate 2 | 3 | Doesn't work outside the `headers` crate, nothing to see here. 4 | -------------------------------------------------------------------------------- /src/util/iter.rs: -------------------------------------------------------------------------------- 1 | pub trait IterExt: Iterator { 2 | fn just_one(&mut self) -> Option { 3 | let one = self.next()?; 4 | match self.next() { 5 | Some(_) => None, 6 | None => Some(one), 7 | } 8 | } 9 | } 10 | 11 | impl IterExt for T {} 12 | -------------------------------------------------------------------------------- /src/util/fmt.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Display; 2 | 3 | use http::HeaderValue; 4 | 5 | pub(crate) fn fmt(fmt: T) -> HeaderValue { 6 | let s = fmt.to_string(); 7 | match HeaderValue::from_maybe_shared(s) { 8 | Ok(val) => val, 9 | Err(err) => panic!("illegal HeaderValue; error = {:?}, fmt = \"{}\"", err, fmt), 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust http headers 2 | 3 | [![crates.io](https://img.shields.io/crates/v/headers.svg)](https://crates.io/crates/headers) 4 | [![Released API docs](https://docs.rs/headers/badge.svg)](https://docs.rs/headers) 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE) 6 | [![Build Status](https://github.com/hyperium/headers/workflows/CI/badge.svg)](https://github.com/hyperium/headers/actions?query=workflow%3ACI) 7 | 8 | Typed HTTP headers. 9 | -------------------------------------------------------------------------------- /headers-derive/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "headers-derive" 3 | version = "0.1.1" # don't forget to update html_root_url 4 | description = "derive(Header)" 5 | license = "MIT" 6 | readme = "README.md" 7 | homepage = "https://hyper.rs" 8 | repository = "https://github.com/hyperium/headers" 9 | authors = ["Sean McArthur "] 10 | 11 | [lib] 12 | name = "headers_derive" 13 | proc-macro = true 14 | 15 | [dependencies] 16 | proc-macro2 = "1" 17 | quote = "1" 18 | syn = "1" 19 | -------------------------------------------------------------------------------- /headers-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "headers-core" 3 | version = "0.3.0" # don't forget to update html_root_url 4 | description = "typed HTTP headers core trait" 5 | license = "MIT" 6 | readme = "README.md" 7 | homepage = "https://hyper.rs" 8 | repository = "https://github.com/hyperium/headers" 9 | authors = ["Sean McArthur "] 10 | keywords = ["http", "headers", "hyper", "hyperium"] 11 | edition = "2018" 12 | rust-version = "1.49" 13 | 14 | [dependencies] 15 | http = "1.0.0" 16 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "headers" 3 | version = "0.4.1" 4 | description = "typed HTTP headers" 5 | license = "MIT" 6 | readme = "README.md" 7 | homepage = "https://hyper.rs" 8 | repository = "https://github.com/hyperium/headers" 9 | authors = ["Sean McArthur "] 10 | keywords = ["http", "headers", "hyper", "hyperium"] 11 | categories = ["web-programming"] 12 | edition = "2018" 13 | rust-version = "1.56" 14 | 15 | [workspace] 16 | members = ["headers-core"] 17 | 18 | [dependencies] 19 | http = "1.0.0" 20 | headers-core = { version = "0.3", path = "./headers-core" } 21 | base64 = "0.22" 22 | bytes = "1" 23 | mime = "0.3.14" 24 | sha1 = "0.10" 25 | httpdate = "1" 26 | 27 | [features] 28 | nightly = [] 29 | -------------------------------------------------------------------------------- /src/common/sec_websocket_key.rs: -------------------------------------------------------------------------------- 1 | use base64::{engine::general_purpose::STANDARD, Engine}; 2 | use http::HeaderValue; 3 | 4 | /// The `Sec-Websocket-Key` header. 5 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 6 | pub struct SecWebsocketKey(pub(super) HeaderValue); 7 | 8 | derive_header! { 9 | SecWebsocketKey(_), 10 | name: SEC_WEBSOCKET_KEY 11 | } 12 | 13 | impl From<[u8; 16]> for SecWebsocketKey { 14 | fn from(bytes: [u8; 16]) -> Self { 15 | let mut value = HeaderValue::from_str(&STANDARD.encode(bytes)).unwrap(); 16 | value.set_sensitive(true); 17 | Self(value) 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod tests { 23 | use super::*; 24 | 25 | #[test] 26 | fn from_bytes() { 27 | let bytes: [u8; 16] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]; 28 | let _ = SecWebsocketKey::from(bytes); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/disabled/from.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `From` header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.1) 3 | /// 4 | /// The `From` header field contains an Internet email address for a 5 | /// human user who controls the requesting user agent. The address ought 6 | /// to be machine-usable. 7 | /// 8 | /// # ABNF 9 | /// 10 | /// ```text 11 | /// From = mailbox 12 | /// mailbox = 13 | /// ``` 14 | /// 15 | /// # Example 16 | /// 17 | /// ``` 18 | /// use headers::{Headers, From}; 19 | /// 20 | /// let mut headers = Headers::new(); 21 | /// headers.set(From("webmaster@example.org".to_owned())); 22 | /// ``` 23 | // FIXME: Maybe use mailbox? 24 | (From, FROM) => [String] 25 | 26 | test_from { 27 | test_header!(test1, vec![b"webmaster@example.org"]); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/disabled/content_language.rs: -------------------------------------------------------------------------------- 1 | use util::FlatCsv; 2 | 3 | /// `Content-Language` header, defined in 4 | /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) 5 | /// 6 | /// The `Content-Language` header field describes the natural language(s) 7 | /// of the intended audience for the representation. Note that this 8 | /// might not be equivalent to all the languages used within the 9 | /// representation. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Content-Language = 1#language-tag 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// 19 | /// * `da` 20 | /// * `mi, en` 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// #[macro_use] extern crate language_tags; 26 | /// use headers::ContentLanguage; 27 | /// # 28 | /// # fn main() { 29 | /// let con_lang = ContentLanguage::new([langtag!(en)]) 30 | /// # } 31 | /// ``` 32 | #[derive(Clone, Debug, PartialEq, Header)] 33 | pub struct ContentLanguage(FlatCsv); 34 | 35 | -------------------------------------------------------------------------------- /src/common/date.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use crate::util::HttpDate; 4 | 5 | /// `Date` header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2) 6 | /// 7 | /// The `Date` header field represents the date and time at which the 8 | /// message was originated. 9 | /// 10 | /// ## ABNF 11 | /// 12 | /// ```text 13 | /// Date = HTTP-date 14 | /// ``` 15 | /// 16 | /// ## Example values 17 | /// 18 | /// * `Tue, 15 Nov 1994 08:12:31 GMT` 19 | /// 20 | /// # Example 21 | /// 22 | /// ``` 23 | /// use headers::Date; 24 | /// use std::time::SystemTime; 25 | /// 26 | /// let date = Date::from(SystemTime::now()); 27 | /// ``` 28 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 29 | pub struct Date(HttpDate); 30 | 31 | derive_header! { 32 | Date(_), 33 | name: DATE 34 | } 35 | 36 | impl From for Date { 37 | fn from(time: SystemTime) -> Date { 38 | Date(time.into()) 39 | } 40 | } 41 | 42 | impl From for SystemTime { 43 | fn from(date: Date) -> SystemTime { 44 | date.0.into() 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2025 Sean McArthur 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | 21 | -------------------------------------------------------------------------------- /src/common/te.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use crate::util::FlatCsv; 4 | 5 | /// `TE` header, defined in 6 | /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-4.3) 7 | /// 8 | /// As RFC7230 states, "The "TE" header field in a request indicates what transfer codings, 9 | /// besides chunked, the client is willing to accept in response, and 10 | /// whether or not the client is willing to accept trailer fields in a 11 | /// chunked transfer coding." 12 | /// 13 | /// For HTTP/1.1 compliant clients `chunked` transfer codings are assumed to be acceptable and 14 | /// so should never appear in this header. 15 | /// 16 | /// # ABNF 17 | /// 18 | /// ```text 19 | /// TE = "TE" ":" #( t-codings ) 20 | /// t-codings = "trailers" | ( transfer-extension [ accept-params ] ) 21 | /// ``` 22 | /// 23 | /// # Example values 24 | /// * `trailers` 25 | /// * `trailers, deflate;q=0.5` 26 | /// * `` 27 | /// 28 | /// # Examples 29 | /// 30 | #[derive(Clone, Debug, PartialEq)] 31 | pub struct Te(FlatCsv); 32 | 33 | derive_header! { 34 | Te(_), 35 | name: TE 36 | } 37 | 38 | impl Te { 39 | /// Create a `TE: trailers` header. 40 | pub fn trailers() -> Self { 41 | Te(HeaderValue::from_static("trailers").into()) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/common/access_control_max_age.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::util::Seconds; 4 | 5 | /// `Access-Control-Max-Age` header, part of 6 | /// [CORS](http://www.w3.org/TR/cors/#access-control-max-age-response-header) 7 | /// 8 | /// The `Access-Control-Max-Age` header indicates how long the results of a 9 | /// preflight request can be cached in a preflight result cache. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Access-Control-Max-Age = \"Access-Control-Max-Age\" \":\" delta-seconds 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// 19 | /// * `531` 20 | /// 21 | /// # Examples 22 | /// 23 | /// ``` 24 | /// use std::time::Duration; 25 | /// use headers::AccessControlMaxAge; 26 | /// 27 | /// let max_age = AccessControlMaxAge::from(Duration::from_secs(531)); 28 | /// ``` 29 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 30 | pub struct AccessControlMaxAge(Seconds); 31 | 32 | derive_header! { 33 | AccessControlMaxAge(_), 34 | name: ACCESS_CONTROL_MAX_AGE 35 | } 36 | 37 | impl From for AccessControlMaxAge { 38 | fn from(dur: Duration) -> AccessControlMaxAge { 39 | AccessControlMaxAge(dur.into()) 40 | } 41 | } 42 | 43 | impl From for Duration { 44 | fn from(acma: AccessControlMaxAge) -> Duration { 45 | acma.0.into() 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/common/expires.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use crate::util::HttpDate; 4 | 5 | /// `Expires` header, defined in [RFC7234](https://datatracker.ietf.org/doc/html/rfc7234#section-5.3) 6 | /// 7 | /// The `Expires` header field gives the date/time after which the 8 | /// response is considered stale. 9 | /// 10 | /// The presence of an Expires field does not imply that the original 11 | /// resource will change or cease to exist at, before, or after that 12 | /// time. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Expires = HTTP-date 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `Thu, 01 Dec 1994 16:00:00 GMT` 22 | /// 23 | /// # Example 24 | /// 25 | /// ``` 26 | /// use headers::Expires; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let time = SystemTime::now() + Duration::from_secs(60 * 60 * 24); 30 | /// let expires = Expires::from(time); 31 | /// ``` 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 33 | pub struct Expires(HttpDate); 34 | 35 | derive_header! { 36 | Expires(_), 37 | name: EXPIRES 38 | } 39 | 40 | impl From for Expires { 41 | fn from(time: SystemTime) -> Expires { 42 | Expires(time.into()) 43 | } 44 | } 45 | 46 | impl From for SystemTime { 47 | fn from(date: Expires) -> SystemTime { 48 | date.0.into() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | style: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v4 10 | - uses: dtolnay/rust-toolchain@stable 11 | with: 12 | components: rustfmt 13 | - run: cargo fmt --all --check 14 | 15 | test: 16 | name: Test 17 | runs-on: ubuntu-latest 18 | 19 | strategy: 20 | matrix: 21 | rust: [stable, beta, nightly] 22 | 23 | steps: 24 | - uses: actions/checkout@v4 25 | - uses: dtolnay/rust-toolchain@master 26 | with: 27 | toolchain: ${{ matrix.rust }} 28 | components: rustfmt 29 | - run: cargo test --workspace 30 | - if: matrix.rust == 'nightly' 31 | run: cargo test --benches 32 | 33 | minimal-versions: 34 | runs-on: ubuntu-latest 35 | steps: 36 | - uses: actions/checkout@v4 37 | - uses: dtolnay/rust-toolchain@nightly 38 | - uses: dtolnay/rust-toolchain@stable 39 | - uses: taiki-e/install-action@cargo-hack 40 | - uses: taiki-e/install-action@cargo-minimal-versions 41 | - run: cargo minimal-versions check --workspace 42 | 43 | MSRV: 44 | runs-on: ubuntu-latest 45 | 46 | steps: 47 | - uses: actions/checkout@v4 48 | - uses: taiki-e/install-action@cargo-hack 49 | - run: cargo hack --rust-version --no-dev-deps check --workspace 50 | -------------------------------------------------------------------------------- /src/common/location.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | /// `Location` header, defined in 4 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.2) 5 | /// 6 | /// The `Location` header field is used in some responses to refer to a 7 | /// specific resource in relation to the response. The type of 8 | /// relationship is defined by the combination of request method and 9 | /// status code semantics. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Location = URI-reference 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// * `/People.html#tim` 19 | /// * `http://www.example.net/index.html` 20 | /// 21 | /// # Examples 22 | /// 23 | #[derive(Clone, Debug, PartialEq)] 24 | pub struct Location(HeaderValue); 25 | 26 | derive_header! { 27 | Location(_), 28 | name: LOCATION 29 | } 30 | 31 | #[cfg(test)] 32 | mod tests { 33 | use super::super::test_decode; 34 | use super::*; 35 | 36 | #[test] 37 | fn absolute_uri() { 38 | let s = "http://www.example.net/index.html"; 39 | let loc = test_decode::(&[s]).unwrap(); 40 | 41 | assert_eq!(loc, Location(HeaderValue::from_static(s))); 42 | } 43 | 44 | #[test] 45 | fn relative_uri_with_fragment() { 46 | let s = "/People.html#tim"; 47 | let loc = test_decode::(&[s]).unwrap(); 48 | 49 | assert_eq!(loc, Location(HeaderValue::from_static(s))); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/common/last_modified.rs: -------------------------------------------------------------------------------- 1 | use crate::util::HttpDate; 2 | use std::time::SystemTime; 3 | 4 | /// `Last-Modified` header, defined in 5 | /// [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232#section-2.2) 6 | /// 7 | /// The `Last-Modified` header field in a response provides a timestamp 8 | /// indicating the date and time at which the origin server believes the 9 | /// selected representation was last modified, as determined at the 10 | /// conclusion of handling the request. 11 | /// 12 | /// # ABNF 13 | /// 14 | /// ```text 15 | /// Expires = HTTP-date 16 | /// ``` 17 | /// 18 | /// # Example values 19 | /// 20 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// use headers::LastModified; 26 | /// use std::time::{Duration, SystemTime}; 27 | /// 28 | /// let modified = LastModified::from( 29 | /// SystemTime::now() - Duration::from_secs(60 * 60 * 24) 30 | /// ); 31 | /// ``` 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 33 | pub struct LastModified(pub(super) HttpDate); 34 | 35 | derive_header! { 36 | LastModified(_), 37 | name: LAST_MODIFIED 38 | } 39 | 40 | impl From for LastModified { 41 | fn from(time: SystemTime) -> LastModified { 42 | LastModified(time.into()) 43 | } 44 | } 45 | 46 | impl From for SystemTime { 47 | fn from(date: LastModified) -> SystemTime { 48 | date.0.into() 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/disabled/last_event_id.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use util::HeaderValueString; 4 | 5 | /// `Last-Event-ID` header, defined in 6 | /// [RFC3864](https://html.spec.whatwg.org/multipage/references.html#refsRFC3864) 7 | /// 8 | /// The `Last-Event-ID` header contains information about 9 | /// the last event in an http interaction so that it's easier to 10 | /// track of event state. This is helpful when working 11 | /// with [Server-Sent-Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/). If the connection were to be dropped, for example, it'd 12 | /// be useful to let the server know what the last event you 13 | /// received was. 14 | /// 15 | /// The spec is a String with the id of the last event, it can be 16 | /// an empty string which acts a sort of "reset". 17 | // NOTE: This module is disabled since there is no const LAST_EVENT_ID to be 18 | // used for the `impl Header`. It should be possible to enable this module 19 | // when `HeaderName::from_static` can become a `const fn`. 20 | #[derive(Clone, Debug, PartialEq, Header)] 21 | pub struct LastEventId(HeaderValueString); 22 | 23 | 24 | impl fmt::Display for LastEventId { 25 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | fmt::Display::fmt(&self.0, f) 27 | } 28 | } 29 | 30 | #[cfg(test)] 31 | mod tests { 32 | 33 | /* 34 | // Initial state 35 | test_header!(test1, vec![b""]); 36 | // Own testcase 37 | test_header!(test2, vec![b"1"], Some(LastEventId("1".to_owned()))); 38 | */ 39 | } 40 | 41 | -------------------------------------------------------------------------------- /src/common/host.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::fmt; 3 | 4 | use http::uri::Authority; 5 | use http::{HeaderName, HeaderValue}; 6 | 7 | use crate::{Error, Header}; 8 | 9 | /// The `Host` header. 10 | #[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd)] 11 | pub struct Host(Authority); 12 | 13 | impl Host { 14 | /// Get the hostname, such as example.domain. 15 | pub fn hostname(&self) -> &str { 16 | self.0.host() 17 | } 18 | 19 | /// Get the optional port number. 20 | pub fn port(&self) -> Option { 21 | self.0.port_u16() 22 | } 23 | } 24 | 25 | impl Header for Host { 26 | fn name() -> &'static HeaderName { 27 | &::http::header::HOST 28 | } 29 | 30 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 31 | values 32 | .next() 33 | .cloned() 34 | .and_then(|val| Authority::try_from(val.as_bytes()).ok()) 35 | .map(Host) 36 | .ok_or_else(Error::invalid) 37 | } 38 | 39 | fn encode>(&self, values: &mut E) { 40 | let bytes = self.0.as_str().as_bytes(); 41 | let val = HeaderValue::from_bytes(bytes).expect("Authority is a valid HeaderValue"); 42 | 43 | values.extend(::std::iter::once(val)); 44 | } 45 | } 46 | 47 | impl From for Host { 48 | fn from(auth: Authority) -> Host { 49 | Host(auth) 50 | } 51 | } 52 | 53 | impl fmt::Display for Host { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | fmt::Display::fmt(&self.0, f) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/common/content_location.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | /// `Content-Location` header, defined in 4 | /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.4.2) 5 | /// 6 | /// The header can be used by both the client in requests and the server 7 | /// in responses with different semantics. Client sets `Content-Location` 8 | /// to refer to the URI where original representation of the body was 9 | /// obtained. 10 | /// 11 | /// In responses `Content-Location` represents URI for the representation 12 | /// that was content negotiated, created or for the response payload. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Content-Location = absolute-URI / partial-URI 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// 22 | /// * `/hypertext/Overview.html` 23 | /// * `http://www.example.org/hypertext/Overview.html` 24 | /// 25 | /// # Examples 26 | /// 27 | #[derive(Clone, Debug, PartialEq)] 28 | pub struct ContentLocation(HeaderValue); 29 | 30 | derive_header! { 31 | ContentLocation(_), 32 | name: CONTENT_LOCATION 33 | } 34 | 35 | #[cfg(test)] 36 | mod tests { 37 | use super::super::test_decode; 38 | use super::*; 39 | 40 | #[test] 41 | fn absolute_uri() { 42 | let s = "http://www.example.net/index.html"; 43 | let loc = test_decode::(&[s]).unwrap(); 44 | 45 | assert_eq!(loc, ContentLocation(HeaderValue::from_static(s))); 46 | } 47 | 48 | #[test] 49 | fn relative_uri_with_fragment() { 50 | let s = "/People.html#tim"; 51 | let loc = test_decode::(&[s]).unwrap(); 52 | 53 | assert_eq!(loc, ContentLocation(HeaderValue::from_static(s))); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/common/upgrade.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | /// `Upgrade` header, defined in [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-6.7) 4 | /// 5 | /// The `Upgrade` header field is intended to provide a simple mechanism 6 | /// for transitioning from HTTP/1.1 to some other protocol on the same 7 | /// connection. A client MAY send a list of protocols in the Upgrade 8 | /// header field of a request to invite the server to switch to one or 9 | /// more of those protocols, in order of descending preference, before 10 | /// sending the final response. A server MAY ignore a received Upgrade 11 | /// header field if it wishes to continue using the current protocol on 12 | /// that connection. Upgrade cannot be used to insist on a protocol 13 | /// change. 14 | /// 15 | /// ## ABNF 16 | /// 17 | /// ```text 18 | /// Upgrade = 1#protocol 19 | /// 20 | /// protocol = protocol-name ["/" protocol-version] 21 | /// protocol-name = token 22 | /// protocol-version = token 23 | /// ``` 24 | /// 25 | /// ## Example values 26 | /// 27 | /// * `HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11` 28 | /// 29 | /// # Note 30 | /// 31 | /// In practice, the `Upgrade` header is never that complicated. In most cases, 32 | /// it is only ever a single value, such as `"websocket"`. 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use headers::Upgrade; 38 | /// 39 | /// let ws = Upgrade::websocket(); 40 | /// ``` 41 | #[derive(Clone, Debug, PartialEq)] 42 | pub struct Upgrade(HeaderValue); 43 | 44 | derive_header! { 45 | Upgrade(_), 46 | name: UPGRADE 47 | } 48 | 49 | impl Upgrade { 50 | /// Constructs an `Upgrade: websocket` header. 51 | pub fn websocket() -> Upgrade { 52 | Upgrade(HeaderValue::from_static("websocket")) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/disabled/util/encoding.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str; 3 | 4 | pub use self::Encoding::{Chunked, Brotli, Gzip, Deflate, Compress, Identity, EncodingExt, Trailers}; 5 | 6 | /// A value to represent an encoding used in `Transfer-Encoding` 7 | /// or `Accept-Encoding` header. 8 | #[derive(Clone, PartialEq, Debug)] 9 | pub enum Encoding { 10 | /// The `chunked` encoding. 11 | Chunked, 12 | /// The `br` encoding. 13 | Brotli, 14 | /// The `gzip` encoding. 15 | Gzip, 16 | /// The `deflate` encoding. 17 | Deflate, 18 | /// The `compress` encoding. 19 | Compress, 20 | /// The `identity` encoding. 21 | Identity, 22 | /// The `trailers` encoding. 23 | Trailers, 24 | /// Some other encoding that is less common, can be any String. 25 | EncodingExt(String) 26 | } 27 | 28 | impl fmt::Display for Encoding { 29 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 30 | f.write_str(match *self { 31 | Chunked => "chunked", 32 | Brotli => "br", 33 | Gzip => "gzip", 34 | Deflate => "deflate", 35 | Compress => "compress", 36 | Identity => "identity", 37 | Trailers => "trailers", 38 | EncodingExt(ref s) => s.as_ref() 39 | }) 40 | } 41 | } 42 | 43 | impl str::FromStr for Encoding { 44 | type Err = ::Error; 45 | fn from_str(s: &str) -> ::Result { 46 | match s { 47 | "chunked" => Ok(Chunked), 48 | "br" => Ok(Brotli), 49 | "deflate" => Ok(Deflate), 50 | "gzip" => Ok(Gzip), 51 | "compress" => Ok(Compress), 52 | "identity" => Ok(Identity), 53 | "trailers" => Ok(Trailers), 54 | _ => Ok(EncodingExt(s.to_owned())) 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/util/seconds.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::time::Duration; 3 | 4 | use http::HeaderValue; 5 | 6 | use crate::util::IterExt; 7 | use crate::Error; 8 | 9 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 10 | pub(crate) struct Seconds(Duration); 11 | 12 | impl Seconds { 13 | pub(crate) fn from_val(val: &HeaderValue) -> Option { 14 | let secs = val.to_str().ok()?.parse().ok()?; 15 | 16 | Some(Self::from_secs(secs)) 17 | } 18 | 19 | pub(crate) fn from_secs(secs: u64) -> Self { 20 | Self::from(Duration::from_secs(secs)) 21 | } 22 | 23 | pub(crate) fn as_u64(&self) -> u64 { 24 | self.0.as_secs() 25 | } 26 | } 27 | 28 | impl super::TryFromValues for Seconds { 29 | fn try_from_values<'i, I>(values: &mut I) -> Result 30 | where 31 | I: Iterator, 32 | { 33 | values 34 | .just_one() 35 | .and_then(Seconds::from_val) 36 | .ok_or_else(Error::invalid) 37 | } 38 | } 39 | 40 | impl<'a> From<&'a Seconds> for HeaderValue { 41 | fn from(secs: &'a Seconds) -> HeaderValue { 42 | secs.0.as_secs().into() 43 | } 44 | } 45 | 46 | impl From for Seconds { 47 | fn from(dur: Duration) -> Seconds { 48 | debug_assert!(dur.subsec_nanos() == 0); 49 | Seconds(dur) 50 | } 51 | } 52 | 53 | impl From for Duration { 54 | fn from(secs: Seconds) -> Duration { 55 | secs.0 56 | } 57 | } 58 | 59 | impl fmt::Debug for Seconds { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | write!(f, "{}s", self.0.as_secs()) 62 | } 63 | } 64 | 65 | impl fmt::Display for Seconds { 66 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 67 | fmt::Display::fmt(&self.0.as_secs(), f) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/common/pragma.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | /// The `Pragma` header defined by HTTP/1.0. 4 | /// 5 | /// > The "Pragma" header field allows backwards compatibility with 6 | /// > HTTP/1.0 caches, so that clients can specify a "no-cache" request 7 | /// > that they will understand (as Cache-Control was not defined until 8 | /// > HTTP/1.1). When the Cache-Control header field is also present and 9 | /// > understood in a request, Pragma is ignored. 10 | /// > In HTTP/1.0, Pragma was defined as an extensible field for 11 | /// > implementation-specified directives for recipients. This 12 | /// > specification deprecates such extensions to improve interoperability. 13 | /// 14 | /// Spec: [https://tools.ietf.org/html/rfc7234#section-5.4][url] 15 | /// 16 | /// [url]: https://tools.ietf.org/html/rfc7234#section-5.4 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// use headers::Pragma; 22 | /// 23 | /// let pragma = Pragma::no_cache(); 24 | /// ``` 25 | #[derive(Clone, Debug, PartialEq)] 26 | pub struct Pragma(HeaderValue); 27 | 28 | derive_header! { 29 | Pragma(_), 30 | name: PRAGMA 31 | } 32 | 33 | impl Pragma { 34 | /// Construct the literal `no-cache` Pragma header. 35 | pub fn no_cache() -> Pragma { 36 | Pragma(HeaderValue::from_static("no-cache")) 37 | } 38 | 39 | /// Return whether this pragma is `no-cache`. 40 | pub fn is_no_cache(&self) -> bool { 41 | self.0 == "no-cache" 42 | } 43 | } 44 | 45 | #[cfg(test)] 46 | mod tests { 47 | use super::super::test_decode; 48 | use super::Pragma; 49 | 50 | #[test] 51 | fn no_cache_is_no_cache() { 52 | assert!(Pragma::no_cache().is_no_cache()); 53 | } 54 | 55 | #[test] 56 | fn etc_is_not_no_cache() { 57 | let ext = test_decode::(&["dexter"]).unwrap(); 58 | assert!(!ext.is_no_cache()); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/common/proxy_authorization.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderName, HeaderValue}; 2 | 3 | use super::authorization::{Authorization, Credentials}; 4 | use crate::{Error, Header}; 5 | 6 | /// `Proxy-Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.4) 7 | /// 8 | /// The `Proxy-Authorization` header field allows a user agent to authenticate 9 | /// itself with an HTTP proxy -- usually, but not necessarily, after 10 | /// receiving a 407 (Proxy Authentication Required) response and the 11 | /// `Proxy-Authenticate` header. Its value consists of credentials containing 12 | /// the authentication information of the user agent for the realm of the 13 | /// resource being requested. 14 | /// 15 | /// # ABNF 16 | /// 17 | /// ```text 18 | /// Proxy-Authorization = credentials 19 | /// ``` 20 | /// 21 | /// # Example values 22 | /// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` 23 | /// * `Bearer fpKL54jvWmEGVoRdCNjG` 24 | /// 25 | /// # Examples 26 | /// 27 | #[derive(Clone, PartialEq, Debug)] 28 | pub struct ProxyAuthorization(pub C); 29 | 30 | impl Header for ProxyAuthorization { 31 | fn name() -> &'static HeaderName { 32 | &::http::header::PROXY_AUTHORIZATION 33 | } 34 | 35 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 36 | Authorization::decode(values).map(|auth| ProxyAuthorization(auth.0)) 37 | } 38 | 39 | fn encode>(&self, values: &mut E) { 40 | let value = self.0.encode(); 41 | debug_assert!( 42 | value.as_bytes().starts_with(C::SCHEME.as_bytes()), 43 | "Credentials::encode should include its scheme: scheme = {:?}, encoded = {:?}", 44 | C::SCHEME, 45 | value, 46 | ); 47 | 48 | values.extend(::std::iter::once(value)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/common/allow.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | use http::{HeaderValue, Method}; 4 | 5 | use crate::util::FlatCsv; 6 | 7 | /// `Allow` header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.1) 8 | /// 9 | /// The `Allow` header field lists the set of methods advertised as 10 | /// supported by the target resource. The purpose of this field is 11 | /// strictly to inform the recipient of valid request methods associated 12 | /// with the resource. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Allow = #method 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `GET, HEAD, PUT` 22 | /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` 23 | /// * `` 24 | /// 25 | /// # Examples 26 | /// 27 | /// ``` 28 | /// extern crate http; 29 | /// use headers::Allow; 30 | /// use http::Method; 31 | /// 32 | /// let allow = vec![Method::GET, Method::POST] 33 | /// .into_iter() 34 | /// .collect::(); 35 | /// ``` 36 | #[derive(Clone, Debug, PartialEq)] 37 | pub struct Allow(FlatCsv); 38 | 39 | derive_header! { 40 | Allow(_), 41 | name: ALLOW 42 | } 43 | 44 | impl Allow { 45 | /// Returns an iterator over `Method`s contained within. 46 | pub fn iter(&self) -> impl Iterator + '_ { 47 | self.0.iter().filter_map(|s| s.parse().ok()) 48 | } 49 | } 50 | 51 | impl FromIterator for Allow { 52 | fn from_iter(iter: I) -> Self 53 | where 54 | I: IntoIterator, 55 | { 56 | let flat = iter 57 | .into_iter() 58 | .map(|method| { 59 | method 60 | .as_str() 61 | .parse::() 62 | .expect("Method is a valid HeaderValue") 63 | }) 64 | .collect(); 65 | Allow(flat) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/common/sec_websocket_version.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderName, HeaderValue}; 2 | 3 | use crate::{Error, Header}; 4 | 5 | /// The `Sec-Websocket-Version` header. 6 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 7 | pub struct SecWebsocketVersion(u8); 8 | 9 | impl SecWebsocketVersion { 10 | /// `Sec-Websocket-Version: 13` 11 | pub const V13: SecWebsocketVersion = SecWebsocketVersion(13); 12 | } 13 | 14 | impl Header for SecWebsocketVersion { 15 | fn name() -> &'static HeaderName { 16 | &::http::header::SEC_WEBSOCKET_VERSION 17 | } 18 | 19 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 20 | values 21 | .next() 22 | .and_then(|value| { 23 | if value == "13" { 24 | Some(SecWebsocketVersion::V13) 25 | } else { 26 | None 27 | } 28 | }) 29 | .ok_or_else(Error::invalid) 30 | } 31 | 32 | fn encode>(&self, values: &mut E) { 33 | debug_assert_eq!(self.0, 13); 34 | 35 | values.extend(::std::iter::once(HeaderValue::from_static("13"))); 36 | } 37 | } 38 | 39 | #[cfg(test)] 40 | mod tests { 41 | use super::super::{test_decode, test_encode}; 42 | use super::SecWebsocketVersion; 43 | 44 | #[test] 45 | fn decode_v13() { 46 | assert_eq!( 47 | test_decode::(&["13"]), 48 | Some(SecWebsocketVersion::V13), 49 | ); 50 | } 51 | 52 | #[test] 53 | fn decode_fail() { 54 | assert_eq!(test_decode::(&["1"]), None,); 55 | } 56 | 57 | #[test] 58 | fn encode_v13() { 59 | let headers = test_encode(SecWebsocketVersion::V13); 60 | assert_eq!(headers["sec-websocket-version"], "13"); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/common/referer.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | 4 | use crate::util::HeaderValueString; 5 | 6 | /// `Referer` header, defined in 7 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.2) 8 | /// 9 | /// The `Referer` \[sic\] header field allows the user agent to specify a 10 | /// URI reference for the resource from which the target URI was obtained 11 | /// (i.e., the "referrer", though the field name is misspelled). A user 12 | /// agent MUST NOT include the fragment and userinfo components of the 13 | /// URI reference, if any, when generating the Referer field value. 14 | /// 15 | /// ## ABNF 16 | /// 17 | /// ```text 18 | /// Referer = absolute-URI / partial-URI 19 | /// ``` 20 | /// 21 | /// ## Example values 22 | /// 23 | /// * `http://www.example.org/hypertext/Overview.html` 24 | /// 25 | /// # Examples 26 | /// 27 | /// ``` 28 | /// use headers::Referer; 29 | /// 30 | /// let r = Referer::from_static("/People.html#tim"); 31 | /// ``` 32 | #[derive(Debug, Clone, PartialEq)] 33 | pub struct Referer(HeaderValueString); 34 | 35 | derive_header! { 36 | Referer(_), 37 | name: REFERER 38 | } 39 | 40 | impl Referer { 41 | /// Create a `Referer` with a static string. 42 | /// 43 | /// # Panic 44 | /// 45 | /// Panics if the string is not a legal header value. 46 | pub const fn from_static(s: &'static str) -> Referer { 47 | Referer(HeaderValueString::from_static(s)) 48 | } 49 | } 50 | 51 | error_type!(InvalidReferer); 52 | 53 | impl FromStr for Referer { 54 | type Err = InvalidReferer; 55 | fn from_str(src: &str) -> Result { 56 | HeaderValueString::from_str(src) 57 | .map(Referer) 58 | .map_err(|_| InvalidReferer { _inner: () }) 59 | } 60 | } 61 | 62 | impl fmt::Display for Referer { 63 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 64 | fmt::Display::fmt(&self.0, f) 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/disabled/accept_charset.rs: -------------------------------------------------------------------------------- 1 | use {Charset, QualityItem}; 2 | 3 | header! { 4 | /// `Accept-Charset` header, defined in 5 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.3) 6 | /// 7 | /// The `Accept-Charset` header field can be sent by a user agent to 8 | /// indicate what charsets are acceptable in textual response content. 9 | /// This field allows user agents capable of understanding more 10 | /// comprehensive or special-purpose charsets to signal that capability 11 | /// to an origin server that is capable of representing information in 12 | /// those charsets. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `iso-8859-5, unicode-1-1;q=0.8` 22 | /// 23 | /// # Examples 24 | /// ``` 25 | /// use headers::{Headers, AcceptCharset, Charset, qitem}; 26 | /// 27 | /// let mut headers = Headers::new(); 28 | /// headers.set( 29 | /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) 30 | /// ); 31 | /// ``` 32 | /// ``` 33 | /// use headers::{Headers, AcceptCharset, Charset, q, QualityItem}; 34 | /// 35 | /// let mut headers = Headers::new(); 36 | /// headers.set( 37 | /// AcceptCharset(vec![ 38 | /// QualityItem::new(Charset::Us_Ascii, q(900)), 39 | /// QualityItem::new(Charset::Iso_8859_10, q(200)), 40 | /// ]) 41 | /// ); 42 | /// ``` 43 | /// ``` 44 | /// use headers::{Headers, AcceptCharset, Charset, qitem}; 45 | /// 46 | /// let mut headers = Headers::new(); 47 | /// headers.set( 48 | /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) 49 | /// ); 50 | /// ``` 51 | (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ 52 | 53 | test_accept_charset { 54 | /// Testcase from RFC 55 | test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/common/age.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use crate::util::Seconds; 4 | 5 | /// `Age` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.1) 6 | /// 7 | /// The "Age" header field conveys the sender's estimate of the amount of 8 | /// time since the response was generated or successfully validated at 9 | /// the origin server. Age values are calculated as specified in 10 | /// [Section 4.2.3](https://tools.ietf.org/html/rfc7234#section-4.2.3). 11 | /// 12 | /// ## ABNF 13 | /// 14 | /// ```text 15 | /// Age = delta-seconds 16 | /// ``` 17 | /// 18 | /// The Age field-value is a non-negative integer, representing time in 19 | /// seconds (see [Section 1.2.1](https://tools.ietf.org/html/rfc7234#section-1.2.1)). 20 | /// 21 | /// The presence of an Age header field implies that the response was not 22 | /// generated or validated by the origin server for this request. 23 | /// However, lack of an Age header field does not imply the origin was 24 | /// contacted, since the response might have been received from an 25 | /// HTTP/1.0 cache that does not implement Age. 26 | /// 27 | /// ## Example values 28 | /// 29 | /// * `3600` 30 | /// 31 | /// # Example 32 | /// 33 | /// ``` 34 | /// use headers::Age; 35 | /// 36 | /// let len = Age::from_secs(60); 37 | /// ``` 38 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 39 | pub struct Age(Seconds); 40 | 41 | derive_header! { 42 | Age(_), 43 | name: AGE 44 | } 45 | 46 | impl Age { 47 | /// Creates a new `Age` header from the specified number of whole seconds. 48 | pub fn from_secs(secs: u64) -> Self { 49 | Self(Seconds::from_secs(secs)) 50 | } 51 | 52 | /// Returns the number of seconds for this `Age` header. 53 | pub fn as_secs(&self) -> u64 { 54 | self.0.as_u64() 55 | } 56 | } 57 | 58 | impl From for Age { 59 | fn from(dur: Duration) -> Self { 60 | Age(Seconds::from(dur)) 61 | } 62 | } 63 | 64 | impl From for Duration { 65 | fn from(age: Age) -> Self { 66 | age.0.into() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/util/csv.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use http::HeaderValue; 4 | 5 | use crate::Error; 6 | 7 | /// Reads a comma-delimited raw header into a Vec. 8 | pub(crate) fn from_comma_delimited<'i, I, T, E>(values: &mut I) -> Result 9 | where 10 | I: Iterator, 11 | T: ::std::str::FromStr, 12 | E: ::std::iter::FromIterator, 13 | { 14 | values 15 | .flat_map(|value| { 16 | value.to_str().into_iter().flat_map(|string| { 17 | let mut in_quotes = false; 18 | string 19 | .split(move |c| { 20 | #[allow(clippy::collapsible_else_if)] 21 | if in_quotes { 22 | if c == '"' { 23 | in_quotes = false; 24 | } 25 | false // dont split 26 | } else { 27 | if c == ',' { 28 | true // split 29 | } else { 30 | if c == '"' { 31 | in_quotes = true; 32 | } 33 | false // dont split 34 | } 35 | } 36 | }) 37 | .filter_map(|x| match x.trim() { 38 | "" => None, 39 | y => Some(y), 40 | }) 41 | .map(|x| x.parse().map_err(|_| Error::invalid())) 42 | }) 43 | }) 44 | .collect() 45 | } 46 | 47 | /// Format an array into a comma-delimited string. 48 | pub(crate) fn fmt_comma_delimited( 49 | f: &mut fmt::Formatter, 50 | mut iter: impl Iterator, 51 | ) -> fmt::Result { 52 | if let Some(part) = iter.next() { 53 | fmt::Display::fmt(&part, f)?; 54 | } 55 | for part in iter { 56 | f.write_str(", ")?; 57 | fmt::Display::fmt(&part, f)?; 58 | } 59 | Ok(()) 60 | } 61 | -------------------------------------------------------------------------------- /headers-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![deny(missing_debug_implementations)] 3 | #![cfg_attr(test, deny(warnings))] 4 | #![doc(html_root_url = "https://docs.rs/headers-core/0.3.0")] 5 | 6 | //! # headers-core 7 | //! 8 | //! This is the core crate of the typed HTTP headers system, providing only 9 | //! the relevant traits. All actual header implementations are in other crates. 10 | 11 | pub use http::header::{self, HeaderName, HeaderValue}; 12 | 13 | use std::error; 14 | use std::fmt::{self, Display, Formatter}; 15 | 16 | /// A trait for any object that will represent a header field and value. 17 | /// 18 | /// This trait represents the construction and identification of headers, 19 | /// and contains trait-object unsafe methods. 20 | pub trait Header { 21 | /// The name of this header. 22 | fn name() -> &'static HeaderName; 23 | 24 | /// Decode this type from an iterator of [`HeaderValue`]s. 25 | fn decode<'i, I>(values: &mut I) -> Result 26 | where 27 | Self: Sized, 28 | I: Iterator; 29 | 30 | /// Encode this type to a [`HeaderValue`], and add it to a container 31 | /// which has [`HeaderValue`] type as each element. 32 | /// 33 | /// This function should be infallible. Any errors converting to a 34 | /// `HeaderValue` should have been caught when parsing or constructing 35 | /// this value. 36 | fn encode>(&self, values: &mut E); 37 | } 38 | 39 | /// Errors trying to decode a header. 40 | #[derive(Debug)] 41 | pub struct Error { 42 | kind: Kind, 43 | } 44 | 45 | #[derive(Debug)] 46 | enum Kind { 47 | Invalid, 48 | } 49 | 50 | impl Error { 51 | /// Create an 'invalid' Error. 52 | pub fn invalid() -> Error { 53 | Error { 54 | kind: Kind::Invalid, 55 | } 56 | } 57 | } 58 | 59 | impl Display for Error { 60 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 61 | match &self.kind { 62 | Kind::Invalid => f.write_str("invalid HTTP header"), 63 | } 64 | } 65 | } 66 | 67 | impl error::Error for Error {} 68 | -------------------------------------------------------------------------------- /src/common/sec_websocket_accept.rs: -------------------------------------------------------------------------------- 1 | use base64::engine::general_purpose::STANDARD as ENGINE; 2 | use base64::Engine; 3 | use bytes::Bytes; 4 | use http::HeaderValue; 5 | use sha1::{Digest, Sha1}; 6 | 7 | use super::SecWebsocketKey; 8 | 9 | /// The `Sec-Websocket-Accept` header. 10 | /// 11 | /// This header is used in the Websocket handshake, sent back by the 12 | /// server indicating a successful handshake. It is a signature 13 | /// of the `Sec-Websocket-Key` header. 14 | /// 15 | /// # Example 16 | /// 17 | /// ```no_run 18 | /// use headers::{SecWebsocketAccept, SecWebsocketKey}; 19 | /// 20 | /// let sec_key: SecWebsocketKey = /* from request headers */ 21 | /// # unimplemented!(); 22 | /// 23 | /// let sec_accept = SecWebsocketAccept::from(sec_key); 24 | /// ``` 25 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 26 | pub struct SecWebsocketAccept(HeaderValue); 27 | 28 | derive_header! { 29 | SecWebsocketAccept(_), 30 | name: SEC_WEBSOCKET_ACCEPT 31 | } 32 | 33 | impl From for SecWebsocketAccept { 34 | fn from(key: SecWebsocketKey) -> SecWebsocketAccept { 35 | sign(key.0.as_bytes()) 36 | } 37 | } 38 | 39 | fn sign(key: &[u8]) -> SecWebsocketAccept { 40 | let mut sha1 = Sha1::default(); 41 | sha1.update(key); 42 | sha1.update(&b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"[..]); 43 | let b64 = Bytes::from(ENGINE.encode(sha1.finalize())); 44 | 45 | let val = HeaderValue::from_maybe_shared(b64).expect("base64 is a valid value"); 46 | 47 | SecWebsocketAccept(val) 48 | } 49 | 50 | #[cfg(test)] 51 | mod tests { 52 | use super::super::{test_decode, test_encode}; 53 | use super::*; 54 | 55 | #[test] 56 | fn key_to_accept() { 57 | // From https://tools.ietf.org/html/rfc6455#section-1.2 58 | let key = test_decode::(&["dGhlIHNhbXBsZSBub25jZQ=="]).expect("key"); 59 | let accept = SecWebsocketAccept::from(key); 60 | let headers = test_encode(accept); 61 | 62 | assert_eq!( 63 | headers["sec-websocket-accept"], 64 | "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" 65 | ); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/common/server.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | 4 | use crate::util::HeaderValueString; 5 | 6 | /// `Server` header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.4.2) 7 | /// 8 | /// The `Server` header field contains information about the software 9 | /// used by the origin server to handle the request, which is often used 10 | /// by clients to help identify the scope of reported interoperability 11 | /// problems, to work around or tailor requests to avoid particular 12 | /// server limitations, and for analytics regarding server or operating 13 | /// system use. An origin server MAY generate a Server field in its 14 | /// responses. 15 | /// 16 | /// # ABNF 17 | /// 18 | /// ```text 19 | /// Server = product *( RWS ( product / comment ) ) 20 | /// ``` 21 | /// 22 | /// # Example values 23 | /// * `CERN/3.0 libwww/2.17` 24 | /// 25 | /// # Example 26 | /// 27 | /// ``` 28 | /// use headers::Server; 29 | /// 30 | /// let server = Server::from_static("hyper/0.12.2"); 31 | /// ``` 32 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 33 | pub struct Server(HeaderValueString); 34 | 35 | derive_header! { 36 | Server(_), 37 | name: SERVER 38 | } 39 | 40 | impl Server { 41 | /// Construct a `Server` from a static string. 42 | /// 43 | /// # Panic 44 | /// 45 | /// Panics if the static string is not a legal header value. 46 | pub const fn from_static(s: &'static str) -> Server { 47 | Server(HeaderValueString::from_static(s)) 48 | } 49 | 50 | /// View this `Server` as a `&str`. 51 | pub fn as_str(&self) -> &str { 52 | self.0.as_str() 53 | } 54 | } 55 | 56 | error_type!(InvalidServer); 57 | 58 | impl FromStr for Server { 59 | type Err = InvalidServer; 60 | fn from_str(src: &str) -> Result { 61 | HeaderValueString::from_str(src) 62 | .map(Server) 63 | .map_err(|_| InvalidServer { _inner: () }) 64 | } 65 | } 66 | 67 | impl fmt::Display for Server { 68 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 69 | fmt::Display::fmt(&self.0, f) 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/common/if_modified_since.rs: -------------------------------------------------------------------------------- 1 | use crate::util::HttpDate; 2 | use std::time::SystemTime; 3 | 4 | /// `If-Modified-Since` header, defined in 5 | /// [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232#section-3.3) 6 | /// 7 | /// The `If-Modified-Since` header field makes a GET or HEAD request 8 | /// method conditional on the selected representation's modification date 9 | /// being more recent than the date provided in the field-value. 10 | /// Transfer of the selected representation's data is avoided if that 11 | /// data has not changed. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// If-Modified-Since = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// use headers::IfModifiedSince; 26 | /// use std::time::{Duration, SystemTime}; 27 | /// 28 | /// let time = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 29 | /// let if_mod = IfModifiedSince::from(time); 30 | /// ``` 31 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 32 | pub struct IfModifiedSince(HttpDate); 33 | 34 | derive_header! { 35 | IfModifiedSince(_), 36 | name: IF_MODIFIED_SINCE 37 | } 38 | 39 | impl IfModifiedSince { 40 | /// Check if the supplied time means the resource has been modified. 41 | pub fn is_modified(&self, last_modified: SystemTime) -> bool { 42 | self.0 < last_modified.into() 43 | } 44 | } 45 | 46 | impl From for IfModifiedSince { 47 | fn from(time: SystemTime) -> IfModifiedSince { 48 | IfModifiedSince(time.into()) 49 | } 50 | } 51 | 52 | impl From for SystemTime { 53 | fn from(date: IfModifiedSince) -> SystemTime { 54 | date.0.into() 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use super::*; 61 | use std::time::Duration; 62 | 63 | #[test] 64 | fn is_modified() { 65 | let newer = SystemTime::now(); 66 | let exact = newer - Duration::from_secs(2); 67 | let older = newer - Duration::from_secs(4); 68 | 69 | let if_mod = IfModifiedSince::from(exact); 70 | assert!(if_mod.is_modified(newer)); 71 | assert!(!if_mod.is_modified(exact)); 72 | assert!(!if_mod.is_modified(older)); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/common/expect.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use http::{HeaderName, HeaderValue}; 4 | 5 | use crate::util::IterExt; 6 | use crate::{Error, Header}; 7 | 8 | /// The `Expect` header. 9 | /// 10 | /// > The "Expect" header field in a request indicates a certain set of 11 | /// > behaviors (expectations) that need to be supported by the server in 12 | /// > order to properly handle this request. The only such expectation 13 | /// > defined by this specification is 100-continue. 14 | /// > 15 | /// > Expect = "100-continue" 16 | /// 17 | /// # Example 18 | /// 19 | /// ``` 20 | /// use headers::Expect; 21 | /// 22 | /// let expect = Expect::CONTINUE; 23 | /// ``` 24 | #[derive(Clone, PartialEq)] 25 | pub struct Expect(()); 26 | 27 | impl Expect { 28 | /// "100-continue" 29 | pub const CONTINUE: Expect = Expect(()); 30 | } 31 | 32 | impl Header for Expect { 33 | fn name() -> &'static HeaderName { 34 | &::http::header::EXPECT 35 | } 36 | 37 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 38 | values 39 | .just_one() 40 | .and_then(|value| { 41 | if value == "100-continue" { 42 | Some(Expect::CONTINUE) 43 | } else { 44 | None 45 | } 46 | }) 47 | .ok_or_else(Error::invalid) 48 | } 49 | 50 | fn encode>(&self, values: &mut E) { 51 | values.extend(::std::iter::once(HeaderValue::from_static("100-continue"))); 52 | } 53 | } 54 | 55 | impl fmt::Debug for Expect { 56 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 57 | f.debug_tuple("Expect").field(&"100-continue").finish() 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use super::super::test_decode; 64 | use super::Expect; 65 | 66 | #[test] 67 | fn expect_continue() { 68 | assert_eq!( 69 | test_decode::(&["100-continue"]), 70 | Some(Expect::CONTINUE), 71 | ); 72 | } 73 | 74 | #[test] 75 | fn expectation_failed() { 76 | assert_eq!(test_decode::(&["sandwich"]), None,); 77 | } 78 | 79 | #[test] 80 | fn too_many_values() { 81 | assert_eq!( 82 | test_decode::(&["100-continue", "100-continue"]), 83 | None, 84 | ); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/common/if_unmodified_since.rs: -------------------------------------------------------------------------------- 1 | use crate::util::HttpDate; 2 | use std::time::SystemTime; 3 | 4 | /// `If-Unmodified-Since` header, defined in 5 | /// [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232#section-3.4) 6 | /// 7 | /// The `If-Unmodified-Since` header field makes the request method 8 | /// conditional on the selected representation's last modification date 9 | /// being earlier than or equal to the date provided in the field-value. 10 | /// This field accomplishes the same purpose as If-Match for cases where 11 | /// the user agent does not have an entity-tag for the representation. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// If-Unmodified-Since = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// 21 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 22 | /// 23 | /// # Example 24 | /// 25 | /// ``` 26 | /// use headers::IfUnmodifiedSince; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let time = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 30 | /// let if_unmod = IfUnmodifiedSince::from(time); 31 | /// ``` 32 | #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 33 | pub struct IfUnmodifiedSince(HttpDate); 34 | 35 | derive_header! { 36 | IfUnmodifiedSince(_), 37 | name: IF_UNMODIFIED_SINCE 38 | } 39 | 40 | impl IfUnmodifiedSince { 41 | /// Check if the supplied time passes the precondtion. 42 | pub fn precondition_passes(&self, last_modified: SystemTime) -> bool { 43 | self.0 >= last_modified.into() 44 | } 45 | } 46 | 47 | impl From for IfUnmodifiedSince { 48 | fn from(time: SystemTime) -> IfUnmodifiedSince { 49 | IfUnmodifiedSince(time.into()) 50 | } 51 | } 52 | 53 | impl From for SystemTime { 54 | fn from(date: IfUnmodifiedSince) -> SystemTime { 55 | date.0.into() 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod tests { 61 | use super::*; 62 | use std::time::Duration; 63 | 64 | #[test] 65 | fn precondition_passes() { 66 | let newer = SystemTime::now(); 67 | let exact = newer - Duration::from_secs(2); 68 | let older = newer - Duration::from_secs(4); 69 | 70 | let if_unmod = IfUnmodifiedSince::from(exact); 71 | assert!(!if_unmod.precondition_passes(newer)); 72 | assert!(if_unmod.precondition_passes(exact)); 73 | assert!(if_unmod.precondition_passes(older)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/common/user_agent.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | 4 | use crate::util::HeaderValueString; 5 | 6 | /// `User-Agent` header, defined in 7 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.5.3) 8 | /// 9 | /// The `User-Agent` header field contains information about the user 10 | /// agent originating the request, which is often used by servers to help 11 | /// identify the scope of reported interoperability problems, to work 12 | /// around or tailor responses to avoid particular user agent 13 | /// limitations, and for analytics regarding browser or operating system 14 | /// use. A user agent SHOULD send a User-Agent field in each request 15 | /// unless specifically configured not to do so. 16 | /// 17 | /// # ABNF 18 | /// 19 | /// ```text 20 | /// User-Agent = product *( RWS ( product / comment ) ) 21 | /// product = token ["/" product-version] 22 | /// product-version = token 23 | /// ``` 24 | /// 25 | /// # Example values 26 | /// 27 | /// * `CERN-LineMode/2.15 libwww/2.17b3` 28 | /// * `Bunnies` 29 | /// 30 | /// # Notes 31 | /// 32 | /// * The parser does not split the value 33 | /// 34 | /// # Example 35 | /// 36 | /// ``` 37 | /// use headers::UserAgent; 38 | /// 39 | /// let ua = UserAgent::from_static("hyper/0.12.2"); 40 | /// ``` 41 | #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] 42 | pub struct UserAgent(HeaderValueString); 43 | 44 | derive_header! { 45 | UserAgent(_), 46 | name: USER_AGENT 47 | } 48 | 49 | impl UserAgent { 50 | /// Create a `UserAgent` from a static string. 51 | /// 52 | /// # Panic 53 | /// 54 | /// Panics if the static string is not a legal header value. 55 | pub const fn from_static(src: &'static str) -> UserAgent { 56 | UserAgent(HeaderValueString::from_static(src)) 57 | } 58 | 59 | /// View this `UserAgent` as a `&str`. 60 | pub fn as_str(&self) -> &str { 61 | self.0.as_str() 62 | } 63 | } 64 | 65 | error_type!(InvalidUserAgent); 66 | 67 | impl FromStr for UserAgent { 68 | type Err = InvalidUserAgent; 69 | fn from_str(src: &str) -> Result { 70 | HeaderValueString::from_str(src) 71 | .map(UserAgent) 72 | .map_err(|_| InvalidUserAgent { _inner: () }) 73 | } 74 | } 75 | 76 | impl fmt::Display for UserAgent { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 78 | fmt::Display::fmt(&self.0, f) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/common/vary.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderName, HeaderValue}; 2 | 3 | use crate::util::FlatCsv; 4 | 5 | /// `Vary` header, defined in [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.4) 6 | /// 7 | /// The "Vary" header field in a response describes what parts of a 8 | /// request message, aside from the method, Host header field, and 9 | /// request target, might influence the origin server's process for 10 | /// selecting and representing this response. The value consists of 11 | /// either a single asterisk ("*") or a list of header field names 12 | /// (case-insensitive). 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Vary = "*" / 1#field-name 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// 22 | /// * `accept-encoding, accept-language` 23 | /// 24 | /// # Example 25 | /// 26 | /// ``` 27 | /// use headers::Vary; 28 | /// 29 | /// let vary = Vary::any(); 30 | /// ``` 31 | #[derive(Debug, Clone, PartialEq)] 32 | pub struct Vary(FlatCsv); 33 | 34 | derive_header! { 35 | Vary(_), 36 | name: VARY 37 | } 38 | 39 | impl Vary { 40 | /// Create a new `Very: *` header. 41 | pub fn any() -> Vary { 42 | Vary(HeaderValue::from_static("*").into()) 43 | } 44 | 45 | /// Check if this includes `*`. 46 | pub fn is_any(&self) -> bool { 47 | self.0.iter().any(|val| val == "*") 48 | } 49 | 50 | /// Iterate the header names of this `Vary`. 51 | pub fn iter_strs(&self) -> impl Iterator { 52 | self.0.iter() 53 | } 54 | } 55 | 56 | impl From for Vary { 57 | fn from(name: HeaderName) -> Self { 58 | Vary(HeaderValue::from(name).into()) 59 | } 60 | } 61 | 62 | /* 63 | test_vary { 64 | test_header!(test1, vec![b"accept-encoding, accept-language"]); 65 | 66 | #[test] 67 | fn test2() { 68 | let mut vary: ::Result; 69 | 70 | vary = Header::parse_header(&"*".into()); 71 | assert_eq!(vary.ok(), Some(Vary::Any)); 72 | 73 | vary = Header::parse_header(&"etag,cookie,allow".into()); 74 | assert_eq!(vary.ok(), Some(Vary::Items(vec!["eTag".parse().unwrap(), 75 | "cookIE".parse().unwrap(), 76 | "AlLOw".parse().unwrap(),]))); 77 | } 78 | } 79 | */ 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::*; 84 | 85 | #[test] 86 | fn any_is_any() { 87 | assert!(Vary::any().is_any()); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/disabled/accept_language.rs: -------------------------------------------------------------------------------- 1 | use language_tags::LanguageTag; 2 | use QualityItem; 3 | 4 | header! { 5 | /// `Accept-Language` header, defined in 6 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.5) 7 | /// 8 | /// The `Accept-Language` header field can be used by user agents to 9 | /// indicate the set of natural languages that are preferred in the 10 | /// response. 11 | /// 12 | /// # ABNF 13 | /// 14 | /// ```text 15 | /// Accept-Language = 1#( language-range [ weight ] ) 16 | /// language-range = 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `da, en-gb;q=0.8, en;q=0.7` 21 | /// * `en-us;q=1.0, en;q=0.5, fr` 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// use headers::{Headers, AcceptLanguage, LanguageTag, qitem}; 27 | /// 28 | /// let mut headers = Headers::new(); 29 | /// let mut langtag: LanguageTag = Default::default(); 30 | /// langtag.language = Some("en".to_owned()); 31 | /// langtag.region = Some("US".to_owned()); 32 | /// headers.set( 33 | /// AcceptLanguage(vec![ 34 | /// qitem(langtag), 35 | /// ]) 36 | /// ); 37 | /// ``` 38 | /// 39 | /// ``` 40 | /// # #[macro_use] extern crate language_tags; 41 | /// # use headers::{Headers, AcceptLanguage, QualityItem, q, qitem}; 42 | /// # 43 | /// # fn main() { 44 | /// let mut headers = Headers::new(); 45 | /// headers.set( 46 | /// AcceptLanguage(vec![ 47 | /// qitem(langtag!(da)), 48 | /// QualityItem::new(langtag!(en;;;GB), q(800)), 49 | /// QualityItem::new(langtag!(en), q(700)), 50 | /// ]) 51 | /// ); 52 | /// # } 53 | /// ``` 54 | (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ 55 | 56 | test_accept_language { 57 | // From the RFC 58 | test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); 59 | // Own test 60 | test_header!( 61 | test2, vec![b"en-US, en; q=0.5, fr"], 62 | Some(AcceptLanguage(vec![ 63 | qitem("en-US".parse().unwrap()), 64 | QualityItem::new("en".parse().unwrap(), q(500)), 65 | qitem("fr".parse().unwrap()), 66 | ]))); 67 | } 68 | } 69 | 70 | bench_header!(bench, AcceptLanguage, 71 | { vec![b"en-us;q=1.0, en;q=0.5, fr".to_vec()] }); 72 | -------------------------------------------------------------------------------- /src/common/access_control_request_method.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderName, HeaderValue, Method}; 2 | 3 | use crate::{Error, Header}; 4 | 5 | /// `Access-Control-Request-Method` header, part of 6 | /// [CORS](http://www.w3.org/TR/cors/#access-control-request-method-request-header) 7 | /// 8 | /// The `Access-Control-Request-Method` header indicates which method will be 9 | /// used in the actual request as part of the preflight request. 10 | /// # ABNF 11 | /// 12 | /// ```text 13 | /// Access-Control-Request-Method: \"Access-Control-Request-Method\" \":\" Method 14 | /// ``` 15 | /// 16 | /// # Example values 17 | /// * `GET` 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// extern crate http; 23 | /// use headers::AccessControlRequestMethod; 24 | /// use http::Method; 25 | /// 26 | /// let req_method = AccessControlRequestMethod::from(Method::GET); 27 | /// ``` 28 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 29 | pub struct AccessControlRequestMethod(Method); 30 | 31 | impl Header for AccessControlRequestMethod { 32 | fn name() -> &'static HeaderName { 33 | &::http::header::ACCESS_CONTROL_REQUEST_METHOD 34 | } 35 | 36 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 37 | values 38 | .next() 39 | .and_then(|value| Method::from_bytes(value.as_bytes()).ok()) 40 | .map(AccessControlRequestMethod) 41 | .ok_or_else(Error::invalid) 42 | } 43 | 44 | fn encode>(&self, values: &mut E) { 45 | // For the more common methods, try to use a static string. 46 | let s = match self.0 { 47 | Method::GET => "GET", 48 | Method::POST => "POST", 49 | Method::PUT => "PUT", 50 | Method::DELETE => "DELETE", 51 | _ => { 52 | let val = HeaderValue::from_str(self.0.as_ref()) 53 | .expect("Methods are also valid HeaderValues"); 54 | values.extend(::std::iter::once(val)); 55 | return; 56 | } 57 | }; 58 | 59 | values.extend(::std::iter::once(HeaderValue::from_static(s))); 60 | } 61 | } 62 | 63 | impl From for AccessControlRequestMethod { 64 | fn from(method: Method) -> AccessControlRequestMethod { 65 | AccessControlRequestMethod(method) 66 | } 67 | } 68 | 69 | impl From for Method { 70 | fn from(method: AccessControlRequestMethod) -> Method { 71 | method.0 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/common/access_control_allow_credentials.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderName, HeaderValue}; 2 | 3 | use crate::{Error, Header}; 4 | 5 | /// `Access-Control-Allow-Credentials` header, part of 6 | /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) 7 | /// 8 | /// > The Access-Control-Allow-Credentials HTTP response header indicates whether the 9 | /// > response to request can be exposed when the credentials flag is true. When part 10 | /// > of the response to an preflight request it indicates that the actual request can 11 | /// > be made with credentials. The Access-Control-Allow-Credentials HTTP header must 12 | /// > match the following ABNF: 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Access-Control-Allow-Credentials: "Access-Control-Allow-Credentials" ":" "true" 18 | /// ``` 19 | /// 20 | /// Since there is only one acceptable field value, the header struct does not accept 21 | /// any values at all. Setting an empty `AccessControlAllowCredentials` header is 22 | /// sufficient. See the examples below. 23 | /// 24 | /// # Example values 25 | /// * "true" 26 | /// 27 | /// # Examples 28 | /// 29 | /// ``` 30 | /// use headers::AccessControlAllowCredentials; 31 | /// 32 | /// let allow_creds = AccessControlAllowCredentials; 33 | /// ``` 34 | #[derive(Clone, PartialEq, Eq, Debug)] 35 | pub struct AccessControlAllowCredentials; 36 | 37 | impl Header for AccessControlAllowCredentials { 38 | fn name() -> &'static HeaderName { 39 | &::http::header::ACCESS_CONTROL_ALLOW_CREDENTIALS 40 | } 41 | 42 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 43 | values 44 | .next() 45 | .and_then(|value| { 46 | if value == "true" { 47 | Some(AccessControlAllowCredentials) 48 | } else { 49 | None 50 | } 51 | }) 52 | .ok_or_else(Error::invalid) 53 | } 54 | 55 | fn encode>(&self, values: &mut E) { 56 | values.extend(::std::iter::once(HeaderValue::from_static("true"))); 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::super::test_decode; 63 | use super::*; 64 | 65 | #[test] 66 | fn allow_credentials_is_case_sensitive() { 67 | let allow_header = test_decode::(&["true"]); 68 | assert!(allow_header.is_some()); 69 | 70 | let allow_header = test_decode::(&["True"]); 71 | assert!(allow_header.is_none()); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/disabled/accept_encoding.rs: -------------------------------------------------------------------------------- 1 | use {Encoding, QualityItem}; 2 | 3 | header! { 4 | /// `Accept-Encoding` header, defined in 5 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.4) 6 | /// 7 | /// The `Accept-Encoding` header field can be used by user agents to 8 | /// indicate what response content-codings are 9 | /// acceptable in the response. An `identity` token is used as a synonym 10 | /// for "no encoding" in order to communicate when no encoding is 11 | /// preferred. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Accept-Encoding = #( codings [ weight ] ) 17 | /// codings = content-coding / "identity" / "*" 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `compress, gzip` 22 | /// * `` 23 | /// * `*` 24 | /// * `compress;q=0.5, gzip;q=1` 25 | /// * `gzip;q=1.0, identity; q=0.5, *;q=0` 26 | /// 27 | /// # Examples 28 | /// ``` 29 | /// use headers::{Headers, AcceptEncoding, Encoding, qitem}; 30 | /// 31 | /// let mut headers = Headers::new(); 32 | /// headers.set( 33 | /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) 34 | /// ); 35 | /// ``` 36 | /// ``` 37 | /// use headers::{Headers, AcceptEncoding, Encoding, qitem}; 38 | /// 39 | /// let mut headers = Headers::new(); 40 | /// headers.set( 41 | /// AcceptEncoding(vec![ 42 | /// qitem(Encoding::Chunked), 43 | /// qitem(Encoding::Gzip), 44 | /// qitem(Encoding::Deflate), 45 | /// ]) 46 | /// ); 47 | /// ``` 48 | /// ``` 49 | /// use headers::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; 50 | /// 51 | /// let mut headers = Headers::new(); 52 | /// headers.set( 53 | /// AcceptEncoding(vec![ 54 | /// qitem(Encoding::Chunked), 55 | /// QualityItem::new(Encoding::Gzip, q(600)), 56 | /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), 57 | /// ]) 58 | /// ); 59 | /// ``` 60 | (AcceptEncoding, ACCEPT_ENCODING) => (QualityItem)* 61 | 62 | test_accept_encoding { 63 | // From the RFC 64 | test_header!(test1, vec![b"compress, gzip"]); 65 | test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); 66 | test_header!(test3, vec![b"*"]); 67 | // Note: Removed quality 1 from gzip 68 | test_header!(test4, vec![b"compress;q=0.5, gzip"]); 69 | // Note: Removed quality 1 from gzip 70 | test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/map_ext.rs: -------------------------------------------------------------------------------- 1 | use super::{Error, Header, HeaderValue}; 2 | 3 | /// An extension trait adding "typed" methods to `http::HeaderMap`. 4 | pub trait HeaderMapExt: self::sealed::Sealed { 5 | /// Inserts the typed `Header` into this `HeaderMap`. 6 | fn typed_insert(&mut self, header: H) 7 | where 8 | H: Header; 9 | 10 | /// Tries to find the header by name, and then decode it into `H`. 11 | fn typed_get(&self) -> Option 12 | where 13 | H: Header; 14 | 15 | /// Tries to find the header by name, and then decode it into `H`. 16 | fn typed_try_get(&self) -> Result, Error> 17 | where 18 | H: Header; 19 | } 20 | 21 | impl HeaderMapExt for http::HeaderMap { 22 | fn typed_insert(&mut self, header: H) 23 | where 24 | H: Header, 25 | { 26 | let entry = self.entry(H::name()); 27 | let mut values = ToValues { 28 | state: State::First(entry), 29 | }; 30 | header.encode(&mut values); 31 | } 32 | 33 | fn typed_get(&self) -> Option 34 | where 35 | H: Header, 36 | { 37 | HeaderMapExt::typed_try_get(self).unwrap_or(None) 38 | } 39 | 40 | fn typed_try_get(&self) -> Result, Error> 41 | where 42 | H: Header, 43 | { 44 | let mut values = self.get_all(H::name()).iter(); 45 | if values.size_hint() == (0, Some(0)) { 46 | Ok(None) 47 | } else { 48 | H::decode(&mut values).map(Some) 49 | } 50 | } 51 | } 52 | 53 | struct ToValues<'a> { 54 | state: State<'a>, 55 | } 56 | 57 | #[derive(Debug)] 58 | enum State<'a> { 59 | First(http::header::Entry<'a, HeaderValue>), 60 | Latter(http::header::OccupiedEntry<'a, HeaderValue>), 61 | Tmp, 62 | } 63 | 64 | impl Extend for ToValues<'_> { 65 | fn extend>(&mut self, iter: T) { 66 | for value in iter { 67 | let entry = match ::std::mem::replace(&mut self.state, State::Tmp) { 68 | State::First(http::header::Entry::Occupied(mut e)) => { 69 | e.insert(value); 70 | e 71 | } 72 | State::First(http::header::Entry::Vacant(e)) => e.insert_entry(value), 73 | State::Latter(mut e) => { 74 | e.append(value); 75 | e 76 | } 77 | State::Tmp => unreachable!("ToValues State::Tmp"), 78 | }; 79 | self.state = State::Latter(entry); 80 | } 81 | } 82 | } 83 | 84 | mod sealed { 85 | pub trait Sealed {} 86 | impl Sealed for ::http::HeaderMap {} 87 | } 88 | -------------------------------------------------------------------------------- /src/common/access_control_expose_headers.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | use http::{HeaderName, HeaderValue}; 4 | 5 | use crate::util::FlatCsv; 6 | 7 | /// `Access-Control-Expose-Headers` header, part of 8 | /// [CORS](http://www.w3.org/TR/cors/#access-control-expose-headers-response-header) 9 | /// 10 | /// The Access-Control-Expose-Headers header indicates which headers are safe to expose to the 11 | /// API of a CORS API specification. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Access-Control-Expose-Headers = "Access-Control-Expose-Headers" ":" #field-name 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `ETag, Content-Length` 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// extern crate http; 26 | /// # fn main() { 27 | /// use http::header::{CONTENT_LENGTH, ETAG}; 28 | /// use headers::AccessControlExposeHeaders; 29 | /// 30 | /// let expose = vec![CONTENT_LENGTH, ETAG] 31 | /// .into_iter() 32 | /// .collect::(); 33 | /// # } 34 | /// ``` 35 | #[derive(Clone, Debug)] 36 | pub struct AccessControlExposeHeaders(FlatCsv); 37 | 38 | derive_header! { 39 | AccessControlExposeHeaders(_), 40 | name: ACCESS_CONTROL_EXPOSE_HEADERS 41 | } 42 | 43 | impl AccessControlExposeHeaders { 44 | /// Returns an iterator over `HeaderName`s contained within. 45 | pub fn iter(&self) -> impl Iterator + '_ { 46 | self.0.iter().filter_map(|s| s.parse().ok()) 47 | } 48 | } 49 | 50 | impl FromIterator for AccessControlExposeHeaders { 51 | fn from_iter(iter: I) -> Self 52 | where 53 | I: IntoIterator, 54 | { 55 | let flat = iter.into_iter().map(HeaderValue::from).collect(); 56 | AccessControlExposeHeaders(flat) 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use super::super::{test_decode, test_encode}; 63 | use super::*; 64 | 65 | #[test] 66 | fn iter() { 67 | let expose_headers = test_decode::(&["foo, bar"]).unwrap(); 68 | 69 | let as_vec = expose_headers.iter().collect::>(); 70 | assert_eq!(as_vec.len(), 2); 71 | assert_eq!(as_vec[0], "foo"); 72 | assert_eq!(as_vec[1], "bar"); 73 | } 74 | 75 | #[test] 76 | fn from_iter() { 77 | let expose: AccessControlExposeHeaders = 78 | vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] 79 | .into_iter() 80 | .collect(); 81 | 82 | let headers = test_encode(expose); 83 | assert_eq!( 84 | headers["access-control-expose-headers"], 85 | "cache-control, if-range" 86 | ); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![deny(missing_debug_implementations)] 3 | #![cfg_attr(test, deny(warnings))] 4 | #![cfg_attr(all(test, feature = "nightly"), feature(test))] 5 | 6 | //! # Typed HTTP Headers 7 | //! 8 | //! hyper has the opinion that headers should be strongly-typed, because that's 9 | //! why we're using Rust in the first place. To set or get any header, an object 10 | //! must implement the `Header` trait from this module. Several common headers 11 | //! are already provided, such as `Host`, `ContentType`, `UserAgent`, and others. 12 | //! 13 | //! # Why Typed? 14 | //! 15 | //! Or, why not stringly-typed? Types give the following advantages: 16 | //! 17 | //! - More difficult to typo, since typos in types should be caught by the compiler 18 | //! - Parsing to a proper type by default 19 | //! 20 | //! # Defining Custom Headers 21 | //! 22 | //! ## Implementing the `Header` trait 23 | //! 24 | //! Consider a Do Not Track header. It can be true or false, but it represents 25 | //! that via the numerals `1` and `0`. 26 | //! 27 | //! ``` 28 | //! extern crate http; 29 | //! extern crate headers; 30 | //! 31 | //! use headers::{Header, HeaderName, HeaderValue}; 32 | //! 33 | //! struct Dnt(bool); 34 | //! 35 | //! impl Header for Dnt { 36 | //! fn name() -> &'static HeaderName { 37 | //! &http::header::DNT 38 | //! } 39 | //! 40 | //! fn decode<'i, I>(values: &mut I) -> Result 41 | //! where 42 | //! I: Iterator, 43 | //! { 44 | //! let value = values 45 | //! .next() 46 | //! .ok_or_else(headers::Error::invalid)?; 47 | //! 48 | //! if value == "0" { 49 | //! Ok(Dnt(false)) 50 | //! } else if value == "1" { 51 | //! Ok(Dnt(true)) 52 | //! } else { 53 | //! Err(headers::Error::invalid()) 54 | //! } 55 | //! } 56 | //! 57 | //! fn encode(&self, values: &mut E) 58 | //! where 59 | //! E: Extend, 60 | //! { 61 | //! let s = if self.0 { 62 | //! "1" 63 | //! } else { 64 | //! "0" 65 | //! }; 66 | //! 67 | //! let value = HeaderValue::from_static(s); 68 | //! 69 | //! values.extend(std::iter::once(value)); 70 | //! } 71 | //! } 72 | //! ``` 73 | 74 | #[cfg(all(test, feature = "nightly"))] 75 | extern crate test; 76 | 77 | pub use headers_core::{Error, Header}; 78 | 79 | pub use mime::Mime; 80 | 81 | #[doc(hidden)] 82 | pub use http::HeaderMap; 83 | 84 | #[doc(hidden)] 85 | pub use http::header::{HeaderName, HeaderValue}; 86 | 87 | #[macro_use] 88 | mod util; 89 | mod common; 90 | mod map_ext; 91 | 92 | pub use self::common::*; 93 | pub use self::map_ext::HeaderMapExt; 94 | -------------------------------------------------------------------------------- /src/common/access_control_allow_methods.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | use http::{HeaderValue, Method}; 4 | 5 | use crate::util::FlatCsv; 6 | 7 | /// `Access-Control-Allow-Methods` header, part of 8 | /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-methods-response-header) 9 | /// 10 | /// The `Access-Control-Allow-Methods` header indicates, as part of the 11 | /// response to a preflight request, which methods can be used during the 12 | /// actual request. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Access-Control-Allow-Methods: "Access-Control-Allow-Methods" ":" #Method 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `PUT, DELETE, XMODIFY` 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// extern crate http; 27 | /// use http::Method; 28 | /// use headers::AccessControlAllowMethods; 29 | /// 30 | /// let allow_methods = vec![Method::GET, Method::PUT] 31 | /// .into_iter() 32 | /// .collect::(); 33 | /// ``` 34 | #[derive(Clone, Debug, PartialEq)] 35 | pub struct AccessControlAllowMethods(FlatCsv); 36 | 37 | derive_header! { 38 | AccessControlAllowMethods(_), 39 | name: ACCESS_CONTROL_ALLOW_METHODS 40 | } 41 | 42 | impl AccessControlAllowMethods { 43 | /// Returns an iterator over `Method`s contained within. 44 | pub fn iter(&self) -> impl Iterator + '_ { 45 | self.0.iter().filter_map(|s| s.parse().ok()) 46 | } 47 | } 48 | 49 | impl FromIterator for AccessControlAllowMethods { 50 | fn from_iter(iter: I) -> Self 51 | where 52 | I: IntoIterator, 53 | { 54 | let methods = iter 55 | .into_iter() 56 | .map(|method| { 57 | method 58 | .as_str() 59 | .parse::() 60 | .expect("Method is a valid HeaderValue") 61 | }) 62 | .collect(); 63 | 64 | AccessControlAllowMethods(methods) 65 | } 66 | } 67 | 68 | #[cfg(test)] 69 | mod tests { 70 | use super::super::{test_decode, test_encode}; 71 | use super::*; 72 | 73 | #[test] 74 | fn iter() { 75 | let allowed = test_decode::(&["GET, PUT"]).unwrap(); 76 | 77 | let as_vec = allowed.iter().collect::>(); 78 | assert_eq!(as_vec.len(), 2); 79 | assert_eq!(as_vec[0], Method::GET); 80 | assert_eq!(as_vec[1], Method::PUT); 81 | } 82 | 83 | #[test] 84 | fn from_iter() { 85 | let allow: AccessControlAllowMethods = vec![Method::GET, Method::PUT].into_iter().collect(); 86 | 87 | let headers = test_encode(allow); 88 | assert_eq!(headers["access-control-allow-methods"], "GET, PUT"); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/common/access_control_request_headers.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | use http::{HeaderName, HeaderValue}; 4 | 5 | use crate::util::FlatCsv; 6 | 7 | /// `Access-Control-Request-Headers` header, part of 8 | /// [CORS](http://www.w3.org/TR/cors/#access-control-request-headers-request-header) 9 | /// 10 | /// The `Access-Control-Request-Headers` header indicates which headers will 11 | /// be used in the actual request as part of the preflight request. 12 | /// during the actual request. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `accept-language, date` 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// extern crate http; 27 | /// # fn main() { 28 | /// use http::header::{ACCEPT_LANGUAGE, DATE}; 29 | /// use headers::AccessControlRequestHeaders; 30 | /// 31 | /// let req_headers = vec![ACCEPT_LANGUAGE, DATE] 32 | /// .into_iter() 33 | /// .collect::(); 34 | /// # } 35 | /// ``` 36 | #[derive(Clone, Debug)] 37 | pub struct AccessControlRequestHeaders(FlatCsv); 38 | 39 | derive_header! { 40 | AccessControlRequestHeaders(_), 41 | name: ACCESS_CONTROL_REQUEST_HEADERS 42 | } 43 | 44 | impl AccessControlRequestHeaders { 45 | /// Returns an iterator over `HeaderName`s contained within. 46 | pub fn iter(&self) -> impl Iterator + '_ { 47 | self.0.iter().filter_map(|s| s.parse().ok()) 48 | } 49 | } 50 | 51 | impl FromIterator for AccessControlRequestHeaders { 52 | fn from_iter(iter: I) -> Self 53 | where 54 | I: IntoIterator, 55 | { 56 | let flat = iter.into_iter().map(HeaderValue::from).collect(); 57 | AccessControlRequestHeaders(flat) 58 | } 59 | } 60 | 61 | #[cfg(test)] 62 | mod tests { 63 | use super::super::{test_decode, test_encode}; 64 | use super::*; 65 | 66 | #[test] 67 | fn iter() { 68 | let req_headers = test_decode::(&["foo, bar"]).unwrap(); 69 | 70 | let as_vec = req_headers.iter().collect::>(); 71 | assert_eq!(as_vec.len(), 2); 72 | assert_eq!(as_vec[0], "foo"); 73 | assert_eq!(as_vec[1], "bar"); 74 | } 75 | 76 | #[test] 77 | fn from_iter() { 78 | let req_headers: AccessControlRequestHeaders = 79 | vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] 80 | .into_iter() 81 | .collect(); 82 | 83 | let headers = test_encode(req_headers); 84 | assert_eq!( 85 | headers["access-control-request-headers"], 86 | "cache-control, if-range" 87 | ); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/util/mod.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use crate::Error; 4 | 5 | //pub use self::charset::Charset; 6 | //pub use self::encoding::Encoding; 7 | pub(crate) use self::entity::{EntityTag, EntityTagRange}; 8 | pub(crate) use self::flat_csv::{FlatCsv, SemiColon}; 9 | pub(crate) use self::fmt::fmt; 10 | pub(crate) use self::http_date::HttpDate; 11 | pub(crate) use self::iter::IterExt; 12 | //pub use language_tags::LanguageTag; 13 | //pub use self::quality_value::{Quality, QualityValue}; 14 | pub(crate) use self::seconds::Seconds; 15 | pub(crate) use self::value_string::HeaderValueString; 16 | 17 | //mod charset; 18 | pub(crate) mod csv; 19 | //mod encoding; 20 | mod entity; 21 | mod flat_csv; 22 | mod fmt; 23 | mod http_date; 24 | mod iter; 25 | //mod quality_value; 26 | mod seconds; 27 | mod value_string; 28 | 29 | macro_rules! error_type { 30 | ($name:ident) => { 31 | #[doc(hidden)] 32 | pub struct $name { 33 | _inner: (), 34 | } 35 | 36 | impl ::std::fmt::Debug for $name { 37 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 38 | f.debug_struct(stringify!($name)).finish() 39 | } 40 | } 41 | 42 | impl ::std::fmt::Display for $name { 43 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 44 | f.write_str(stringify!($name)) 45 | } 46 | } 47 | 48 | impl ::std::error::Error for $name {} 49 | }; 50 | } 51 | 52 | macro_rules! derive_header { 53 | ($type:ident(_), name: $name:ident) => { 54 | impl crate::Header for $type { 55 | fn name() -> &'static ::http::header::HeaderName { 56 | &::http::header::$name 57 | } 58 | 59 | fn decode<'i, I>(values: &mut I) -> Result 60 | where 61 | I: Iterator, 62 | { 63 | crate::util::TryFromValues::try_from_values(values).map($type) 64 | } 65 | 66 | fn encode>(&self, values: &mut E) { 67 | values.extend(::std::iter::once((&self.0).into())); 68 | } 69 | } 70 | }; 71 | } 72 | 73 | /// A helper trait for use when deriving `Header`. 74 | pub(crate) trait TryFromValues: Sized { 75 | /// Try to convert from the values into an instance of `Self`. 76 | fn try_from_values<'i, I>(values: &mut I) -> Result 77 | where 78 | Self: Sized, 79 | I: Iterator; 80 | } 81 | 82 | impl TryFromValues for HeaderValue { 83 | fn try_from_values<'i, I>(values: &mut I) -> Result 84 | where 85 | I: Iterator, 86 | { 87 | values.next().cloned().ok_or_else(Error::invalid) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/util/value_string.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt, 3 | str::{self, FromStr}, 4 | }; 5 | 6 | use bytes::Bytes; 7 | use http::header::HeaderValue; 8 | 9 | use super::IterExt; 10 | use crate::Error; 11 | 12 | /// A value that is both a valid `HeaderValue` and `String`. 13 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 14 | pub(crate) struct HeaderValueString { 15 | /// Care must be taken to only set this value when it is also 16 | /// a valid `String`, since `as_str` will convert to a `&str` 17 | /// in an unchecked manner. 18 | value: HeaderValue, 19 | } 20 | 21 | impl HeaderValueString { 22 | pub(crate) fn from_val(val: &HeaderValue) -> Result { 23 | if val.to_str().is_ok() { 24 | Ok(HeaderValueString { value: val.clone() }) 25 | } else { 26 | Err(Error::invalid()) 27 | } 28 | } 29 | 30 | pub(crate) fn from_string(src: String) -> Option { 31 | // A valid `str` (the argument)... 32 | let bytes = Bytes::from(src); 33 | HeaderValue::from_maybe_shared(bytes) 34 | .ok() 35 | .map(|value| HeaderValueString { value }) 36 | } 37 | 38 | pub(crate) const fn from_static(src: &'static str) -> HeaderValueString { 39 | // A valid `str` (the argument)... 40 | HeaderValueString { 41 | value: HeaderValue::from_static(src), 42 | } 43 | } 44 | 45 | pub(crate) fn as_str(&self) -> &str { 46 | // HeaderValueString is only created from HeaderValues 47 | // that have validated they are also UTF-8 strings. 48 | unsafe { str::from_utf8_unchecked(self.value.as_bytes()) } 49 | } 50 | } 51 | 52 | impl fmt::Debug for HeaderValueString { 53 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 54 | fmt::Debug::fmt(self.as_str(), f) 55 | } 56 | } 57 | 58 | impl fmt::Display for HeaderValueString { 59 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 60 | fmt::Display::fmt(self.as_str(), f) 61 | } 62 | } 63 | 64 | impl super::TryFromValues for HeaderValueString { 65 | fn try_from_values<'i, I>(values: &mut I) -> Result 66 | where 67 | I: Iterator, 68 | { 69 | values 70 | .just_one() 71 | .map(HeaderValueString::from_val) 72 | .unwrap_or_else(|| Err(Error::invalid())) 73 | } 74 | } 75 | 76 | impl<'a> From<&'a HeaderValueString> for HeaderValue { 77 | fn from(src: &'a HeaderValueString) -> HeaderValue { 78 | src.value.clone() 79 | } 80 | } 81 | 82 | #[derive(Debug)] 83 | pub(crate) struct FromStrError(()); 84 | 85 | impl FromStr for HeaderValueString { 86 | type Err = FromStrError; 87 | 88 | fn from_str(src: &str) -> Result { 89 | // A valid `str` (the argument)... 90 | src.parse() 91 | .map(|value| HeaderValueString { value }) 92 | .map_err(|_| FromStrError(())) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/common/content_encoding.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use self::sealed::AsCoding; 4 | use crate::util::FlatCsv; 5 | 6 | /// `Content-Encoding` header, defined in 7 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.2.2) 8 | /// 9 | /// The `Content-Encoding` header field indicates what content codings 10 | /// have been applied to the representation, beyond those inherent in the 11 | /// media type, and thus what decoding mechanisms have to be applied in 12 | /// order to obtain data in the media type referenced by the Content-Type 13 | /// header field. Content-Encoding is primarily used to allow a 14 | /// representation's data to be compressed without losing the identity of 15 | /// its underlying media type. 16 | /// 17 | /// # ABNF 18 | /// 19 | /// ```text 20 | /// Content-Encoding = 1#content-coding 21 | /// ``` 22 | /// 23 | /// # Example values 24 | /// 25 | /// * `gzip` 26 | /// * `br` 27 | /// * `zstd` 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use headers::ContentEncoding; 33 | /// 34 | /// let content_enc = ContentEncoding::gzip(); 35 | /// ``` 36 | #[derive(Clone, Debug)] 37 | pub struct ContentEncoding(FlatCsv); 38 | 39 | derive_header! { 40 | ContentEncoding(_), 41 | name: CONTENT_ENCODING 42 | } 43 | 44 | impl ContentEncoding { 45 | /// A constructor to easily create a `Content-Encoding: gzip` header. 46 | #[inline] 47 | pub fn gzip() -> ContentEncoding { 48 | ContentEncoding(HeaderValue::from_static("gzip").into()) 49 | } 50 | 51 | /// A constructor to easily create a `Content-Encoding: br` header. 52 | #[inline] 53 | pub fn brotli() -> ContentEncoding { 54 | ContentEncoding(HeaderValue::from_static("br").into()) 55 | } 56 | 57 | /// A constructor to easily create a `Content-Encoding: zstd` header. 58 | #[inline] 59 | pub fn zstd() -> ContentEncoding { 60 | ContentEncoding(HeaderValue::from_static("zstd").into()) 61 | } 62 | 63 | /// Check if this header contains a given "coding". 64 | /// 65 | /// This can be used with these argument types: 66 | /// 67 | /// - `&str` 68 | /// 69 | /// # Example 70 | /// 71 | /// ``` 72 | /// use headers::ContentEncoding; 73 | /// 74 | /// let content_enc = ContentEncoding::gzip(); 75 | /// 76 | /// assert!(content_enc.contains("gzip")); 77 | /// assert!(!content_enc.contains("br")); 78 | /// ``` 79 | pub fn contains(&self, coding: impl AsCoding) -> bool { 80 | let s = coding.as_coding(); 81 | self.0.iter().any(|opt| opt == s) 82 | } 83 | } 84 | 85 | mod sealed { 86 | pub trait AsCoding: Sealed {} 87 | 88 | pub trait Sealed { 89 | fn as_coding(&self) -> &str; 90 | } 91 | 92 | impl AsCoding for &str {} 93 | 94 | impl Sealed for &str { 95 | fn as_coding(&self) -> &str { 96 | self 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/common/access_control_allow_headers.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | use http::{HeaderName, HeaderValue}; 4 | 5 | use crate::util::FlatCsv; 6 | 7 | /// `Access-Control-Allow-Headers` header, part of 8 | /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) 9 | /// 10 | /// The `Access-Control-Allow-Headers` header indicates, as part of the 11 | /// response to a preflight request, which header field names can be used 12 | /// during the actual request. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// * `accept-language, date` 22 | /// 23 | /// # Examples 24 | /// 25 | /// ``` 26 | /// extern crate http; 27 | /// use http::header::{CACHE_CONTROL, CONTENT_TYPE}; 28 | /// use headers::AccessControlAllowHeaders; 29 | /// 30 | /// let allow_headers = vec![CACHE_CONTROL, CONTENT_TYPE] 31 | /// .into_iter() 32 | /// .collect::(); 33 | /// ``` 34 | #[derive(Clone, Debug, PartialEq)] 35 | pub struct AccessControlAllowHeaders(FlatCsv); 36 | 37 | derive_header! { 38 | AccessControlAllowHeaders(_), 39 | name: ACCESS_CONTROL_ALLOW_HEADERS 40 | } 41 | 42 | impl AccessControlAllowHeaders { 43 | /// Returns an iterator over `HeaderName`s contained within. 44 | pub fn iter(&self) -> impl Iterator + '_ { 45 | self.0 46 | .iter() 47 | .map(|s| s.parse().ok()) 48 | .take_while(|val| val.is_some()) 49 | .flatten() 50 | } 51 | } 52 | 53 | impl FromIterator for AccessControlAllowHeaders { 54 | fn from_iter(iter: I) -> Self 55 | where 56 | I: IntoIterator, 57 | { 58 | let flat = iter.into_iter().map(HeaderValue::from).collect(); 59 | AccessControlAllowHeaders(flat) 60 | } 61 | } 62 | 63 | #[cfg(test)] 64 | mod tests { 65 | use super::super::{test_decode, test_encode}; 66 | use super::*; 67 | 68 | #[test] 69 | fn iter() { 70 | let allow_headers = test_decode::(&["foo, bar"]).unwrap(); 71 | 72 | let as_vec = allow_headers.iter().collect::>(); 73 | assert_eq!(as_vec.len(), 2); 74 | assert_eq!(as_vec[0], "foo"); 75 | assert_eq!(as_vec[1], "bar"); 76 | } 77 | 78 | #[test] 79 | fn from_iter() { 80 | let allow: AccessControlAllowHeaders = 81 | vec![::http::header::CACHE_CONTROL, ::http::header::IF_RANGE] 82 | .into_iter() 83 | .collect(); 84 | 85 | let headers = test_encode(allow); 86 | assert_eq!( 87 | headers["access-control-allow-headers"], 88 | "cache-control, if-range" 89 | ); 90 | } 91 | 92 | #[test] 93 | fn test_with_invalid() { 94 | let allow_headers = test_decode::(&["foo foo, bar"]).unwrap(); 95 | 96 | assert!(allow_headers.iter().collect::>().is_empty()); 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/common/accept_ranges.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use crate::util::FlatCsv; 4 | 5 | /// `Accept-Ranges` header, defined in [RFC7233](https://datatracker.ietf.org/doc/html/rfc7233#section-2.3) 6 | /// 7 | /// The `Accept-Ranges` header field allows a server to indicate that it 8 | /// supports range requests for the target resource. 9 | /// 10 | /// # ABNF 11 | /// 12 | /// ```text 13 | /// Accept-Ranges = acceptable-ranges 14 | /// acceptable-ranges = 1#range-unit / \"none\" 15 | /// 16 | /// # Example values 17 | /// * `bytes` 18 | /// * `none` 19 | /// * `unknown-unit` 20 | /// ``` 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// use headers::{AcceptRanges, HeaderMap, HeaderMapExt}; 26 | /// 27 | /// let mut headers = HeaderMap::new(); 28 | /// 29 | /// headers.typed_insert(AcceptRanges::bytes()); 30 | /// ``` 31 | #[derive(Clone, Debug, PartialEq)] 32 | pub struct AcceptRanges(FlatCsv); 33 | 34 | derive_header! { 35 | AcceptRanges(_), 36 | name: ACCEPT_RANGES 37 | } 38 | 39 | const ACCEPT_RANGES_BYTES: &str = "bytes"; 40 | const ACCEPT_RANGES_NONE: &str = "none"; 41 | 42 | impl AcceptRanges { 43 | /// A constructor to easily create the common `Accept-Ranges: bytes` header. 44 | pub fn bytes() -> Self { 45 | AcceptRanges(HeaderValue::from_static(ACCEPT_RANGES_BYTES).into()) 46 | } 47 | 48 | /// Check if the unit is `bytes`. 49 | pub fn is_bytes(&self) -> bool { 50 | self.0.value == ACCEPT_RANGES_BYTES 51 | } 52 | 53 | /// A constructor to easily create the common `Accept-Ranges: none` header. 54 | pub fn none() -> Self { 55 | AcceptRanges(HeaderValue::from_static(ACCEPT_RANGES_NONE).into()) 56 | } 57 | 58 | /// Check if the unit is `none`. 59 | pub fn is_none(&self) -> bool { 60 | self.0.value == ACCEPT_RANGES_NONE 61 | } 62 | } 63 | 64 | #[cfg(test)] 65 | mod tests { 66 | use super::super::test_decode; 67 | use super::*; 68 | 69 | fn accept_ranges(s: &str) -> AcceptRanges { 70 | test_decode(&[s]).unwrap() 71 | } 72 | 73 | // bytes 74 | #[test] 75 | fn bytes_constructor() { 76 | assert_eq!(accept_ranges("bytes"), AcceptRanges::bytes()); 77 | } 78 | 79 | #[test] 80 | fn is_bytes_method_successful_with_bytes_ranges() { 81 | assert!(accept_ranges("bytes").is_bytes()); 82 | } 83 | 84 | #[test] 85 | fn is_bytes_method_successful_with_bytes_ranges_by_constructor() { 86 | assert!(AcceptRanges::bytes().is_bytes()); 87 | } 88 | 89 | #[test] 90 | fn is_bytes_method_failed_with_not_bytes_ranges() { 91 | assert!(!accept_ranges("dummy").is_bytes()); 92 | } 93 | 94 | // none 95 | #[test] 96 | fn none_constructor() { 97 | assert_eq!(accept_ranges("none"), AcceptRanges::none()); 98 | } 99 | 100 | #[test] 101 | fn is_none_method_successful_with_none_ranges() { 102 | assert!(accept_ranges("none").is_none()); 103 | } 104 | 105 | #[test] 106 | fn is_none_method_successful_with_none_ranges_by_constructor() { 107 | assert!(AcceptRanges::none().is_none()); 108 | } 109 | 110 | #[test] 111 | fn is_none_method_failed_with_not_none_ranges() { 112 | assert!(!accept_ranges("dummy").is_none()); 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /src/common/if_match.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use super::ETag; 4 | use crate::util::EntityTagRange; 5 | 6 | /// `If-Match` header, defined in 7 | /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) 8 | /// 9 | /// The `If-Match` header field makes the request method conditional on 10 | /// the recipient origin server either having at least one current 11 | /// representation of the target resource, when the field-value is "*", 12 | /// or having a current representation of the target resource that has an 13 | /// entity-tag matching a member of the list of entity-tags provided in 14 | /// the field-value. 15 | /// 16 | /// An origin server MUST use the strong comparison function when 17 | /// comparing entity-tags for `If-Match`, since the client 18 | /// intends this precondition to prevent the method from being applied if 19 | /// there have been any changes to the representation data. 20 | /// 21 | /// # ABNF 22 | /// 23 | /// ```text 24 | /// If-Match = "*" / 1#entity-tag 25 | /// ``` 26 | /// 27 | /// # Example values 28 | /// 29 | /// * `"xyzzy"` 30 | /// * "xyzzy", "r2d2xxxx", "c3piozzzz" 31 | /// 32 | /// # Examples 33 | /// 34 | /// ``` 35 | /// use headers::IfMatch; 36 | /// 37 | /// let if_match = IfMatch::any(); 38 | /// ``` 39 | #[derive(Clone, Debug, PartialEq)] 40 | pub struct IfMatch(EntityTagRange); 41 | 42 | derive_header! { 43 | IfMatch(_), 44 | name: IF_MATCH 45 | } 46 | 47 | impl IfMatch { 48 | /// Create a new `If-Match: *` header. 49 | pub fn any() -> IfMatch { 50 | IfMatch(EntityTagRange::Any) 51 | } 52 | 53 | /// Returns whether this is `If-Match: *`, matching any entity tag. 54 | pub fn is_any(&self) -> bool { 55 | match self.0 { 56 | EntityTagRange::Any => true, 57 | EntityTagRange::Tags(..) => false, 58 | } 59 | } 60 | 61 | /// Checks whether the `ETag` strongly matches. 62 | pub fn precondition_passes(&self, etag: &ETag) -> bool { 63 | self.0.matches_strong(&etag.0) 64 | } 65 | } 66 | 67 | impl From for IfMatch { 68 | fn from(etag: ETag) -> IfMatch { 69 | IfMatch(EntityTagRange::Tags(HeaderValue::from(etag.0).into())) 70 | } 71 | } 72 | 73 | #[cfg(test)] 74 | mod tests { 75 | use super::*; 76 | 77 | #[test] 78 | fn is_any() { 79 | assert!(IfMatch::any().is_any()); 80 | assert!(!IfMatch::from(ETag::from_static("\"yolo\"")).is_any()); 81 | } 82 | 83 | #[test] 84 | fn precondition_fails() { 85 | let if_match = IfMatch::from(ETag::from_static("\"foo\"")); 86 | 87 | let bar = ETag::from_static("\"bar\""); 88 | let weak_foo = ETag::from_static("W/\"foo\""); 89 | 90 | assert!(!if_match.precondition_passes(&bar)); 91 | assert!(!if_match.precondition_passes(&weak_foo)); 92 | } 93 | 94 | #[test] 95 | fn precondition_passes() { 96 | let foo = ETag::from_static("\"foo\""); 97 | 98 | let if_match = IfMatch::from(foo.clone()); 99 | 100 | assert!(if_match.precondition_passes(&foo)); 101 | } 102 | 103 | #[test] 104 | fn precondition_any() { 105 | let foo = ETag::from_static("\"foo\""); 106 | 107 | let if_match = IfMatch::any(); 108 | 109 | assert!(if_match.precondition_passes(&foo)); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/common/content_length.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use crate::{Error, Header}; 4 | 5 | /// `Content-Length` header, defined in 6 | /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2) 7 | /// 8 | /// When a message does not have a `Transfer-Encoding` header field, a 9 | /// Content-Length header field can provide the anticipated size, as a 10 | /// decimal number of octets, for a potential payload body. For messages 11 | /// that do include a payload body, the Content-Length field-value 12 | /// provides the framing information necessary for determining where the 13 | /// body (and message) ends. For messages that do not include a payload 14 | /// body, the Content-Length indicates the size of the selected 15 | /// representation. 16 | /// 17 | /// Note that setting this header will *remove* any previously set 18 | /// `Transfer-Encoding` header, in accordance with 19 | /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2): 20 | /// 21 | /// > A sender MUST NOT send a Content-Length header field in any message 22 | /// > that contains a Transfer-Encoding header field. 23 | /// 24 | /// ## ABNF 25 | /// 26 | /// ```text 27 | /// Content-Length = 1*DIGIT 28 | /// ``` 29 | /// 30 | /// ## Example values 31 | /// 32 | /// * `3495` 33 | /// 34 | /// # Example 35 | /// 36 | /// ``` 37 | /// use headers::ContentLength; 38 | /// 39 | /// let len = ContentLength(1_000); 40 | /// ``` 41 | #[derive(Clone, Copy, Debug, PartialEq)] 42 | pub struct ContentLength(pub u64); 43 | 44 | impl Header for ContentLength { 45 | fn name() -> &'static ::http::header::HeaderName { 46 | &::http::header::CONTENT_LENGTH 47 | } 48 | 49 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 50 | // If multiple Content-Length headers were sent, everything can still 51 | // be alright if they all contain the same value, and all parse 52 | // correctly. If not, then it's an error. 53 | let mut len = None; 54 | for value in values { 55 | let parsed = value 56 | .to_str() 57 | .map_err(|_| Error::invalid())? 58 | .parse::() 59 | .map_err(|_| Error::invalid())?; 60 | 61 | if let Some(prev) = len { 62 | if prev != parsed { 63 | return Err(Error::invalid()); 64 | } 65 | } else { 66 | len = Some(parsed); 67 | } 68 | } 69 | 70 | len.map(ContentLength).ok_or_else(Error::invalid) 71 | } 72 | 73 | fn encode>(&self, values: &mut E) { 74 | values.extend(::std::iter::once(self.0.into())); 75 | } 76 | } 77 | 78 | /* 79 | __hyper__tm!(ContentLength, tests { 80 | // Testcase from RFC 81 | test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); 82 | 83 | test_header!(test_invalid, vec![b"34v95"], None); 84 | 85 | // Can't use the test_header macro because "5, 5" gets cleaned to "5". 86 | #[test] 87 | fn test_duplicates() { 88 | let parsed = HeaderField::parse_header(&vec![b"5".to_vec(), 89 | b"5".to_vec()].into()).unwrap(); 90 | assert_eq!(parsed, HeaderField(5)); 91 | assert_eq!(format!("{}", parsed), "5"); 92 | } 93 | 94 | test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None); 95 | }); 96 | */ 97 | -------------------------------------------------------------------------------- /src/common/transfer_encoding.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use crate::util::FlatCsv; 4 | 5 | /// `Transfer-Encoding` header, defined in 6 | /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.1) 7 | /// 8 | /// The `Transfer-Encoding` header field lists the transfer coding names 9 | /// corresponding to the sequence of transfer codings that have been (or 10 | /// will be) applied to the payload body in order to form the message 11 | /// body. 12 | /// 13 | /// Note that setting this header will *remove* any previously set 14 | /// `Content-Length` header, in accordance with 15 | /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-3.3.2): 16 | /// 17 | /// > A sender MUST NOT send a Content-Length header field in any message 18 | /// > that contains a Transfer-Encoding header field. 19 | /// 20 | /// # ABNF 21 | /// 22 | /// ```text 23 | /// Transfer-Encoding = 1#transfer-coding 24 | /// ``` 25 | /// 26 | /// # Example values 27 | /// 28 | /// * `chunked` 29 | /// * `gzip, chunked` 30 | /// 31 | /// # Example 32 | /// 33 | /// ``` 34 | /// use headers::TransferEncoding; 35 | /// 36 | /// let transfer = TransferEncoding::chunked(); 37 | /// ``` 38 | // This currently is just a `HeaderValue`, instead of a `Vec`, since 39 | // the most common by far instance is simply the string `chunked`. It'd be a 40 | // waste to need to allocate just for that. 41 | #[derive(Clone, Debug)] 42 | pub struct TransferEncoding(FlatCsv); 43 | 44 | derive_header! { 45 | TransferEncoding(_), 46 | name: TRANSFER_ENCODING 47 | } 48 | 49 | impl TransferEncoding { 50 | /// Constructor for the most common Transfer-Encoding, `chunked`. 51 | pub fn chunked() -> TransferEncoding { 52 | TransferEncoding(HeaderValue::from_static("chunked").into()) 53 | } 54 | 55 | /// Returns whether this ends with the `chunked` encoding. 56 | pub fn is_chunked(&self) -> bool { 57 | self.0 58 | .value 59 | //TODO(perf): use split and trim (not an actual method) on &[u8] 60 | .to_str() 61 | .map(|s| { 62 | s.split(',') 63 | .next_back() 64 | .map(|encoding| encoding.trim() == "chunked") 65 | .expect("split always has at least 1 item") 66 | }) 67 | .unwrap_or(false) 68 | } 69 | } 70 | 71 | #[cfg(test)] 72 | mod tests { 73 | use super::super::test_decode; 74 | use super::TransferEncoding; 75 | 76 | #[test] 77 | fn chunked_is_chunked() { 78 | assert!(TransferEncoding::chunked().is_chunked()); 79 | } 80 | 81 | #[test] 82 | fn decode_gzip_chunked_is_chunked() { 83 | let te = test_decode::(&["gzip, chunked"]).unwrap(); 84 | assert!(te.is_chunked()); 85 | } 86 | 87 | #[test] 88 | fn decode_chunked_gzip_is_not_chunked() { 89 | let te = test_decode::(&["chunked, gzip"]).unwrap(); 90 | assert!(!te.is_chunked()); 91 | } 92 | 93 | #[test] 94 | fn decode_notchunked_is_not_chunked() { 95 | let te = test_decode::(&["notchunked"]).unwrap(); 96 | assert!(!te.is_chunked()); 97 | } 98 | 99 | #[test] 100 | fn decode_multiple_is_chunked() { 101 | let te = test_decode::(&["gzip", "chunked"]).unwrap(); 102 | assert!(te.is_chunked()); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/common/if_none_match.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use super::ETag; 4 | use crate::util::EntityTagRange; 5 | 6 | /// `If-None-Match` header, defined in 7 | /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) 8 | /// 9 | /// The `If-None-Match` header field makes the request method conditional 10 | /// on a recipient cache or origin server either not having any current 11 | /// representation of the target resource, when the field-value is "*", 12 | /// or having a selected representation with an entity-tag that does not 13 | /// match any of those listed in the field-value. 14 | /// 15 | /// A recipient MUST use the weak comparison function when comparing 16 | /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags 17 | /// can be used for cache validation even if there have been changes to 18 | /// the representation data. 19 | /// 20 | /// # ABNF 21 | /// 22 | /// ```text 23 | /// If-None-Match = "*" / 1#entity-tag 24 | /// ``` 25 | /// 26 | /// # Example values 27 | /// 28 | /// * `"xyzzy"` 29 | /// * `W/"xyzzy"` 30 | /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` 31 | /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` 32 | /// * `*` 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use headers::IfNoneMatch; 38 | /// 39 | /// let if_none_match = IfNoneMatch::any(); 40 | /// ``` 41 | #[derive(Clone, Debug, PartialEq)] 42 | pub struct IfNoneMatch(EntityTagRange); 43 | 44 | derive_header! { 45 | IfNoneMatch(_), 46 | name: IF_NONE_MATCH 47 | } 48 | 49 | impl IfNoneMatch { 50 | /// Create a new `If-None-Match: *` header. 51 | pub fn any() -> IfNoneMatch { 52 | IfNoneMatch(EntityTagRange::Any) 53 | } 54 | 55 | /// Checks whether the ETag passes this precondition. 56 | pub fn precondition_passes(&self, etag: &ETag) -> bool { 57 | !self.0.matches_weak(&etag.0) 58 | } 59 | } 60 | 61 | impl From for IfNoneMatch { 62 | fn from(etag: ETag) -> IfNoneMatch { 63 | IfNoneMatch(EntityTagRange::Tags(HeaderValue::from(etag.0).into())) 64 | } 65 | } 66 | 67 | /* 68 | test_if_none_match { 69 | test_header!(test1, vec![b"\"xyzzy\""]); 70 | test_header!(test2, vec![b"W/\"xyzzy\""]); 71 | test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); 72 | test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); 73 | test_header!(test5, vec![b"*"]); 74 | } 75 | */ 76 | 77 | #[cfg(test)] 78 | mod tests { 79 | use super::*; 80 | 81 | #[test] 82 | fn precondition_fails() { 83 | let foo = ETag::from_static("\"foo\""); 84 | let weak_foo = ETag::from_static("W/\"foo\""); 85 | 86 | let if_none = IfNoneMatch::from(foo.clone()); 87 | 88 | assert!(!if_none.precondition_passes(&foo)); 89 | assert!(!if_none.precondition_passes(&weak_foo)); 90 | } 91 | 92 | #[test] 93 | fn precondition_passes() { 94 | let if_none = IfNoneMatch::from(ETag::from_static("\"foo\"")); 95 | 96 | let bar = ETag::from_static("\"bar\""); 97 | let weak_bar = ETag::from_static("W/\"bar\""); 98 | 99 | assert!(if_none.precondition_passes(&bar)); 100 | assert!(if_none.precondition_passes(&weak_bar)); 101 | } 102 | 103 | #[test] 104 | fn precondition_any() { 105 | let foo = ETag::from_static("\"foo\""); 106 | 107 | let if_none = IfNoneMatch::any(); 108 | 109 | assert!(!if_none.precondition_passes(&foo)); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/common/etag.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use crate::util::EntityTag; 4 | 5 | /// `ETag` header, defined in [RFC7232](https://datatracker.ietf.org/doc/html/rfc7232#section-2.3) 6 | /// 7 | /// The `ETag` header field in a response provides the current entity-tag 8 | /// for the selected representation, as determined at the conclusion of 9 | /// handling the request. An entity-tag is an opaque validator for 10 | /// differentiating between multiple representations of the same 11 | /// resource, regardless of whether those multiple representations are 12 | /// due to resource state changes over time, content negotiation 13 | /// resulting in multiple representations being valid at the same time, 14 | /// or both. An entity-tag consists of an opaque quoted string, possibly 15 | /// prefixed by a weakness indicator. 16 | /// 17 | /// # ABNF 18 | /// 19 | /// ```text 20 | /// ETag = entity-tag 21 | /// ``` 22 | /// 23 | /// # Example values 24 | /// 25 | /// * `"xyzzy"` 26 | /// * `W/"xyzzy"` 27 | /// * `""` 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// let etag = "\"xyzzy\"".parse::().unwrap(); 33 | /// ``` 34 | #[derive(Clone, Debug, PartialEq, Eq)] 35 | pub struct ETag(pub(super) EntityTag); 36 | 37 | derive_header! { 38 | ETag(_), 39 | name: ETAG 40 | } 41 | 42 | impl ETag { 43 | #[cfg(test)] 44 | pub(crate) fn from_static(src: &'static str) -> ETag { 45 | ETag(EntityTag::from_static(src)) 46 | } 47 | } 48 | 49 | error_type!(InvalidETag); 50 | 51 | impl FromStr for ETag { 52 | type Err = InvalidETag; 53 | fn from_str(src: &str) -> Result { 54 | let val = src.parse().map_err(|_| InvalidETag { _inner: () })?; 55 | 56 | EntityTag::from_owned(val) 57 | .map(ETag) 58 | .ok_or(InvalidETag { _inner: () }) 59 | } 60 | } 61 | 62 | /* 63 | test_etag { 64 | // From the RFC 65 | test_header!(test1, 66 | vec![b"\"xyzzy\""], 67 | Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); 68 | test_header!(test2, 69 | vec![b"W/\"xyzzy\""], 70 | Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); 71 | test_header!(test3, 72 | vec![b"\"\""], 73 | Some(ETag(EntityTag::new(false, "".to_owned())))); 74 | // Own tests 75 | test_header!(test4, 76 | vec![b"\"foobar\""], 77 | Some(ETag(EntityTag::new(false, "foobar".to_owned())))); 78 | test_header!(test5, 79 | vec![b"\"\""], 80 | Some(ETag(EntityTag::new(false, "".to_owned())))); 81 | test_header!(test6, 82 | vec![b"W/\"weak-etag\""], 83 | Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); 84 | test_header!(test7, 85 | vec![b"W/\"\x65\x62\""], 86 | Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); 87 | test_header!(test8, 88 | vec![b"W/\"\""], 89 | Some(ETag(EntityTag::new(true, "".to_owned())))); 90 | test_header!(test9, 91 | vec![b"no-dquotes"], 92 | None::); 93 | test_header!(test10, 94 | vec![b"w/\"the-first-w-is-case-sensitive\""], 95 | None::); 96 | test_header!(test11, 97 | vec![b""], 98 | None::); 99 | test_header!(test12, 100 | vec![b"\"unmatched-dquotes1"], 101 | None::); 102 | test_header!(test13, 103 | vec![b"unmatched-dquotes2\""], 104 | None::); 105 | test_header!(test14, 106 | vec![b"matched-\"dquotes\""], 107 | None::); 108 | test_header!(test15, 109 | vec![b"\""], 110 | None::); 111 | } 112 | */ 113 | -------------------------------------------------------------------------------- /src/disabled/preference_applied.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use {Header, Raw, Preference}; 3 | use parsing::{from_comma_delimited, fmt_comma_delimited}; 4 | 5 | /// `Preference-Applied` header, defined in [RFC7240](https://datatracker.ietf.org/doc/html/rfc7240) 6 | /// 7 | /// The `Preference-Applied` response header may be included within a 8 | /// response message as an indication as to which `Prefer` header tokens were 9 | /// honored by the server and applied to the processing of a request. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Preference-Applied = "Preference-Applied" ":" 1#applied-pref 15 | /// applied-pref = token [ BWS "=" BWS word ] 16 | /// ``` 17 | /// 18 | /// # Example values 19 | /// 20 | /// * `respond-async` 21 | /// * `return=minimal` 22 | /// * `wait=30` 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// use headers::{Headers, PreferenceApplied, Preference}; 28 | /// 29 | /// let mut headers = Headers::new(); 30 | /// headers.set( 31 | /// PreferenceApplied(vec![Preference::RespondAsync]) 32 | /// ); 33 | /// ``` 34 | /// 35 | /// ``` 36 | /// use headers::{Headers, PreferenceApplied, Preference}; 37 | /// 38 | /// let mut headers = Headers::new(); 39 | /// headers.set( 40 | /// PreferenceApplied(vec![ 41 | /// Preference::RespondAsync, 42 | /// Preference::ReturnRepresentation, 43 | /// Preference::Wait(10u32), 44 | /// Preference::Extension("foo".to_owned(), 45 | /// "bar".to_owned(), 46 | /// vec![]), 47 | /// ]) 48 | /// ); 49 | /// ``` 50 | #[derive(PartialEq, Clone, Debug)] 51 | pub struct PreferenceApplied(pub Vec); 52 | 53 | __hyper__deref!(PreferenceApplied => Vec); 54 | 55 | impl Header for PreferenceApplied { 56 | fn header_name() -> &'static str { 57 | static NAME: &'static str = "Preference-Applied"; 58 | NAME 59 | } 60 | 61 | fn parse_header(raw: &Raw) -> ::Result { 62 | let preferences = try!(from_comma_delimited(raw)); 63 | if !preferences.is_empty() { 64 | Ok(PreferenceApplied(preferences)) 65 | } else { 66 | Err(::Error::Header) 67 | } 68 | } 69 | 70 | fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { 71 | f.fmt_line(self) 72 | } 73 | } 74 | 75 | impl fmt::Display for PreferenceApplied { 76 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 77 | //TODO: format this without allocating a Vec and cloning contents 78 | let preferences: Vec<_> = self.0.iter().map(|pref| match pref { 79 | // The spec ignores parameters in `Preferences-Applied` 80 | &Preference::Extension(ref name, ref value, _) => Preference::Extension( 81 | name.to_owned(), 82 | value.to_owned(), 83 | vec![] 84 | ), 85 | preference => preference.clone() 86 | }).collect(); 87 | fmt_comma_delimited(f, &preferences) 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use Preference; 94 | use super::*; 95 | 96 | #[test] 97 | fn test_format_ignore_parameters() { 98 | assert_eq!( 99 | format!("{}", PreferenceApplied(vec![Preference::Extension( 100 | "foo".to_owned(), 101 | "bar".to_owned(), 102 | vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())] 103 | )])), 104 | "foo=bar".to_owned() 105 | ); 106 | } 107 | } 108 | 109 | bench_header!(normal, 110 | PreferenceApplied, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); 111 | -------------------------------------------------------------------------------- /src/common/retry_after.rs: -------------------------------------------------------------------------------- 1 | use std::time::{Duration, SystemTime}; 2 | 3 | use http::HeaderValue; 4 | 5 | use crate::util::{HttpDate, Seconds, TryFromValues}; 6 | use crate::Error; 7 | 8 | /// The `Retry-After` header. 9 | /// 10 | /// The `Retry-After` response-header field can be used with a 503 (Service 11 | /// Unavailable) response to indicate how long the service is expected to be 12 | /// unavailable to the requesting client. This field MAY also be used with any 13 | /// 3xx (Redirection) response to indicate the minimum time the user-agent is 14 | /// asked wait before issuing the redirected request. The value of this field 15 | /// can be either an HTTP-date or an integer number of seconds (in decimal) 16 | /// after the time of the response. 17 | /// 18 | /// # Examples 19 | /// ``` 20 | /// use std::time::{Duration, SystemTime}; 21 | /// use headers::RetryAfter; 22 | /// 23 | /// let delay = RetryAfter::delay(Duration::from_secs(300)); 24 | /// let date = RetryAfter::date(SystemTime::now()); 25 | /// ``` 26 | /// 27 | /// Retry-After header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.3) 28 | #[derive(Debug, Clone, PartialEq, Eq)] 29 | pub struct RetryAfter(After); 30 | 31 | derive_header! { 32 | RetryAfter(_), 33 | name: RETRY_AFTER 34 | } 35 | 36 | #[derive(Debug, Clone, PartialEq, Eq)] 37 | enum After { 38 | /// Retry after the given DateTime 39 | DateTime(HttpDate), 40 | /// Retry after this duration has elapsed 41 | Delay(Seconds), 42 | } 43 | 44 | impl RetryAfter { 45 | /// Create an `RetryAfter` header with a date value. 46 | pub fn date(time: SystemTime) -> RetryAfter { 47 | RetryAfter(After::DateTime(time.into())) 48 | } 49 | 50 | /// Create an `RetryAfter` header with a date value. 51 | pub fn delay(dur: Duration) -> RetryAfter { 52 | RetryAfter(After::Delay(dur.into())) 53 | } 54 | } 55 | 56 | impl TryFromValues for After { 57 | fn try_from_values<'i, I>(values: &mut I) -> Result 58 | where 59 | I: Iterator, 60 | { 61 | values 62 | .next() 63 | .and_then(|val| { 64 | if let Some(delay) = Seconds::from_val(val) { 65 | return Some(After::Delay(delay)); 66 | } 67 | 68 | let date = HttpDate::from_val(val)?; 69 | Some(After::DateTime(date)) 70 | }) 71 | .ok_or_else(Error::invalid) 72 | } 73 | } 74 | 75 | impl<'a> From<&'a After> for HeaderValue { 76 | fn from(after: &'a After) -> HeaderValue { 77 | match *after { 78 | After::Delay(ref delay) => delay.into(), 79 | After::DateTime(ref date) => date.into(), 80 | } 81 | } 82 | } 83 | 84 | #[cfg(test)] 85 | mod tests { 86 | use std::time::Duration; 87 | 88 | use super::super::test_decode; 89 | use super::RetryAfter; 90 | use crate::util::HttpDate; 91 | 92 | #[test] 93 | fn delay_decode() { 94 | let r: RetryAfter = test_decode(&["1234"]).unwrap(); 95 | assert_eq!(r, RetryAfter::delay(Duration::from_secs(1234)),); 96 | } 97 | 98 | macro_rules! test_retry_after_datetime { 99 | ($name:ident, $s:expr) => { 100 | #[test] 101 | fn $name() { 102 | let r: RetryAfter = test_decode(&[$s]).unwrap(); 103 | let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::().unwrap(); 104 | 105 | assert_eq!(r, RetryAfter(super::After::DateTime(dt))); 106 | } 107 | }; 108 | } 109 | 110 | test_retry_after_datetime!(date_decode_rfc1123, "Sun, 06 Nov 1994 08:49:37 GMT"); 111 | test_retry_after_datetime!(date_decode_rfc850, "Sunday, 06-Nov-94 08:49:37 GMT"); 112 | test_retry_after_datetime!(date_decode_asctime, "Sun Nov 6 08:49:37 1994"); 113 | } 114 | -------------------------------------------------------------------------------- /src/common/connection.rs: -------------------------------------------------------------------------------- 1 | use std::iter::FromIterator; 2 | 3 | use http::{HeaderName, HeaderValue}; 4 | 5 | use self::sealed::AsConnectionOption; 6 | use crate::util::FlatCsv; 7 | 8 | /// `Connection` header, defined in 9 | /// [RFC7230](https://datatracker.ietf.org/doc/html/rfc7230#section-6.1) 10 | /// 11 | /// The `Connection` header field allows the sender to indicate desired 12 | /// control options for the current connection. In order to avoid 13 | /// confusing downstream recipients, a proxy or gateway MUST remove or 14 | /// replace any received connection options before forwarding the 15 | /// message. 16 | /// 17 | /// # ABNF 18 | /// 19 | /// ```text 20 | /// Connection = 1#connection-option 21 | /// connection-option = token 22 | /// 23 | /// # Example values 24 | /// * `close` 25 | /// * `keep-alive` 26 | /// * `upgrade` 27 | /// ``` 28 | /// 29 | /// # Examples 30 | /// 31 | /// ``` 32 | /// use headers::Connection; 33 | /// 34 | /// let keep_alive = Connection::keep_alive(); 35 | /// ``` 36 | // This is frequently just 1 or 2 values, so optimize for that case. 37 | #[derive(Clone, Debug)] 38 | pub struct Connection(FlatCsv); 39 | 40 | derive_header! { 41 | Connection(_), 42 | name: CONNECTION 43 | } 44 | 45 | impl Connection { 46 | /// A constructor to easily create a `Connection: close` header. 47 | #[inline] 48 | pub fn close() -> Connection { 49 | Connection(HeaderValue::from_static("close").into()) 50 | } 51 | 52 | /// A constructor to easily create a `Connection: keep-alive` header. 53 | #[inline] 54 | pub fn keep_alive() -> Connection { 55 | Connection(HeaderValue::from_static("keep-alive").into()) 56 | } 57 | 58 | /// A constructor to easily create a `Connection: Upgrade` header. 59 | #[inline] 60 | pub fn upgrade() -> Connection { 61 | Connection(HeaderValue::from_static("upgrade").into()) 62 | } 63 | 64 | /// Check if this header contains a given "connection option". 65 | /// 66 | /// This can be used with various argument types: 67 | /// 68 | /// - `&str` 69 | /// - `&HeaderName` 70 | /// - `HeaderName` 71 | /// 72 | /// # Example 73 | /// 74 | /// ``` 75 | /// extern crate http; 76 | /// 77 | /// use http::header::UPGRADE; 78 | /// use headers::Connection; 79 | /// 80 | /// let conn = Connection::keep_alive(); 81 | /// 82 | /// assert!(!conn.contains("close")); 83 | /// assert!(!conn.contains(UPGRADE)); 84 | /// assert!(conn.contains("keep-alive")); 85 | /// assert!(conn.contains("Keep-Alive")); 86 | /// ``` 87 | pub fn contains(&self, name: impl AsConnectionOption) -> bool { 88 | let s = name.as_connection_option(); 89 | self.0.iter().any(|opt| opt.eq_ignore_ascii_case(s)) 90 | } 91 | } 92 | 93 | impl FromIterator for Connection { 94 | fn from_iter(iter: I) -> Self 95 | where 96 | I: IntoIterator, 97 | { 98 | let flat = iter.into_iter().map(HeaderValue::from).collect(); 99 | Connection(flat) 100 | } 101 | } 102 | 103 | mod sealed { 104 | use http::HeaderName; 105 | 106 | pub trait AsConnectionOption: Sealed { 107 | fn as_connection_option(&self) -> &str; 108 | } 109 | pub trait Sealed {} 110 | 111 | impl AsConnectionOption for &str { 112 | fn as_connection_option(&self) -> &str { 113 | self 114 | } 115 | } 116 | 117 | impl Sealed for &str {} 118 | 119 | impl AsConnectionOption for &HeaderName { 120 | fn as_connection_option(&self) -> &str { 121 | self.as_ref() 122 | } 123 | } 124 | 125 | impl Sealed for &HeaderName {} 126 | 127 | impl AsConnectionOption for HeaderName { 128 | fn as_connection_option(&self) -> &str { 129 | self.as_ref() 130 | } 131 | } 132 | 133 | impl Sealed for HeaderName {} 134 | } 135 | -------------------------------------------------------------------------------- /src/common/set_cookie.rs: -------------------------------------------------------------------------------- 1 | use http::{HeaderName, HeaderValue}; 2 | 3 | use crate::{Error, Header}; 4 | 5 | /// `Set-Cookie` header, defined [RFC6265](https://datatracker.ietf.org/doc/html/rfc6265#section-4.1) 6 | /// 7 | /// The Set-Cookie HTTP response header is used to send cookies from the 8 | /// server to the user agent. 9 | /// 10 | /// Informally, the Set-Cookie response header contains the header name 11 | /// "Set-Cookie" followed by a ":" and a cookie. Each cookie begins with 12 | /// a name-value-pair, followed by zero or more attribute-value pairs. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// set-cookie-header = "Set-Cookie:" SP set-cookie-string 18 | /// set-cookie-string = cookie-pair *( ";" SP cookie-av ) 19 | /// cookie-pair = cookie-name "=" cookie-value 20 | /// cookie-name = token 21 | /// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) 22 | /// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 23 | /// ; US-ASCII characters excluding CTLs, 24 | /// ; whitespace DQUOTE, comma, semicolon, 25 | /// ; and backslash 26 | /// token = 27 | /// 28 | /// cookie-av = expires-av / max-age-av / domain-av / 29 | /// path-av / secure-av / httponly-av / 30 | /// extension-av 31 | /// expires-av = "Expires=" sane-cookie-date 32 | /// sane-cookie-date = 33 | /// max-age-av = "Max-Age=" non-zero-digit *DIGIT 34 | /// ; In practice, both expires-av and max-age-av 35 | /// ; are limited to dates representable by the 36 | /// ; user agent. 37 | /// non-zero-digit = %x31-39 38 | /// ; digits 1 through 9 39 | /// domain-av = "Domain=" domain-value 40 | /// domain-value = 41 | /// ; defined in [RFC1034], Section 3.5, as 42 | /// ; enhanced by [RFC1123], Section 2.1 43 | /// path-av = "Path=" path-value 44 | /// path-value = 45 | /// secure-av = "Secure" 46 | /// httponly-av = "HttpOnly" 47 | /// extension-av = 48 | /// ``` 49 | /// 50 | /// # Example values 51 | /// 52 | /// * `SID=31d4d96e407aad42` 53 | /// * `lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT` 54 | /// * `lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT` 55 | /// * `lang=en-US; Path=/; Domain=example.com` 56 | /// 57 | /// # Example 58 | #[derive(Clone, Debug)] 59 | pub struct SetCookie(Vec); 60 | 61 | impl Header for SetCookie { 62 | fn name() -> &'static HeaderName { 63 | &::http::header::SET_COOKIE 64 | } 65 | 66 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 67 | let vec = values.cloned().collect::>(); 68 | 69 | if !vec.is_empty() { 70 | Ok(SetCookie(vec)) 71 | } else { 72 | Err(Error::invalid()) 73 | } 74 | } 75 | 76 | fn encode>(&self, values: &mut E) { 77 | values.extend(self.0.iter().cloned()); 78 | } 79 | } 80 | 81 | #[cfg(test)] 82 | mod tests { 83 | use super::super::{test_decode, test_encode}; 84 | use super::*; 85 | 86 | #[test] 87 | fn decode() { 88 | let set_cookie = test_decode::(&["foo=bar", "baz=quux"]).unwrap(); 89 | assert_eq!(set_cookie.0.len(), 2); 90 | assert_eq!(set_cookie.0[0], "foo=bar"); 91 | assert_eq!(set_cookie.0[1], "baz=quux"); 92 | } 93 | 94 | #[test] 95 | fn encode() { 96 | let set_cookie = SetCookie(vec![ 97 | HeaderValue::from_static("foo=bar"), 98 | HeaderValue::from_static("baz=quux"), 99 | ]); 100 | 101 | let headers = test_encode(set_cookie); 102 | let mut vals = headers.get_all("set-cookie").into_iter(); 103 | assert_eq!(vals.next().unwrap(), "foo=bar"); 104 | assert_eq!(vals.next().unwrap(), "baz=quux"); 105 | assert_eq!(vals.next(), None); 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/common/if_range.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | 3 | use http::HeaderValue; 4 | 5 | use super::{ETag, LastModified}; 6 | use crate::util::{EntityTag, HttpDate, TryFromValues}; 7 | use crate::Error; 8 | 9 | /// `If-Range` header, defined in [RFC7233](https://datatracker.ietf.org/doc/html/rfc7233#section-3.2) 10 | /// 11 | /// If a client has a partial copy of a representation and wishes to have 12 | /// an up-to-date copy of the entire representation, it could use the 13 | /// Range header field with a conditional GET (using either or both of 14 | /// If-Unmodified-Since and If-Match.) However, if the precondition 15 | /// fails because the representation has been modified, the client would 16 | /// then have to make a second request to obtain the entire current 17 | /// representation. 18 | /// 19 | /// The `If-Range` header field allows a client to \"short-circuit\" the 20 | /// second request. Informally, its meaning is as follows: if the 21 | /// representation is unchanged, send me the part(s) that I am requesting 22 | /// in Range; otherwise, send me the entire representation. 23 | /// 24 | /// # ABNF 25 | /// 26 | /// ```text 27 | /// If-Range = entity-tag / HTTP-date 28 | /// ``` 29 | /// 30 | /// # Example values 31 | /// 32 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 33 | /// * `\"xyzzy\"` 34 | /// 35 | /// # Examples 36 | /// 37 | /// ``` 38 | /// use headers::IfRange; 39 | /// use std::time::{SystemTime, Duration}; 40 | /// 41 | /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 42 | /// let if_range = IfRange::date(fetched); 43 | /// ``` 44 | #[derive(Clone, Debug, PartialEq)] 45 | pub struct IfRange(IfRange_); 46 | 47 | derive_header! { 48 | IfRange(_), 49 | name: IF_RANGE 50 | } 51 | 52 | impl IfRange { 53 | /// Create an `IfRange` header with an entity tag. 54 | pub fn etag(tag: ETag) -> IfRange { 55 | IfRange(IfRange_::EntityTag(tag.0)) 56 | } 57 | 58 | /// Create an `IfRange` header with a date value. 59 | pub fn date(time: SystemTime) -> IfRange { 60 | IfRange(IfRange_::Date(time.into())) 61 | } 62 | 63 | /// Checks if the resource has been modified, or if the range request 64 | /// can be served. 65 | pub fn is_modified(&self, etag: Option<&ETag>, last_modified: Option<&LastModified>) -> bool { 66 | match self.0 { 67 | IfRange_::Date(since) => last_modified.map(|time| since < time.0).unwrap_or(true), 68 | IfRange_::EntityTag(ref entity) => { 69 | etag.map(|etag| !etag.0.strong_eq(entity)).unwrap_or(true) 70 | } 71 | } 72 | } 73 | } 74 | 75 | #[derive(Clone, Debug, PartialEq)] 76 | enum IfRange_ { 77 | /// The entity-tag the client has of the resource 78 | EntityTag(EntityTag), 79 | /// The date when the client retrieved the resource 80 | Date(HttpDate), 81 | } 82 | 83 | impl TryFromValues for IfRange_ { 84 | fn try_from_values<'i, I>(values: &mut I) -> Result 85 | where 86 | I: Iterator, 87 | { 88 | values 89 | .next() 90 | .and_then(|val| { 91 | if let Some(tag) = EntityTag::from_val(val) { 92 | return Some(IfRange_::EntityTag(tag)); 93 | } 94 | 95 | let date = HttpDate::from_val(val)?; 96 | Some(IfRange_::Date(date)) 97 | }) 98 | .ok_or_else(Error::invalid) 99 | } 100 | } 101 | 102 | impl<'a> From<&'a IfRange_> for HeaderValue { 103 | fn from(if_range: &'a IfRange_) -> HeaderValue { 104 | match *if_range { 105 | IfRange_::EntityTag(ref tag) => tag.into(), 106 | IfRange_::Date(ref date) => date.into(), 107 | } 108 | } 109 | } 110 | 111 | /* 112 | #[cfg(test)] 113 | mod tests { 114 | use std::str; 115 | use *; 116 | use super::IfRange as HeaderField; 117 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); 118 | test_header!(test2, vec![b"\"xyzzy\""]); 119 | test_header!(test3, vec![b"this-is-invalid"], None::); 120 | } 121 | */ 122 | 123 | #[cfg(test)] 124 | mod tests { 125 | use super::*; 126 | 127 | #[test] 128 | fn test_is_modified_etag() { 129 | let etag = ETag::from_static("\"xyzzy\""); 130 | let if_range = IfRange::etag(etag.clone()); 131 | 132 | assert!(!if_range.is_modified(Some(&etag), None)); 133 | 134 | let etag = ETag::from_static("W/\"xyzzy\""); 135 | assert!(if_range.is_modified(Some(&etag), None)); 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /src/util/http_date.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | use std::time::SystemTime; 4 | 5 | use bytes::Bytes; 6 | use http::header::HeaderValue; 7 | 8 | use super::IterExt; 9 | 10 | /// A timestamp with HTTP formatting and parsing 11 | // Prior to 1995, there were three different formats commonly used by 12 | // servers to communicate timestamps. For compatibility with old 13 | // implementations, all three are defined here. The preferred format is 14 | // a fixed-length and single-zone subset of the date and time 15 | // specification used by the Internet Message Format [RFC5322]. 16 | // 17 | // HTTP-date = IMF-fixdate / obs-date 18 | // 19 | // An example of the preferred format is 20 | // 21 | // Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate 22 | // 23 | // Examples of the two obsolete formats are 24 | // 25 | // Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format 26 | // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format 27 | // 28 | // A recipient that parses a timestamp value in an HTTP header field 29 | // MUST accept all three HTTP-date formats. When a sender generates a 30 | // header field that contains one or more timestamps defined as 31 | // HTTP-date, the sender MUST generate those timestamps in the 32 | // IMF-fixdate format. 33 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] 34 | pub(crate) struct HttpDate(httpdate::HttpDate); 35 | 36 | impl HttpDate { 37 | pub(crate) fn from_val(val: &HeaderValue) -> Option { 38 | val.to_str().ok()?.parse().ok() 39 | } 40 | } 41 | 42 | // TODO: remove this and FromStr? 43 | #[derive(Debug)] 44 | pub struct Error(()); 45 | 46 | impl super::TryFromValues for HttpDate { 47 | fn try_from_values<'i, I>(values: &mut I) -> Result 48 | where 49 | I: Iterator, 50 | { 51 | values 52 | .just_one() 53 | .and_then(HttpDate::from_val) 54 | .ok_or_else(crate::Error::invalid) 55 | } 56 | } 57 | 58 | impl From for HeaderValue { 59 | fn from(date: HttpDate) -> HeaderValue { 60 | (&date).into() 61 | } 62 | } 63 | 64 | impl<'a> From<&'a HttpDate> for HeaderValue { 65 | fn from(date: &'a HttpDate) -> HeaderValue { 66 | // TODO: could be just BytesMut instead of String 67 | let s = date.to_string(); 68 | let bytes = Bytes::from(s); 69 | HeaderValue::from_maybe_shared(bytes).expect("HttpDate always is a valid value") 70 | } 71 | } 72 | 73 | impl FromStr for HttpDate { 74 | type Err = Error; 75 | fn from_str(s: &str) -> Result { 76 | Ok(HttpDate(s.parse().map_err(|_| Error(()))?)) 77 | } 78 | } 79 | 80 | impl fmt::Debug for HttpDate { 81 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 82 | fmt::Display::fmt(&self.0, f) 83 | } 84 | } 85 | 86 | impl fmt::Display for HttpDate { 87 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 88 | fmt::Display::fmt(&self.0, f) 89 | } 90 | } 91 | 92 | impl From for HttpDate { 93 | fn from(sys: SystemTime) -> HttpDate { 94 | HttpDate(sys.into()) 95 | } 96 | } 97 | 98 | impl From for SystemTime { 99 | fn from(date: HttpDate) -> SystemTime { 100 | SystemTime::from(date.0) 101 | } 102 | } 103 | 104 | #[cfg(test)] 105 | mod tests { 106 | use super::HttpDate; 107 | 108 | use std::time::{Duration, UNIX_EPOCH}; 109 | 110 | // The old tests had Sunday, but 1994-11-07 is a Monday. 111 | // See https://github.com/pyfisch/httpdate/pull/6#issuecomment-846881001 112 | fn nov_07() -> HttpDate { 113 | HttpDate((UNIX_EPOCH + Duration::new(784198117, 0)).into()) 114 | } 115 | 116 | #[test] 117 | fn test_display_is_imf_fixdate() { 118 | assert_eq!("Mon, 07 Nov 1994 08:48:37 GMT", &nov_07().to_string()); 119 | } 120 | 121 | #[test] 122 | fn test_imf_fixdate() { 123 | assert_eq!( 124 | "Mon, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), 125 | nov_07() 126 | ); 127 | } 128 | 129 | #[test] 130 | fn test_rfc_850() { 131 | assert_eq!( 132 | "Monday, 07-Nov-94 08:48:37 GMT" 133 | .parse::() 134 | .unwrap(), 135 | nov_07() 136 | ); 137 | } 138 | 139 | #[test] 140 | fn test_asctime() { 141 | assert_eq!( 142 | "Mon Nov 7 08:48:37 1994".parse::().unwrap(), 143 | nov_07() 144 | ); 145 | } 146 | 147 | #[test] 148 | fn test_no_date() { 149 | assert!("this-is-no-date".parse::().is_err()); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/disabled/accept.rs: -------------------------------------------------------------------------------- 1 | use mime::{self, Mime}; 2 | 3 | use {QualityItem, qitem}; 4 | 5 | header! { 6 | /// `Accept` header, defined in [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2) 7 | /// 8 | /// The `Accept` header field can be used by user agents to specify 9 | /// response media types that are acceptable. Accept header fields can 10 | /// be used to indicate that the request is specifically limited to a 11 | /// small set of desired types, as in the case of a request for an 12 | /// in-line image 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Accept = #( media-range [ accept-params ] ) 18 | /// 19 | /// media-range = ( "*/*" 20 | /// / ( type "/" "*" ) 21 | /// / ( type "/" subtype ) 22 | /// ) *( OWS ";" OWS parameter ) 23 | /// accept-params = weight *( accept-ext ) 24 | /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] 25 | /// ``` 26 | /// 27 | /// # Example values 28 | /// * `audio/*; q=0.2, audio/basic` 29 | /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` 30 | /// 31 | /// # Examples 32 | /// ``` 33 | /// extern crate mime; 34 | /// use headers::{Headers, Accept, qitem}; 35 | /// 36 | /// let mut headers = Headers::new(); 37 | /// 38 | /// headers.set( 39 | /// Accept(vec![ 40 | /// qitem(mime::TEXT_HTML), 41 | /// ]) 42 | /// ); 43 | /// ``` 44 | /// 45 | /// ``` 46 | /// extern crate mime; 47 | /// use headers::{Headers, Accept, qitem}; 48 | /// 49 | /// let mut headers = Headers::new(); 50 | /// headers.set( 51 | /// Accept(vec![ 52 | /// qitem(mime::APPLICATION_JSON), 53 | /// ]) 54 | /// ); 55 | /// ``` 56 | /// ``` 57 | /// extern crate mime; 58 | /// use headers::{Headers, Accept, QualityItem, q, qitem}; 59 | /// 60 | /// let mut headers = Headers::new(); 61 | /// 62 | /// headers.set( 63 | /// Accept(vec![ 64 | /// qitem(mime::TEXT_HTML), 65 | /// qitem("application/xhtml+xml".parse().unwrap()), 66 | /// QualityItem::new( 67 | /// mime::TEXT_XML, 68 | /// q(900) 69 | /// ), 70 | /// qitem("image/webp".parse().unwrap()), 71 | /// QualityItem::new( 72 | /// mime::STAR_STAR, 73 | /// q(800) 74 | /// ), 75 | /// ]) 76 | /// ); 77 | /// ``` 78 | (Accept, ACCEPT) => (QualityItem)+ 79 | 80 | test_accept { 81 | // Tests from the RFC 82 | test_header!( 83 | test1, 84 | vec![b"audio/*; q=0.2, audio/basic"], 85 | Some(HeaderField(vec![ 86 | QualityItem::new("audio/*".parse().unwrap(), q(200)), 87 | qitem("audio/basic".parse().unwrap()), 88 | ]))); 89 | test_header!( 90 | test2, 91 | vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], 92 | Some(HeaderField(vec![ 93 | QualityItem::new(TEXT_PLAIN, q(500)), 94 | qitem(TEXT_HTML), 95 | QualityItem::new( 96 | "text/x-dvi".parse().unwrap(), 97 | q(800)), 98 | qitem("text/x-c".parse().unwrap()), 99 | ]))); 100 | // Custom tests 101 | test_header!( 102 | test3, 103 | vec![b"text/plain; charset=utf-8"], 104 | Some(Accept(vec![ 105 | qitem(TEXT_PLAIN_UTF_8), 106 | ]))); 107 | test_header!( 108 | test4, 109 | vec![b"text/plain; charset=utf-8; q=0.5"], 110 | Some(Accept(vec![ 111 | QualityItem::new(TEXT_PLAIN_UTF_8, 112 | q(500)), 113 | ]))); 114 | 115 | #[test] 116 | fn test_fuzzing1() { 117 | let raw: Raw = "chunk#;e".into(); 118 | let header = Accept::parse_header(&raw); 119 | assert!(header.is_ok()); 120 | } 121 | } 122 | } 123 | 124 | impl Accept { 125 | /// A constructor to easily create `Accept: */*`. 126 | pub fn star() -> Accept { 127 | Accept(vec![qitem(mime::STAR_STAR)]) 128 | } 129 | 130 | /// A constructor to easily create `Accept: application/json`. 131 | pub fn json() -> Accept { 132 | Accept(vec![qitem(mime::APPLICATION_JSON)]) 133 | } 134 | 135 | /// A constructor to easily create `Accept: text/*`. 136 | pub fn text() -> Accept { 137 | Accept(vec![qitem(mime::TEXT_STAR)]) 138 | } 139 | 140 | /// A constructor to easily create `Accept: image/*`. 141 | pub fn image() -> Accept { 142 | Accept(vec![qitem(mime::IMAGE_STAR)]) 143 | } 144 | } 145 | 146 | 147 | bench_header!(bench, Accept, { vec![b"text/plain; q=0.5, text/html".to_vec()] }); 148 | -------------------------------------------------------------------------------- /src/common/access_control_allow_origin.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use http::HeaderValue; 4 | 5 | use super::origin::Origin; 6 | use crate::util::{IterExt, TryFromValues}; 7 | use crate::Error; 8 | 9 | /// The `Access-Control-Allow-Origin` response header, 10 | /// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header) 11 | /// 12 | /// The `Access-Control-Allow-Origin` header indicates whether a resource 13 | /// can be shared based by returning the value of the Origin request header, 14 | /// `*`, or `null` in the response. 15 | /// 16 | /// ## ABNF 17 | /// 18 | /// ```text 19 | /// Access-Control-Allow-Origin = "Access-Control-Allow-Origin" ":" origin-list-or-null | "*" 20 | /// ``` 21 | /// 22 | /// ## Example values 23 | /// * `null` 24 | /// * `*` 25 | /// * `http://google.com/` 26 | /// 27 | /// # Examples 28 | /// 29 | /// ``` 30 | /// use headers::AccessControlAllowOrigin; 31 | /// use std::convert::TryFrom; 32 | /// 33 | /// let any_origin = AccessControlAllowOrigin::ANY; 34 | /// let null_origin = AccessControlAllowOrigin::NULL; 35 | /// let origin = AccessControlAllowOrigin::try_from("http://web-platform.test:8000"); 36 | /// ``` 37 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 38 | pub struct AccessControlAllowOrigin(OriginOrAny); 39 | 40 | derive_header! { 41 | AccessControlAllowOrigin(_), 42 | name: ACCESS_CONTROL_ALLOW_ORIGIN 43 | } 44 | 45 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 46 | enum OriginOrAny { 47 | Origin(Origin), 48 | /// Allow all origins 49 | Any, 50 | } 51 | 52 | impl AccessControlAllowOrigin { 53 | /// `Access-Control-Allow-Origin: *` 54 | pub const ANY: AccessControlAllowOrigin = AccessControlAllowOrigin(OriginOrAny::Any); 55 | /// `Access-Control-Allow-Origin: null` 56 | pub const NULL: AccessControlAllowOrigin = 57 | AccessControlAllowOrigin(OriginOrAny::Origin(Origin::NULL)); 58 | 59 | /// Returns the origin if there's one specified. 60 | pub fn origin(&self) -> Option<&Origin> { 61 | match self.0 { 62 | OriginOrAny::Origin(ref origin) => Some(origin), 63 | _ => None, 64 | } 65 | } 66 | } 67 | 68 | impl TryFrom<&str> for AccessControlAllowOrigin { 69 | type Error = Error; 70 | 71 | fn try_from(s: &str) -> Result { 72 | let header_value = HeaderValue::from_str(s).map_err(|_| Error::invalid())?; 73 | let origin = OriginOrAny::try_from(&header_value)?; 74 | Ok(Self(origin)) 75 | } 76 | } 77 | 78 | impl TryFrom<&HeaderValue> for OriginOrAny { 79 | type Error = Error; 80 | 81 | fn try_from(header_value: &HeaderValue) -> Result { 82 | Origin::try_from_value(header_value) 83 | .map(OriginOrAny::Origin) 84 | .ok_or_else(Error::invalid) 85 | } 86 | } 87 | 88 | impl TryFromValues for OriginOrAny { 89 | fn try_from_values<'i, I>(values: &mut I) -> Result 90 | where 91 | I: Iterator, 92 | { 93 | values 94 | .just_one() 95 | .and_then(|value| { 96 | if value == "*" { 97 | return Some(OriginOrAny::Any); 98 | } 99 | 100 | Origin::try_from_value(value).map(OriginOrAny::Origin) 101 | }) 102 | .ok_or_else(Error::invalid) 103 | } 104 | } 105 | 106 | impl<'a> From<&'a OriginOrAny> for HeaderValue { 107 | fn from(origin: &'a OriginOrAny) -> HeaderValue { 108 | match origin { 109 | OriginOrAny::Origin(ref origin) => origin.to_value(), 110 | OriginOrAny::Any => HeaderValue::from_static("*"), 111 | } 112 | } 113 | } 114 | 115 | #[cfg(test)] 116 | mod tests { 117 | 118 | use super::super::{test_decode, test_encode}; 119 | use super::*; 120 | 121 | #[test] 122 | fn origin() { 123 | let s = "http://web-platform.test:8000"; 124 | 125 | let allow_origin = test_decode::(&[s]).unwrap(); 126 | { 127 | let origin = allow_origin.origin().unwrap(); 128 | assert_eq!(origin.scheme(), "http"); 129 | assert_eq!(origin.hostname(), "web-platform.test"); 130 | assert_eq!(origin.port(), Some(8000)); 131 | } 132 | 133 | let headers = test_encode(allow_origin); 134 | assert_eq!(headers["access-control-allow-origin"], s); 135 | } 136 | 137 | #[test] 138 | fn try_from_origin() { 139 | let s = "http://web-platform.test:8000"; 140 | 141 | let allow_origin = AccessControlAllowOrigin::try_from(s).unwrap(); 142 | { 143 | let origin = allow_origin.origin().unwrap(); 144 | assert_eq!(origin.scheme(), "http"); 145 | assert_eq!(origin.hostname(), "web-platform.test"); 146 | assert_eq!(origin.port(), Some(8000)); 147 | } 148 | 149 | let headers = test_encode(allow_origin); 150 | assert_eq!(headers["access-control-allow-origin"], s); 151 | } 152 | 153 | #[test] 154 | fn any() { 155 | let allow_origin = test_decode::(&["*"]).unwrap(); 156 | assert_eq!(allow_origin, AccessControlAllowOrigin::ANY); 157 | 158 | let headers = test_encode(allow_origin); 159 | assert_eq!(headers["access-control-allow-origin"], "*"); 160 | } 161 | 162 | #[test] 163 | fn null() { 164 | let allow_origin = test_decode::(&["null"]).unwrap(); 165 | assert_eq!(allow_origin, AccessControlAllowOrigin::NULL); 166 | 167 | let headers = test_encode(allow_origin); 168 | assert_eq!(headers["access-control-allow-origin"], "null"); 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /src/common/content_type.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use http::{HeaderName, HeaderValue}; 4 | use mime::Mime; 5 | 6 | use crate::{Error, Header}; 7 | 8 | /// `Content-Type` header, defined in 9 | /// [RFC7231](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5) 10 | /// 11 | /// The `Content-Type` header field indicates the media type of the 12 | /// associated representation: either the representation enclosed in the 13 | /// message payload or the selected representation, as determined by the 14 | /// message semantics. The indicated media type defines both the data 15 | /// format and how that data is intended to be processed by a recipient, 16 | /// within the scope of the received message semantics, after any content 17 | /// codings indicated by Content-Encoding are decoded. 18 | /// 19 | /// Although the `mime` crate allows the mime options to be any slice, this crate 20 | /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If 21 | /// this is an issue, it's possible to implement `Header` on a custom struct. 22 | /// 23 | /// # ABNF 24 | /// 25 | /// ```text 26 | /// Content-Type = media-type 27 | /// ``` 28 | /// 29 | /// # Example values 30 | /// 31 | /// * `text/html; charset=utf-8` 32 | /// * `application/json` 33 | /// 34 | /// # Examples 35 | /// 36 | /// ``` 37 | /// use headers::ContentType; 38 | /// 39 | /// let ct = ContentType::json(); 40 | /// ``` 41 | #[derive(Clone, Debug, PartialEq)] 42 | pub struct ContentType(Mime); 43 | 44 | impl ContentType { 45 | /// A constructor to easily create a `Content-Type: application/json` header. 46 | #[inline] 47 | pub fn json() -> ContentType { 48 | ContentType(mime::APPLICATION_JSON) 49 | } 50 | 51 | /// A constructor to easily create a `Content-Type: text/plain` header. 52 | #[inline] 53 | pub fn text() -> ContentType { 54 | ContentType(mime::TEXT_PLAIN) 55 | } 56 | 57 | /// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header. 58 | #[inline] 59 | pub fn text_utf8() -> ContentType { 60 | ContentType(mime::TEXT_PLAIN_UTF_8) 61 | } 62 | 63 | /// A constructor to easily create a `Content-Type: text/html` header. 64 | #[inline] 65 | pub fn html() -> ContentType { 66 | ContentType(mime::TEXT_HTML) 67 | } 68 | 69 | /// A constructor to easily create a `Content-Type: text/xml` header. 70 | #[inline] 71 | pub fn xml() -> ContentType { 72 | ContentType(mime::TEXT_XML) 73 | } 74 | 75 | /// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header. 76 | #[inline] 77 | pub fn form_url_encoded() -> ContentType { 78 | ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) 79 | } 80 | /// A constructor to easily create a `Content-Type: image/jpeg` header. 81 | #[inline] 82 | pub fn jpeg() -> ContentType { 83 | ContentType(mime::IMAGE_JPEG) 84 | } 85 | 86 | /// A constructor to easily create a `Content-Type: image/png` header. 87 | #[inline] 88 | pub fn png() -> ContentType { 89 | ContentType(mime::IMAGE_PNG) 90 | } 91 | 92 | /// A constructor to easily create a `Content-Type: application/octet-stream` header. 93 | #[inline] 94 | pub fn octet_stream() -> ContentType { 95 | ContentType(mime::APPLICATION_OCTET_STREAM) 96 | } 97 | } 98 | 99 | impl Header for ContentType { 100 | fn name() -> &'static HeaderName { 101 | &::http::header::CONTENT_TYPE 102 | } 103 | 104 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 105 | values 106 | .next() 107 | .and_then(|v| v.to_str().ok()?.parse().ok()) 108 | .map(ContentType) 109 | .ok_or_else(Error::invalid) 110 | } 111 | 112 | fn encode>(&self, values: &mut E) { 113 | let value = self 114 | .0 115 | .as_ref() 116 | .parse() 117 | .expect("Mime is always a valid HeaderValue"); 118 | values.extend(::std::iter::once(value)); 119 | } 120 | } 121 | 122 | impl From for ContentType { 123 | fn from(m: mime::Mime) -> ContentType { 124 | ContentType(m) 125 | } 126 | } 127 | 128 | impl From for mime::Mime { 129 | fn from(ct: ContentType) -> mime::Mime { 130 | ct.0 131 | } 132 | } 133 | 134 | impl fmt::Display for ContentType { 135 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 136 | fmt::Display::fmt(&self.0, f) 137 | } 138 | } 139 | 140 | impl std::str::FromStr for ContentType { 141 | type Err = Error; 142 | 143 | fn from_str(s: &str) -> Result { 144 | s.parse::() 145 | .map(|m| m.into()) 146 | .map_err(|_| Error::invalid()) 147 | } 148 | } 149 | 150 | #[cfg(test)] 151 | mod tests { 152 | use super::super::test_decode; 153 | use super::ContentType; 154 | 155 | #[test] 156 | fn json() { 157 | assert_eq!( 158 | test_decode::(&["application/json"]), 159 | Some(ContentType::json()), 160 | ); 161 | } 162 | 163 | #[test] 164 | fn from_str() { 165 | assert_eq!( 166 | "application/json".parse::().unwrap(), 167 | ContentType::json(), 168 | ); 169 | assert!("invalid-mimetype".parse::().is_err()); 170 | } 171 | 172 | bench_header!(bench_plain, ContentType, "text/plain"); 173 | bench_header!(bench_json, ContentType, "application/json"); 174 | bench_header!( 175 | bench_formdata, 176 | ContentType, 177 | "multipart/form-data; boundary=---------------abcd" 178 | ); 179 | } 180 | -------------------------------------------------------------------------------- /src/common/referrer_policy.rs: -------------------------------------------------------------------------------- 1 | use http::HeaderValue; 2 | 3 | use crate::util::TryFromValues; 4 | use crate::Error; 5 | 6 | /// `Referrer-Policy` header, part of 7 | /// [Referrer Policy](https://www.w3.org/TR/referrer-policy/#referrer-policy-header) 8 | /// 9 | /// The `Referrer-Policy` HTTP header specifies the referrer 10 | /// policy that the user agent applies when determining what 11 | /// referrer information should be included with requests made, 12 | /// and with browsing contexts created from the context of the 13 | /// protected resource. 14 | /// 15 | /// # ABNF 16 | /// 17 | /// ```text 18 | /// Referrer-Policy: 1#policy-token 19 | /// policy-token = "no-referrer" / "no-referrer-when-downgrade" 20 | /// / "same-origin" / "origin" 21 | /// / "origin-when-cross-origin" / "unsafe-url" 22 | /// ``` 23 | /// 24 | /// # Example values 25 | /// 26 | /// * `no-referrer` 27 | /// 28 | /// # Example 29 | /// 30 | /// ``` 31 | /// use headers::ReferrerPolicy; 32 | /// 33 | /// let rp = ReferrerPolicy::NO_REFERRER; 34 | /// ``` 35 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 36 | pub struct ReferrerPolicy(Policy); 37 | 38 | derive_header! { 39 | ReferrerPolicy(_), 40 | name: REFERRER_POLICY 41 | } 42 | 43 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 44 | enum Policy { 45 | NoReferrer, 46 | NoReferrerWhenDowngrade, 47 | SameOrigin, 48 | Origin, 49 | OriginWhenCrossOrigin, 50 | UnsafeUrl, 51 | StrictOrigin, 52 | StrictOriginWhenCrossOrigin, 53 | } 54 | 55 | impl ReferrerPolicy { 56 | /// `no-referrer` 57 | pub const NO_REFERRER: Self = ReferrerPolicy(Policy::NoReferrer); 58 | 59 | /// `no-referrer-when-downgrade` 60 | pub const NO_REFERRER_WHEN_DOWNGRADE: Self = ReferrerPolicy(Policy::NoReferrerWhenDowngrade); 61 | 62 | /// `same-origin` 63 | pub const SAME_ORIGIN: Self = ReferrerPolicy(Policy::SameOrigin); 64 | 65 | /// `origin` 66 | pub const ORIGIN: Self = ReferrerPolicy(Policy::Origin); 67 | 68 | /// `origin-when-cross-origin` 69 | pub const ORIGIN_WHEN_CROSS_ORIGIN: Self = ReferrerPolicy(Policy::OriginWhenCrossOrigin); 70 | 71 | /// `unsafe-url` 72 | pub const UNSAFE_URL: Self = ReferrerPolicy(Policy::UnsafeUrl); 73 | 74 | /// `strict-origin` 75 | pub const STRICT_ORIGIN: Self = ReferrerPolicy(Policy::StrictOrigin); 76 | 77 | ///`strict-origin-when-cross-origin` 78 | pub const STRICT_ORIGIN_WHEN_CROSS_ORIGIN: Self = 79 | ReferrerPolicy(Policy::StrictOriginWhenCrossOrigin); 80 | } 81 | 82 | impl TryFromValues for Policy { 83 | fn try_from_values<'i, I>(values: &mut I) -> Result 84 | where 85 | I: Iterator, 86 | { 87 | // See https://www.w3.org/TR/referrer-policy/#determine-policy-for-token 88 | // tl;dr - Pick *last* known policy in the list 89 | let mut known = None; 90 | for s in csv(values) { 91 | known = Some(match s { 92 | "no-referrer" | "never" => Policy::NoReferrer, 93 | "no-referrer-when-downgrade" | "default" => Policy::NoReferrerWhenDowngrade, 94 | "same-origin" => Policy::SameOrigin, 95 | "origin" => Policy::Origin, 96 | "origin-when-cross-origin" => Policy::OriginWhenCrossOrigin, 97 | "strict-origin" => Policy::StrictOrigin, 98 | "strict-origin-when-cross-origin" => Policy::StrictOriginWhenCrossOrigin, 99 | "unsafe-url" | "always" => Policy::UnsafeUrl, 100 | _ => continue, 101 | }); 102 | } 103 | 104 | known.ok_or_else(Error::invalid) 105 | } 106 | } 107 | 108 | impl<'a> From<&'a Policy> for HeaderValue { 109 | fn from(policy: &'a Policy) -> HeaderValue { 110 | HeaderValue::from_static(match *policy { 111 | Policy::NoReferrer => "no-referrer", 112 | Policy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade", 113 | Policy::SameOrigin => "same-origin", 114 | Policy::Origin => "origin", 115 | Policy::OriginWhenCrossOrigin => "origin-when-cross-origin", 116 | Policy::StrictOrigin => "strict-origin", 117 | Policy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin", 118 | Policy::UnsafeUrl => "unsafe-url", 119 | }) 120 | } 121 | } 122 | 123 | fn csv<'i, I>(values: I) -> impl Iterator 124 | where 125 | I: Iterator, 126 | { 127 | values.flat_map(|value| { 128 | value.to_str().into_iter().flat_map(|string| { 129 | string.split(',').filter_map(|x| match x.trim() { 130 | "" => None, 131 | y => Some(y), 132 | }) 133 | }) 134 | }) 135 | } 136 | 137 | #[cfg(test)] 138 | mod tests { 139 | use super::super::test_decode; 140 | use super::ReferrerPolicy; 141 | 142 | #[test] 143 | fn decode_as_last_policy() { 144 | assert_eq!( 145 | test_decode::(&["same-origin, origin"]), 146 | Some(ReferrerPolicy::ORIGIN), 147 | ); 148 | 149 | assert_eq!( 150 | test_decode::(&["origin", "same-origin"]), 151 | Some(ReferrerPolicy::SAME_ORIGIN), 152 | ); 153 | } 154 | 155 | #[test] 156 | fn decode_as_last_known() { 157 | assert_eq!( 158 | test_decode::(&["origin, nope, nope, nope"]), 159 | Some(ReferrerPolicy::ORIGIN), 160 | ); 161 | 162 | assert_eq!( 163 | test_decode::(&["nope, origin, nope, nope"]), 164 | Some(ReferrerPolicy::ORIGIN), 165 | ); 166 | 167 | assert_eq!( 168 | test_decode::(&["nope, origin", "nope, nope"]), 169 | Some(ReferrerPolicy::ORIGIN), 170 | ); 171 | 172 | assert_eq!( 173 | test_decode::(&["nope", "origin", "nope, nope"]), 174 | Some(ReferrerPolicy::ORIGIN), 175 | ); 176 | } 177 | 178 | #[test] 179 | fn decode_unknown() { 180 | assert_eq!(test_decode::(&["nope"]), None,); 181 | } 182 | 183 | #[test] 184 | fn matching() { 185 | let rp = ReferrerPolicy::ORIGIN; 186 | 187 | match rp { 188 | ReferrerPolicy::ORIGIN => (), 189 | _ => panic!("matched wrong"), 190 | } 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/disabled/warning.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::{FromStr}; 3 | use {Header, HttpDate, Raw}; 4 | use parsing::from_one_raw_str; 5 | 6 | /// `Warning` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.5) 7 | /// 8 | /// The `Warning` header field can be used to carry additional information 9 | /// about the status or transformation of a message that might not be reflected 10 | /// in the status code. This header is sometimes used as backwards 11 | /// compatible way to notify of a deprecated API. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Warning = 1#warning-value 17 | /// warning-value = warn-code SP warn-agent SP warn-text 18 | /// [ SP warn-date ] 19 | /// warn-code = 3DIGIT 20 | /// warn-agent = ( uri-host [ ":" port ] ) / pseudonym 21 | /// ; the name or pseudonym of the server adding 22 | /// ; the Warning header field, for use in debugging 23 | /// ; a single "-" is recommended when agent unknown 24 | /// warn-text = quoted-string 25 | /// warn-date = DQUOTE HTTP-date DQUOTE 26 | /// ``` 27 | /// 28 | /// # Example values 29 | /// 30 | /// * `Warning: 112 - "network down" "Sat, 25 Aug 2012 23:34:45 GMT"` 31 | /// * `Warning: 299 - "Deprecated API " "Tue, 15 Nov 1994 08:12:31 GMT"` 32 | /// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead."` 33 | /// * `Warning: 299 api.hyper.rs:8080 "Deprecated API : use newapi.hyper.rs instead." "Tue, 15 Nov 1994 08:12:31 GMT"` 34 | /// 35 | /// # Examples 36 | /// 37 | /// ``` 38 | /// use headers::{Headers, Warning}; 39 | /// 40 | /// let mut headers = Headers::new(); 41 | /// headers.set( 42 | /// Warning{ 43 | /// code: 299, 44 | /// agent: "api.hyper.rs".to_owned(), 45 | /// text: "Deprecated".to_owned(), 46 | /// date: None 47 | /// } 48 | /// ); 49 | /// ``` 50 | /// 51 | /// ``` 52 | /// use headers::{Headers, HttpDate, Warning}; 53 | /// 54 | /// let mut headers = Headers::new(); 55 | /// headers.set( 56 | /// Warning{ 57 | /// code: 299, 58 | /// agent: "api.hyper.rs".to_owned(), 59 | /// text: "Deprecated".to_owned(), 60 | /// date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() 61 | /// } 62 | /// ); 63 | /// ``` 64 | /// 65 | /// ``` 66 | /// use std::time::SystemTime; 67 | /// use headers::{Headers, Warning}; 68 | /// 69 | /// let mut headers = Headers::new(); 70 | /// headers.set( 71 | /// Warning{ 72 | /// code: 199, 73 | /// agent: "api.hyper.rs".to_owned(), 74 | /// text: "Deprecated".to_owned(), 75 | /// date: Some(SystemTime::now().into()) 76 | /// } 77 | /// ); 78 | /// ``` 79 | #[derive(PartialEq, Clone, Debug)] 80 | pub struct Warning { 81 | /// The 3 digit warn code. 82 | pub code: u16, 83 | /// The name or pseudonym of the server adding this header. 84 | pub agent: String, 85 | /// The warning message describing the error. 86 | pub text: String, 87 | /// An optional warning date. 88 | pub date: Option 89 | } 90 | 91 | impl Header for Warning { 92 | fn header_name() -> &'static str { 93 | static NAME: &'static str = "Warning"; 94 | NAME 95 | } 96 | 97 | fn parse_header(raw: &Raw) -> ::Result { 98 | from_one_raw_str(raw) 99 | } 100 | 101 | fn fmt_header(&self, f: &mut ::Formatter) -> fmt::Result { 102 | f.fmt_line(self) 103 | } 104 | } 105 | 106 | impl fmt::Display for Warning { 107 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 108 | match self.date { 109 | Some(date) => write!(f, "{:03} {} \"{}\" \"{}\"", self.code, self.agent, self.text, date), 110 | None => write!(f, "{:03} {} \"{}\"", self.code, self.agent, self.text) 111 | } 112 | } 113 | } 114 | 115 | impl FromStr for Warning { 116 | type Err = ::Error; 117 | 118 | fn from_str(s: &str) -> ::Result { 119 | let mut warning_split = s.split_whitespace(); 120 | let code = match warning_split.next() { 121 | Some(c) => match c.parse::() { 122 | Ok(c) => c, 123 | Err(..) => return Err(::Error::Header) 124 | }, 125 | None => return Err(::Error::Header) 126 | }; 127 | let agent = match warning_split.next() { 128 | Some(a) => a.to_string(), 129 | None => return Err(::Error::Header) 130 | }; 131 | 132 | let mut warning_split = s.split('"').skip(1); 133 | let text = match warning_split.next() { 134 | Some(t) => t.to_string(), 135 | None => return Err(::Error::Header) 136 | }; 137 | let date = match warning_split.skip(1).next() { 138 | Some(d) => d.parse::().ok(), 139 | None => None // Optional 140 | }; 141 | 142 | Ok(Warning { 143 | code: code, 144 | agent: agent, 145 | text: text, 146 | date: date 147 | }) 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use super::Warning; 154 | use {Header, HttpDate}; 155 | 156 | #[test] 157 | fn test_parsing() { 158 | let warning = Header::parse_header(&vec![b"112 - \"network down\" \"Sat, 25 Aug 2012 23:34:45 GMT\"".to_vec()].into()); 159 | assert_eq!(warning.ok(), Some(Warning { 160 | code: 112, 161 | agent: "-".to_owned(), 162 | text: "network down".to_owned(), 163 | date: "Sat, 25 Aug 2012 23:34:45 GMT".parse::().ok() 164 | })); 165 | 166 | let warning = Header::parse_header(&vec![b"299 api.hyper.rs:8080 \"Deprecated API : use newapi.hyper.rs instead.\"".to_vec()].into()); 167 | assert_eq!(warning.ok(), Some(Warning { 168 | code: 299, 169 | agent: "api.hyper.rs:8080".to_owned(), 170 | text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), 171 | date: None 172 | })); 173 | 174 | 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()); 175 | assert_eq!(warning.ok(), Some(Warning { 176 | code: 299, 177 | agent: "api.hyper.rs:8080".to_owned(), 178 | text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), 179 | date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() 180 | })); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/util/flat_csv.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::iter::FromIterator; 3 | use std::marker::PhantomData; 4 | 5 | use bytes::BytesMut; 6 | use http::HeaderValue; 7 | 8 | use crate::util::TryFromValues; 9 | use crate::Error; 10 | 11 | // A single `HeaderValue` that can flatten multiple values with commas. 12 | #[derive(Clone, PartialEq, Eq, Hash)] 13 | pub(crate) struct FlatCsv { 14 | pub(crate) value: HeaderValue, 15 | _marker: PhantomData, 16 | } 17 | 18 | pub(crate) trait Separator { 19 | const BYTE: u8; 20 | const CHAR: char; 21 | } 22 | 23 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 24 | pub(crate) enum Comma {} 25 | 26 | impl Separator for Comma { 27 | const BYTE: u8 = b','; 28 | const CHAR: char = ','; 29 | } 30 | 31 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 32 | pub(crate) enum SemiColon {} 33 | 34 | impl Separator for SemiColon { 35 | const BYTE: u8 = b';'; 36 | const CHAR: char = ';'; 37 | } 38 | 39 | impl FlatCsv { 40 | pub(crate) fn iter(&self) -> impl Iterator { 41 | self.value.to_str().ok().into_iter().flat_map(|value_str| { 42 | let mut in_quotes = false; 43 | value_str 44 | .split(move |c| { 45 | #[allow(clippy::collapsible_else_if)] 46 | if in_quotes { 47 | if c == '"' { 48 | in_quotes = false; 49 | } 50 | false // dont split 51 | } else { 52 | if c == Sep::CHAR { 53 | true // split 54 | } else { 55 | if c == '"' { 56 | in_quotes = true; 57 | } 58 | false // dont split 59 | } 60 | } 61 | }) 62 | .map(|item| item.trim()) 63 | }) 64 | } 65 | } 66 | 67 | impl TryFromValues for FlatCsv { 68 | fn try_from_values<'i, I>(values: &mut I) -> Result 69 | where 70 | I: Iterator, 71 | { 72 | let flat = values.collect(); 73 | Ok(flat) 74 | } 75 | } 76 | 77 | impl From for FlatCsv { 78 | fn from(value: HeaderValue) -> Self { 79 | FlatCsv { 80 | value, 81 | _marker: PhantomData, 82 | } 83 | } 84 | } 85 | 86 | impl<'a, Sep> From<&'a FlatCsv> for HeaderValue { 87 | fn from(flat: &'a FlatCsv) -> HeaderValue { 88 | flat.value.clone() 89 | } 90 | } 91 | 92 | impl fmt::Debug for FlatCsv { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 94 | fmt::Debug::fmt(&self.value, f) 95 | } 96 | } 97 | 98 | impl<'a, Sep: Separator> FromIterator<&'a HeaderValue> for FlatCsv { 99 | fn from_iter(iter: I) -> Self 100 | where 101 | I: IntoIterator, 102 | { 103 | let mut values = iter.into_iter(); 104 | 105 | // Common case is there is only 1 value, optimize for that 106 | if let (1, Some(1)) = values.size_hint() { 107 | return values 108 | .next() 109 | .expect("size_hint claimed 1 item") 110 | .clone() 111 | .into(); 112 | } 113 | 114 | // Otherwise, there are multiple, so this should merge them into 1. 115 | let mut buf = values 116 | .next() 117 | .cloned() 118 | .map(|val| BytesMut::from(val.as_bytes())) 119 | .unwrap_or_default(); 120 | 121 | for val in values { 122 | buf.extend_from_slice(&[Sep::BYTE, b' ']); 123 | buf.extend_from_slice(val.as_bytes()); 124 | } 125 | 126 | let val = HeaderValue::from_maybe_shared(buf.freeze()) 127 | .expect("comma separated HeaderValues are valid"); 128 | 129 | val.into() 130 | } 131 | } 132 | 133 | // TODO: would be great if there was a way to de-dupe these with above 134 | impl FromIterator for FlatCsv { 135 | fn from_iter(iter: I) -> Self 136 | where 137 | I: IntoIterator, 138 | { 139 | let mut values = iter.into_iter(); 140 | 141 | // Common case is there is only 1 value, optimize for that 142 | if let (1, Some(1)) = values.size_hint() { 143 | return values.next().expect("size_hint claimed 1 item").into(); 144 | } 145 | 146 | // Otherwise, there are multiple, so this should merge them into 1. 147 | let mut buf = values 148 | .next() 149 | .map(|val| BytesMut::from(val.as_bytes())) 150 | .unwrap_or_default(); 151 | 152 | for val in values { 153 | buf.extend_from_slice(&[Sep::BYTE, b' ']); 154 | buf.extend_from_slice(val.as_bytes()); 155 | } 156 | 157 | let val = HeaderValue::from_maybe_shared(buf.freeze()) 158 | .expect("comma separated HeaderValues are valid"); 159 | 160 | val.into() 161 | } 162 | } 163 | 164 | #[cfg(test)] 165 | mod tests { 166 | use super::*; 167 | 168 | #[test] 169 | fn comma() { 170 | let val = HeaderValue::from_static("aaa, b; bb, ccc"); 171 | let csv = FlatCsv::::from(val); 172 | 173 | let mut values = csv.iter(); 174 | assert_eq!(values.next(), Some("aaa")); 175 | assert_eq!(values.next(), Some("b; bb")); 176 | assert_eq!(values.next(), Some("ccc")); 177 | assert_eq!(values.next(), None); 178 | } 179 | 180 | #[test] 181 | fn semicolon() { 182 | let val = HeaderValue::from_static("aaa; b, bb; ccc"); 183 | let csv = FlatCsv::::from(val); 184 | 185 | let mut values = csv.iter(); 186 | assert_eq!(values.next(), Some("aaa")); 187 | assert_eq!(values.next(), Some("b, bb")); 188 | assert_eq!(values.next(), Some("ccc")); 189 | assert_eq!(values.next(), None); 190 | } 191 | 192 | #[test] 193 | fn quoted_text() { 194 | let val = HeaderValue::from_static("foo=\"bar,baz\", sherlock=holmes"); 195 | let csv = FlatCsv::::from(val); 196 | 197 | let mut values = csv.iter(); 198 | assert_eq!(values.next(), Some("foo=\"bar,baz\"")); 199 | assert_eq!(values.next(), Some("sherlock=holmes")); 200 | assert_eq!(values.next(), None); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /src/common/mod.rs: -------------------------------------------------------------------------------- 1 | //! A Collection of Header implementations for common HTTP Headers. 2 | //! 3 | //! ## Mime 4 | //! 5 | //! Several header fields use MIME values for their contents. Keeping with the 6 | //! strongly-typed theme, the [mime](https://docs.rs/mime) crate 7 | //! is used, such as `ContentType(pub Mime)`. 8 | 9 | //pub use self::accept_charset::AcceptCharset; 10 | //pub use self::accept_encoding::AcceptEncoding; 11 | //pub use self::accept_language::AcceptLanguage; 12 | pub use self::accept_ranges::AcceptRanges; 13 | //pub use self::accept::Accept; 14 | pub use self::access_control_allow_credentials::AccessControlAllowCredentials; 15 | pub use self::access_control_allow_headers::AccessControlAllowHeaders; 16 | pub use self::access_control_allow_methods::AccessControlAllowMethods; 17 | pub use self::access_control_allow_origin::AccessControlAllowOrigin; 18 | pub use self::access_control_expose_headers::AccessControlExposeHeaders; 19 | pub use self::access_control_max_age::AccessControlMaxAge; 20 | pub use self::access_control_request_headers::AccessControlRequestHeaders; 21 | pub use self::access_control_request_method::AccessControlRequestMethod; 22 | pub use self::age::Age; 23 | pub use self::allow::Allow; 24 | pub use self::authorization::Authorization; 25 | pub use self::cache_control::CacheControl; 26 | pub use self::connection::Connection; 27 | pub use self::content_disposition::ContentDisposition; 28 | pub use self::content_encoding::ContentEncoding; 29 | //pub use self::content_language::ContentLanguage; 30 | pub use self::content_length::ContentLength; 31 | pub use self::content_location::ContentLocation; 32 | pub use self::content_range::ContentRange; 33 | pub use self::content_type::ContentType; 34 | pub use self::cookie::Cookie; 35 | pub use self::date::Date; 36 | pub use self::etag::ETag; 37 | pub use self::expect::Expect; 38 | pub use self::expires::Expires; 39 | //pub use self::from::From; 40 | pub use self::host::Host; 41 | pub use self::if_match::IfMatch; 42 | pub use self::if_modified_since::IfModifiedSince; 43 | pub use self::if_none_match::IfNoneMatch; 44 | pub use self::if_range::IfRange; 45 | pub use self::if_unmodified_since::IfUnmodifiedSince; 46 | //pub use self::last_event_id::LastEventId; 47 | pub use self::last_modified::LastModified; 48 | //pub use self::link::{Link, LinkValue, RelationType, MediaDesc}; 49 | pub use self::location::Location; 50 | pub use self::origin::Origin; 51 | pub use self::pragma::Pragma; 52 | //pub use self::prefer::{Prefer, Preference}; 53 | //pub use self::preference_applied::PreferenceApplied; 54 | pub use self::proxy_authorization::ProxyAuthorization; 55 | pub use self::range::Range; 56 | pub use self::referer::Referer; 57 | pub use self::referrer_policy::ReferrerPolicy; 58 | pub use self::retry_after::RetryAfter; 59 | pub use self::sec_websocket_accept::SecWebsocketAccept; 60 | pub use self::sec_websocket_key::SecWebsocketKey; 61 | pub use self::sec_websocket_version::SecWebsocketVersion; 62 | pub use self::server::Server; 63 | pub use self::set_cookie::SetCookie; 64 | pub use self::strict_transport_security::StrictTransportSecurity; 65 | pub use self::te::Te; 66 | pub use self::transfer_encoding::TransferEncoding; 67 | pub use self::upgrade::Upgrade; 68 | pub use self::user_agent::UserAgent; 69 | pub use self::vary::Vary; 70 | //pub use self::warning::Warning; 71 | 72 | #[cfg(test)] 73 | fn test_decode(values: &[&str]) -> Option { 74 | use crate::HeaderMapExt; 75 | let mut map = ::http::HeaderMap::new(); 76 | for val in values { 77 | map.append(T::name(), val.parse().unwrap()); 78 | } 79 | map.typed_get() 80 | } 81 | 82 | #[cfg(test)] 83 | fn test_encode(header: T) -> ::http::HeaderMap { 84 | use crate::HeaderMapExt; 85 | let mut map = ::http::HeaderMap::new(); 86 | map.typed_insert(header); 87 | map 88 | } 89 | 90 | #[cfg(test)] 91 | macro_rules! bench_header { 92 | ($mod:ident, $ty:ident, $value:expr) => { 93 | #[cfg(feature = "nightly")] 94 | mod $mod { 95 | use super::$ty; 96 | use crate::HeaderMapExt; 97 | 98 | #[bench] 99 | fn bench_decode(b: &mut ::test::Bencher) { 100 | let mut map = ::http::HeaderMap::new(); 101 | map.append( 102 | <$ty as crate::Header>::name(), 103 | $value.parse().expect("HeaderValue::from_str($value)"), 104 | ); 105 | b.bytes = $value.len() as u64; 106 | b.iter(|| { 107 | map.typed_get::<$ty>().unwrap(); 108 | }); 109 | } 110 | 111 | #[bench] 112 | fn bench_encode(b: &mut ::test::Bencher) { 113 | let mut map = ::http::HeaderMap::new(); 114 | map.append( 115 | <$ty as crate::Header>::name(), 116 | $value.parse().expect("HeaderValue::from_str($value)"), 117 | ); 118 | let typed = map.typed_get::<$ty>().unwrap(); 119 | b.bytes = $value.len() as u64; 120 | b.iter(|| { 121 | map.typed_insert(typed.clone()); 122 | map.clear(); 123 | }); 124 | } 125 | } 126 | }; 127 | } 128 | 129 | //mod accept; 130 | //mod accept_charset; 131 | //mod accept_encoding; 132 | //mod accept_language; 133 | mod accept_ranges; 134 | mod access_control_allow_credentials; 135 | mod access_control_allow_headers; 136 | mod access_control_allow_methods; 137 | mod access_control_allow_origin; 138 | mod access_control_expose_headers; 139 | mod access_control_max_age; 140 | mod access_control_request_headers; 141 | mod access_control_request_method; 142 | mod age; 143 | mod allow; 144 | pub mod authorization; 145 | mod cache_control; 146 | mod connection; 147 | mod content_disposition; 148 | mod content_encoding; 149 | //mod content_language; 150 | mod content_length; 151 | mod content_location; 152 | mod content_range; 153 | mod content_type; 154 | mod cookie; 155 | mod date; 156 | mod etag; 157 | mod expect; 158 | mod expires; 159 | //mod from; 160 | mod host; 161 | mod if_match; 162 | mod if_modified_since; 163 | mod if_none_match; 164 | mod if_range; 165 | mod if_unmodified_since; 166 | //mod last_event_id; 167 | mod last_modified; 168 | //mod link; 169 | mod location; 170 | mod origin; 171 | mod pragma; 172 | //mod prefer; 173 | //mod preference_applied; 174 | mod proxy_authorization; 175 | mod range; 176 | mod referer; 177 | mod referrer_policy; 178 | mod retry_after; 179 | mod sec_websocket_accept; 180 | mod sec_websocket_key; 181 | mod sec_websocket_version; 182 | mod server; 183 | mod set_cookie; 184 | mod strict_transport_security; 185 | mod te; 186 | mod transfer_encoding; 187 | mod upgrade; 188 | mod user_agent; 189 | mod vary; 190 | //mod warning; 191 | -------------------------------------------------------------------------------- /src/common/origin.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::fmt; 3 | 4 | use bytes::Bytes; 5 | use http::uri::{self, Authority, Scheme, Uri}; 6 | use http::HeaderValue; 7 | 8 | use crate::util::{IterExt, TryFromValues}; 9 | use crate::Error; 10 | 11 | /// The `Origin` header. 12 | /// 13 | /// 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. 14 | /// This header is often used to inform recipients of the security context of where the request was initiated. 15 | /// 16 | /// Following the spec, [https://fetch.spec.whatwg.org/#origin-header][url], the value of this header is composed of 17 | /// a String (scheme), Host (host/port) 18 | /// 19 | /// [url]: https://fetch.spec.whatwg.org/#origin-header 20 | /// 21 | /// # Examples 22 | /// 23 | /// ``` 24 | /// use headers::Origin; 25 | /// 26 | /// let origin = Origin::NULL; 27 | /// ``` 28 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 29 | pub struct Origin(OriginOrNull); 30 | 31 | derive_header! { 32 | Origin(_), 33 | name: ORIGIN 34 | } 35 | 36 | #[derive(Clone, Debug, PartialEq, Eq, Hash)] 37 | enum OriginOrNull { 38 | Origin(Scheme, Authority), 39 | Null, 40 | } 41 | 42 | impl Origin { 43 | /// The literal `null` Origin header. 44 | pub const NULL: Origin = Origin(OriginOrNull::Null); 45 | 46 | /// Checks if `Origin` is `null`. 47 | #[inline] 48 | pub fn is_null(&self) -> bool { 49 | matches!(self.0, OriginOrNull::Null) 50 | } 51 | 52 | /// Get the "scheme" part of this origin. 53 | #[inline] 54 | pub fn scheme(&self) -> &str { 55 | match self.0 { 56 | OriginOrNull::Origin(ref scheme, _) => scheme.as_str(), 57 | OriginOrNull::Null => "", 58 | } 59 | } 60 | 61 | /// Get the "hostname" part of this origin. 62 | #[inline] 63 | pub fn hostname(&self) -> &str { 64 | match self.0 { 65 | OriginOrNull::Origin(_, ref auth) => auth.host(), 66 | OriginOrNull::Null => "", 67 | } 68 | } 69 | 70 | /// Get the "port" part of this origin. 71 | #[inline] 72 | pub fn port(&self) -> Option { 73 | match self.0 { 74 | OriginOrNull::Origin(_, ref auth) => auth.port_u16(), 75 | OriginOrNull::Null => None, 76 | } 77 | } 78 | 79 | /// Tries to build a `Origin` from three parts, the scheme, the host and an optional port. 80 | pub fn try_from_parts( 81 | scheme: &str, 82 | host: &str, 83 | port: impl Into>, 84 | ) -> Result { 85 | struct MaybePort(Option); 86 | 87 | impl fmt::Display for MaybePort { 88 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 89 | if let Some(port) = self.0 { 90 | write!(f, ":{}", port) 91 | } else { 92 | Ok(()) 93 | } 94 | } 95 | } 96 | 97 | let bytes = Bytes::from(format!("{}://{}{}", scheme, host, MaybePort(port.into()))); 98 | HeaderValue::from_maybe_shared(bytes) 99 | .ok() 100 | .and_then(|val| Self::try_from_value(&val)) 101 | .ok_or(InvalidOrigin { _inner: () }) 102 | } 103 | 104 | // Used in AccessControlAllowOrigin 105 | pub(super) fn try_from_value(value: &HeaderValue) -> Option { 106 | OriginOrNull::try_from_value(value).map(Origin) 107 | } 108 | 109 | pub(super) fn to_value(&self) -> HeaderValue { 110 | (&self.0).into() 111 | } 112 | } 113 | 114 | impl fmt::Display for Origin { 115 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 116 | match self.0 { 117 | OriginOrNull::Origin(ref scheme, ref auth) => write!(f, "{}://{}", scheme, auth), 118 | OriginOrNull::Null => f.write_str("null"), 119 | } 120 | } 121 | } 122 | 123 | error_type!(InvalidOrigin); 124 | 125 | impl OriginOrNull { 126 | fn try_from_value(value: &HeaderValue) -> Option { 127 | if value == "null" { 128 | return Some(OriginOrNull::Null); 129 | } 130 | 131 | let uri = Uri::try_from(value.as_bytes()).ok()?; 132 | 133 | let (scheme, auth) = match uri.into_parts() { 134 | uri::Parts { 135 | scheme: Some(scheme), 136 | authority: Some(auth), 137 | path_and_query: None, 138 | .. 139 | } => (scheme, auth), 140 | uri::Parts { 141 | scheme: Some(ref scheme), 142 | authority: Some(ref auth), 143 | path_and_query: Some(ref p), 144 | .. 145 | } if p == "/" => (scheme.clone(), auth.clone()), 146 | _ => { 147 | return None; 148 | } 149 | }; 150 | 151 | Some(OriginOrNull::Origin(scheme, auth)) 152 | } 153 | } 154 | 155 | impl TryFromValues for OriginOrNull { 156 | fn try_from_values<'i, I>(values: &mut I) -> Result 157 | where 158 | I: Iterator, 159 | { 160 | values 161 | .just_one() 162 | .and_then(OriginOrNull::try_from_value) 163 | .ok_or_else(Error::invalid) 164 | } 165 | } 166 | 167 | impl<'a> From<&'a OriginOrNull> for HeaderValue { 168 | fn from(origin: &'a OriginOrNull) -> HeaderValue { 169 | match origin { 170 | OriginOrNull::Origin(ref scheme, ref auth) => { 171 | let s = format!("{}://{}", scheme, auth); 172 | let bytes = Bytes::from(s); 173 | HeaderValue::from_maybe_shared(bytes) 174 | .expect("Scheme and Authority are valid header values") 175 | } 176 | // Serialized as "null" per ASCII serialization of an origin 177 | // https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin 178 | OriginOrNull::Null => HeaderValue::from_static("null"), 179 | } 180 | } 181 | } 182 | 183 | #[cfg(test)] 184 | mod tests { 185 | use super::super::{test_decode, test_encode}; 186 | use super::*; 187 | 188 | #[test] 189 | fn origin() { 190 | let s = "http://web-platform.test:8000"; 191 | let origin = test_decode::(&[s]).unwrap(); 192 | assert_eq!(origin.scheme(), "http"); 193 | assert_eq!(origin.hostname(), "web-platform.test"); 194 | assert_eq!(origin.port(), Some(8000)); 195 | 196 | let headers = test_encode(origin); 197 | assert_eq!(headers["origin"], s); 198 | } 199 | 200 | #[test] 201 | fn null() { 202 | assert_eq!(test_decode::(&["null"]), Some(Origin::NULL),); 203 | 204 | let headers = test_encode(Origin::NULL); 205 | assert_eq!(headers["origin"], "null"); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /src/common/cookie.rs: -------------------------------------------------------------------------------- 1 | use crate::util::{FlatCsv, SemiColon}; 2 | 3 | /// `Cookie` header, defined in [RFC6265](https://datatracker.ietf.org/doc/html/rfc6265#section-5.4) 4 | /// 5 | /// If the user agent does attach a Cookie header field to an HTTP 6 | /// request, the user agent must send the cookie-string 7 | /// as the value of the header field. 8 | /// 9 | /// When the user agent generates an HTTP request, the user agent MUST NOT 10 | /// attach more than one Cookie header field. 11 | /// 12 | /// # Example values 13 | /// * `SID=31d4d96e407aad42` 14 | /// * `SID=31d4d96e407aad42; lang=en-US` 15 | /// 16 | #[derive(Clone, Debug)] 17 | pub struct Cookie(FlatCsv); 18 | 19 | derive_header! { 20 | Cookie(_), 21 | name: COOKIE 22 | } 23 | 24 | impl Cookie { 25 | /// Lookup a value for a cookie name. 26 | /// 27 | /// # Example 28 | /// 29 | /// ``` 30 | /// use headers::{Cookie, HeaderMap, HeaderMapExt, HeaderValue}; 31 | /// 32 | /// // Setup the header map with strings... 33 | /// let mut headers = HeaderMap::new(); 34 | /// headers.insert("cookie", HeaderValue::from_static("lang=en-US")); 35 | /// 36 | /// // Parse a `Cookie` so we can play with it... 37 | /// let cookie = headers 38 | /// .typed_get::() 39 | /// .expect("we just inserted a valid Cookie"); 40 | /// 41 | /// assert_eq!(cookie.get("lang"), Some("en-US")); 42 | /// assert_eq!(cookie.get("SID"), None); 43 | /// ``` 44 | pub fn get(&self, name: &str) -> Option<&str> { 45 | self.iter() 46 | .find(|&(key, _)| key == name) 47 | .map(|(_, val)| val) 48 | } 49 | 50 | /// Get the number of key-value pairs this `Cookie` contains. 51 | pub fn len(&self) -> usize { 52 | self.iter().count() 53 | } 54 | 55 | /// Iterator the key-value pairs of this `Cookie` header. 56 | pub fn iter(&self) -> impl Iterator { 57 | self.0.iter().filter_map(|kv| { 58 | let mut iter = kv.splitn(2, '='); 59 | let key = iter.next()?.trim(); 60 | let val = iter.next()?.trim(); 61 | Some((key, val)) 62 | }) 63 | } 64 | } 65 | 66 | /* 67 | impl PartialEq for Cookie { 68 | fn eq(&self, other: &Cookie) -> bool { 69 | if self.0.len() == other.0.len() { 70 | for &(ref k, ref v) in self.0.iter() { 71 | if other.get(k) != Some(v) { 72 | return false; 73 | } 74 | } 75 | true 76 | } else { 77 | false 78 | } 79 | } 80 | } 81 | */ 82 | 83 | #[cfg(test)] 84 | mod tests { 85 | use super::super::test_decode; 86 | use super::Cookie; 87 | 88 | #[test] 89 | fn test_parse() { 90 | let cookie = test_decode::(&["foo=bar"]).unwrap(); 91 | 92 | assert_eq!(cookie.get("foo"), Some("bar")); 93 | assert_eq!(cookie.get("bar"), None); 94 | } 95 | 96 | #[test] 97 | fn test_multipe_same_name() { 98 | let cookie = test_decode::(&["foo=bar; foo=baz"]).unwrap(); 99 | 100 | assert_eq!(cookie.get("foo"), Some("bar")); 101 | } 102 | 103 | #[test] 104 | fn test_multipe_lines() { 105 | let cookie = test_decode::(&["foo=bar", "lol = cat"]).unwrap(); 106 | 107 | assert_eq!(cookie.get("foo"), Some("bar")); 108 | assert_eq!(cookie.get("lol"), Some("cat")); 109 | } 110 | 111 | /* 112 | #[test] 113 | fn test_set_and_get() { 114 | let mut cookie = Cookie::new(); 115 | cookie.append("foo", "bar"); 116 | cookie.append(String::from("dyn"), String::from("amic")); 117 | 118 | assert_eq!(cookie.get("foo"), Some("bar")); 119 | assert_eq!(cookie.get("dyn"), Some("amic")); 120 | assert!(cookie.get("nope").is_none()); 121 | 122 | cookie.append("foo", "notbar"); 123 | assert_eq!(cookie.get("foo"), Some("bar")); 124 | 125 | cookie.set("foo", "hi"); 126 | assert_eq!(cookie.get("foo"), Some("hi")); 127 | assert_eq!(cookie.get("dyn"), Some("amic")); 128 | } 129 | 130 | #[test] 131 | fn test_eq() { 132 | let mut cookie = Cookie::new(); 133 | let mut cookie2 = Cookie::new(); 134 | 135 | // empty is equal 136 | assert_eq!(cookie, cookie2); 137 | 138 | // left has more params 139 | cookie.append("foo", "bar"); 140 | assert_ne!(cookie, cookie2); 141 | 142 | // same len, different params 143 | cookie2.append("bar", "foo"); 144 | assert_ne!(cookie, cookie2); 145 | 146 | 147 | // right has more params, and matching KV 148 | cookie2.append("foo", "bar"); 149 | assert_ne!(cookie, cookie2); 150 | 151 | // same params, different order 152 | cookie.append("bar", "foo"); 153 | assert_eq!(cookie, cookie2); 154 | } 155 | 156 | #[test] 157 | fn test_parse() { 158 | let mut cookie = Cookie::new(); 159 | 160 | let parsed = Cookie::parse_header(&b"foo=bar".to_vec().into()).unwrap(); 161 | cookie.append("foo", "bar"); 162 | assert_eq!(cookie, parsed); 163 | 164 | let parsed = Cookie::parse_header(&b"foo=bar;".to_vec().into()).unwrap(); 165 | assert_eq!(cookie, parsed); 166 | 167 | let parsed = Cookie::parse_header(&b"foo=bar; baz=quux".to_vec().into()).unwrap(); 168 | cookie.append("baz", "quux"); 169 | assert_eq!(cookie, parsed); 170 | 171 | let parsed = Cookie::parse_header(&b"foo=bar;; baz=quux".to_vec().into()).unwrap(); 172 | assert_eq!(cookie, parsed); 173 | 174 | let parsed = Cookie::parse_header(&b"foo=bar; invalid ; bad; ;; baz=quux".to_vec().into()) 175 | .unwrap(); 176 | assert_eq!(cookie, parsed); 177 | 178 | let parsed = Cookie::parse_header(&b" foo = bar;baz= quux ".to_vec().into()).unwrap(); 179 | assert_eq!(cookie, parsed); 180 | 181 | let parsed = 182 | Cookie::parse_header(&vec![b"foo = bar".to_vec(), b"baz= quux ".to_vec()].into()) 183 | .unwrap(); 184 | assert_eq!(cookie, parsed); 185 | 186 | let parsed = Cookie::parse_header(&b"foo=bar; baz=quux ; empty=".to_vec().into()).unwrap(); 187 | cookie.append("empty", ""); 188 | assert_eq!(cookie, parsed); 189 | 190 | 191 | let mut cookie = Cookie::new(); 192 | 193 | let parsed = Cookie::parse_header(&b"middle=equals=in=the=middle".to_vec().into()).unwrap(); 194 | cookie.append("middle", "equals=in=the=middle"); 195 | assert_eq!(cookie, parsed); 196 | 197 | let parsed = 198 | Cookie::parse_header(&b"middle=equals=in=the=middle; double==2".to_vec().into()) 199 | .unwrap(); 200 | cookie.append("double", "=2"); 201 | assert_eq!(cookie, parsed); 202 | } 203 | */ 204 | } 205 | -------------------------------------------------------------------------------- /headers-derive/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![recursion_limit = "128"] 2 | 3 | extern crate proc_macro; 4 | extern crate proc_macro2; 5 | #[macro_use] 6 | extern crate quote; 7 | extern crate syn; 8 | 9 | use proc_macro::TokenStream; 10 | use proc_macro2::Span; 11 | use syn::{Data, Fields, Ident, Lit, Meta, NestedMeta}; 12 | 13 | #[proc_macro_derive(Header, attributes(header))] 14 | pub fn derive_header(input: TokenStream) -> TokenStream { 15 | let ast = syn::parse(input).unwrap(); 16 | impl_header(&ast).into() 17 | } 18 | 19 | fn impl_header(ast: &syn::DeriveInput) -> proc_macro2::TokenStream { 20 | let fns = match impl_fns(ast) { 21 | Ok(fns) => fns, 22 | Err(msg) => { 23 | return quote! { 24 | compile_error!(#msg); 25 | } 26 | .into(); 27 | } 28 | }; 29 | 30 | let decode = fns.decode; 31 | let encode = fns.encode; 32 | 33 | let ty = &ast.ident; 34 | let hname = fns.name.unwrap_or_else(|| to_header_name(&ty.to_string())); 35 | let hname_ident = Ident::new(&hname, Span::call_site()); 36 | let dummy_const = Ident::new(&format!("_IMPL_HEADER_FOR_{}", hname), Span::call_site()); 37 | let impl_block = quote! { 38 | impl __hc::Header for #ty { 39 | fn name() -> &'static __hc::HeaderName { 40 | &__hc::header::#hname_ident 41 | } 42 | fn decode<'i, I>(values: &mut I) -> Result 43 | where 44 | I: Iterator, 45 | { 46 | #decode 47 | } 48 | fn encode>(&self, values: &mut E) { 49 | #encode 50 | } 51 | } 52 | }; 53 | 54 | quote! { 55 | const #dummy_const: () = { 56 | extern crate headers_core as __hc; 57 | #impl_block 58 | }; 59 | } 60 | } 61 | 62 | struct Fns { 63 | encode: proc_macro2::TokenStream, 64 | decode: proc_macro2::TokenStream, 65 | name: Option, 66 | } 67 | 68 | fn impl_fns(ast: &syn::DeriveInput) -> Result { 69 | let ty = &ast.ident; 70 | 71 | // Only structs are allowed... 72 | let st = match ast.data { 73 | Data::Struct(ref st) => st, 74 | _ => return Err("derive(Header) only works on structs".into()), 75 | }; 76 | 77 | // Check attributes for `#[header(...)]` that may influence the code 78 | // that is generated... 79 | let mut name = None; 80 | for attr in &ast.attrs { 81 | if attr.path.segments.len() != 1 { 82 | continue; 83 | } 84 | if attr.path.segments[0].ident != "header" { 85 | continue; 86 | } 87 | 88 | match attr.parse_meta() { 89 | Ok(Meta::List(list)) => { 90 | for meta in &list.nested { 91 | match meta { 92 | NestedMeta::Meta(Meta::NameValue(ref kv)) 93 | if kv.path.is_ident("name_const") => 94 | { 95 | if name.is_some() { 96 | return Err( 97 | "repeated 'name_const' option in #[header] attribute".into() 98 | ); 99 | } 100 | name = match kv.lit { 101 | Lit::Str(ref s) => Some(s.value()), 102 | _ => { 103 | return Err( 104 | "illegal literal in #[header(name_const = ..)] attribute" 105 | .into(), 106 | ); 107 | } 108 | }; 109 | } 110 | _ => return Err("illegal option in #[header(..)] attribute".into()), 111 | } 112 | } 113 | } 114 | Ok(Meta::NameValue(_)) => return Err("illegal #[header = ..] attribute".into()), 115 | Ok(Meta::Path(_)) => return Err("empty #[header] attributes do nothing".into()), 116 | Err(e) => { 117 | // TODO stringify attribute to return better error 118 | return Err(format!("illegal #[header ??] attribute: {:?}", e)); 119 | } 120 | } 121 | } 122 | 123 | let decode_res = quote! { 124 | ::util::TryFromValues::try_from_values(values) 125 | }; 126 | 127 | let (decode, encode_name) = match st.fields { 128 | Fields::Named(ref fields) => { 129 | if fields.named.len() != 1 { 130 | return Err("derive(Header) doesn't support multiple fields".into()); 131 | } 132 | 133 | let field = fields 134 | .named 135 | .iter() 136 | .next() 137 | .expect("just checked for len() == 1"); 138 | let field_name = field.ident.as_ref().unwrap(); 139 | 140 | let decode = quote! { 141 | #decode_res 142 | .map(|inner| #ty { 143 | #field_name: inner, 144 | }) 145 | }; 146 | 147 | let encode_name = Ident::new(&field_name.to_string(), Span::call_site()); 148 | (decode, Value::Named(encode_name)) 149 | } 150 | Fields::Unnamed(ref fields) => { 151 | if fields.unnamed.len() != 1 { 152 | return Err("derive(Header) doesn't support multiple fields".into()); 153 | } 154 | 155 | let decode = quote! { 156 | #decode_res 157 | .map(#ty) 158 | }; 159 | 160 | (decode, Value::Unnamed) 161 | } 162 | Fields::Unit => return Err("derive(Header) doesn't support unit structs".into()), 163 | }; 164 | 165 | let encode = { 166 | let field = if let Value::Named(field) = encode_name { 167 | quote! { 168 | (&self.#field) 169 | } 170 | } else { 171 | quote! { 172 | (&self.0) 173 | } 174 | }; 175 | quote! { 176 | values.extend(::std::iter::once((#field).into())); 177 | } 178 | }; 179 | 180 | Ok(Fns { 181 | decode, 182 | encode, 183 | name, 184 | }) 185 | } 186 | 187 | fn to_header_name(ty_name: &str) -> String { 188 | let mut out = String::new(); 189 | let mut first = true; 190 | for c in ty_name.chars() { 191 | if first { 192 | out.push(c.to_ascii_uppercase()); 193 | first = false; 194 | } else { 195 | if c.is_uppercase() { 196 | out.push('_'); 197 | } 198 | out.push(c.to_ascii_uppercase()); 199 | } 200 | } 201 | out 202 | } 203 | 204 | enum Value { 205 | Named(Ident), 206 | Unnamed, 207 | } 208 | -------------------------------------------------------------------------------- /src/disabled/util/charset.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | 4 | /// A Mime charset. 5 | /// 6 | /// The string representation is normalised to upper case. 7 | /// 8 | /// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. 9 | /// 10 | /// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml 11 | #[derive(Clone, PartialEq)] 12 | pub struct Charset(Charset_); 13 | 14 | impl Charset { 15 | /// US ASCII 16 | pub const US_ASCII: Charset = Charset(Charset_::Us_Ascii); 17 | 18 | /// ISO-8859-1 19 | pub const ISO_8859_1: Charset = Charset(Charset_::Iso_8859_1); 20 | 21 | /// ISO-8859-2 22 | pub const ISO_8859_2: Charset = Charset(Charset_::Iso_8859_2); 23 | 24 | /// ISO-8859-3 25 | pub const ISO_8859_3: Charset = Charset(Charset_::Iso_8859_3); 26 | 27 | /// ISO-8859-4 28 | pub const ISO_8859_4: Charset = Charset(Charset_::Iso_8859_4); 29 | 30 | /// ISO-8859-5 31 | pub const ISO_8859_5: Charset = Charset(Charset_::Iso_8859_5); 32 | 33 | /// ISO-8859-6 34 | pub const ISO_8859_6: Charset = Charset(Charset_::Iso_8859_6); 35 | 36 | /// ISO-8859-7 37 | pub const ISO_8859_7: Charset = Charset(Charset_::Iso_8859_7); 38 | 39 | /// ISO-8859-8 40 | pub const ISO_8859_8: Charset = Charset(Charset_::Iso_8859_8); 41 | 42 | /// ISO-8859-9 43 | pub const ISO_8859_9: Charset = Charset(Charset_::Iso_8859_9); 44 | 45 | /// ISO-8859-10 46 | pub const ISO_8859_10: Charset = Charset(Charset_::Iso_8859_10); 47 | 48 | /// Shift_JIS 49 | pub const SHIFT_JIS: Charset = Charset(Charset_::Shift_Jis); 50 | 51 | /// EUC-JP 52 | pub const EUC_JP: Charset = Charset(Charset_::Euc_Jp); 53 | 54 | /// ISO-2022-KR 55 | pub const ISO_2022_KR: Charset = Charset(Charset_::Iso_2022_Kr); 56 | 57 | /// EUC-KR 58 | pub const EUC_KR: Charset: Charset(Charset_::Euc_Kr); 59 | 60 | /// ISO-2022-JP 61 | pub const ISO_2022_JP: Charset = Charset(Charset_::Iso_2022_Jp); 62 | 63 | /// ISO-2022-JP-2 64 | pub const ISO_2022_JP_2: Charset = Charset(Charset_::Iso_2022_Jp_2); 65 | 66 | /// ISO-8859-6-E 67 | pub const ISO_8859_6_E: Charset = Charset(Charset_::Iso_8859_6_E); 68 | 69 | /// ISO-8859-6-I 70 | pub const ISO_8859_6_I: Charset = Charset(Charset_::Iso_8859_6_I); 71 | 72 | /// ISO-8859-8-E 73 | pub const ISO_8859_8_E: Charset = Charset(Charset_::Iso_8859_8_E); 74 | 75 | /// ISO-8859-8-I 76 | pub const ISO_8859_8_I: Charset = Charset(Charset_::Iso_8859_8_I); 77 | 78 | /// GB2312 79 | pub const GB_2312: Charset = Charset(Charset_::Gb2312); 80 | 81 | /// Big5 82 | pub const BIG_5: Charset = Charset(Charset_::Big5); 83 | 84 | /// KOI8-R 85 | pub const KOI8_R: Charset = Charset(Charset_::Koi8_R); 86 | } 87 | 88 | #[derive(Clone, Debug, PartialEq)] 89 | #[allow(non_camel_case_types)] 90 | enum Charset_ { 91 | /// US ASCII 92 | Us_Ascii, 93 | /// ISO-8859-1 94 | Iso_8859_1, 95 | /// ISO-8859-2 96 | Iso_8859_2, 97 | /// ISO-8859-3 98 | Iso_8859_3, 99 | /// ISO-8859-4 100 | Iso_8859_4, 101 | /// ISO-8859-5 102 | Iso_8859_5, 103 | /// ISO-8859-6 104 | Iso_8859_6, 105 | /// ISO-8859-7 106 | Iso_8859_7, 107 | /// ISO-8859-8 108 | Iso_8859_8, 109 | /// ISO-8859-9 110 | Iso_8859_9, 111 | /// ISO-8859-10 112 | Iso_8859_10, 113 | /// Shift_JIS 114 | Shift_Jis, 115 | /// EUC-JP 116 | Euc_Jp, 117 | /// ISO-2022-KR 118 | Iso_2022_Kr, 119 | /// EUC-KR 120 | Euc_Kr, 121 | /// ISO-2022-JP 122 | Iso_2022_Jp, 123 | /// ISO-2022-JP-2 124 | Iso_2022_Jp_2, 125 | /// ISO-8859-6-E 126 | Iso_8859_6_E, 127 | /// ISO-8859-6-I 128 | Iso_8859_6_I, 129 | /// ISO-8859-8-E 130 | Iso_8859_8_E, 131 | /// ISO-8859-8-I 132 | Iso_8859_8_I, 133 | /// GB2312 134 | Gb2312, 135 | /// Big5 136 | Big5, 137 | /// KOI8-R 138 | Koi8_R, 139 | 140 | _Unknown, 141 | } 142 | 143 | impl Charset { 144 | fn name(&self) -> &'static str { 145 | match self.0 { 146 | Charset_::Us_Ascii => "US-ASCII", 147 | Charset_::Iso_8859_1 => "ISO-8859-1", 148 | Charset_::Iso_8859_2 => "ISO-8859-2", 149 | Charset_::Iso_8859_3 => "ISO-8859-3", 150 | Charset_::Iso_8859_4 => "ISO-8859-4", 151 | Charset_::Iso_8859_5 => "ISO-8859-5", 152 | Charset_::Iso_8859_6 => "ISO-8859-6", 153 | Charset_::Iso_8859_7 => "ISO-8859-7", 154 | Charset_::Iso_8859_8 => "ISO-8859-8", 155 | Charset_::Iso_8859_9 => "ISO-8859-9", 156 | Charset_::Iso_8859_10 => "ISO-8859-10", 157 | Charset_::Shift_Jis => "Shift-JIS", 158 | Charset_::Euc_Jp => "EUC-JP", 159 | Charset_::Iso_2022_Kr => "ISO-2022-KR", 160 | Charset_::Euc_Kr => "EUC-KR", 161 | Charset_::Iso_2022_Jp => "ISO-2022-JP", 162 | Charset_::Iso_2022_Jp_2 => "ISO-2022-JP-2", 163 | Charset_::Iso_8859_6_E => "ISO-8859-6-E", 164 | Charset_::Iso_8859_6_I => "ISO-8859-6-I", 165 | Charset_::Iso_8859_8_E => "ISO-8859-8-E", 166 | Charset_::Iso_8859_8_I => "ISO-8859-8-I", 167 | Charset_::Gb2312 => "GB2312", 168 | Charset_::Big5 => "5", 169 | Charset_::Koi8_R => "KOI8-R", 170 | Charset_::_Unknown => unreachable!("Charset::_Unknown"), 171 | } 172 | } 173 | } 174 | 175 | impl fmt::Display for Charset { 176 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 177 | f.write_str(self.name()) 178 | } 179 | } 180 | 181 | #[derive(Debug)] 182 | pub struct CharsetFromStrError(()); 183 | 184 | impl FromStr for Charset { 185 | type Err = CharsetFromStrError; 186 | fn from_str(s: &str) -> Result { 187 | Ok(Charset(match s.to_ascii_uppercase().as_ref() { 188 | "US-ASCII" => Charset_::Us_Ascii, 189 | "ISO-8859-1" => Charset_::Iso_8859_1, 190 | "ISO-8859-2" => Charset_::Iso_8859_2, 191 | "ISO-8859-3" => Charset_::Iso_8859_3, 192 | "ISO-8859-4" => Charset_::Iso_8859_4, 193 | "ISO-8859-5" => Charset_::Iso_8859_5, 194 | "ISO-8859-6" => Charset_::Iso_8859_6, 195 | "ISO-8859-7" => Charset_::Iso_8859_7, 196 | "ISO-8859-8" => Charset_::Iso_8859_8, 197 | "ISO-8859-9" => Charset_::Iso_8859_9, 198 | "ISO-8859-10" => Charset_::Iso_8859_10, 199 | "SHIFT-JIS" => Charset_::Shift_Jis, 200 | "EUC-JP" => Charset_::Euc_Jp, 201 | "ISO-2022-KR" => Charset_::Iso_2022_Kr, 202 | "EUC-KR" => Charset_::Euc_Kr, 203 | "ISO-2022-JP" => Charset_::Iso_2022_Jp, 204 | "ISO-2022-JP-2" => Charset_::Iso_2022_Jp_2, 205 | "ISO-8859-6-E" => Charset_::Iso_8859_6_E, 206 | "ISO-8859-6-I" => Charset_::Iso_8859_6_I, 207 | "ISO-8859-8-E" => Charset_::Iso_8859_8_E, 208 | "ISO-8859-8-I" => Charset_::Iso_8859_8_I, 209 | "GB2312" => Charset_::Gb2312, 210 | "5" => Charset_::Big5, 211 | "KOI8-R" => Charset_::Koi8_R, 212 | _unknown => return Err(CharsetFromStrError(())), 213 | })) 214 | } 215 | } 216 | 217 | #[test] 218 | fn test_parse() { 219 | assert_eq!(Charset::US_ASCII,"us-ascii".parse().unwrap()); 220 | assert_eq!(Charset::US_ASCII,"US-Ascii".parse().unwrap()); 221 | assert_eq!(Charset::US_ASCII,"US-ASCII".parse().unwrap()); 222 | assert_eq!(Charset::SHIFT_JIS,"Shift-JIS".parse().unwrap()); 223 | assert!("abcd".parse(::().is_err()); 224 | } 225 | 226 | #[test] 227 | fn test_display() { 228 | assert_eq!("US-ASCII", format!("{}", Charset::US_ASCII)); 229 | } 230 | -------------------------------------------------------------------------------- /src/common/content_range.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::ops::{Bound, RangeBounds}; 3 | 4 | use http::{HeaderName, HeaderValue}; 5 | 6 | use crate::{util, Error, Header}; 7 | 8 | /// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) 9 | /// 10 | /// # ABNF 11 | /// 12 | /// ```text 13 | /// Content-Range = byte-content-range 14 | /// / other-content-range 15 | /// 16 | /// byte-content-range = bytes-unit SP 17 | /// ( byte-range-resp / unsatisfied-range ) 18 | /// 19 | /// byte-range-resp = byte-range "/" ( complete-length / "*" ) 20 | /// byte-range = first-byte-pos "-" last-byte-pos 21 | /// unsatisfied-range = "*/" complete-length 22 | /// 23 | /// complete-length = 1*DIGIT 24 | /// 25 | /// other-content-range = other-range-unit SP other-range-resp 26 | /// other-range-resp = *CHAR 27 | /// ``` 28 | /// 29 | /// # Example 30 | /// 31 | /// ``` 32 | /// use headers::ContentRange; 33 | /// 34 | /// // 100 bytes (included byte 199), with a full length of 3,400 35 | /// let cr = ContentRange::bytes(100..200, 3400).unwrap(); 36 | /// ``` 37 | //NOTE: only supporting bytes-content-range, YAGNI the extension 38 | #[derive(Clone, Debug, PartialEq)] 39 | pub struct ContentRange { 40 | /// First and last bytes of the range, omitted if request could not be 41 | /// satisfied 42 | range: Option<(u64, u64)>, 43 | 44 | /// Total length of the instance, can be omitted if unknown 45 | complete_length: Option, 46 | } 47 | 48 | error_type!(InvalidContentRange); 49 | 50 | impl ContentRange { 51 | /// Construct a new `Content-Range: bytes ..` header. 52 | pub fn bytes( 53 | range: impl RangeBounds, 54 | complete_length: impl Into>, 55 | ) -> Result { 56 | let complete_length = complete_length.into(); 57 | 58 | let start = match range.start_bound() { 59 | Bound::Included(&s) => s, 60 | Bound::Excluded(&s) => s + 1, 61 | Bound::Unbounded => 0, 62 | }; 63 | 64 | let end = match range.end_bound() { 65 | Bound::Included(&e) => e, 66 | Bound::Excluded(&e) => e - 1, 67 | Bound::Unbounded => match complete_length { 68 | Some(max) => max - 1, 69 | None => return Err(InvalidContentRange { _inner: () }), 70 | }, 71 | }; 72 | 73 | Ok(ContentRange { 74 | range: Some((start, end)), 75 | complete_length, 76 | }) 77 | } 78 | 79 | /// Create a new `ContentRange` stating the range could not be satisfied. 80 | /// 81 | /// The passed argument is the complete length of the entity. 82 | pub fn unsatisfied_bytes(complete_length: u64) -> Self { 83 | ContentRange { 84 | range: None, 85 | complete_length: Some(complete_length), 86 | } 87 | } 88 | 89 | /// Get the byte range if satisified. 90 | /// 91 | /// Note that these byte ranges are inclusive on both ends. 92 | pub fn bytes_range(&self) -> Option<(u64, u64)> { 93 | self.range 94 | } 95 | 96 | /// Get the bytes complete length if available. 97 | pub fn bytes_len(&self) -> Option { 98 | self.complete_length 99 | } 100 | } 101 | 102 | impl Header for ContentRange { 103 | fn name() -> &'static HeaderName { 104 | &::http::header::CONTENT_RANGE 105 | } 106 | 107 | fn decode<'i, I: Iterator>(values: &mut I) -> Result { 108 | values 109 | .next() 110 | .and_then(|v| v.to_str().ok()) 111 | .and_then(|s| split_in_two(s, ' ')) 112 | .and_then(|(unit, spec)| { 113 | if unit != "bytes" { 114 | // For now, this only supports bytes-content-range. nani? 115 | return None; 116 | } 117 | 118 | let (range, complete_length) = split_in_two(spec, '/')?; 119 | 120 | let complete_length = if complete_length == "*" { 121 | None 122 | } else { 123 | Some(complete_length.parse().ok()?) 124 | }; 125 | 126 | let range = if range == "*" { 127 | None 128 | } else { 129 | let (first_byte, last_byte) = split_in_two(range, '-')?; 130 | let first_byte = first_byte.parse().ok()?; 131 | let last_byte = last_byte.parse().ok()?; 132 | if last_byte < first_byte { 133 | return None; 134 | } 135 | Some((first_byte, last_byte)) 136 | }; 137 | 138 | Some(ContentRange { 139 | range, 140 | complete_length, 141 | }) 142 | }) 143 | .ok_or_else(Error::invalid) 144 | } 145 | 146 | fn encode>(&self, values: &mut E) { 147 | struct Adapter<'a>(&'a ContentRange); 148 | 149 | impl fmt::Display for Adapter<'_> { 150 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 151 | f.write_str("bytes ")?; 152 | 153 | if let Some((first_byte, last_byte)) = self.0.range { 154 | write!(f, "{}-{}", first_byte, last_byte)?; 155 | } else { 156 | f.write_str("*")?; 157 | } 158 | 159 | f.write_str("/")?; 160 | 161 | if let Some(v) = self.0.complete_length { 162 | write!(f, "{}", v) 163 | } else { 164 | f.write_str("*") 165 | } 166 | } 167 | } 168 | 169 | values.extend(::std::iter::once(util::fmt(Adapter(self)))); 170 | } 171 | } 172 | 173 | fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { 174 | let mut iter = s.splitn(2, separator); 175 | match (iter.next(), iter.next()) { 176 | (Some(a), Some(b)) => Some((a, b)), 177 | _ => None, 178 | } 179 | } 180 | 181 | /* 182 | test_header!(test_bytes, 183 | vec![b"bytes 0-499/500"], 184 | Some(ContentRange(ContentRangeSpec::Bytes { 185 | range: Some((0, 499)), 186 | complete_length: Some(500) 187 | }))); 188 | 189 | test_header!(test_bytes_unknown_len, 190 | vec![b"bytes 0-499/*"], 191 | Some(ContentRange(ContentRangeSpec::Bytes { 192 | range: Some((0, 499)), 193 | complete_length: None 194 | }))); 195 | 196 | test_header!(test_bytes_unknown_range, 197 | vec![b"bytes */ 198 | 500"], 199 | Some(ContentRange(ContentRangeSpec::Bytes { 200 | range: None, 201 | complete_length: Some(500) 202 | }))); 203 | 204 | test_header!(test_unregistered, 205 | vec![b"seconds 1-2"], 206 | Some(ContentRange(ContentRangeSpec::Unregistered { 207 | unit: "seconds".to_owned(), 208 | resp: "1-2".to_owned() 209 | }))); 210 | 211 | test_header!(test_no_len, 212 | vec![b"bytes 0-499"], 213 | None::); 214 | 215 | test_header!(test_only_unit, 216 | vec![b"bytes"], 217 | None::); 218 | 219 | test_header!(test_end_less_than_start, 220 | vec![b"bytes 499-0/500"], 221 | None::); 222 | 223 | test_header!(test_blank, 224 | vec![b""], 225 | None::); 226 | 227 | test_header!(test_bytes_many_spaces, 228 | vec![b"bytes 1-2/500 3"], 229 | None::); 230 | 231 | test_header!(test_bytes_many_slashes, 232 | vec![b"bytes 1-2/500/600"], 233 | None::); 234 | 235 | test_header!(test_bytes_many_dashes, 236 | vec![b"bytes 1-2-3/500"], 237 | None::); 238 | */ 239 | --------------------------------------------------------------------------------