├── .gitignore ├── .github ├── PULL_REQUEST_TEMPLATE └── workflows │ └── ci.yml ├── clippy.toml ├── src ├── header │ ├── internals │ │ ├── mod.rs │ │ ├── item.rs │ │ ├── vec_map.rs │ │ └── cell.rs │ ├── shared │ │ ├── mod.rs │ │ ├── encoding.rs │ │ ├── httpdate.rs │ │ └── charset.rs │ └── common │ │ ├── from.rs │ │ ├── access_control_max_age.rs │ │ ├── access_control_request_method.rs │ │ ├── date.rs │ │ ├── server.rs │ │ ├── expires.rs │ │ ├── last_modified.rs │ │ ├── location.rs │ │ ├── referer.rs │ │ ├── if_modified_since.rs │ │ ├── user_agent.rs │ │ ├── if_unmodified_since.rs │ │ ├── access_control_allow_methods.rs │ │ ├── content_location.rs │ │ ├── content_language.rs │ │ ├── content_encoding.rs │ │ ├── expect.rs │ │ ├── access_control_allow_headers.rs │ │ ├── access_control_request_headers.rs │ │ ├── access_control_expose_headers.rs │ │ ├── accept_charset.rs │ │ ├── last_event_id.rs │ │ ├── transfer_encoding.rs │ │ ├── vary.rs │ │ ├── te.rs │ │ ├── accept_language.rs │ │ ├── allow.rs │ │ ├── accept_encoding.rs │ │ ├── if_match.rs │ │ ├── pragma.rs │ │ ├── access_control_allow_origin.rs │ │ ├── if_none_match.rs │ │ ├── access_control_allow_credentials.rs │ │ ├── if_range.rs │ │ ├── accept_ranges.rs │ │ ├── etag.rs │ │ ├── content_length.rs │ │ ├── preference_applied.rs │ │ ├── host.rs │ │ ├── content_type.rs │ │ ├── referrer_policy.rs │ │ ├── set_cookie.rs │ │ ├── accept.rs │ │ ├── connection.rs │ │ ├── origin.rs │ │ ├── upgrade.rs │ │ ├── content_range.rs │ │ ├── retry_after.rs │ │ ├── warning.rs │ │ ├── proxy_authorization.rs │ │ ├── strict_transport_security.rs │ │ └── cache_control.rs ├── lib.rs └── error.rs ├── LICENSE ├── Cargo.toml ├── README.md ├── Cargo.lock └── CHANGELOG.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.46.0" 2 | -------------------------------------------------------------------------------- /src/header/internals/mod.rs: -------------------------------------------------------------------------------- 1 | 2 | #[cfg(feature = "headers")] 3 | pub use self::item::Item; 4 | 5 | pub use self::vec_map::VecMap; 6 | 7 | #[cfg(feature = "headers")] 8 | pub use self::vec_map::Entry; 9 | 10 | #[cfg(feature = "headers")] 11 | mod cell; 12 | 13 | #[cfg(feature = "headers")] 14 | mod item; 15 | 16 | mod vec_map; 17 | -------------------------------------------------------------------------------- /src/header/shared/mod.rs: -------------------------------------------------------------------------------- 1 | pub use self::charset::Charset; 2 | pub use self::encoding::Encoding; 3 | pub use self::entity::EntityTag; 4 | pub use self::httpdate::HttpDate; 5 | pub use language_tags::LanguageTag; 6 | pub use self::quality_item::{Quality, QualityItem, qitem, q}; 7 | 8 | mod charset; 9 | mod encoding; 10 | mod entity; 11 | mod httpdate; 12 | mod quality_item; 13 | -------------------------------------------------------------------------------- /src/header/common/from.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `From` header, defined in [RFC7231](http://tools.ietf.org/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 | /// # extern crate http; 19 | /// use hyperx::header::{From, TypedHeaders}; 20 | /// 21 | /// let mut headers = http::HeaderMap::new(); 22 | /// headers.encode(&From("webmaster@example.org".to_owned())); 23 | /// ``` 24 | // FIXME: Maybe use mailbox? 25 | (From, "From") => [String] 26 | 27 | test_from { 28 | test_header!(test1, vec![b"webmaster@example.org"]); 29 | } 30 | } 31 | 32 | standard_header!(From, FROM); 33 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![deny(missing_docs)] 2 | #![deny(missing_debug_implementations)] 3 | #![deny(unused_extern_crates)] 4 | #![cfg_attr(all(test, feature = "nightly"), feature(test))] 5 | 6 | //! # hyper*x* 7 | //! 8 | //! Hyper is the low-level HTTP implementation for Rust. Hyper*x* is an 9 | //! e*x*traction of the hyper 0.11 typed header module, with minimized 10 | //! dependencies, for continued use with hyper 0.12 or later, where 11 | //! this module was removed in preference to the byte-oriented `http::header` 12 | //! module. 13 | //! 14 | //! See the [*header*](header/index.html) module for more details. 15 | 16 | extern crate base64; 17 | extern crate bytes; 18 | extern crate http; 19 | extern crate language_tags; 20 | pub extern crate mime; 21 | extern crate percent_encoding; 22 | extern crate httpdate; 23 | extern crate unicase; 24 | 25 | #[cfg(all(test, feature = "nightly"))] 26 | extern crate test; 27 | 28 | pub use error::{Result, Error}; 29 | 30 | #[cfg(feature = "headers")] 31 | pub use header::Headers; 32 | 33 | pub use method::Method; 34 | 35 | mod error; 36 | mod method; 37 | pub mod header; 38 | -------------------------------------------------------------------------------- /src/header/common/access_control_max_age.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `Access-Control-Max-Age` header, part of 3 | /// [CORS](http://www.w3.org/TR/cors/#access-control-max-age-response-header) 4 | /// 5 | /// The `Access-Control-Max-Age` header indicates how long the results of a 6 | /// preflight request can be cached in a preflight result cache. 7 | /// 8 | /// # ABNF 9 | /// 10 | /// ```text 11 | /// Access-Control-Max-Age = \"Access-Control-Max-Age\" \":\" delta-seconds 12 | /// ``` 13 | /// 14 | /// # Example values 15 | /// 16 | /// * `531` 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// # extern crate http; 22 | /// use hyperx::header::{AccessControlMaxAge, TypedHeaders}; 23 | /// 24 | /// let mut headers = http::HeaderMap::new(); 25 | /// headers.encode(&AccessControlMaxAge(1728000u32)); 26 | /// ``` 27 | (AccessControlMaxAge, "Access-Control-Max-Age") => [u32] 28 | 29 | test_access_control_max_age { 30 | test_header!(test1, vec![b"531"]); 31 | } 32 | } 33 | 34 | standard_header!(AccessControlMaxAge, ACCESS_CONTROL_MAX_AGE); 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2018 Sean McArthur 2 | Copyright (c) 2018 The hyperx Contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "hyperx" 3 | version = "1.4.0" 4 | description = "Hyper's typed header module, eXtracted and improved" 5 | readme = "README.md" 6 | documentation = "https://docs.rs/hyperx" 7 | repository = "https://github.com/dekellum/hyperx" 8 | license = "MIT" 9 | authors = ["David Kellum "] 10 | keywords = ["http", "hyper", "hyperium"] 11 | categories = [ 12 | "network-programming", 13 | "web-programming::http-client", 14 | "web-programming::http-server" 15 | ] 16 | exclude = [ 17 | ".gitignore", 18 | ".travis.yml", 19 | "appveyor.yml", 20 | ] 21 | build = "build.rs" 22 | 23 | [dependencies] 24 | base64 = { version=">=0.10.1, <0.14" } 25 | bytes = { version=">=1.0.0, <1.2.0" } 26 | http = { version=">=0.2.2, <0.3" } 27 | httpdate = { version=">=0.3.2, <1.1" } 28 | language-tags = { version=">=0.3.1, <0.4" } 29 | mime = { version=">=0.3.2, <0.4" } 30 | percent-encoding = { version=">=2.1.0, <2.2" } 31 | unicase = { version=">=2.6.0, <2.7" } 32 | 33 | [features] 34 | nightly = [] 35 | compat = [] # no-op for backward compatibility 36 | headers = [] 37 | 38 | [package.metadata.docs.rs] 39 | features = ["headers"] 40 | -------------------------------------------------------------------------------- /src/header/common/access_control_request_method.rs: -------------------------------------------------------------------------------- 1 | use method::Method; 2 | 3 | header! { 4 | /// `Access-Control-Request-Method` header, part of 5 | /// [CORS](http://www.w3.org/TR/cors/#access-control-request-method-request-header) 6 | /// 7 | /// The `Access-Control-Request-Method` header indicates which method will be 8 | /// used in the actual request as part of the preflight request. 9 | /// # ABNF 10 | /// 11 | /// ```text 12 | /// Access-Control-Request-Method: \"Access-Control-Request-Method\" \":\" Method 13 | /// ``` 14 | /// 15 | /// # Example values 16 | /// * `GET` 17 | /// 18 | /// # Examples 19 | /// 20 | /// ``` 21 | /// # extern crate http; 22 | /// use hyperx::header::{AccessControlRequestMethod, TypedHeaders}; 23 | /// use hyperx::Method; 24 | /// 25 | /// let mut headers = http::HeaderMap::new(); 26 | /// headers.encode(&AccessControlRequestMethod(Method::Get)); 27 | /// ``` 28 | (AccessControlRequestMethod, "Access-Control-Request-Method") => [Method] 29 | 30 | test_access_control_request_method { 31 | test_header!(test1, vec![b"GET"]); 32 | } 33 | } 34 | 35 | standard_header!(AccessControlRequestMethod, ACCESS_CONTROL_REQUEST_METHOD); 36 | -------------------------------------------------------------------------------- /src/header/common/date.rs: -------------------------------------------------------------------------------- 1 | use header::HttpDate; 2 | 3 | header! { 4 | /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) 5 | /// 6 | /// The `Date` header field represents the date and time at which the 7 | /// message was originated. 8 | /// 9 | /// # ABNF 10 | /// 11 | /// ```text 12 | /// Date = HTTP-date 13 | /// ``` 14 | /// 15 | /// # Example values 16 | /// 17 | /// * `Tue, 15 Nov 1994 08:12:31 GMT` 18 | /// 19 | /// # Example 20 | /// 21 | /// ``` 22 | /// # extern crate http; 23 | /// use hyperx::header::{Date, TypedHeaders}; 24 | /// use std::time::SystemTime; 25 | /// 26 | /// let mut headers = http::HeaderMap::new(); 27 | /// headers.encode(&Date(SystemTime::now().into())); 28 | /// ``` 29 | (Date, "Date") => [HttpDate] 30 | 31 | test_date { 32 | test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); 33 | } 34 | } 35 | 36 | bench_header!(imf_fixdate, Date, { vec![b"Mon, 07 Nov 1994 08:48:37 GMT".to_vec()] }); 37 | bench_header!(rfc_850, Date, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); 38 | bench_header!(asctime, Date, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); 39 | 40 | standard_header!(Date, DATE); 41 | -------------------------------------------------------------------------------- /src/header/common/server.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `Server` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.2) 3 | /// 4 | /// The `Server` header field contains information about the software 5 | /// used by the origin server to handle the request, which is often used 6 | /// by clients to help identify the scope of reported interoperability 7 | /// problems, to work around or tailor requests to avoid particular 8 | /// server limitations, and for analytics regarding server or operating 9 | /// system use. An origin server MAY generate a Server field in its 10 | /// responses. 11 | /// 12 | /// # ABNF 13 | /// 14 | /// ```text 15 | /// Server = product *( RWS ( product / comment ) ) 16 | /// ``` 17 | /// 18 | /// # Example values 19 | /// * `CERN/3.0 libwww/2.17` 20 | /// 21 | /// # Example 22 | /// 23 | /// ``` 24 | /// # extern crate http; 25 | /// use hyperx::header::{Server, TypedHeaders}; 26 | /// 27 | /// let mut headers = http::HeaderMap::new(); 28 | /// headers.encode(&Server::new("hyper/0.5.2")); 29 | /// ``` 30 | // TODO: Maybe parse as defined in the spec? 31 | (Server, "Server") => Cow[str] 32 | 33 | test_server { 34 | // Testcase from RFC 35 | test_header!(test1, vec![b"CERN/3.0 libwww/2.17"]); 36 | } 37 | } 38 | 39 | bench_header!(bench, Server, { vec![b"Some String".to_vec()] }); 40 | 41 | standard_header!(Server, SERVER); 42 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hyper*x* 2 | 3 | [![Rustdoc](https://docs.rs/hyperx/badge.svg)](https://docs.rs/hyperx) 4 | [![Change Log](https://img.shields.io/crates/v/hyperx.svg?maxAge=3600&label=change%20log&color=9cf)](https://github.com/dekellum/hyperx/blob/main/CHANGELOG.md) 5 | [![crates.io](https://img.shields.io/crates/v/hyperx.svg?maxAge=3600)](https://crates.io/crates/hyperx) 6 | [![CI Status](https://github.com/dekellum/hyperx/workflows/CI/badge.svg?branch=main)](https://github.com/dekellum/hyperx/actions?query=workflow%3ACI) 7 | [![deps status](https://deps.rs/repo/github/dekellum/hyperx/status.svg)](https://deps.rs/repo/github/dekellum/hyperx) 8 | 9 | [Hyper] is the low-level HTTP implementation for Rust. Hyper*x* is an 10 | e*x*traction of the hyper 0.11 typed header module, with minimized 11 | dependencies, for continued use with hyper 0.12 or later, 12 | where it was dropped. 13 | 14 | [Hyper]: https://github.com/hyperium/hyper 15 | 16 | ## Minimum supported rust version 17 | 18 | MSRV := 1.46.0 19 | 20 | The crate will fail fast on any lower rustc (via a build.rs version check) and 21 | is also CI tested on this version. MSRV will only be increased in a new MINOR 22 | (or MAJOR) release of this crate. However, some direct or transitive 23 | dependencies unfortunately have or may increase MSRV in PATCH releases. Users 24 | may need to selectively control updates by preserving/distributing a Cargo.lock 25 | file in order to control MSRV. 26 | 27 | ## License 28 | 29 | The MIT license ([LICENSE](LICENSE) or http://opensource.org/licenses/MIT) 30 | -------------------------------------------------------------------------------- /src/header/common/expires.rs: -------------------------------------------------------------------------------- 1 | use header::HttpDate; 2 | 3 | header! { 4 | /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) 5 | /// 6 | /// The `Expires` header field gives the date/time after which the 7 | /// response is considered stale. 8 | /// 9 | /// The presence of an Expires field does not imply that the original 10 | /// resource will change or cease to exist at, before, or after that 11 | /// time. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Expires = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `Thu, 01 Dec 1994 16:00:00 GMT` 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// # extern crate http; 26 | /// use hyperx::header::{Expires, TypedHeaders}; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let mut headers = http::HeaderMap::new(); 30 | /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); 31 | /// headers.encode(&Expires(expiration.into())); 32 | /// ``` 33 | (Expires, "Expires") => [HttpDate] 34 | 35 | test_expires { 36 | // Testcase from RFC 37 | test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); 38 | } 39 | } 40 | 41 | bench_header!(imf_fixdate, Expires, { vec![b"Mon, 07 Nov 1994 08:48:37 GMT".to_vec()] }); 42 | bench_header!(rfc_850, Expires, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); 43 | bench_header!(asctime, Expires, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); 44 | 45 | standard_header!(Expires, EXPIRES); 46 | -------------------------------------------------------------------------------- /src/header/common/last_modified.rs: -------------------------------------------------------------------------------- 1 | use header::HttpDate; 2 | 3 | header! { 4 | /// `Last-Modified` header, defined in 5 | /// [RFC7232](http://tools.ietf.org/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 | /// # extern crate http; 26 | /// use hyperx::header::{LastModified, TypedHeaders}; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let mut headers = http::HeaderMap::new(); 30 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 31 | /// headers.encode(&LastModified(modified.into())); 32 | /// ``` 33 | (LastModified, "Last-Modified") => [HttpDate] 34 | 35 | test_last_modified { 36 | // Testcase from RFC 37 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} 38 | } 39 | 40 | bench_header!(imf_fixdate, LastModified, { vec![b"Mon, 07 Nov 1994 08:48:37 GMT".to_vec()] }); 41 | bench_header!(rfc_850, LastModified, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); 42 | bench_header!(asctime, LastModified, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); 43 | 44 | standard_header!(LastModified, LAST_MODIFIED); 45 | -------------------------------------------------------------------------------- /src/header/common/location.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `Location` header, defined in 3 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.2) 4 | /// 5 | /// The `Location` header field is used in some responses to refer to a 6 | /// specific resource in relation to the response. The type of 7 | /// relationship is defined by the combination of request method and 8 | /// status code semantics. 9 | /// 10 | /// # ABNF 11 | /// 12 | /// ```text 13 | /// Location = URI-reference 14 | /// ``` 15 | /// 16 | /// # Example values 17 | /// * `/People.html#tim` 18 | /// * `http://www.example.net/index.html` 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// # extern crate http; 24 | /// use hyperx::header::{Location, TypedHeaders}; 25 | /// 26 | /// let mut headers = http::HeaderMap::new(); 27 | /// headers.encode(&Location::new("/People.html#tim")); 28 | /// ``` 29 | /// 30 | /// ``` 31 | /// # extern crate http; 32 | /// use hyperx::header::{Location, TypedHeaders}; 33 | /// 34 | /// let mut headers = http::HeaderMap::new(); 35 | /// headers.encode(&Location::new("http://www.example.com/index.html")); 36 | /// ``` 37 | // TODO: Use URL 38 | (Location, "Location") => Cow[str] 39 | 40 | test_location { 41 | // Testcase from RFC 42 | test_header!(test1, vec![b"/People.html#tim"]); 43 | test_header!(test2, vec![b"http://www.example.net/index.html"]); 44 | } 45 | 46 | } 47 | 48 | bench_header!(bench, Location, { vec![b"http://foo.com/hello:3000".to_vec()] }); 49 | 50 | standard_header!(Location, LOCATION); 51 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | pull_request: 4 | push: 5 | branches: 6 | - main 7 | - dev 8 | schedule: 9 | - cron: '47 15 * * 1,4' 10 | 11 | env: 12 | RUST_BACKTRACE: 1 13 | 14 | jobs: 15 | 16 | test: 17 | name: ${{ matrix.rust }} ${{ matrix.os }} ${{ join(matrix.extras) }} 18 | runs-on: ${{ matrix.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | include: 23 | - rust: 1.46.0 24 | os: ubuntu-20.04 25 | - rust: 1.46.0 26 | os: ubuntu-20.04 27 | extras: [update] 28 | - rust: 1.46.0 29 | os: windows-latest 30 | extras: [update] 31 | - rust: stable 32 | os: ubuntu-20.04 33 | extras: [update] 34 | - rust: nightly 35 | os: ubuntu-20.04 36 | - rust: nightly 37 | os: ubuntu-20.04 38 | extras: [update] 39 | 40 | steps: 41 | - name: Checkout 42 | uses: actions/checkout@v2 43 | 44 | - name: Install rust (${{ matrix.rust }}) 45 | uses: actions-rs/toolchain@v1 46 | with: 47 | profile: minimal 48 | toolchain: ${{ matrix.rust }} 49 | override: true 50 | 51 | - name: Update deps 52 | if: ${{ contains(matrix.extras, 'update') }} 53 | run: cargo update 54 | 55 | - name: Test 56 | run: cargo test 57 | 58 | - name: Test headers feature 59 | run: cargo test --features headers 60 | 61 | - name: Test all features/targets 62 | if: ${{ matrix.rust == 'nightly' }} 63 | run: cargo test --all-features --all-targets 64 | -------------------------------------------------------------------------------- /src/header/common/referer.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `Referer` header, defined in 3 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.2) 4 | /// 5 | /// The `Referer` header field allows the user agent to specify a 6 | /// URI reference for the resource from which the target URI was obtained 7 | /// (i.e., the "referrer", though the field name is misspelled). A user 8 | /// agent MUST NOT include the fragment and userinfo components of the 9 | /// URI reference, if any, when generating the Referer field value. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Referer = absolute-URI / partial-URI 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// 19 | /// * `http://www.example.org/hypertext/Overview.html` 20 | /// 21 | /// # Examples 22 | /// 23 | /// ``` 24 | /// # extern crate http; 25 | /// use hyperx::header::{Referer, TypedHeaders}; 26 | /// 27 | /// let mut headers = http::HeaderMap::new(); 28 | /// headers.encode(&Referer::new("/People.html#tim")); 29 | /// ``` 30 | /// 31 | /// ``` 32 | /// # extern crate http; 33 | /// use hyperx::header::{Referer, TypedHeaders}; 34 | /// 35 | /// let mut headers = http::HeaderMap::new(); 36 | /// headers.encode(&Referer::new("http://www.example.com/index.html")); 37 | /// ``` 38 | // TODO Use URL 39 | (Referer, "Referer") => Cow[str] 40 | 41 | test_referer { 42 | // Testcase from the RFC 43 | test_header!(test1, vec![b"http://www.example.org/hypertext/Overview.html"]); 44 | } 45 | } 46 | 47 | bench_header!(bench, Referer, { vec![b"http://foo.com/hello:3000".to_vec()] }); 48 | 49 | standard_header!(Referer, REFERER); 50 | -------------------------------------------------------------------------------- /src/header/common/if_modified_since.rs: -------------------------------------------------------------------------------- 1 | use header::HttpDate; 2 | 3 | header! { 4 | /// `If-Modified-Since` header, defined in 5 | /// [RFC7232](http://tools.ietf.org/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-Unmodified-Since = HTTP-date 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 21 | /// 22 | /// # Example 23 | /// 24 | /// ``` 25 | /// # extern crate http; 26 | /// use hyperx::header::{IfModifiedSince, TypedHeaders}; 27 | /// use std::time::{SystemTime, Duration}; 28 | /// 29 | /// let mut headers = http::HeaderMap::new(); 30 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 31 | /// headers.encode(&IfModifiedSince(modified.into())); 32 | /// ``` 33 | (IfModifiedSince, "If-Modified-Since") => [HttpDate] 34 | 35 | test_if_modified_since { 36 | // Testcase from RFC 37 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); 38 | } 39 | } 40 | 41 | bench_header!(imf_fixdate, IfModifiedSince, { vec![b"Mon, 07 Nov 1994 08:48:37 GMT".to_vec()] }); 42 | bench_header!(rfc_850, IfModifiedSince, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); 43 | bench_header!(asctime, IfModifiedSince, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); 44 | 45 | standard_header!(IfModifiedSince, IF_MODIFIED_SINCE); 46 | -------------------------------------------------------------------------------- /src/header/common/user_agent.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `User-Agent` header, defined in 3 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.5.3) 4 | /// 5 | /// The `User-Agent` header field contains information about the user 6 | /// agent originating the request, which is often used by servers to help 7 | /// identify the scope of reported interoperability problems, to work 8 | /// around or tailor responses to avoid particular user agent 9 | /// limitations, and for analytics regarding browser or operating system 10 | /// use. A user agent SHOULD send a User-Agent field in each request 11 | /// unless specifically configured not to do so. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// User-Agent = product *( RWS ( product / comment ) ) 17 | /// product = token ["/" product-version] 18 | /// product-version = token 19 | /// ``` 20 | /// 21 | /// # Example values 22 | /// 23 | /// * `CERN-LineMode/2.15 libwww/2.17b3` 24 | /// * `Bunnies` 25 | /// 26 | /// # Notes 27 | /// 28 | /// * The parser does not split the value 29 | /// 30 | /// # Example 31 | /// 32 | /// ``` 33 | /// # extern crate http; 34 | /// use hyperx::header::{TypedHeaders, UserAgent}; 35 | /// 36 | /// let mut headers = http::HeaderMap::new(); 37 | /// headers.encode(&UserAgent::new("hyper/0.5.2")); 38 | /// ``` 39 | (UserAgent, "User-Agent") => Cow[str] 40 | 41 | test_user_agent { 42 | // Testcase from RFC 43 | test_header!(test1, vec![b"CERN-LineMode/2.15 libwww/2.17b3"]); 44 | // Own testcase 45 | test_header!(test2, vec![b"Bunnies"], Some(UserAgent::new("Bunnies"))); 46 | } 47 | } 48 | 49 | standard_header!(UserAgent, USER_AGENT); 50 | -------------------------------------------------------------------------------- /src/header/shared/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/header/common/if_unmodified_since.rs: -------------------------------------------------------------------------------- 1 | use header::HttpDate; 2 | 3 | header! { 4 | /// `If-Unmodified-Since` header, defined in 5 | /// [RFC7232](http://tools.ietf.org/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 | /// # extern crate http; 27 | /// use hyperx::header::{IfUnmodifiedSince, TypedHeaders}; 28 | /// use std::time::{SystemTime, Duration}; 29 | /// 30 | /// let mut headers = http::HeaderMap::new(); 31 | /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 32 | /// headers.encode(&IfUnmodifiedSince(modified.into())); 33 | /// ``` 34 | (IfUnmodifiedSince, "If-Unmodified-Since") => [HttpDate] 35 | 36 | test_if_unmodified_since { 37 | // Testcase from RFC 38 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); 39 | } 40 | } 41 | 42 | bench_header!(imf_fixdate, IfUnmodifiedSince, { vec![b"Mon, 07 Nov 1994 08:48:37 GMT".to_vec()] }); 43 | bench_header!(rfc_850, IfUnmodifiedSince, { vec![b"Sunday, 06-Nov-94 08:49:37 GMT".to_vec()] }); 44 | bench_header!(asctime, IfUnmodifiedSince, { vec![b"Sun Nov 6 08:49:37 1994".to_vec()] }); 45 | 46 | standard_header!(IfUnmodifiedSince, IF_UNMODIFIED_SINCE); 47 | -------------------------------------------------------------------------------- /src/header/common/access_control_allow_methods.rs: -------------------------------------------------------------------------------- 1 | use method::Method; 2 | 3 | header! { 4 | /// `Access-Control-Allow-Methods` header, part of 5 | /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-methods-response-header) 6 | /// 7 | /// The `Access-Control-Allow-Methods` header indicates, as part of the 8 | /// response to a preflight request, which methods can be used during the 9 | /// actual request. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Access-Control-Allow-Methods: "Access-Control-Allow-Methods" ":" #Method 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// * `PUT, DELETE, XMODIFY` 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// # extern crate http; 24 | /// use hyperx::header::{AccessControlAllowMethods, TypedHeaders}; 25 | /// use hyperx::Method; 26 | /// 27 | /// let mut headers = http::HeaderMap::new(); 28 | /// headers.encode( 29 | /// &AccessControlAllowMethods(vec![Method::Get]) 30 | /// ); 31 | /// ``` 32 | /// 33 | /// ``` 34 | /// # extern crate http; 35 | /// use hyperx::header::{AccessControlAllowMethods, TypedHeaders}; 36 | /// use hyperx::Method; 37 | /// 38 | /// let mut headers = http::HeaderMap::new(); 39 | /// headers.encode( 40 | /// &AccessControlAllowMethods(vec![ 41 | /// Method::Get, 42 | /// Method::Post, 43 | /// Method::Patch, 44 | /// Method::Extension("COPY".to_owned()), 45 | /// ]) 46 | /// ); 47 | /// ``` 48 | (AccessControlAllowMethods, "Access-Control-Allow-Methods") => (Method)* 49 | 50 | test_access_control_allow_methods { 51 | test_header!(test1, vec![b"PUT, DELETE, XMODIFY"]); 52 | } 53 | } 54 | 55 | standard_header!(AccessControlAllowMethods, ACCESS_CONTROL_ALLOW_METHODS); 56 | -------------------------------------------------------------------------------- /src/header/common/content_location.rs: -------------------------------------------------------------------------------- 1 | header! { 2 | /// `Content-Location` header, defined in 3 | /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.4.2) 4 | /// 5 | /// The header can be used by both the client in requests and the server 6 | /// in responses with different semantics. Client sets `Content-Location` 7 | /// to refer to the URI where original representation of the body was 8 | /// obtained. 9 | /// 10 | /// In responses `Content-Location` represents URI for the representation 11 | /// that was content negotiated, created or for the response payload. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Content-Location = absolute-URI / partial-URI 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// 21 | /// * `/hypertext/Overview.html` 22 | /// * `http://www.example.org/hypertext/Overview.html` 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// # extern crate http; 28 | /// use hyperx::header::{ContentLocation, TypedHeaders}; 29 | /// 30 | /// let mut headers = http::HeaderMap::new(); 31 | /// headers.encode(&ContentLocation("/hypertext/Overview.html".to_owned())); 32 | /// ``` 33 | /// 34 | /// ``` 35 | /// # extern crate http; 36 | /// use hyperx::header::{ContentLocation, TypedHeaders}; 37 | /// 38 | /// let mut headers = http::HeaderMap::new(); 39 | /// headers.encode(&ContentLocation("http://www.example.org/hypertext/Overview.html".to_owned())); 40 | /// ``` 41 | // TODO: use URL 42 | (ContentLocation, "Content-Location") => [String] 43 | 44 | test_content_location { 45 | test_header!(partial_query, vec![b"/hypertext/Overview.html?q=tim"]); 46 | 47 | test_header!(absolute, vec![b"http://www.example.org/hypertext/Overview.html"]); 48 | } 49 | } 50 | 51 | standard_header!(ContentLocation, CONTENT_LOCATION); 52 | -------------------------------------------------------------------------------- /src/header/common/content_language.rs: -------------------------------------------------------------------------------- 1 | use language_tags::LanguageTag; 2 | use header::QualityItem; 3 | 4 | header! { 5 | /// `Content-Language` header, defined in 6 | /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) 7 | /// 8 | /// The `Content-Language` header field describes the natural language(s) 9 | /// of the intended audience for the representation. Note that this 10 | /// might not be equivalent to all the languages used within the 11 | /// representation. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Content-Language = 1#language-tag 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// 21 | /// * `da` 22 | /// * `mi, en` 23 | /// 24 | /// # Examples 25 | /// 26 | /// ``` 27 | /// # extern crate http; 28 | /// # extern crate hyperx; 29 | /// extern crate language_tags; 30 | /// # use hyperx::header::{ContentLanguage, qitem, TypedHeaders}; 31 | /// # 32 | /// # fn main() { 33 | /// let mut headers = http::HeaderMap::new(); 34 | /// headers.encode( 35 | /// &ContentLanguage(vec![ 36 | /// qitem("en".parse().unwrap()), 37 | /// ]) 38 | /// ); 39 | /// # } 40 | /// ``` 41 | /// 42 | /// ``` 43 | /// # extern crate http; 44 | /// # extern crate hyperx; 45 | /// extern crate language_tags; 46 | /// # use hyperx::header::{ContentLanguage, qitem, TypedHeaders}; 47 | /// # fn main() { 48 | /// 49 | /// let mut headers = http::HeaderMap::new(); 50 | /// headers.encode( 51 | /// &ContentLanguage(vec![ 52 | /// qitem("da".parse().unwrap()), 53 | /// qitem("en-GB".parse().unwrap()), 54 | /// ]) 55 | /// ); 56 | /// # } 57 | /// ``` 58 | (ContentLanguage, "Content-Language") => (QualityItem)+ 59 | 60 | test_content_language { 61 | test_header!(test1, vec![b"da"]); 62 | test_header!(test2, vec![b"mi, en"]); 63 | } 64 | } 65 | 66 | standard_header!(ContentLanguage, CONTENT_LANGUAGE); 67 | -------------------------------------------------------------------------------- /src/header/common/content_encoding.rs: -------------------------------------------------------------------------------- 1 | use header::Encoding; 2 | 3 | header! { 4 | /// `Content-Encoding` header, defined in 5 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.2.2) 6 | /// 7 | /// The `Content-Encoding` header field indicates what content codings 8 | /// have been applied to the representation, beyond those inherent in the 9 | /// media type, and thus what decoding mechanisms have to be applied in 10 | /// order to obtain data in the media type referenced by the Content-Type 11 | /// header field. Content-Encoding is primarily used to allow a 12 | /// representation's data to be compressed without losing the identity of 13 | /// its underlying media type. 14 | /// 15 | /// # ABNF 16 | /// 17 | /// ```text 18 | /// Content-Encoding = 1#content-coding 19 | /// ``` 20 | /// 21 | /// # Example values 22 | /// 23 | /// * `gzip` 24 | /// 25 | /// # Examples 26 | /// 27 | /// ``` 28 | /// # extern crate http; 29 | /// use hyperx::header::{ContentEncoding, Encoding, TypedHeaders}; 30 | /// 31 | /// let mut headers = http::HeaderMap::new(); 32 | /// headers.encode(&ContentEncoding(vec![Encoding::Chunked])); 33 | /// ``` 34 | /// 35 | /// ``` 36 | /// # extern crate http; 37 | /// use hyperx::header::{ContentEncoding, Encoding, TypedHeaders}; 38 | /// 39 | /// let mut headers = http::HeaderMap::new(); 40 | /// headers.encode( 41 | /// &ContentEncoding(vec![ 42 | /// Encoding::Gzip, 43 | /// Encoding::Chunked, 44 | /// ]) 45 | /// ); 46 | /// ``` 47 | (ContentEncoding, "Content-Encoding") => (Encoding)+ 48 | 49 | test_content_encoding { 50 | // Testcase from the RFC 51 | test_header!(test1, vec![b"gzip"], Some(ContentEncoding(vec![Encoding::Gzip]))); 52 | } 53 | } 54 | 55 | standard_header!(ContentEncoding, CONTENT_ENCODING); 56 | 57 | bench_header!(single, ContentEncoding, { vec![b"gzip".to_vec()] }); 58 | bench_header!(multiple, ContentEncoding, { vec![b"gzip, deflate".to_vec()] }); 59 | -------------------------------------------------------------------------------- /src/header/common/expect.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str; 3 | 4 | use unicase; 5 | 6 | use header::{Header, RawLike}; 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 | /// # extern crate http; 20 | /// use hyperx::header::{Expect, TypedHeaders}; 21 | /// let mut headers = http::HeaderMap::new(); 22 | /// headers.encode(&Expect::Continue); 23 | /// ``` 24 | #[derive(Copy, Clone, PartialEq, Debug)] 25 | pub enum Expect { 26 | /// The value `100-continue`. 27 | Continue 28 | } 29 | 30 | impl Header for Expect { 31 | fn header_name() -> &'static str { 32 | static NAME: &'static str = "Expect"; 33 | NAME 34 | } 35 | 36 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 37 | where T: RawLike<'a> 38 | { 39 | if let Some(line) = raw.one() { 40 | let text = unsafe { 41 | // safe because: 42 | // 1. we don't actually care if it's utf8, we just want to 43 | // compare the bytes with the "case" normalized. If it's not 44 | // utf8, then the byte comparison will fail, and we'll return 45 | // None. No big deal. 46 | str::from_utf8_unchecked(line) 47 | }; 48 | if unicase::eq_ascii(text, "100-continue") { 49 | Ok(Expect::Continue) 50 | } else { 51 | Err(::Error::Header) 52 | } 53 | } else { 54 | Err(::Error::Header) 55 | } 56 | } 57 | 58 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 59 | f.fmt_line(self) 60 | } 61 | } 62 | 63 | impl fmt::Display for Expect { 64 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 65 | f.write_str("100-continue") 66 | } 67 | } 68 | 69 | standard_header!(Expect, EXPECT); 70 | -------------------------------------------------------------------------------- /src/header/common/access_control_allow_headers.rs: -------------------------------------------------------------------------------- 1 | use unicase::Ascii; 2 | 3 | header! { 4 | /// `Access-Control-Allow-Headers` header, part of 5 | /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) 6 | /// 7 | /// The `Access-Control-Allow-Headers` header indicates, as part of the 8 | /// response to a preflight request, which header field names can be used 9 | /// during the actual request. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// * `accept-language, date` 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// # extern crate http; 24 | /// # extern crate hyperx; 25 | /// # extern crate unicase; 26 | /// # fn main() { 27 | /// // extern crate unicase; 28 | /// 29 | /// use hyperx::header::{AccessControlAllowHeaders, TypedHeaders}; 30 | /// use unicase::Ascii; 31 | /// 32 | /// let mut headers = http::HeaderMap::new(); 33 | /// headers.encode( 34 | /// &AccessControlAllowHeaders(vec![Ascii::new("date".to_owned())]) 35 | /// ); 36 | /// # } 37 | /// ``` 38 | /// 39 | /// ``` 40 | /// # extern crate http; 41 | /// # extern crate hyperx; 42 | /// # extern crate unicase; 43 | /// # fn main() { 44 | /// // extern crate unicase; 45 | /// 46 | /// use hyperx::header::{AccessControlAllowHeaders, TypedHeaders}; 47 | /// use unicase::Ascii; 48 | /// 49 | /// let mut headers = http::HeaderMap::new(); 50 | /// headers.encode( 51 | /// &AccessControlAllowHeaders(vec![ 52 | /// Ascii::new("accept-language".to_owned()), 53 | /// Ascii::new("date".to_owned()), 54 | /// ]) 55 | /// ); 56 | /// # } 57 | /// ``` 58 | (AccessControlAllowHeaders, "Access-Control-Allow-Headers") => (Ascii)* 59 | 60 | test_access_control_allow_headers { 61 | test_header!(test1, vec![b"accept-language, date"]); 62 | } 63 | } 64 | 65 | standard_header!(AccessControlAllowHeaders, ACCESS_CONTROL_ALLOW_HEADERS); 66 | -------------------------------------------------------------------------------- /src/header/common/access_control_request_headers.rs: -------------------------------------------------------------------------------- 1 | use unicase::Ascii; 2 | 3 | header! { 4 | /// `Access-Control-Request-Headers` header, part of 5 | /// [CORS](http://www.w3.org/TR/cors/#access-control-request-headers-request-header) 6 | /// 7 | /// The `Access-Control-Request-Headers` header indicates which headers will 8 | /// be used in the actual request as part of the preflight request. 9 | /// during the actual request. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Access-Control-Allow-Headers: "Access-Control-Allow-Headers" ":" #field-name 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// * `accept-language, date` 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// # extern crate http; 24 | /// # extern crate hyperx; 25 | /// # extern crate unicase; 26 | /// # fn main() { 27 | /// // extern crate unicase; 28 | /// 29 | /// use hyperx::header::{AccessControlRequestHeaders, TypedHeaders}; 30 | /// use unicase::Ascii; 31 | /// 32 | /// let mut headers = http::HeaderMap::new(); 33 | /// headers.encode( 34 | /// &AccessControlRequestHeaders(vec![Ascii::new("date".to_owned())]) 35 | /// ); 36 | /// # } 37 | /// ``` 38 | /// 39 | /// ``` 40 | /// # extern crate http; 41 | /// # extern crate hyperx; 42 | /// # extern crate unicase; 43 | /// # fn main() { 44 | /// // extern crate unicase; 45 | /// 46 | /// use hyperx::header::{AccessControlRequestHeaders, TypedHeaders}; 47 | /// use unicase::Ascii; 48 | /// 49 | /// let mut headers = http::HeaderMap::new(); 50 | /// headers.encode( 51 | /// &AccessControlRequestHeaders(vec![ 52 | /// Ascii::new("accept-language".to_owned()), 53 | /// Ascii::new("date".to_owned()), 54 | /// ]) 55 | /// ); 56 | /// # } 57 | /// ``` 58 | (AccessControlRequestHeaders, "Access-Control-Request-Headers") => (Ascii)* 59 | 60 | test_access_control_request_headers { 61 | test_header!(test1, vec![b"accept-language, date"]); 62 | } 63 | } 64 | 65 | standard_header!(AccessControlRequestHeaders, ACCESS_CONTROL_REQUEST_HEADERS); 66 | -------------------------------------------------------------------------------- /src/header/common/access_control_expose_headers.rs: -------------------------------------------------------------------------------- 1 | use unicase::Ascii; 2 | 3 | header! { 4 | /// `Access-Control-Expose-Headers` header, part of 5 | /// [CORS](http://www.w3.org/TR/cors/#access-control-expose-headers-response-header) 6 | /// 7 | /// The Access-Control-Expose-Headers header indicates which headers are safe to expose to the 8 | /// API of a CORS API specification. 9 | /// 10 | /// # ABNF 11 | /// 12 | /// ```text 13 | /// Access-Control-Expose-Headers = "Access-Control-Expose-Headers" ":" #field-name 14 | /// ``` 15 | /// 16 | /// # Example values 17 | /// * `ETag, Content-Length` 18 | /// 19 | /// # Examples 20 | /// 21 | /// ``` 22 | /// # extern crate http; 23 | /// # extern crate hyperx; 24 | /// # extern crate unicase; 25 | /// # fn main() { 26 | /// // extern crate unicase; 27 | /// 28 | /// use hyperx::header::{AccessControlExposeHeaders, TypedHeaders}; 29 | /// use unicase::Ascii; 30 | /// 31 | /// let mut headers = http::HeaderMap::new(); 32 | /// headers.encode( 33 | /// &AccessControlExposeHeaders(vec![ 34 | /// Ascii::new("etag".to_owned()), 35 | /// Ascii::new("content-length".to_owned()) 36 | /// ]) 37 | /// ); 38 | /// # } 39 | /// ``` 40 | /// 41 | /// ``` 42 | /// # extern crate http; 43 | /// # extern crate hyperx; 44 | /// # extern crate unicase; 45 | /// # fn main() { 46 | /// // extern crate unicase; 47 | /// 48 | /// use hyperx::header::{AccessControlExposeHeaders, TypedHeaders}; 49 | /// use unicase::Ascii; 50 | /// 51 | /// let mut headers = http::HeaderMap::new(); 52 | /// headers.encode( 53 | /// &AccessControlExposeHeaders(vec![ 54 | /// Ascii::new("etag".to_owned()), 55 | /// Ascii::new("content-length".to_owned()) 56 | /// ]) 57 | /// ); 58 | /// # } 59 | /// ``` 60 | (AccessControlExposeHeaders, "Access-Control-Expose-Headers") => (Ascii)* 61 | 62 | test_access_control_expose_headers { 63 | test_header!(test1, vec![b"etag, content-length"]); 64 | } 65 | } 66 | 67 | standard_header!(AccessControlExposeHeaders, ACCESS_CONTROL_EXPOSE_HEADERS); 68 | -------------------------------------------------------------------------------- /src/header/common/accept_charset.rs: -------------------------------------------------------------------------------- 1 | use header::{Charset, QualityItem}; 2 | 3 | header! { 4 | /// `Accept-Charset` header, defined in 5 | /// [RFC7231](http://tools.ietf.org/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 | /// # extern crate http; 26 | /// use hyperx::header::{AcceptCharset, Charset, qitem, TypedHeaders}; 27 | /// 28 | /// let mut headers = http::HeaderMap::new(); 29 | /// headers.encode( 30 | /// &AcceptCharset(vec![qitem(Charset::Us_Ascii)]) 31 | /// ); 32 | /// ``` 33 | /// ``` 34 | /// # extern crate http; 35 | /// use hyperx::header::{AcceptCharset, Charset, q, QualityItem, TypedHeaders}; 36 | /// 37 | /// let mut headers = http::HeaderMap::new(); 38 | /// headers.encode( 39 | /// &AcceptCharset(vec![ 40 | /// QualityItem::new(Charset::Us_Ascii, q(900)), 41 | /// QualityItem::new(Charset::Iso_8859_10, q(200)), 42 | /// ]) 43 | /// ); 44 | /// ``` 45 | /// ``` 46 | /// # extern crate http; 47 | /// use hyperx::header::{AcceptCharset, Charset, qitem, TypedHeaders}; 48 | /// 49 | /// let mut headers = http::HeaderMap::new(); 50 | /// headers.encode( 51 | /// &AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) 52 | /// ); 53 | /// ``` 54 | (AcceptCharset, "Accept-Charset") => (QualityItem)+ 55 | 56 | test_accept_charset { 57 | // Testcase from RFC 58 | test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); 59 | } 60 | } 61 | 62 | standard_header!(AcceptCharset, ACCEPT_CHARSET); 63 | -------------------------------------------------------------------------------- /src/header/common/last_event_id.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use header::{self, Header, RawLike}; 3 | 4 | /// `Last-Event-ID` header, defined in 5 | /// [RFC3864](https://html.spec.whatwg.org/multipage/references.html#refsRFC3864) 6 | /// 7 | /// The `Last-Event-ID` header contains information about 8 | /// the last event in an http interaction so that it's easier to 9 | /// track of event state. This is helpful when working 10 | /// with [Server-Sent-Events](http://www.html5rocks.com/en/tutorials/eventsource/basics/). If the connection were to be dropped, for example, it'd 11 | /// be useful to let the server know what the last event you 12 | /// received was. 13 | /// 14 | /// The spec is a String with the id of the last event, it can be 15 | /// an empty string which acts a sort of "reset". 16 | /// 17 | /// # Example 18 | /// ``` 19 | /// # extern crate http; 20 | /// use hyperx::header::LastEventId; 21 | /// 22 | /// let mut headers = http::HeaderMap::new(); 23 | /// headers.insert( 24 | /// "last-event-id", 25 | /// LastEventId("1".to_owned()).to_string().parse().unwrap() 26 | /// ); 27 | /// ``` 28 | #[derive(Clone, Debug, PartialEq)] 29 | pub struct LastEventId(pub String); 30 | 31 | impl Header for LastEventId { 32 | #[inline] 33 | fn header_name() -> &'static str { 34 | static NAME: &'static str = "Last-Event-ID"; 35 | NAME 36 | } 37 | 38 | #[inline] 39 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 40 | where T: RawLike<'a> 41 | { 42 | match raw.one() { 43 | Some(line) if line.is_empty() => Ok(LastEventId("".to_owned())), 44 | Some(line) => header::parsing::from_raw_str(line).map(LastEventId), 45 | None => Err(::Error::Header), 46 | } 47 | } 48 | 49 | #[inline] 50 | fn fmt_header(&self, f: &mut header::Formatter) -> fmt::Result { 51 | f.fmt_line(self) 52 | } 53 | } 54 | 55 | impl Display for LastEventId { 56 | #[inline] 57 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 58 | Display::fmt(&self.0, f) 59 | } 60 | } 61 | 62 | __hyper__deref!(LastEventId => String); 63 | 64 | __hyper__tm!(LastEventId, tests { 65 | // Initial state 66 | test_header!(test1, vec![b""]); 67 | // Own testcase 68 | test_header!(test2, vec![b"1"], Some(LastEventId("1".to_owned()))); 69 | }); 70 | -------------------------------------------------------------------------------- /src/header/common/transfer_encoding.rs: -------------------------------------------------------------------------------- 1 | use header::Encoding; 2 | 3 | header! { 4 | /// `Transfer-Encoding` header, defined in 5 | /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.1) 6 | /// 7 | /// The `Transfer-Encoding` header field lists the transfer coding names 8 | /// corresponding to the sequence of transfer codings that have been (or 9 | /// will be) applied to the payload body in order to form the message 10 | /// body. 11 | /// 12 | /// Note that setting this header will *remove* any previously set 13 | /// `Content-Length` header, in accordance with 14 | /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-3.3.2): 15 | /// 16 | /// > A sender MUST NOT send a Content-Length header field in any message 17 | /// > that contains a Transfer-Encoding header field. 18 | /// 19 | /// # ABNF 20 | /// 21 | /// ```text 22 | /// Transfer-Encoding = 1#transfer-coding 23 | /// ``` 24 | /// 25 | /// # Example values 26 | /// 27 | /// * `gzip, chunked` 28 | /// 29 | /// # Example 30 | /// 31 | /// ``` 32 | /// # extern crate http; 33 | /// use hyperx::header::{TransferEncoding, Encoding, TypedHeaders}; 34 | /// 35 | /// let mut headers = http::HeaderMap::new(); 36 | /// headers.encode( 37 | /// &TransferEncoding(vec![ 38 | /// Encoding::Gzip, 39 | /// Encoding::Chunked, 40 | /// ]) 41 | /// ); 42 | /// ``` 43 | (TransferEncoding, "Transfer-Encoding") => (Encoding)+ 44 | 45 | transfer_encoding { 46 | test_header!( 47 | test1, 48 | vec![b"gzip, chunked"], 49 | Some(HeaderField( 50 | vec![Encoding::Gzip, Encoding::Chunked] 51 | ))); 52 | // Issue: #683 53 | test_header!( 54 | test2, 55 | vec![b"chunked", b"chunked"], 56 | Some(HeaderField( 57 | vec![Encoding::Chunked, Encoding::Chunked] 58 | ))); 59 | 60 | } 61 | } 62 | 63 | impl TransferEncoding { 64 | /// Constructor for the most common Transfer-Encoding, `chunked`. 65 | pub fn chunked() -> TransferEncoding { 66 | TransferEncoding(vec![Encoding::Chunked]) 67 | } 68 | } 69 | 70 | bench_header!(normal, TransferEncoding, { vec![b"chunked, gzip".to_vec()] }); 71 | bench_header!(ext, TransferEncoding, { vec![b"ext".to_vec()] }); 72 | 73 | standard_header!(TransferEncoding, TRANSFER_ENCODING); 74 | -------------------------------------------------------------------------------- /src/header/common/vary.rs: -------------------------------------------------------------------------------- 1 | use unicase::Ascii; 2 | 3 | header! { 4 | /// `Vary` header, defined in [RFC7231](https://tools.ietf.org/html/rfc7231#section-7.1.4) 5 | /// 6 | /// The "Vary" header field in a response describes what parts of a 7 | /// request message, aside from the method, Host header field, and 8 | /// request target, might influence the origin server's process for 9 | /// selecting and representing this response. The value consists of 10 | /// either a single asterisk ("*") or a list of header field names 11 | /// (case-insensitive). 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Vary = "*" / 1#field-name 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// 21 | /// * `accept-encoding, accept-language` 22 | /// 23 | /// # Example 24 | /// 25 | /// ``` 26 | /// # extern crate http; 27 | /// use hyperx::header::{TypedHeaders, Vary}; 28 | /// 29 | /// let mut headers = http::HeaderMap::new(); 30 | /// headers.encode(&Vary::Any); 31 | /// ``` 32 | /// 33 | /// # Example 34 | /// 35 | /// ``` 36 | /// # extern crate http; 37 | /// # extern crate hyperx; 38 | /// # extern crate unicase; 39 | /// # fn main() { 40 | /// // extern crate unicase; 41 | /// 42 | /// use hyperx::header::{TypedHeaders, Vary}; 43 | /// use unicase::Ascii; 44 | /// 45 | /// let mut headers = http::HeaderMap::new(); 46 | /// headers.encode( 47 | /// &Vary::Items(vec![ 48 | /// Ascii::new("accept-encoding".to_owned()), 49 | /// Ascii::new("accept-language".to_owned()), 50 | /// ]) 51 | /// ); 52 | /// # } 53 | /// ``` 54 | (Vary, "Vary") => {Any / (Ascii)+} 55 | 56 | test_vary { 57 | test_header!(test1, vec![b"accept-encoding, accept-language"]); 58 | 59 | #[test] 60 | fn test2() { 61 | let mut vary: ::Result; 62 | let r: Raw = "*".into(); 63 | vary = Header::parse_header(&r); 64 | assert_eq!(vary.ok(), Some(Vary::Any)); 65 | 66 | let r: Raw = "etag,cookie,allow".into(); 67 | vary = Header::parse_header(&r); 68 | assert_eq!( 69 | vary.ok(), 70 | Some(Vary::Items(vec![ 71 | "eTag".parse().unwrap(), 72 | "cookIE".parse().unwrap(), 73 | "AlLOw".parse().unwrap(), 74 | ]))); 75 | } 76 | } 77 | } 78 | 79 | standard_header!(Vary, VARY); 80 | -------------------------------------------------------------------------------- /src/header/common/te.rs: -------------------------------------------------------------------------------- 1 | use header::{Encoding, QualityItem}; 2 | 3 | header! { 4 | /// `TE` header, defined in 5 | /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-4.3) 6 | /// 7 | /// As RFC7230 states, "The "TE" header field in a request indicates what transfer codings, 8 | /// besides chunked, the client is willing to accept in response, and 9 | /// whether or not the client is willing to accept trailer fields in a 10 | /// chunked transfer coding." 11 | /// 12 | /// For HTTP/1.1 compliant clients `chunked` transfer codings are assumed to be acceptable and 13 | /// so should never appear in this header. 14 | /// 15 | /// # ABNF 16 | /// 17 | /// ```text 18 | /// TE = "TE" ":" #( t-codings ) 19 | /// t-codings = "trailers" | ( transfer-extension [ accept-params ] ) 20 | /// ``` 21 | /// 22 | /// # Example values 23 | /// * `trailers` 24 | /// * `trailers, deflate;q=0.5` 25 | /// * `` 26 | /// 27 | /// # Examples 28 | /// 29 | /// ``` 30 | /// # extern crate http; 31 | /// use hyperx::header::{Te, Encoding, qitem, TypedHeaders}; 32 | /// 33 | /// let mut headers = http::HeaderMap::new(); 34 | /// headers.encode( 35 | /// &Te(vec![qitem(Encoding::Trailers)]) 36 | /// ); 37 | /// ``` 38 | /// 39 | /// ``` 40 | /// # extern crate http; 41 | /// use hyperx::header::{Te, Encoding, qitem, TypedHeaders}; 42 | /// 43 | /// let mut headers = http::HeaderMap::new(); 44 | /// headers.encode( 45 | /// &Te(vec![ 46 | /// qitem(Encoding::Trailers), 47 | /// qitem(Encoding::Gzip), 48 | /// qitem(Encoding::Deflate), 49 | /// ]) 50 | /// ); 51 | /// ``` 52 | /// 53 | /// ``` 54 | /// # extern crate http; 55 | /// use hyperx::header::{Te, Encoding, QualityItem, q, qitem, TypedHeaders}; 56 | /// 57 | /// let mut headers = http::HeaderMap::new(); 58 | /// headers.encode( 59 | /// &Te(vec![ 60 | /// qitem(Encoding::Trailers), 61 | /// QualityItem::new(Encoding::Gzip, q(600)), 62 | /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), 63 | /// ]) 64 | /// ); 65 | /// ``` 66 | (Te, "TE") => (QualityItem)* 67 | 68 | test_te { 69 | // From the RFC 70 | test_header!(test1, vec![b"trailers"]); 71 | test_header!(test2, vec![b"trailers, deflate;q=0.5"]); 72 | test_header!(test3, vec![b""]); 73 | } 74 | } 75 | 76 | standard_header!(Te, TE); 77 | -------------------------------------------------------------------------------- /src/header/common/accept_language.rs: -------------------------------------------------------------------------------- 1 | use language_tags::LanguageTag; 2 | use header::QualityItem; 3 | 4 | header! { 5 | /// `Accept-Language` header, defined in 6 | /// [RFC7231](http://tools.ietf.org/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 | /// # extern crate http; 27 | /// use hyperx::header::{AcceptLanguage, qitem, TypedHeaders}; 28 | /// 29 | /// let mut headers = http::HeaderMap::new(); 30 | /// let mut langtag = "en-US".parse().unwrap(); 31 | /// headers.encode( 32 | /// &AcceptLanguage(vec![ 33 | /// qitem(langtag), 34 | /// ]) 35 | /// ); 36 | /// ``` 37 | /// 38 | /// ``` 39 | /// # extern crate http; 40 | /// # extern crate hyperx; 41 | /// # #[macro_use] extern crate language_tags; 42 | /// # use hyperx::header::{AcceptLanguage, QualityItem, q, qitem, TypedHeaders}; 43 | /// # 44 | /// # fn main() { 45 | /// let mut headers = http::HeaderMap::new(); 46 | /// headers.encode( 47 | /// &AcceptLanguage(vec![ 48 | /// qitem("da".parse().unwrap()), 49 | /// QualityItem::new("en-US".parse().unwrap(), q(800)), 50 | /// QualityItem::new("en".parse().unwrap(), q(700)), 51 | /// ]) 52 | /// ); 53 | /// # } 54 | /// ``` 55 | (AcceptLanguage, "Accept-Language") => (QualityItem)+ 56 | 57 | test_accept_language { 58 | // From the RFC 59 | test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); 60 | // Own test 61 | test_header!( 62 | test2, vec![b"en-US, en; q=0.5, fr"], 63 | Some(AcceptLanguage(vec![ 64 | qitem("en-US".parse().unwrap()), 65 | QualityItem::new("en".parse().unwrap(), q(500)), 66 | qitem("fr".parse().unwrap()), 67 | ]))); 68 | } 69 | } 70 | 71 | bench_header!(bench, AcceptLanguage, 72 | { vec![b"en-us;q=1.0, en;q=0.5, fr".to_vec()] }); 73 | 74 | standard_header!(AcceptLanguage, ACCEPT_LANGUAGE); 75 | -------------------------------------------------------------------------------- /src/header/common/allow.rs: -------------------------------------------------------------------------------- 1 | use method::Method; 2 | 3 | header! { 4 | /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) 5 | /// 6 | /// The `Allow` header field lists the set of methods advertised as 7 | /// supported by the target resource. The purpose of this field is 8 | /// strictly to inform the recipient of valid request methods associated 9 | /// with the resource. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Allow = #method 15 | /// ``` 16 | /// 17 | /// # Example values 18 | /// * `GET, HEAD, PUT` 19 | /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` 20 | /// * `` 21 | /// 22 | /// # Examples 23 | /// 24 | /// ``` 25 | /// # extern crate http; 26 | /// use hyperx::header::{Allow, TypedHeaders}; 27 | /// use hyperx::Method; 28 | /// 29 | /// let mut headers = http::HeaderMap::new(); 30 | /// headers.encode( 31 | /// &Allow(vec![Method::Get]) 32 | /// ); 33 | /// ``` 34 | /// 35 | /// ``` 36 | /// # extern crate http; 37 | /// use hyperx::header::{Allow, TypedHeaders}; 38 | /// use hyperx::Method; 39 | /// 40 | /// let mut headers = http::HeaderMap::new(); 41 | /// headers.encode( 42 | /// &Allow(vec![ 43 | /// Method::Get, 44 | /// Method::Post, 45 | /// Method::Patch, 46 | /// Method::Extension("COPY".to_owned()), 47 | /// ]) 48 | /// ); 49 | /// ``` 50 | (Allow, "Allow") => (Method)* 51 | 52 | test_allow { 53 | // From the RFC 54 | test_header!( 55 | test1, 56 | vec![b"GET, HEAD, PUT"], 57 | Some(HeaderField(vec![Method::Get, Method::Head, Method::Put]))); 58 | // Own tests 59 | test_header!( 60 | test2, 61 | vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr"], 62 | Some(HeaderField(vec![ 63 | Method::Options, 64 | Method::Get, 65 | Method::Put, 66 | Method::Post, 67 | Method::Delete, 68 | Method::Head, 69 | Method::Trace, 70 | Method::Connect, 71 | Method::Patch, 72 | Method::Extension("fOObAr".to_owned())]))); 73 | test_header!( 74 | test3, 75 | vec![b""], 76 | Some(HeaderField(Vec::::new()))); 77 | } 78 | } 79 | 80 | bench_header!(bench, 81 | Allow, { vec![b"OPTIONS,GET,PUT,POST,DELETE,HEAD,TRACE,CONNECT,PATCH,fOObAr".to_vec()] }); 82 | 83 | standard_header!(Allow, ALLOW); 84 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "base64" 7 | version = "0.13.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 10 | 11 | [[package]] 12 | name = "bytes" 13 | version = "1.0.1" 14 | source = "registry+https://github.com/rust-lang/crates.io-index" 15 | checksum = "b700ce4376041dcd0a327fd0097c41095743c4c8af8887265942faf1100bd040" 16 | 17 | [[package]] 18 | name = "fnv" 19 | version = "1.0.7" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 22 | 23 | [[package]] 24 | name = "http" 25 | version = "0.2.3" 26 | source = "registry+https://github.com/rust-lang/crates.io-index" 27 | checksum = "7245cd7449cc792608c3c8a9eaf69bd4eabbabf802713748fd739c98b82f0747" 28 | dependencies = [ 29 | "bytes", 30 | "fnv", 31 | "itoa", 32 | ] 33 | 34 | [[package]] 35 | name = "httpdate" 36 | version = "0.3.2" 37 | source = "registry+https://github.com/rust-lang/crates.io-index" 38 | checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" 39 | 40 | [[package]] 41 | name = "hyperx" 42 | version = "1.4.0" 43 | dependencies = [ 44 | "base64", 45 | "bytes", 46 | "http", 47 | "httpdate", 48 | "language-tags", 49 | "mime", 50 | "percent-encoding", 51 | "unicase", 52 | ] 53 | 54 | [[package]] 55 | name = "itoa" 56 | version = "0.4.7" 57 | source = "registry+https://github.com/rust-lang/crates.io-index" 58 | checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" 59 | 60 | [[package]] 61 | name = "language-tags" 62 | version = "0.3.1" 63 | source = "registry+https://github.com/rust-lang/crates.io-index" 64 | checksum = "f11dc871dd28acc3ac816c5bbe2c5c7e60c4a41f82ce79699a0a44a8fdbc2c7c" 65 | 66 | [[package]] 67 | name = "mime" 68 | version = "0.3.16" 69 | source = "registry+https://github.com/rust-lang/crates.io-index" 70 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 71 | 72 | [[package]] 73 | name = "percent-encoding" 74 | version = "2.1.0" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 77 | 78 | [[package]] 79 | name = "unicase" 80 | version = "2.6.0" 81 | source = "registry+https://github.com/rust-lang/crates.io-index" 82 | checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" 83 | dependencies = [ 84 | "version_check", 85 | ] 86 | 87 | [[package]] 88 | name = "version_check" 89 | version = "0.9.2" 90 | source = "registry+https://github.com/rust-lang/crates.io-index" 91 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 92 | -------------------------------------------------------------------------------- /src/header/common/accept_encoding.rs: -------------------------------------------------------------------------------- 1 | use header::{Encoding, QualityItem}; 2 | 3 | header! { 4 | /// `Accept-Encoding` header, defined in 5 | /// [RFC7231](http://tools.ietf.org/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 | /// # extern crate http; 30 | /// use hyperx::header::{AcceptEncoding, Encoding, qitem, TypedHeaders}; 31 | /// 32 | /// let mut headers = http::HeaderMap::new(); 33 | /// headers.encode( 34 | /// &AcceptEncoding(vec![qitem(Encoding::Chunked)]) 35 | /// ); 36 | /// ``` 37 | /// ``` 38 | /// # extern crate http; 39 | /// use hyperx::header::{AcceptEncoding, Encoding, qitem, TypedHeaders}; 40 | /// 41 | /// let mut headers = http::HeaderMap::new(); 42 | /// headers.encode( 43 | /// &AcceptEncoding(vec![ 44 | /// qitem(Encoding::Chunked), 45 | /// qitem(Encoding::Gzip), 46 | /// qitem(Encoding::Deflate), 47 | /// ]) 48 | /// ); 49 | /// ``` 50 | /// ``` 51 | /// # extern crate http; 52 | /// use hyperx::header::{AcceptEncoding, Encoding, QualityItem, q, qitem, TypedHeaders}; 53 | /// 54 | /// let mut headers = http::HeaderMap::new(); 55 | /// headers.encode( 56 | /// &AcceptEncoding(vec![ 57 | /// qitem(Encoding::Chunked), 58 | /// QualityItem::new(Encoding::Gzip, q(600)), 59 | /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), 60 | /// ]) 61 | /// ); 62 | /// ``` 63 | (AcceptEncoding, "Accept-Encoding") => (QualityItem)* 64 | 65 | test_accept_encoding { 66 | // From the RFC 67 | test_header!(test1, vec![b"compress, gzip"]); 68 | test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); 69 | test_header!(test3, vec![b"*"]); 70 | // Note: Removed quality 1 from gzip 71 | test_header!(test4, vec![b"compress;q=0.5, gzip"]); 72 | // Note: Removed quality 1 from gzip 73 | test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); 74 | } 75 | } 76 | 77 | standard_header!(AcceptEncoding, ACCEPT_ENCODING); 78 | -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | //! Error and Result module. 2 | 3 | use std::error::Error as StdError; 4 | use std::fmt; 5 | use std::str::Utf8Error; 6 | use std::string::FromUtf8Error; 7 | 8 | use self::Error::{ 9 | Method, 10 | Version, 11 | Header, 12 | Status, 13 | TooLarge, 14 | Utf8 15 | }; 16 | 17 | /// Result type often returned from methods that can have hyper `Error`s. 18 | pub type Result = ::std::result::Result; 19 | 20 | /// Errors while parsing headers and associated types. 21 | #[derive(Debug)] 22 | pub enum Error { 23 | /// An invalid `Method`, such as `GE,T`. 24 | Method, 25 | /// An invalid `HttpVersion`, such as `HTP/1.1` 26 | Version, 27 | /// An invalid `Header`. 28 | Header, 29 | /// A message head is too large to be reasonable. 30 | TooLarge, 31 | /// An invalid `Status`, such as `1337 ELITE`. 32 | Status, 33 | /// Parsing a field as string failed. 34 | Utf8(Utf8Error), 35 | 36 | #[doc(hidden)] 37 | __Nonexhaustive(Void) 38 | } 39 | 40 | #[doc(hidden)] 41 | pub struct Void(()); 42 | 43 | impl fmt::Debug for Void { 44 | fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { 45 | unreachable!() 46 | } 47 | } 48 | 49 | impl fmt::Display for Error { 50 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 51 | match *self { 52 | Utf8(ref e) => fmt::Display::fmt(e, f), 53 | ref e => f.write_str(e.static_description()), 54 | } 55 | } 56 | } 57 | 58 | impl Error { 59 | fn static_description(&self) -> &str { 60 | match *self { 61 | Method => "invalid Method specified", 62 | Version => "invalid HTTP version specified", 63 | Header => "invalid Header provided", 64 | TooLarge => "message head is too large", 65 | Status => "invalid Status provided", 66 | Utf8(_) => "invalid UTF-8 string", 67 | Error::__Nonexhaustive(..) => unreachable!(), 68 | } 69 | } 70 | } 71 | 72 | impl StdError for Error { 73 | fn description(&self) -> &str { 74 | self.static_description() 75 | } 76 | 77 | fn cause(&self) -> Option<&dyn StdError> { 78 | match *self { 79 | Utf8(ref error) => Some(error), 80 | Error::__Nonexhaustive(..) => unreachable!(), 81 | _ => None, 82 | } 83 | } 84 | } 85 | 86 | impl From for Error { 87 | fn from(err: Utf8Error) -> Error { 88 | Utf8(err) 89 | } 90 | } 91 | 92 | impl From for Error { 93 | fn from(err: FromUtf8Error) -> Error { 94 | Utf8(err.utf8_error()) 95 | } 96 | } 97 | 98 | #[doc(hidden)] 99 | trait AssertSendSync: Send + Sync + 'static {} 100 | #[doc(hidden)] 101 | impl AssertSendSync for Error {} 102 | -------------------------------------------------------------------------------- /src/header/shared/httpdate.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str::FromStr; 3 | use std::time::SystemTime; 4 | 5 | use httpdate::HttpDate as InnerDate; 6 | 7 | /// A timestamp with HTTP formatting and parsing 8 | // Prior to 1995, there were three different formats commonly used by 9 | // servers to communicate timestamps. For compatibility with old 10 | // implementations, all three are defined here. The preferred format is 11 | // a fixed-length and single-zone subset of the date and time 12 | // specification used by the Internet Message Format [RFC5322]. 13 | // 14 | // HTTP-date = IMF-fixdate / obs-date 15 | // 16 | // An example of the preferred format is 17 | // 18 | // Sun, 06 Nov 1994 08:49:37 GMT ; IMF-fixdate 19 | // 20 | // Examples of the two obsolete formats are 21 | // 22 | // Sunday, 06-Nov-94 08:49:37 GMT ; obsolete RFC 850 format 23 | // Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format 24 | // 25 | // A recipient that parses a timestamp value in an HTTP header field 26 | // MUST accept all three HTTP-date formats. When a sender generates a 27 | // header field that contains one or more timestamps defined as 28 | // HTTP-date, the sender MUST generate those timestamps in the 29 | // IMF-fixdate format. 30 | #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] 31 | pub struct HttpDate(InnerDate); 32 | 33 | impl FromStr for HttpDate { 34 | type Err = ::Error; 35 | fn from_str(s: &str) -> ::Result { 36 | InnerDate::from_str(s) 37 | .map(HttpDate) 38 | .map_err(|_| ::Error::Header) 39 | } 40 | } 41 | 42 | impl Display for HttpDate { 43 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 44 | fmt::Display::fmt(&self.0, f) 45 | } 46 | } 47 | 48 | impl From for HttpDate { 49 | fn from(sys: SystemTime) -> HttpDate { 50 | HttpDate(sys.into()) 51 | } 52 | } 53 | 54 | impl From for SystemTime { 55 | fn from(date: HttpDate) -> SystemTime { 56 | date.0.into() 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | use std::time::{SystemTime, Duration}; 63 | 64 | use super::HttpDate; 65 | 66 | macro_rules! test_parse { 67 | ($function: ident, $date: expr) => { 68 | #[test] 69 | fn $function() { 70 | let nov_07 = HttpDate(( 71 | SystemTime::UNIX_EPOCH + Duration::new(784198117, 0) 72 | ).into()); 73 | 74 | assert_eq!($date.parse::().unwrap(), nov_07); 75 | } 76 | }; 77 | } 78 | 79 | test_parse!(test_imf_fixdate, "Mon, 07 Nov 1994 08:48:37 GMT"); 80 | test_parse!(test_rfc_850, "Monday, 07-Nov-94 08:48:37 GMT"); 81 | test_parse!(test_asctime, "Mon Nov 7 08:48:37 1994"); 82 | 83 | #[test] 84 | fn test_no_date() { 85 | assert!("this-is-no-date".parse::().is_err()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/header/common/if_match.rs: -------------------------------------------------------------------------------- 1 | use header::EntityTag; 2 | 3 | header! { 4 | /// `If-Match` header, defined in 5 | /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) 6 | /// 7 | /// The `If-Match` header field makes the request method conditional on 8 | /// the recipient origin server either having at least one current 9 | /// representation of the target resource, when the field-value is "*", 10 | /// or having a current representation of the target resource that has an 11 | /// entity-tag matching a member of the list of entity-tags provided in 12 | /// the field-value. 13 | /// 14 | /// An origin server MUST use the strong comparison function when 15 | /// comparing entity-tags for `If-Match`, since the client 16 | /// intends this precondition to prevent the method from being applied if 17 | /// there have been any changes to the representation data. 18 | /// 19 | /// # ABNF 20 | /// 21 | /// ```text 22 | /// If-Match = "*" / 1#entity-tag 23 | /// ``` 24 | /// 25 | /// # Example values 26 | /// 27 | /// * `"xyzzy"` 28 | /// * "xyzzy", "r2d2xxxx", "c3piozzzz" 29 | /// 30 | /// # Examples 31 | /// 32 | /// ``` 33 | /// # extern crate http; 34 | /// use hyperx::header::{IfMatch, TypedHeaders}; 35 | /// 36 | /// let mut headers = http::HeaderMap::new(); 37 | /// headers.encode(&IfMatch::Any); 38 | /// ``` 39 | /// 40 | /// ``` 41 | /// # extern crate http; 42 | /// use hyperx::header::{IfMatch, EntityTag, TypedHeaders}; 43 | /// 44 | /// let mut headers = http::HeaderMap::new(); 45 | /// headers.encode( 46 | /// &IfMatch::Items(vec![ 47 | /// EntityTag::new(false, "xyzzy".to_owned()), 48 | /// EntityTag::new(false, "foobar".to_owned()), 49 | /// EntityTag::new(false, "bazquux".to_owned()), 50 | /// ]) 51 | /// ); 52 | /// ``` 53 | (IfMatch, "If-Match") => {Any / (EntityTag)+} 54 | 55 | test_if_match { 56 | test_header!( 57 | test1, 58 | vec![b"\"xyzzy\""], 59 | Some(HeaderField::Items( 60 | vec![EntityTag::new(false, "xyzzy".to_owned())]))); 61 | test_header!( 62 | test2, 63 | vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], 64 | Some(HeaderField::Items( 65 | vec![EntityTag::new(false, "xyzzy".to_owned()), 66 | EntityTag::new(false, "r2d2xxxx".to_owned()), 67 | EntityTag::new(false, "c3piozzzz".to_owned())]))); 68 | test_header!(test3, vec![b"*"], Some(IfMatch::Any)); 69 | } 70 | } 71 | 72 | bench_header!(star, IfMatch, { vec![b"*".to_vec()] }); 73 | bench_header!(single , IfMatch, { vec![b"\"xyzzy\"".to_vec()] }); 74 | bench_header!(multi, IfMatch, 75 | { vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\"".to_vec()] }); 76 | 77 | standard_header!(IfMatch, IF_MATCH); 78 | -------------------------------------------------------------------------------- /src/header/common/pragma.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use header::{Header, RawLike, parsing}; 4 | 5 | /// The `Pragma` header defined by HTTP/1.0. 6 | /// 7 | /// > The "Pragma" header field allows backwards compatibility with 8 | /// > HTTP/1.0 caches, so that clients can specify a "no-cache" request 9 | /// > that they will understand (as Cache-Control was not defined until 10 | /// > HTTP/1.1). When the Cache-Control header field is also present and 11 | /// > understood in a request, Pragma is ignored. 12 | /// > In HTTP/1.0, Pragma was defined as an extensible field for 13 | /// > implementation-specified directives for recipients. This 14 | /// > specification deprecates such extensions to improve interoperability. 15 | /// 16 | /// Spec: [https://tools.ietf.org/html/rfc7234#section-5.4][url] 17 | /// 18 | /// [url]: https://tools.ietf.org/html/rfc7234#section-5.4 19 | /// 20 | /// # Examples 21 | /// 22 | /// ``` 23 | /// # extern crate http; 24 | /// use hyperx::header::{Pragma, TypedHeaders}; 25 | /// 26 | /// let mut headers = http::HeaderMap::new(); 27 | /// headers.encode(&Pragma::NoCache); 28 | /// ``` 29 | /// 30 | /// ``` 31 | /// # extern crate http; 32 | /// use hyperx::header::{Pragma, TypedHeaders}; 33 | /// 34 | /// let mut headers = http::HeaderMap::new(); 35 | /// headers.encode(&Pragma::Ext("foobar".to_owned())); 36 | /// ``` 37 | #[derive(Clone, PartialEq, Debug)] 38 | pub enum Pragma { 39 | /// Corresponds to the `no-cache` value. 40 | NoCache, 41 | /// Every value other than `no-cache`. 42 | Ext(String), 43 | } 44 | 45 | impl Header for Pragma { 46 | fn header_name() -> &'static str { 47 | static NAME: &'static str = "Pragma"; 48 | NAME 49 | } 50 | 51 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 52 | where T: RawLike<'a> 53 | { 54 | parsing::from_one_raw_str(raw).and_then(|s: String| { 55 | let slice = &s.to_ascii_lowercase()[..]; 56 | match slice { 57 | "no-cache" => Ok(Pragma::NoCache), 58 | _ => Ok(Pragma::Ext(s)), 59 | } 60 | }) 61 | } 62 | 63 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 64 | f.fmt_line(self) 65 | } 66 | } 67 | 68 | impl fmt::Display for Pragma { 69 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 70 | f.write_str(match *self { 71 | Pragma::NoCache => "no-cache", 72 | Pragma::Ext(ref string) => &string[..], 73 | }) 74 | } 75 | } 76 | 77 | #[test] 78 | fn test_parse_header() { 79 | use header::{Header, Raw}; 80 | 81 | let r: Raw = "no-cache".into(); 82 | let a: Pragma = Header::parse_header(&r).unwrap(); 83 | let b = Pragma::NoCache; 84 | assert_eq!(a, b); 85 | 86 | let r: Raw = "FoObar".into(); 87 | let c: Pragma = Header::parse_header(&r).unwrap(); 88 | let d = Pragma::Ext("FoObar".to_owned()); 89 | assert_eq!(c, d); 90 | 91 | let r: Raw = "".into(); 92 | let e: ::Result = Header::parse_header(&r); 93 | assert_eq!(e.ok(), None); 94 | } 95 | 96 | standard_header!(Pragma, PRAGMA); 97 | -------------------------------------------------------------------------------- /src/header/common/access_control_allow_origin.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str; 3 | 4 | use header::{Header, RawLike}; 5 | 6 | /// The `Access-Control-Allow-Origin` response header, 7 | /// part of [CORS](http://www.w3.org/TR/cors/#access-control-allow-origin-response-header) 8 | /// 9 | /// The `Access-Control-Allow-Origin` header indicates whether a resource 10 | /// can be shared based by returning the value of the Origin request header, 11 | /// `*`, or `null` in the response. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Access-Control-Allow-Origin = "Access-Control-Allow-Origin" ":" origin-list-or-null | "*" 17 | /// ``` 18 | /// 19 | /// # Example values 20 | /// * `null` 21 | /// * `*` 22 | /// * `http://google.com/` 23 | /// 24 | /// # Examples 25 | /// ``` 26 | /// # extern crate http; 27 | /// use hyperx::header::{AccessControlAllowOrigin, TypedHeaders}; 28 | /// 29 | /// let mut headers = http::HeaderMap::new(); 30 | /// headers.encode( 31 | /// &AccessControlAllowOrigin::Any 32 | /// ); 33 | /// ``` 34 | /// ``` 35 | /// # extern crate http; 36 | /// use hyperx::header::{AccessControlAllowOrigin, TypedHeaders}; 37 | /// 38 | /// let mut headers = http::HeaderMap::new(); 39 | /// headers.encode( 40 | /// &AccessControlAllowOrigin::Null, 41 | /// ); 42 | /// ``` 43 | /// ``` 44 | /// # extern crate http; 45 | /// use hyperx::header::{AccessControlAllowOrigin, TypedHeaders}; 46 | /// 47 | /// let mut headers = http::HeaderMap::new(); 48 | /// headers.encode( 49 | /// &AccessControlAllowOrigin::Value("http://hyper.rs".to_owned()) 50 | /// ); 51 | /// ``` 52 | #[derive(Clone, PartialEq, Debug)] 53 | pub enum AccessControlAllowOrigin { 54 | /// Allow all origins 55 | Any, 56 | /// A hidden origin 57 | Null, 58 | /// Allow one particular origin 59 | Value(String), 60 | } 61 | 62 | impl Header for AccessControlAllowOrigin { 63 | fn header_name() -> &'static str { 64 | "Access-Control-Allow-Origin" 65 | } 66 | 67 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 68 | where T: RawLike<'a> 69 | { 70 | if let Some(line) = raw.one() { 71 | Ok(match line { 72 | b"*" => AccessControlAllowOrigin::Any, 73 | b"null" => AccessControlAllowOrigin::Null, 74 | _ => AccessControlAllowOrigin::Value(str::from_utf8(line)?.into()) 75 | }) 76 | } else { 77 | Err(::Error::Header) 78 | } 79 | } 80 | 81 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 82 | f.fmt_line(self) 83 | } 84 | } 85 | 86 | impl Display for AccessControlAllowOrigin { 87 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 88 | match *self { 89 | AccessControlAllowOrigin::Any => f.write_str("*"), 90 | AccessControlAllowOrigin::Null => f.write_str("null"), 91 | AccessControlAllowOrigin::Value(ref url) => Display::fmt(url, f), 92 | } 93 | } 94 | } 95 | 96 | #[cfg(test)] 97 | mod test_access_control_allow_origin { 98 | use header::*; 99 | use super::AccessControlAllowOrigin as HeaderField; 100 | test_header!(test1, vec![b"null"]); 101 | test_header!(test2, vec![b"*"]); 102 | test_header!(test3, vec![b"http://google.com/"]); 103 | } 104 | 105 | standard_header!(AccessControlAllowOrigin, ACCESS_CONTROL_ALLOW_ORIGIN); 106 | -------------------------------------------------------------------------------- /src/header/common/if_none_match.rs: -------------------------------------------------------------------------------- 1 | use header::EntityTag; 2 | 3 | header! { 4 | /// `If-None-Match` header, defined in 5 | /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) 6 | /// 7 | /// The `If-None-Match` header field makes the request method conditional 8 | /// on a recipient cache or origin server either not having any current 9 | /// representation of the target resource, when the field-value is "*", 10 | /// or having a selected representation with an entity-tag that does not 11 | /// match any of those listed in the field-value. 12 | /// 13 | /// A recipient MUST use the weak comparison function when comparing 14 | /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags 15 | /// can be used for cache validation even if there have been changes to 16 | /// the representation data. 17 | /// 18 | /// # ABNF 19 | /// 20 | /// ```text 21 | /// If-None-Match = "*" / 1#entity-tag 22 | /// ``` 23 | /// 24 | /// # Example values 25 | /// 26 | /// * `"xyzzy"` 27 | /// * `W/"xyzzy"` 28 | /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` 29 | /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` 30 | /// * `*` 31 | /// 32 | /// # Examples 33 | /// 34 | /// ``` 35 | /// # extern crate http; 36 | /// use hyperx::header::{IfNoneMatch, TypedHeaders}; 37 | /// 38 | /// let mut headers = http::HeaderMap::new(); 39 | /// headers.encode(&IfNoneMatch::Any); 40 | /// ``` 41 | /// 42 | /// ``` 43 | /// # extern crate http; 44 | /// use hyperx::header::{IfNoneMatch, EntityTag, TypedHeaders}; 45 | /// 46 | /// let mut headers = http::HeaderMap::new(); 47 | /// headers.encode( 48 | /// &IfNoneMatch::Items(vec![ 49 | /// EntityTag::new(false, "xyzzy".to_owned()), 50 | /// EntityTag::new(false, "foobar".to_owned()), 51 | /// EntityTag::new(false, "bazquux".to_owned()), 52 | /// ]) 53 | /// ); 54 | /// ``` 55 | (IfNoneMatch, "If-None-Match") => {Any / (EntityTag)+} 56 | 57 | test_if_none_match { 58 | test_header!(test1, vec![b"\"xyzzy\""]); 59 | test_header!(test2, vec![b"W/\"xyzzy\""]); 60 | test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); 61 | test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); 62 | test_header!(test5, vec![b"*"]); 63 | } 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use super::IfNoneMatch; 69 | use header::{Header, Raw}; 70 | use header::EntityTag; 71 | 72 | #[test] 73 | fn test_if_none_match() { 74 | let mut if_none_match: ::Result; 75 | 76 | let r: Raw = b"*".as_ref().into(); 77 | if_none_match = Header::parse_header(&r); 78 | assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); 79 | 80 | let r: Raw = b"\"foobar\", W/\"weak-etag\"".as_ref().into(); 81 | if_none_match = Header::parse_header(&r); 82 | let mut entities: Vec = Vec::new(); 83 | let foobar_etag = EntityTag::new(false, "foobar".to_owned()); 84 | let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); 85 | entities.push(foobar_etag); 86 | entities.push(weak_etag); 87 | assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); 88 | } 89 | } 90 | 91 | bench_header!(bench, IfNoneMatch, { vec![b"W/\"nonemptytag\"".to_vec()] }); 92 | 93 | standard_header!(IfNoneMatch, IF_NONE_MATCH); 94 | -------------------------------------------------------------------------------- /src/header/common/access_control_allow_credentials.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str; 3 | use unicase; 4 | use header::{Header, RawLike}; 5 | 6 | /// `Access-Control-Allow-Credentials` header, part of 7 | /// [CORS](http://www.w3.org/TR/cors/#access-control-allow-headers-response-header) 8 | /// 9 | /// > The Access-Control-Allow-Credentials HTTP response header indicates whether the 10 | /// > response to request can be exposed when the credentials flag is true. When part 11 | /// > of the response to an preflight request it indicates that the actual request can 12 | /// > be made with credentials. The Access-Control-Allow-Credentials HTTP header must 13 | /// > match the following ABNF: 14 | /// 15 | /// # ABNF 16 | /// 17 | /// ```text 18 | /// Access-Control-Allow-Credentials: "Access-Control-Allow-Credentials" ":" "true" 19 | /// ``` 20 | /// 21 | /// Since there is only one acceptable field value, the header struct does not accept 22 | /// any values at all. Setting an empty `AccessControlAllowCredentials` header is 23 | /// sufficient. See the examples below. 24 | /// 25 | /// # Example values 26 | /// * "true" 27 | /// 28 | /// # Examples 29 | /// 30 | /// ``` 31 | /// # extern crate http; 32 | /// # extern crate hyperx; 33 | /// # fn main() { 34 | /// 35 | /// use hyperx::header::{AccessControlAllowCredentials, TypedHeaders}; 36 | /// 37 | /// let mut headers = http::HeaderMap::new(); 38 | /// headers.encode(&AccessControlAllowCredentials); 39 | /// # } 40 | /// ``` 41 | #[derive(Clone, PartialEq, Debug)] 42 | pub struct AccessControlAllowCredentials; 43 | 44 | const ACCESS_CONTROL_ALLOW_CREDENTIALS_TRUE: &'static str = "true"; 45 | 46 | impl Header for AccessControlAllowCredentials { 47 | fn header_name() -> &'static str { 48 | static NAME: &'static str = "Access-Control-Allow-Credentials"; 49 | NAME 50 | } 51 | 52 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 53 | where T: RawLike<'a> 54 | { 55 | if let Some(line) = raw.one() { 56 | let text = unsafe { 57 | // safe because: 58 | // 1. we don't actually care if it's utf8, we just want to 59 | // compare the bytes with the "case" normalized. If it's not 60 | // utf8, then the byte comparison will fail, and we'll return 61 | // None. No big deal. 62 | str::from_utf8_unchecked(line) 63 | }; 64 | if unicase::eq_ascii(text, ACCESS_CONTROL_ALLOW_CREDENTIALS_TRUE) { 65 | return Ok(AccessControlAllowCredentials); 66 | } 67 | } 68 | Err(::Error::Header) 69 | } 70 | 71 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 72 | f.fmt_line(self) 73 | } 74 | } 75 | 76 | impl Display for AccessControlAllowCredentials { 77 | fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { 78 | f.write_str("true") 79 | } 80 | } 81 | 82 | #[cfg(test)] 83 | mod test_access_control_allow_credentials { 84 | use std::str; 85 | use header::*; 86 | use super::AccessControlAllowCredentials as HeaderField; 87 | test_header!(works, vec![b"true"], Some(HeaderField)); 88 | test_header!(ignores_case, vec![b"True"]); 89 | test_header!(not_bool, vec![b"false"], None); 90 | test_header!(only_single, vec![b"true", b"true"], None); 91 | test_header!(no_gibberish, vec!["\u{645}\u{631}\u{62d}\u{628}\u{627}".as_bytes()], None); 92 | } 93 | 94 | standard_header!(AccessControlAllowCredentials, ACCESS_CONTROL_ALLOW_CREDENTIALS); 95 | -------------------------------------------------------------------------------- /src/header/common/if_range.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use header::{self, Header, RawLike, EntityTag, HttpDate}; 3 | 4 | /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) 5 | /// 6 | /// If a client has a partial copy of a representation and wishes to have 7 | /// an up-to-date copy of the entire representation, it could use the 8 | /// Range header field with a conditional GET (using either or both of 9 | /// If-Unmodified-Since and If-Match.) However, if the precondition 10 | /// fails because the representation has been modified, the client would 11 | /// then have to make a second request to obtain the entire current 12 | /// representation. 13 | /// 14 | /// The `If-Range` header field allows a client to \"short-circuit\" the 15 | /// second request. Informally, its meaning is as follows: if the 16 | /// representation is unchanged, send me the part(s) that I am requesting 17 | /// in Range; otherwise, send me the entire representation. 18 | /// 19 | /// # ABNF 20 | /// 21 | /// ```text 22 | /// If-Range = entity-tag / HTTP-date 23 | /// ``` 24 | /// 25 | /// # Example values 26 | /// 27 | /// * `Sat, 29 Oct 1994 19:43:31 GMT` 28 | /// * `\"xyzzy\"` 29 | /// 30 | /// # Examples 31 | /// 32 | /// ``` 33 | /// # extern crate http; 34 | /// use hyperx::header::{IfRange, EntityTag, TypedHeaders}; 35 | /// 36 | /// let mut headers = http::HeaderMap::new(); 37 | /// headers.encode(&IfRange::EntityTag(EntityTag::new(false, "xyzzy".to_owned()))); 38 | /// ``` 39 | /// 40 | /// ``` 41 | /// # extern crate http; 42 | /// use hyperx::header::{IfRange, TypedHeaders}; 43 | /// use std::time::{SystemTime, Duration}; 44 | /// 45 | /// let mut headers = http::HeaderMap::new(); 46 | /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); 47 | /// headers.encode(&IfRange::Date(fetched.into())); 48 | /// ``` 49 | #[derive(Clone, Debug, PartialEq)] 50 | pub enum IfRange { 51 | /// The entity-tag the client has of the resource 52 | EntityTag(EntityTag), 53 | /// The date when the client retrieved the resource 54 | Date(HttpDate), 55 | } 56 | 57 | impl Header for IfRange { 58 | fn header_name() -> &'static str { 59 | static NAME: &'static str = "If-Range"; 60 | NAME 61 | } 62 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 63 | where T: RawLike<'a> 64 | { 65 | let etag: ::Result = header::parsing::from_one_raw_str(raw); 66 | if let Ok(etag) = etag { 67 | return Ok(IfRange::EntityTag(etag)); 68 | } 69 | let date: ::Result = header::parsing::from_one_raw_str(raw); 70 | if let Ok(date) = date { 71 | return Ok(IfRange::Date(date)); 72 | } 73 | Err(::Error::Header) 74 | } 75 | 76 | fn fmt_header(&self, f: &mut ::header::Formatter) -> ::std::fmt::Result { 77 | f.fmt_line(self) 78 | } 79 | } 80 | 81 | impl Display for IfRange { 82 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 83 | match *self { 84 | IfRange::EntityTag(ref x) => Display::fmt(x, f), 85 | IfRange::Date(ref x) => Display::fmt(x, f), 86 | } 87 | } 88 | } 89 | 90 | #[cfg(test)] 91 | mod test_if_range { 92 | use std::str; 93 | use header::*; 94 | use super::IfRange as HeaderField; 95 | test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); 96 | test_header!(test2, vec![b"\"xyzzy\""]); 97 | test_header!(test3, vec![b"this-is-invalid"], None::); 98 | } 99 | 100 | standard_header!(IfRange, IF_RANGE); 101 | -------------------------------------------------------------------------------- /src/header/common/accept_ranges.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str::FromStr; 3 | 4 | header! { 5 | /// `Accept-Ranges` header, defined in 6 | /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-2.3) 7 | /// 8 | /// The `Accept-Ranges` header field allows a server to indicate that it 9 | /// supports range requests for the target resource. 10 | /// 11 | /// # ABNF 12 | /// 13 | /// ```text 14 | /// Accept-Ranges = acceptable-ranges 15 | /// acceptable-ranges = 1#range-unit / \"none\" 16 | /// 17 | /// # Example values 18 | /// * `bytes` 19 | /// * `none` 20 | /// * `unknown-unit` 21 | /// ``` 22 | /// 23 | /// # Examples 24 | /// ``` 25 | /// # extern crate http; 26 | /// use hyperx::header::{AcceptRanges, RangeUnit, TypedHeaders}; 27 | /// 28 | /// let mut headers = http::HeaderMap::new(); 29 | /// headers.encode(&AcceptRanges(vec![RangeUnit::Bytes])); 30 | /// ``` 31 | /// 32 | /// ``` 33 | /// # extern crate http; 34 | /// use hyperx::header::{AcceptRanges, RangeUnit, TypedHeaders}; 35 | /// 36 | /// let mut headers = http::HeaderMap::new(); 37 | /// headers.encode(&AcceptRanges(vec![RangeUnit::None])); 38 | /// ``` 39 | /// 40 | /// ``` 41 | /// # extern crate http; 42 | /// use hyperx::header::{AcceptRanges, RangeUnit, TypedHeaders}; 43 | /// 44 | /// let mut headers = http::HeaderMap::new(); 45 | /// headers.encode( 46 | /// &AcceptRanges(vec![ 47 | /// RangeUnit::Unregistered("nibbles".to_owned()), 48 | /// RangeUnit::Bytes, 49 | /// RangeUnit::Unregistered("doublets".to_owned()), 50 | /// RangeUnit::Unregistered("quadlets".to_owned()), 51 | /// ]) 52 | /// ); 53 | /// ``` 54 | (AcceptRanges, "Accept-Ranges") => (RangeUnit)+ 55 | 56 | test_acccept_ranges { 57 | test_header!(test1, vec![b"bytes"]); 58 | test_header!(test2, vec![b"none"]); 59 | test_header!(test3, vec![b"unknown-unit"]); 60 | test_header!(test4, vec![b"bytes, unknown-unit"]); 61 | } 62 | } 63 | 64 | /// Range Units, described in [RFC7233](http://tools.ietf.org/html/rfc7233#section-2) 65 | /// 66 | /// A representation can be partitioned into subranges according to 67 | /// various structural units, depending on the structure inherent in the 68 | /// representation's media type. 69 | /// 70 | /// # ABNF 71 | /// 72 | /// ```text 73 | /// range-unit = bytes-unit / other-range-unit 74 | /// bytes-unit = "bytes" 75 | /// other-range-unit = token 76 | /// ``` 77 | #[derive(Clone, Debug, Eq, PartialEq)] 78 | pub enum RangeUnit { 79 | /// Indicating byte-range requests are supported. 80 | Bytes, 81 | /// Reserved as keyword, indicating no ranges are supported. 82 | None, 83 | /// The given range unit is not registered at IANA. 84 | Unregistered(String), 85 | } 86 | 87 | impl FromStr for RangeUnit { 88 | type Err = ::Error; 89 | fn from_str(s: &str) -> ::Result { 90 | match s { 91 | "bytes" => Ok(RangeUnit::Bytes), 92 | "none" => Ok(RangeUnit::None), 93 | // FIXME: Check if s is really a Token 94 | _ => Ok(RangeUnit::Unregistered(s.to_owned())), 95 | } 96 | } 97 | } 98 | 99 | impl Display for RangeUnit { 100 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 101 | match *self { 102 | RangeUnit::Bytes => f.write_str("bytes"), 103 | RangeUnit::None => f.write_str("none"), 104 | RangeUnit::Unregistered(ref x) => f.write_str(x), 105 | } 106 | } 107 | } 108 | 109 | standard_header!(AcceptRanges, ACCEPT_RANGES); 110 | -------------------------------------------------------------------------------- /src/header/common/etag.rs: -------------------------------------------------------------------------------- 1 | use header::EntityTag; 2 | 3 | header! { 4 | /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) 5 | /// 6 | /// The `ETag` header field in a response provides the current entity-tag 7 | /// for the selected representation, as determined at the conclusion of 8 | /// handling the request. An entity-tag is an opaque validator for 9 | /// differentiating between multiple representations of the same 10 | /// resource, regardless of whether those multiple representations are 11 | /// due to resource state changes over time, content negotiation 12 | /// resulting in multiple representations being valid at the same time, 13 | /// or both. An entity-tag consists of an opaque quoted string, possibly 14 | /// prefixed by a weakness indicator. 15 | /// 16 | /// # ABNF 17 | /// 18 | /// ```text 19 | /// ETag = entity-tag 20 | /// ``` 21 | /// 22 | /// # Example values 23 | /// 24 | /// * `"xyzzy"` 25 | /// * `W/"xyzzy"` 26 | /// * `""` 27 | /// 28 | /// # Examples 29 | /// 30 | /// ``` 31 | /// # extern crate http; 32 | /// use hyperx::header::{ETag, EntityTag, TypedHeaders}; 33 | /// 34 | /// let mut headers = http::HeaderMap::new(); 35 | /// headers.encode(&ETag(EntityTag::new(false, "xyzzy".to_owned()))); 36 | /// ``` 37 | /// ``` 38 | /// # extern crate http; 39 | /// use hyperx::header::{ETag, EntityTag, TypedHeaders}; 40 | /// 41 | /// let mut headers = http::HeaderMap::new(); 42 | /// headers.encode(&ETag(EntityTag::new(true, "xyzzy".to_owned()))); 43 | /// ``` 44 | (ETag, "ETag") => [EntityTag] 45 | 46 | test_etag { 47 | // From the RFC 48 | test_header!(test1, 49 | vec![b"\"xyzzy\""], 50 | Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); 51 | test_header!(test2, 52 | vec![b"W/\"xyzzy\""], 53 | Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); 54 | test_header!(test3, 55 | vec![b"\"\""], 56 | Some(ETag(EntityTag::new(false, "".to_owned())))); 57 | // Own tests 58 | test_header!(test4, 59 | vec![b"\"foobar\""], 60 | Some(ETag(EntityTag::new(false, "foobar".to_owned())))); 61 | test_header!(test5, 62 | vec![b"\"\""], 63 | Some(ETag(EntityTag::new(false, "".to_owned())))); 64 | test_header!(test6, 65 | vec![b"W/\"weak-etag\""], 66 | Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); 67 | test_header!(test7, 68 | vec![b"W/\"\x65\x62\""], 69 | Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); 70 | test_header!(test8, 71 | vec![b"W/\"\""], 72 | Some(ETag(EntityTag::new(true, "".to_owned())))); 73 | test_header!(test9, 74 | vec![b"no-dquotes"], 75 | None::); 76 | test_header!(test10, 77 | vec![b"w/\"the-first-w-is-case-sensitive\""], 78 | None::); 79 | test_header!(test11, 80 | vec![b""], 81 | None::); 82 | test_header!(test12, 83 | vec![b"\"unmatched-dquotes1"], 84 | None::); 85 | test_header!(test13, 86 | vec![b"unmatched-dquotes2\""], 87 | None::); 88 | test_header!(test14, 89 | vec![b"matched-\"dquotes\""], 90 | None::); 91 | test_header!(test15, 92 | vec![b"\""], 93 | None::); 94 | } 95 | } 96 | 97 | bench_header!(bench, ETag, { vec![b"W/\"nonemptytag\"".to_vec()] }); 98 | 99 | standard_header!(ETag, ETAG); 100 | -------------------------------------------------------------------------------- /src/header/internals/item.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::any::TypeId; 3 | use std::fmt; 4 | use std::str::from_utf8; 5 | 6 | use super::cell::{OptCell, PtrMapCell}; 7 | use header::{Header, Formatter, Multi, raw, Raw, RawLike}; 8 | 9 | #[derive(Clone)] 10 | pub struct Item { 11 | raw: OptCell, 12 | typed: PtrMapCell 13 | } 14 | 15 | impl Item { 16 | #[inline] 17 | pub fn new_raw(data: Raw) -> Item { 18 | Item { 19 | raw: OptCell::new(Some(data)), 20 | typed: PtrMapCell::new(), 21 | } 22 | } 23 | 24 | #[inline] 25 | pub fn new_typed(val: H) -> Item { 26 | Item { 27 | raw: OptCell::new(None), 28 | typed: PtrMapCell::with_one(TypeId::of::(), Box::new(val)), 29 | } 30 | } 31 | 32 | #[inline] 33 | pub fn raw_mut(&mut self) -> &mut Raw { 34 | self.raw(); 35 | self.typed = PtrMapCell::new(); 36 | unsafe { 37 | self.raw.get_mut() 38 | } 39 | } 40 | 41 | pub fn raw(&self) -> &Raw { 42 | if let Some(ref raw) = *self.raw { 43 | return raw; 44 | } 45 | 46 | let mut raw = raw::new(); 47 | self.write_h1(&mut Formatter(Multi::Raw(&mut raw))).expect("fmt failed"); 48 | self.raw.set(raw); 49 | 50 | self.raw.as_ref().unwrap() 51 | } 52 | 53 | pub fn typed(&self) -> Option<&H> { 54 | let tid = TypeId::of::(); 55 | match self.typed.get(tid) { 56 | Some(val) => Some(val), 57 | None => { 58 | parse::(self.raw.as_ref().expect("item.raw must exist")).and_then(|typed| { 59 | unsafe { self.typed.insert(tid, typed); } 60 | self.typed.get(tid) 61 | }) 62 | } 63 | }.map(|typed| unsafe { typed.downcast_ref_unchecked() }) 64 | } 65 | 66 | pub fn typed_mut(&mut self) -> Option<&mut H> { 67 | let tid = TypeId::of::(); 68 | if self.typed.get_mut(tid).is_none() { 69 | parse::(self.raw.as_ref().expect("item.raw must exist")).map(|typed| { 70 | unsafe { self.typed.insert(tid, typed); } 71 | }); 72 | } 73 | if self.raw.is_some() && self.typed.get_mut(tid).is_some() { 74 | self.raw = OptCell::new(None); 75 | } 76 | self.typed.get_mut(tid).map(|typed| unsafe { typed.downcast_mut_unchecked() }) 77 | } 78 | 79 | pub fn into_typed(self) -> Option { 80 | let tid = TypeId::of::(); 81 | let Item { typed, raw } = self; 82 | typed.into_value(tid) 83 | .or_else(|| raw.as_ref().and_then(parse::)) 84 | .map(|typed| unsafe { typed.downcast_unchecked() }) 85 | } 86 | 87 | pub fn write_h1(&self, f: &mut Formatter) -> fmt::Result { 88 | match *self.raw { 89 | Some(ref raw) => { 90 | for part in raw.iter() { 91 | match from_utf8(&part[..]) { 92 | Ok(s) => { 93 | f.fmt_line(&s)?; 94 | }, 95 | Err(_) => { 96 | return Err(fmt::Error); 97 | } 98 | } 99 | } 100 | Ok(()) 101 | }, 102 | None => { 103 | let typed = unsafe { self.typed.one() }; 104 | typed.fmt_header(f) 105 | } 106 | } 107 | } 108 | } 109 | 110 | #[inline] 111 | fn parse(raw: &Raw) -> Option> { 112 | H::parse_header(raw).map(|h| { 113 | let h: Box = Box::new(h); 114 | h 115 | }).ok() 116 | } 117 | -------------------------------------------------------------------------------- /src/header/common/content_length.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use header::{Header, RawLike, parsing}; 4 | 5 | /// `Content-Length` header, defined in 6 | /// [RFC7230](http://tools.ietf.org/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](http://tools.ietf.org/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 | /// # extern crate http; 38 | /// use hyperx::header::{ContentLength, TypedHeaders}; 39 | /// 40 | /// let mut headers = http::HeaderMap::new(); 41 | /// headers.encode(&ContentLength(1024u64)); 42 | /// ``` 43 | #[derive(Clone, Copy, Debug, PartialEq)] 44 | pub struct ContentLength(pub u64); 45 | 46 | impl Header for ContentLength { 47 | #[inline] 48 | fn header_name() -> &'static str { 49 | static NAME: &'static str = "Content-Length"; 50 | NAME 51 | } 52 | 53 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 54 | where T: RawLike<'a> 55 | { 56 | // If multiple Content-Length headers were sent, everything can still 57 | // be alright if they all contain the same value, and all parse 58 | // correctly. If not, then it's an error. 59 | raw.iter() 60 | .map(parsing::from_raw_str) 61 | .fold(None, |prev, x| { 62 | match (prev, x) { 63 | (None, x) => Some(x), 64 | (e @ Some(Err(_)), _ ) => e, 65 | (Some(Ok(prev)), Ok(x)) if prev == x => Some(Ok(prev)), 66 | _ => Some(Err(::Error::Header)), 67 | } 68 | }) 69 | .unwrap_or(Err(::Error::Header)) 70 | .map(ContentLength) 71 | } 72 | 73 | #[inline] 74 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 75 | f.danger_fmt_line_without_newline_replacer(self) 76 | } 77 | } 78 | 79 | standard_header!(ContentLength, CONTENT_LENGTH); 80 | 81 | impl fmt::Display for ContentLength { 82 | #[inline] 83 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 84 | fmt::Display::fmt(&self.0, f) 85 | } 86 | } 87 | 88 | __hyper__deref!(ContentLength => u64); 89 | 90 | __hyper__tm!(ContentLength, tests { 91 | // Testcase from RFC 92 | test_header!(test1, vec![b"3495"], Some(HeaderField(3495))); 93 | 94 | test_header!(test_invalid, vec![b"34v95"], None); 95 | 96 | // Can't use the test_header macro because "5, 5" gets cleaned to "5". 97 | #[test] 98 | fn test_duplicates() { 99 | let r: Raw = vec![b"5".to_vec(), b"5".to_vec()].into(); 100 | let parsed = HeaderField::parse_header(&r).unwrap(); 101 | assert_eq!(parsed, HeaderField(5)); 102 | assert_eq!(format!("{}", parsed), "5"); 103 | } 104 | 105 | test_header!(test_duplicates_vary, vec![b"5", b"6", b"5"], None); 106 | }); 107 | 108 | bench_header!(bench, ContentLength, { vec![b"42349984".to_vec()] }); 109 | -------------------------------------------------------------------------------- /src/header/common/preference_applied.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use header::{Header, RawLike, Preference}; 3 | use header::parsing::{from_comma_delimited, fmt_comma_delimited}; 4 | 5 | /// `Preference-Applied` header, defined in [RFC7240](http://tools.ietf.org/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 | /// # extern crate http; 28 | /// use hyperx::header::{PreferenceApplied, Preference, TypedHeaders}; 29 | /// 30 | /// let mut headers = http::HeaderMap::new(); 31 | /// headers.insert( 32 | /// "preference-applied", 33 | /// PreferenceApplied(vec![ 34 | /// Preference::RespondAsync 35 | /// ]).to_string().parse().unwrap() 36 | /// ); 37 | /// ``` 38 | /// 39 | /// ``` 40 | /// # extern crate http; 41 | /// use hyperx::header::{PreferenceApplied, Preference, TypedHeaders}; 42 | /// 43 | /// let mut headers = http::HeaderMap::new(); 44 | /// headers.insert( 45 | /// "preference-applied", 46 | /// PreferenceApplied(vec![ 47 | /// Preference::RespondAsync, 48 | /// Preference::ReturnRepresentation, 49 | /// Preference::Wait(10u32), 50 | /// Preference::Extension("foo".to_owned(), 51 | /// "bar".to_owned(), 52 | /// vec![]), 53 | /// ]).to_string().parse().unwrap() 54 | /// ); 55 | /// ``` 56 | #[derive(PartialEq, Clone, Debug)] 57 | pub struct PreferenceApplied(pub Vec); 58 | 59 | __hyper__deref!(PreferenceApplied => Vec); 60 | 61 | impl Header for PreferenceApplied { 62 | fn header_name() -> &'static str { 63 | static NAME: &'static str = "Preference-Applied"; 64 | NAME 65 | } 66 | 67 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 68 | where T: RawLike<'a> 69 | { 70 | let preferences = from_comma_delimited(raw)?; 71 | if !preferences.is_empty() { 72 | Ok(PreferenceApplied(preferences)) 73 | } else { 74 | Err(::Error::Header) 75 | } 76 | } 77 | 78 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 79 | f.fmt_line(self) 80 | } 81 | } 82 | 83 | impl fmt::Display for PreferenceApplied { 84 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 85 | //TODO: format this without allocating a Vec and cloning contents 86 | let preferences: Vec<_> = self.0.iter().map(|pref| match pref { 87 | // The spec ignores parameters in `Preferences-Applied` 88 | &Preference::Extension(ref name, ref value, _) => Preference::Extension( 89 | name.to_owned(), 90 | value.to_owned(), 91 | vec![] 92 | ), 93 | preference => preference.clone() 94 | }).collect(); 95 | fmt_comma_delimited(f, &preferences) 96 | } 97 | } 98 | 99 | #[cfg(test)] 100 | mod tests { 101 | use header::Preference; 102 | use super::*; 103 | 104 | #[test] 105 | fn test_format_ignore_parameters() { 106 | assert_eq!( 107 | format!("{}", PreferenceApplied(vec![Preference::Extension( 108 | "foo".to_owned(), 109 | "bar".to_owned(), 110 | vec![("bar".to_owned(), "foo".to_owned()), ("buz".to_owned(), "".to_owned())] 111 | )])), 112 | "foo=bar".to_owned() 113 | ); 114 | } 115 | } 116 | 117 | bench_header!(normal, 118 | PreferenceApplied, { vec![b"respond-async, return=representation".to_vec(), b"wait=100".to_vec()] }); 119 | -------------------------------------------------------------------------------- /src/header/common/host.rs: -------------------------------------------------------------------------------- 1 | use std::borrow::Cow; 2 | use std::fmt; 3 | use std::str::FromStr; 4 | 5 | use header::{Header, RawLike}; 6 | use header::parsing::from_one_raw_str; 7 | 8 | /// The `Host` header. 9 | /// 10 | /// HTTP/1.1 requires that all requests include a `Host` header, and so hyper 11 | /// client requests add one automatically. 12 | /// 13 | /// # Examples 14 | /// ``` 15 | /// # extern crate http; 16 | /// use hyperx::header::{Host, TypedHeaders}; 17 | /// 18 | /// let mut headers = http::HeaderMap::new(); 19 | /// headers.encode( 20 | /// &Host::new("hyper.rs", None) 21 | /// ); 22 | /// ``` 23 | /// ``` 24 | /// # extern crate http; 25 | /// use hyperx::header::{Host, TypedHeaders}; 26 | /// 27 | /// let mut headers = http::HeaderMap::new(); 28 | /// headers.encode( 29 | /// &Host::new("hyper.rs", 8080) 30 | /// ); 31 | /// ``` 32 | #[derive(Clone, PartialEq, Debug)] 33 | pub struct Host { 34 | hostname: Cow<'static, str>, 35 | port: Option 36 | } 37 | 38 | impl Host { 39 | /// Create a `Host` header, providing the hostname and optional port. 40 | pub fn new(hostname: H, port: P) -> Host 41 | where H: Into>, 42 | P: Into> 43 | { 44 | Host { 45 | hostname: hostname.into(), 46 | port: port.into(), 47 | } 48 | } 49 | 50 | /// Get the hostname, such as example.domain. 51 | pub fn hostname(&self) -> &str { 52 | self.hostname.as_ref() 53 | } 54 | 55 | /// Get the optional port number. 56 | pub fn port(&self) -> Option { 57 | self.port 58 | } 59 | } 60 | 61 | impl Header for Host { 62 | fn header_name() -> &'static str { 63 | static NAME: &'static str = "Host"; 64 | NAME 65 | } 66 | 67 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 68 | where T: RawLike<'a> 69 | { 70 | from_one_raw_str(raw) 71 | } 72 | 73 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 74 | f.fmt_line(self) 75 | } 76 | } 77 | 78 | impl fmt::Display for Host { 79 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 80 | match self.port { 81 | None | Some(80) | Some(443) => f.write_str(&self.hostname[..]), 82 | Some(port) => write!(f, "{}:{}", self.hostname, port) 83 | } 84 | } 85 | } 86 | 87 | impl FromStr for Host { 88 | type Err = ::Error; 89 | 90 | fn from_str(s: &str) -> ::Result { 91 | let idx = s.rfind(':'); 92 | let port = idx.and_then( 93 | |idx| s[idx + 1..].parse().ok() 94 | ); 95 | let hostname = match port { 96 | None => s, 97 | Some(_) => &s[..idx.unwrap()] 98 | }; 99 | 100 | Ok(Host { 101 | hostname: hostname.to_owned().into(), 102 | port: port, 103 | }) 104 | } 105 | } 106 | 107 | #[cfg(test)] 108 | mod tests { 109 | use super::Host; 110 | use header::{Header, Raw}; 111 | 112 | #[test] 113 | fn test_host() { 114 | let r: Raw = vec![b"foo.com".to_vec()].into(); 115 | let host = Header::parse_header(&r); 116 | assert_eq!(host.ok(), Some(Host::new("foo.com", None))); 117 | 118 | let r: Raw = vec![b"foo.com:8080".to_vec()].into(); 119 | let host = Header::parse_header(&r); 120 | assert_eq!(host.ok(), Some(Host::new("foo.com", Some(8080)))); 121 | 122 | let r: Raw = vec![b"foo.com".to_vec()].into(); 123 | let host = Header::parse_header(&r); 124 | assert_eq!(host.ok(), Some(Host::new("foo.com", None))); 125 | 126 | let r: Raw = vec![b"[::1]:8080".to_vec()].into(); 127 | let host = Header::parse_header(&r); 128 | assert_eq!(host.ok(), Some(Host::new("[::1]", Some(8080)))); 129 | 130 | let r: Raw = vec![b"[::1]".to_vec()].into(); 131 | let host = Header::parse_header(&r); 132 | assert_eq!(host.ok(), Some(Host::new("[::1]", None))); 133 | } 134 | } 135 | 136 | bench_header!(bench, Host, { vec![b"foo.com:3000".to_vec()] }); 137 | 138 | standard_header!(Host, HOST); 139 | -------------------------------------------------------------------------------- /src/header/internals/vec_map.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone)] 2 | pub struct VecMap { 3 | vec: Vec<(K, V)>, 4 | } 5 | 6 | impl VecMap { 7 | #[inline] 8 | pub fn with_capacity(cap: usize) -> VecMap { 9 | VecMap { 10 | vec: Vec::with_capacity(cap) 11 | } 12 | } 13 | 14 | #[inline] 15 | pub fn insert(&mut self, key: K, value: V) { 16 | // not using entry or find_mut because of borrowck 17 | for entry in &mut self.vec { 18 | if key == entry.0 { 19 | *entry = (key, value); 20 | return; 21 | } 22 | } 23 | self.vec.push((key, value)); 24 | } 25 | 26 | #[inline] 27 | pub fn append(&mut self, key: K, value: V) { 28 | self.vec.push((key, value)); 29 | } 30 | 31 | #[cfg(feature = "headers")] 32 | #[inline] 33 | pub fn entry(&mut self, key: K) -> Entry { 34 | match self.pos(&key) { 35 | Some(pos) => Entry::Occupied(OccupiedEntry { 36 | vec: &mut self.vec, 37 | pos: pos, 38 | }), 39 | None => Entry::Vacant(VacantEntry { 40 | vec: &mut self.vec, 41 | key: key, 42 | }) 43 | } 44 | } 45 | 46 | #[inline] 47 | pub fn get + ?Sized>(&self, key: &K2) -> Option<&V> { 48 | self.find(key).map(|entry| &entry.1) 49 | } 50 | 51 | #[cfg(feature = "headers")] 52 | #[inline] 53 | pub fn get_mut + ?Sized>(&mut self, key: &K2) -> Option<&mut V> { 54 | self.find_mut(key).map(|entry| &mut entry.1) 55 | } 56 | 57 | #[cfg(feature = "headers")] 58 | #[inline] 59 | pub fn contains_key + ?Sized>(&self, key: &K2) -> bool { 60 | self.find(key).is_some() 61 | } 62 | 63 | #[inline] 64 | pub fn len(&self) -> usize { self.vec.len() } 65 | 66 | #[inline] 67 | pub fn iter(&self) -> ::std::slice::Iter<(K, V)> { 68 | self.vec.iter() 69 | } 70 | 71 | #[cfg(feature = "headers")] 72 | #[inline] 73 | pub fn remove + ?Sized>(&mut self, key: &K2) -> Option { 74 | self.pos(key).map(|pos| self.vec.remove(pos)).map(|(_, v)| v) 75 | } 76 | 77 | #[inline] 78 | pub fn remove_all + ?Sized>(&mut self, key: &K2) { 79 | let len = self.vec.len(); 80 | for i in (0..len).rev() { 81 | if key == &self.vec[i].0 { 82 | self.vec.remove(i); 83 | } 84 | } 85 | } 86 | 87 | #[cfg(feature = "headers")] 88 | #[inline] 89 | pub fn clear(&mut self) { 90 | self.vec.clear(); 91 | } 92 | 93 | #[inline] 94 | fn find + ?Sized>(&self, key: &K2) -> Option<&(K, V)> { 95 | for entry in &self.vec { 96 | if key == &entry.0 { 97 | return Some(entry); 98 | } 99 | } 100 | None 101 | } 102 | 103 | #[cfg(feature = "headers")] 104 | #[inline] 105 | fn find_mut + ?Sized>(&mut self, key: &K2) -> Option<&mut (K, V)> { 106 | for entry in &mut self.vec { 107 | if key == &entry.0 { 108 | return Some(entry); 109 | } 110 | } 111 | None 112 | } 113 | 114 | #[cfg(feature = "headers")] 115 | #[inline] 116 | fn pos + ?Sized>(&self, key: &K2) -> Option { 117 | self.vec.iter().position(|entry| key == &entry.0) 118 | } 119 | } 120 | 121 | #[cfg(feature = "headers")] 122 | pub enum Entry<'a, K: 'a, V: 'a> { 123 | Vacant(VacantEntry<'a, K, V>), 124 | Occupied(OccupiedEntry<'a, K, V>) 125 | } 126 | 127 | #[cfg(feature = "headers")] 128 | pub struct VacantEntry<'a, K: 'a, V: 'a> { 129 | vec: &'a mut Vec<(K, V)>, 130 | key: K, 131 | } 132 | 133 | #[cfg(feature = "headers")] 134 | impl<'a, K, V> VacantEntry<'a, K, V> { 135 | pub fn insert(self, val: V) -> &'a mut V { 136 | self.vec.push((self.key, val)); 137 | let pos = self.vec.len() - 1; 138 | &mut self.vec[pos].1 139 | } 140 | } 141 | 142 | #[cfg(feature = "headers")] 143 | pub struct OccupiedEntry<'a, K: 'a, V: 'a> { 144 | vec: &'a mut Vec<(K, V)>, 145 | pos: usize, 146 | } 147 | 148 | #[cfg(feature = "headers")] 149 | impl<'a, K, V> OccupiedEntry<'a, K, V> { 150 | pub fn into_mut(self) -> &'a mut V { 151 | &mut self.vec[self.pos].1 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/header/common/content_type.rs: -------------------------------------------------------------------------------- 1 | use mime::{self, Mime}; 2 | 3 | header! { 4 | /// `Content-Type` header, defined in 5 | /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) 6 | /// 7 | /// The `Content-Type` header field indicates the media type of the 8 | /// associated representation: either the representation enclosed in the 9 | /// message payload or the selected representation, as determined by the 10 | /// message semantics. The indicated media type defines both the data 11 | /// format and how that data is intended to be processed by a recipient, 12 | /// within the scope of the received message semantics, after any content 13 | /// codings indicated by Content-Encoding are decoded. 14 | /// 15 | /// Although the `mime` crate allows the mime options to be any slice, this crate 16 | /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If 17 | /// this is an issue, it's possible to implement `Header` on a custom struct. 18 | /// 19 | /// # ABNF 20 | /// 21 | /// ```text 22 | /// Content-Type = media-type 23 | /// ``` 24 | /// 25 | /// # Example values 26 | /// 27 | /// * `text/html; charset=utf-8` 28 | /// * `application/json` 29 | /// 30 | /// # Examples 31 | /// 32 | /// ``` 33 | /// # extern crate http; 34 | /// use hyperx::header::{ContentType, TypedHeaders}; 35 | /// 36 | /// let mut headers = http::HeaderMap::new(); 37 | /// 38 | /// headers.encode( 39 | /// &ContentType::json() 40 | /// ); 41 | /// ``` 42 | /// 43 | /// ``` 44 | /// # extern crate http; 45 | /// use hyperx::header::{ContentType, TypedHeaders}; 46 | /// use hyperx::mime; 47 | /// 48 | /// let mut headers = http::HeaderMap::new(); 49 | /// 50 | /// headers.encode( 51 | /// &ContentType(mime::TEXT_HTML) 52 | /// ); 53 | /// ``` 54 | (ContentType, "Content-Type") => danger [Mime] 55 | 56 | test_content_type { 57 | test_header!( 58 | test1, 59 | vec![b"text/html"], 60 | Some(HeaderField(TEXT_HTML))); 61 | } 62 | } 63 | 64 | impl ContentType { 65 | /// A constructor to easily create a `Content-Type: application/json` header. 66 | #[inline] 67 | pub fn json() -> ContentType { 68 | ContentType(mime::APPLICATION_JSON) 69 | } 70 | 71 | // Kind of deprecated, `text()` and `text_utf8()` are better. 72 | // But don't bother with an actual #[deprecated], because the whole 73 | // header system is changing in 0.12 anyways. 74 | #[doc(hidden)] 75 | pub fn plaintext() -> ContentType { 76 | ContentType(mime::TEXT_PLAIN_UTF_8) 77 | } 78 | 79 | /// A constructor to easily create a `Content-Type: text/plain` header. 80 | #[inline] 81 | pub fn text() -> ContentType { 82 | ContentType(mime::TEXT_PLAIN) 83 | } 84 | 85 | /// A constructor to easily create a `Content-Type: text/plain; charset=utf-8` header. 86 | #[inline] 87 | pub fn text_utf8() -> ContentType { 88 | ContentType(mime::TEXT_PLAIN_UTF_8) 89 | } 90 | 91 | /// A constructor to easily create a `Content-Type: text/html` header. 92 | #[inline] 93 | pub fn html() -> ContentType { 94 | ContentType(mime::TEXT_HTML) 95 | } 96 | 97 | /// A constructor to easily create a `Content-Type: text/xml` header. 98 | #[inline] 99 | pub fn xml() -> ContentType { 100 | ContentType(mime::TEXT_XML) 101 | } 102 | 103 | /// A constructor to easily create a `Content-Type: application/www-form-url-encoded` header. 104 | #[inline] 105 | pub fn form_url_encoded() -> ContentType { 106 | ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) 107 | } 108 | /// A constructor to easily create a `Content-Type: image/jpeg` header. 109 | #[inline] 110 | pub fn jpeg() -> ContentType { 111 | ContentType(mime::IMAGE_JPEG) 112 | } 113 | 114 | /// A constructor to easily create a `Content-Type: image/png` header. 115 | #[inline] 116 | pub fn png() -> ContentType { 117 | ContentType(mime::IMAGE_PNG) 118 | } 119 | 120 | /// A constructor to easily create a `Content-Type: application/octet-stream` header. 121 | #[inline] 122 | pub fn octet_stream() -> ContentType { 123 | ContentType(mime::APPLICATION_OCTET_STREAM) 124 | } 125 | } 126 | 127 | impl Eq for ContentType {} 128 | 129 | bench_header!(bench, ContentType, { vec![b"application/json".to_vec()] }); 130 | 131 | standard_header!(ContentType, CONTENT_TYPE); 132 | -------------------------------------------------------------------------------- /src/header/common/referrer_policy.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | 3 | use header::{Header, RawLike, parsing}; 4 | 5 | /// `Referrer-Policy` header, part of 6 | /// [Referrer Policy](https://www.w3.org/TR/referrer-policy/#referrer-policy-header) 7 | /// 8 | /// The `Referrer-Policy` HTTP header specifies the referrer 9 | /// policy that the user agent applies when determining what 10 | /// referrer information should be included with requests made, 11 | /// and with browsing contexts created from the context of the 12 | /// protected resource. 13 | /// 14 | /// # ABNF 15 | /// 16 | /// ```text 17 | /// Referrer-Policy: 1#policy-token 18 | /// policy-token = "no-referrer" / "no-referrer-when-downgrade" 19 | /// / "same-origin" / "origin" 20 | /// / "origin-when-cross-origin" / "unsafe-url" 21 | /// ``` 22 | /// 23 | /// # Example values 24 | /// 25 | /// * `no-referrer` 26 | /// 27 | /// # Example 28 | /// 29 | /// ``` 30 | /// # extern crate http; 31 | /// use hyperx::header::{ReferrerPolicy, TypedHeaders}; 32 | /// 33 | /// let mut headers = http::HeaderMap::new(); 34 | /// headers.encode(&ReferrerPolicy::NoReferrer); 35 | /// ``` 36 | #[derive(Clone, PartialEq, Eq, Debug)] 37 | pub enum ReferrerPolicy { 38 | /// `no-referrer` 39 | NoReferrer, 40 | /// `no-referrer-when-downgrade` 41 | NoReferrerWhenDowngrade, 42 | /// `same-origin` 43 | SameOrigin, 44 | /// `origin` 45 | Origin, 46 | /// `origin-when-cross-origin` 47 | OriginWhenCrossOrigin, 48 | /// `unsafe-url` 49 | UnsafeUrl, 50 | /// `strict-origin` 51 | StrictOrigin, 52 | ///`strict-origin-when-cross-origin` 53 | StrictOriginWhenCrossOrigin, 54 | } 55 | 56 | impl Header for ReferrerPolicy { 57 | fn header_name() -> &'static str { 58 | static NAME: &'static str = "Referrer-Policy"; 59 | NAME 60 | } 61 | 62 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 63 | where T: RawLike<'a> 64 | { 65 | use self::ReferrerPolicy::*; 66 | // See https://www.w3.org/TR/referrer-policy/#determine-policy-for-token 67 | let headers: Vec = parsing::from_comma_delimited(raw)?; 68 | 69 | for h in headers.iter().rev() { 70 | let slice = &h.to_ascii_lowercase()[..]; 71 | match slice { 72 | "no-referrer" | "never" => return Ok(NoReferrer), 73 | "no-referrer-when-downgrade" | "default" => return Ok(NoReferrerWhenDowngrade), 74 | "same-origin" => return Ok(SameOrigin), 75 | "origin" => return Ok(Origin), 76 | "origin-when-cross-origin" => return Ok(OriginWhenCrossOrigin), 77 | "strict-origin" => return Ok(StrictOrigin), 78 | "strict-origin-when-cross-origin" => return Ok(StrictOriginWhenCrossOrigin), 79 | "unsafe-url" | "always" => return Ok(UnsafeUrl), 80 | _ => continue, 81 | } 82 | } 83 | 84 | Err(::Error::Header) 85 | } 86 | 87 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 88 | f.fmt_line(self) 89 | } 90 | } 91 | 92 | impl fmt::Display for ReferrerPolicy { 93 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 94 | use self::ReferrerPolicy::*; 95 | f.write_str(match *self { 96 | NoReferrer => "no-referrer", 97 | NoReferrerWhenDowngrade => "no-referrer-when-downgrade", 98 | SameOrigin => "same-origin", 99 | Origin => "origin", 100 | OriginWhenCrossOrigin => "origin-when-cross-origin", 101 | StrictOrigin => "strict-origin", 102 | StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin", 103 | UnsafeUrl => "unsafe-url", 104 | }) 105 | } 106 | } 107 | 108 | #[cfg(test)] 109 | mod tests { 110 | use super::ReferrerPolicy; 111 | use header::{Header, Raw}; 112 | 113 | #[test] 114 | fn test_parse_header() { 115 | let r: Raw = "origin".into(); 116 | let a: ReferrerPolicy = Header::parse_header(&r).unwrap(); 117 | let b = ReferrerPolicy::Origin; 118 | assert_eq!(a, b); 119 | 120 | let r: Raw = "foobar".into(); 121 | let e: ::Result = Header::parse_header(&r); 122 | assert!(e.is_err()); 123 | } 124 | 125 | #[test] 126 | fn test_rightmost_header() { 127 | let r: Raw = "same-origin, origin, foobar".into(); 128 | let a: ReferrerPolicy = Header::parse_header(&r).unwrap(); 129 | let b = ReferrerPolicy::Origin; 130 | assert_eq!(a, b); 131 | } 132 | } 133 | 134 | standard_header!(ReferrerPolicy, REFERRER_POLICY); 135 | -------------------------------------------------------------------------------- /src/header/shared/charset.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str::FromStr; 3 | 4 | use self::Charset::*; 5 | 6 | /// A Mime charset. 7 | /// 8 | /// The string representation is normalised to upper case. 9 | /// 10 | /// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. 11 | /// 12 | /// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml 13 | #[derive(Clone,Debug,PartialEq)] 14 | #[allow(non_camel_case_types)] 15 | pub enum Charset{ 16 | /// US ASCII 17 | Us_Ascii, 18 | /// ISO-8859-1 19 | Iso_8859_1, 20 | /// ISO-8859-2 21 | Iso_8859_2, 22 | /// ISO-8859-3 23 | Iso_8859_3, 24 | /// ISO-8859-4 25 | Iso_8859_4, 26 | /// ISO-8859-5 27 | Iso_8859_5, 28 | /// ISO-8859-6 29 | Iso_8859_6, 30 | /// ISO-8859-7 31 | Iso_8859_7, 32 | /// ISO-8859-8 33 | Iso_8859_8, 34 | /// ISO-8859-9 35 | Iso_8859_9, 36 | /// ISO-8859-10 37 | Iso_8859_10, 38 | /// Shift_JIS 39 | Shift_Jis, 40 | /// EUC-JP 41 | Euc_Jp, 42 | /// ISO-2022-KR 43 | Iso_2022_Kr, 44 | /// EUC-KR 45 | Euc_Kr, 46 | /// ISO-2022-JP 47 | Iso_2022_Jp, 48 | /// ISO-2022-JP-2 49 | Iso_2022_Jp_2, 50 | /// ISO-8859-6-E 51 | Iso_8859_6_E, 52 | /// ISO-8859-6-I 53 | Iso_8859_6_I, 54 | /// ISO-8859-8-E 55 | Iso_8859_8_E, 56 | /// ISO-8859-8-I 57 | Iso_8859_8_I, 58 | /// GB2312 59 | Gb2312, 60 | /// Big5 61 | Big5, 62 | /// KOI8-R 63 | Koi8_R, 64 | /// An arbitrary charset specified as a string 65 | Ext(String) 66 | } 67 | 68 | impl Charset { 69 | fn name(&self) -> &str { 70 | match *self { 71 | Us_Ascii => "US-ASCII", 72 | Iso_8859_1 => "ISO-8859-1", 73 | Iso_8859_2 => "ISO-8859-2", 74 | Iso_8859_3 => "ISO-8859-3", 75 | Iso_8859_4 => "ISO-8859-4", 76 | Iso_8859_5 => "ISO-8859-5", 77 | Iso_8859_6 => "ISO-8859-6", 78 | Iso_8859_7 => "ISO-8859-7", 79 | Iso_8859_8 => "ISO-8859-8", 80 | Iso_8859_9 => "ISO-8859-9", 81 | Iso_8859_10 => "ISO-8859-10", 82 | Shift_Jis => "Shift-JIS", 83 | Euc_Jp => "EUC-JP", 84 | Iso_2022_Kr => "ISO-2022-KR", 85 | Euc_Kr => "EUC-KR", 86 | Iso_2022_Jp => "ISO-2022-JP", 87 | Iso_2022_Jp_2 => "ISO-2022-JP-2", 88 | Iso_8859_6_E => "ISO-8859-6-E", 89 | Iso_8859_6_I => "ISO-8859-6-I", 90 | Iso_8859_8_E => "ISO-8859-8-E", 91 | Iso_8859_8_I => "ISO-8859-8-I", 92 | Gb2312 => "GB2312", 93 | Big5 => "5", 94 | Koi8_R => "KOI8-R", 95 | Ext(ref s) => s 96 | } 97 | } 98 | } 99 | 100 | impl Display for Charset { 101 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 102 | f.write_str(self.name()) 103 | } 104 | } 105 | 106 | impl FromStr for Charset { 107 | type Err = ::Error; 108 | fn from_str(s: &str) -> ::Result { 109 | Ok(match s.to_ascii_uppercase().as_ref() { 110 | "US-ASCII" => Us_Ascii, 111 | "ISO-8859-1" => Iso_8859_1, 112 | "ISO-8859-2" => Iso_8859_2, 113 | "ISO-8859-3" => Iso_8859_3, 114 | "ISO-8859-4" => Iso_8859_4, 115 | "ISO-8859-5" => Iso_8859_5, 116 | "ISO-8859-6" => Iso_8859_6, 117 | "ISO-8859-7" => Iso_8859_7, 118 | "ISO-8859-8" => Iso_8859_8, 119 | "ISO-8859-9" => Iso_8859_9, 120 | "ISO-8859-10" => Iso_8859_10, 121 | "SHIFT-JIS" => Shift_Jis, 122 | "EUC-JP" => Euc_Jp, 123 | "ISO-2022-KR" => Iso_2022_Kr, 124 | "EUC-KR" => Euc_Kr, 125 | "ISO-2022-JP" => Iso_2022_Jp, 126 | "ISO-2022-JP-2" => Iso_2022_Jp_2, 127 | "ISO-8859-6-E" => Iso_8859_6_E, 128 | "ISO-8859-6-I" => Iso_8859_6_I, 129 | "ISO-8859-8-E" => Iso_8859_8_E, 130 | "ISO-8859-8-I" => Iso_8859_8_I, 131 | "GB2312" => Gb2312, 132 | "5" => Big5, 133 | "KOI8-R" => Koi8_R, 134 | s => Ext(s.to_owned()) 135 | }) 136 | } 137 | } 138 | 139 | #[test] 140 | fn test_parse() { 141 | assert_eq!(Us_Ascii,"us-ascii".parse().unwrap()); 142 | assert_eq!(Us_Ascii,"US-Ascii".parse().unwrap()); 143 | assert_eq!(Us_Ascii,"US-ASCII".parse().unwrap()); 144 | assert_eq!(Shift_Jis,"Shift-JIS".parse().unwrap()); 145 | assert_eq!(Ext("ABCD".to_owned()),"abcd".parse().unwrap()); 146 | } 147 | 148 | #[test] 149 | fn test_display() { 150 | assert_eq!("US-ASCII", format!("{}", Us_Ascii)); 151 | assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); 152 | } 153 | -------------------------------------------------------------------------------- /src/header/common/set_cookie.rs: -------------------------------------------------------------------------------- 1 | use header::{Header, RawLike}; 2 | use std::fmt; 3 | use std::str::from_utf8; 4 | 5 | /// `Set-Cookie` header, defined [RFC6265](http://tools.ietf.org/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 | /// `SetCookie` _must not_ be encoded as a comma-delimited list. For this 15 | /// reason, it doesn't implement `fmt::Display` (and `std::string::ToString`). 16 | /// 17 | /// Consider using the _cookie_ crate for parsing/decoding or encoding 18 | /// cookie values. [Usage example]. 19 | /// 20 | /// [Usage example]: https://github.com/dekellum/hyperx/issues/30#issuecomment-733151305 21 | /// 22 | /// # ABNF 23 | /// 24 | /// ```text 25 | /// set-cookie-header = "Set-Cookie:" SP set-cookie-string 26 | /// set-cookie-string = cookie-pair *( ";" SP cookie-av ) 27 | /// cookie-pair = cookie-name "=" cookie-value 28 | /// cookie-name = token 29 | /// cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) 30 | /// cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 31 | /// ; US-ASCII characters excluding CTLs, 32 | /// ; whitespace DQUOTE, comma, semicolon, 33 | /// ; and backslash 34 | /// token = 35 | /// 36 | /// cookie-av = expires-av / max-age-av / domain-av / 37 | /// path-av / secure-av / httponly-av / 38 | /// extension-av 39 | /// expires-av = "Expires=" sane-cookie-date 40 | /// sane-cookie-date = 41 | /// max-age-av = "Max-Age=" non-zero-digit *DIGIT 42 | /// ; In practice, both expires-av and max-age-av 43 | /// ; are limited to dates representable by the 44 | /// ; user agent. 45 | /// non-zero-digit = %x31-39 46 | /// ; digits 1 through 9 47 | /// domain-av = "Domain=" domain-value 48 | /// domain-value = 49 | /// ; defined in [RFC1034], Section 3.5, as 50 | /// ; enhanced by [RFC1123], Section 2.1 51 | /// path-av = "Path=" path-value 52 | /// path-value = 53 | /// secure-av = "Secure" 54 | /// httponly-av = "HttpOnly" 55 | /// extension-av = 56 | /// ``` 57 | /// 58 | /// # Example values 59 | /// 60 | /// * `SID=31d4d96e407aad42` 61 | /// * `lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT` 62 | /// * `lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT` 63 | /// * `lang=en-US; Path=/; Domain=example.com` 64 | /// 65 | /// # Example 66 | /// 67 | /// ``` 68 | /// # extern crate http; 69 | /// use hyperx::header::{SetCookie, TypedHeaders}; 70 | /// 71 | /// let mut headers = http::HeaderMap::new(); 72 | /// 73 | /// headers.append( 74 | /// http::header::SET_COOKIE, 75 | /// "foo=bar; Path=/path; Domain=example.com".parse().unwrap() 76 | /// ); 77 | /// let cookie: SetCookie = headers.decode().unwrap(); 78 | /// ``` 79 | #[derive(Clone, PartialEq, Debug)] 80 | pub struct SetCookie(pub Vec); 81 | 82 | __hyper__deref!(SetCookie => Vec); 83 | 84 | impl Header for SetCookie { 85 | fn header_name() -> &'static str { 86 | static NAME: &'static str = "Set-Cookie"; 87 | NAME 88 | } 89 | 90 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 91 | where T: RawLike<'a> 92 | { 93 | let mut set_cookies = Vec::with_capacity(raw.len()); 94 | for set_cookies_raw in raw.iter() { 95 | if let Ok(s) = from_utf8(&set_cookies_raw[..]) { 96 | set_cookies.push(s.trim().to_owned()); 97 | } 98 | } 99 | 100 | if !set_cookies.is_empty() { 101 | Ok(SetCookie(set_cookies)) 102 | } else { 103 | Err(::Error::Header) 104 | } 105 | } 106 | 107 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 108 | for cookie in &self.0 { 109 | f.fmt_line(cookie)?; 110 | } 111 | Ok(()) 112 | } 113 | } 114 | 115 | #[cfg(feature = "headers")] 116 | #[test] 117 | fn test_set_cookie_fmt() { 118 | use ::header::Headers; 119 | let mut headers = Headers::new(); 120 | headers.set(SetCookie(vec![ 121 | "foo=bar".into(), 122 | "baz=quux".into(), 123 | ])); 124 | assert_eq!(headers.to_string(), "Set-Cookie: foo=bar\r\nSet-Cookie: baz=quux\r\n"); 125 | } 126 | 127 | standard_header!(SetCookie, SET_COOKIE); 128 | -------------------------------------------------------------------------------- /src/header/common/accept.rs: -------------------------------------------------------------------------------- 1 | use mime::{self, Mime}; 2 | 3 | use header::{QualityItem, qitem}; 4 | 5 | header! { 6 | /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/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 http; 34 | /// use hyperx::header::{Accept, qitem, TypedHeaders}; 35 | /// use hyperx::mime; 36 | /// 37 | /// let mut headers = http::HeaderMap::new(); 38 | /// 39 | /// headers.encode( 40 | /// &Accept(vec![ 41 | /// qitem(mime::TEXT_HTML), 42 | /// ]) 43 | /// ); 44 | /// ``` 45 | /// 46 | /// ``` 47 | /// # extern crate http; 48 | /// use hyperx::header::{Accept, qitem, TypedHeaders}; 49 | /// use hyperx::mime; 50 | /// 51 | /// let mut headers = http::HeaderMap::new(); 52 | /// headers.encode( 53 | /// &Accept(vec![ 54 | /// qitem(mime::APPLICATION_JSON), 55 | /// ]) 56 | /// ); 57 | /// ``` 58 | /// 59 | /// ``` 60 | /// # extern crate http; 61 | /// use hyperx::header::{Accept, QualityItem, q, qitem, TypedHeaders}; 62 | /// use hyperx::mime; 63 | /// 64 | /// let mut headers = http::HeaderMap::new(); 65 | /// 66 | /// headers.encode( 67 | /// &Accept(vec![ 68 | /// qitem(mime::TEXT_HTML), 69 | /// qitem("application/xhtml+xml".parse().unwrap()), 70 | /// QualityItem::new( 71 | /// mime::TEXT_XML, 72 | /// q(900) 73 | /// ), 74 | /// qitem("image/webp".parse().unwrap()), 75 | /// QualityItem::new( 76 | /// mime::STAR_STAR, 77 | /// q(800) 78 | /// ), 79 | /// ]) 80 | /// ); 81 | /// ``` 82 | (Accept, "Accept") => (QualityItem)+ 83 | 84 | test_accept { 85 | // Tests from the RFC 86 | test_header!( 87 | test1, 88 | vec![b"audio/*; q=0.2, audio/basic"], 89 | Some(HeaderField(vec![ 90 | QualityItem::new("audio/*".parse().unwrap(), q(200)), 91 | qitem("audio/basic".parse().unwrap()), 92 | ]))); 93 | test_header!( 94 | test2, 95 | vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], 96 | Some(HeaderField(vec![ 97 | QualityItem::new(TEXT_PLAIN, q(500)), 98 | qitem(TEXT_HTML), 99 | QualityItem::new( 100 | "text/x-dvi".parse().unwrap(), 101 | q(800)), 102 | qitem("text/x-c".parse().unwrap()), 103 | ]))); 104 | // Custom tests 105 | test_header!( 106 | test3, 107 | vec![b"text/plain; charset=utf-8"], 108 | Some(Accept(vec![ 109 | qitem(TEXT_PLAIN_UTF_8), 110 | ]))); 111 | test_header!( 112 | test4, 113 | vec![b"text/plain; charset=utf-8; q=0.5"], 114 | Some(Accept(vec![ 115 | QualityItem::new(TEXT_PLAIN_UTF_8, 116 | q(500)), 117 | ]))); 118 | 119 | #[test] 120 | fn test_fuzzing1() { 121 | let raw: Raw = "chunk#;e".into(); 122 | let header = Accept::parse_header(&raw); 123 | assert!(header.is_ok()); 124 | } 125 | } 126 | } 127 | 128 | impl Accept { 129 | /// A constructor to easily create `Accept: */*`. 130 | pub fn star() -> Accept { 131 | Accept(vec![qitem(mime::STAR_STAR)]) 132 | } 133 | 134 | /// A constructor to easily create `Accept: application/json`. 135 | pub fn json() -> Accept { 136 | Accept(vec![qitem(mime::APPLICATION_JSON)]) 137 | } 138 | 139 | /// A constructor to easily create `Accept: text/*`. 140 | pub fn text() -> Accept { 141 | Accept(vec![qitem(mime::TEXT_STAR)]) 142 | } 143 | 144 | /// A constructor to easily create `Accept: image/*`. 145 | pub fn image() -> Accept { 146 | Accept(vec![qitem(mime::IMAGE_STAR)]) 147 | } 148 | } 149 | 150 | bench_header!(bench, Accept, { vec![b"text/plain; q=0.5, text/html".to_vec()] }); 151 | 152 | standard_header!(Accept, ACCEPT); 153 | -------------------------------------------------------------------------------- /src/header/common/connection.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str::FromStr; 3 | use unicase::Ascii; 4 | 5 | pub use self::ConnectionOption::{KeepAlive, Close, ConnectionHeader}; 6 | 7 | static KEEP_ALIVE: &'static str = "keep-alive"; 8 | static CLOSE: &'static str = "close"; 9 | 10 | /// Values that can be in the `Connection` header. 11 | #[derive(Clone, PartialEq, Debug)] 12 | pub enum ConnectionOption { 13 | /// The `keep-alive` connection value. 14 | KeepAlive, 15 | /// The `close` connection value. 16 | Close, 17 | /// Values in the Connection header that are supposed to be names of other Headers. 18 | /// 19 | /// > When a header field aside from Connection is used to supply control 20 | /// > information for or about the current connection, the sender MUST list 21 | /// > the corresponding field-name within the Connection header field. 22 | // TODO: it would be nice if these "Strings" could be stronger types, since 23 | // they are supposed to relate to other Header fields (which we have strong 24 | // types for). 25 | ConnectionHeader(Ascii), 26 | } 27 | 28 | impl FromStr for ConnectionOption { 29 | type Err = (); 30 | fn from_str(s: &str) -> Result { 31 | if Ascii::new(s) == KEEP_ALIVE { 32 | Ok(KeepAlive) 33 | } else if Ascii::new(s) == CLOSE { 34 | Ok(Close) 35 | } else { 36 | Ok(ConnectionHeader(Ascii::new(s.to_owned()))) 37 | } 38 | } 39 | } 40 | 41 | impl Display for ConnectionOption { 42 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 | f.write_str(match *self { 44 | KeepAlive => "keep-alive", 45 | Close => "close", 46 | ConnectionHeader(ref s) => s.as_ref() 47 | }) 48 | } 49 | } 50 | 51 | header! { 52 | /// `Connection` header, defined in 53 | /// [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.1) 54 | /// 55 | /// The `Connection` header field allows the sender to indicate desired 56 | /// control options for the current connection. In order to avoid 57 | /// confusing downstream recipients, a proxy or gateway MUST remove or 58 | /// replace any received connection options before forwarding the 59 | /// message. 60 | /// 61 | /// # ABNF 62 | /// 63 | /// ```text 64 | /// Connection = 1#connection-option 65 | /// connection-option = token 66 | /// 67 | /// # Example values 68 | /// * `close` 69 | /// * `keep-alive` 70 | /// * `upgrade` 71 | /// ``` 72 | /// 73 | /// # Examples 74 | /// 75 | /// ``` 76 | /// # extern crate http; 77 | /// use hyperx::header::{Connection, TypedHeaders}; 78 | /// 79 | /// let mut headers = http::HeaderMap::new(); 80 | /// headers.encode(&Connection::keep_alive()); 81 | /// ``` 82 | /// 83 | /// ``` 84 | /// # extern crate http; 85 | /// # extern crate hyperx; 86 | /// # extern crate unicase; 87 | /// # fn main() { 88 | /// // extern crate unicase; 89 | /// 90 | /// use hyperx::header::{Connection, ConnectionOption, TypedHeaders}; 91 | /// use unicase::Ascii; 92 | /// 93 | /// let mut headers = http::HeaderMap::new(); 94 | /// headers.encode( 95 | /// &Connection(vec![ 96 | /// ConnectionOption::ConnectionHeader(Ascii::new("upgrade".to_owned())), 97 | /// ]) 98 | /// ); 99 | /// # } 100 | /// ``` 101 | (Connection, "Connection") => (ConnectionOption)+ 102 | 103 | test_connection { 104 | test_header!(test1, vec![b"close"]); 105 | test_header!(test2, vec![b"keep-alive"]); 106 | test_header!(test3, vec![b"upgrade"]); 107 | } 108 | } 109 | 110 | impl Connection { 111 | /// A constructor to easily create a `Connection: close` header. 112 | #[inline] 113 | pub fn close() -> Connection { 114 | Connection(vec![ConnectionOption::Close]) 115 | } 116 | 117 | /// A constructor to easily create a `Connection: keep-alive` header. 118 | #[inline] 119 | pub fn keep_alive() -> Connection { 120 | Connection(vec![ConnectionOption::KeepAlive]) 121 | } 122 | } 123 | 124 | bench_header!(close, Connection, { vec![b"close".to_vec()] }); 125 | bench_header!(keep_alive, Connection, { vec![b"keep-alive".to_vec()] }); 126 | bench_header!(header, Connection, { vec![b"authorization".to_vec()] }); 127 | 128 | #[cfg(test)] 129 | mod tests { 130 | use super::{Connection,ConnectionHeader}; 131 | use header::{Header, Raw}; 132 | use unicase::Ascii; 133 | 134 | fn parse_option(header: Vec) -> Connection { 135 | let val: Raw = header.into(); 136 | let connection: Connection = Header::parse_header(&val).unwrap(); 137 | connection 138 | } 139 | 140 | #[test] 141 | fn test_parse() { 142 | assert_eq!(Connection::close(),parse_option(b"close".to_vec())); 143 | assert_eq!(Connection::keep_alive(),parse_option(b"keep-alive".to_vec())); 144 | assert_eq!(Connection::keep_alive(),parse_option(b"Keep-Alive".to_vec())); 145 | assert_eq!(Connection(vec![ConnectionHeader(Ascii::new("upgrade".to_owned()))]), 146 | parse_option(b"upgrade".to_vec())); 147 | } 148 | } 149 | 150 | standard_header!(Connection, CONNECTION); 151 | -------------------------------------------------------------------------------- /src/header/common/origin.rs: -------------------------------------------------------------------------------- 1 | use header::{Header, RawLike, Host}; 2 | use std::borrow::Cow; 3 | use std::fmt; 4 | use std::str::FromStr; 5 | use header::parsing::from_one_raw_str; 6 | 7 | /// The `Origin` header. 8 | /// 9 | /// 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. 10 | /// This header is often used to inform recipients of the security context of where the request was initiated. 11 | /// 12 | /// Following the spec, [https://fetch.spec.whatwg.org/#origin-header][url], the value of this header is composed of 13 | /// a String (scheme), header::Host (host/port) 14 | /// 15 | /// [url]: https://fetch.spec.whatwg.org/#origin-header 16 | /// 17 | /// # Examples 18 | /// 19 | /// ``` 20 | /// # extern crate http; 21 | /// use hyperx::header::{Origin, TypedHeaders}; 22 | /// 23 | /// let mut headers = http::HeaderMap::new(); 24 | /// headers.encode( 25 | /// &Origin::new("http", "hyper.rs", None) 26 | /// ); 27 | /// ``` 28 | /// 29 | /// ``` 30 | /// # extern crate http; 31 | /// use hyperx::header::{Origin, TypedHeaders}; 32 | /// 33 | /// let mut headers = http::HeaderMap::new(); 34 | /// headers.encode( 35 | /// &Origin::new("https", "wikipedia.org", Some(443)) 36 | /// ); 37 | /// ``` 38 | #[derive(PartialEq, Clone, Debug)] 39 | pub struct Origin(OriginOrNull); 40 | 41 | #[derive(PartialEq, Clone, Debug)] 42 | enum OriginOrNull { 43 | Origin { 44 | /// The scheme, such as http or https 45 | scheme: Cow<'static,str>, 46 | /// The host, such as Host{hostname: "hyper.rs".to_owned(), port: None} 47 | host: Host, 48 | }, 49 | Null, 50 | } 51 | 52 | impl Origin { 53 | /// Creates a new `Origin` header. 54 | pub fn new>, H: Into>>(scheme: S, hostname: H, port: Option) -> Origin{ 55 | Origin(OriginOrNull::Origin { 56 | scheme: scheme.into(), 57 | host: Host::new(hostname, port), 58 | }) 59 | } 60 | 61 | /// Creates a `Null` `Origin` header. 62 | pub fn null() -> Origin { 63 | Origin(OriginOrNull::Null) 64 | } 65 | 66 | /// Checks if `Origin` is `Null`. 67 | pub fn is_null(&self) -> bool { 68 | match self { 69 | &Origin(OriginOrNull::Null) => true, 70 | _ => false, 71 | } 72 | } 73 | 74 | /// The scheme, such as http or https. 75 | /// 76 | /// ``` 77 | /// use hyperx::header::Origin; 78 | /// let origin = Origin::new("https", "foo.com", Some(443)); 79 | /// assert_eq!(origin.scheme(), Some("https")); 80 | /// ``` 81 | pub fn scheme(&self) -> Option<&str> { 82 | match self { 83 | &Origin(OriginOrNull::Origin { ref scheme, .. }) => Some(&scheme), 84 | _ => None, 85 | } 86 | } 87 | 88 | /// The host, such as `Host { hostname: "hyper.rs".to_owned(), port: None}`. 89 | /// 90 | /// ``` 91 | /// use hyperx::header::{Origin,Host}; 92 | /// let origin = Origin::new("https", "foo.com", Some(443)); 93 | /// assert_eq!(origin.host(), Some(&Host::new("foo.com", Some(443)))); 94 | /// ``` 95 | pub fn host(&self) -> Option<&Host> { 96 | match self { 97 | &Origin(OriginOrNull::Origin { ref host, .. }) => Some(&host), 98 | _ => None, 99 | } 100 | } 101 | } 102 | 103 | impl Header for Origin { 104 | fn header_name() -> &'static str { 105 | static NAME: &'static str = "Origin"; 106 | NAME 107 | } 108 | 109 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 110 | where T: RawLike<'a> 111 | { 112 | from_one_raw_str(raw) 113 | } 114 | 115 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 116 | f.fmt_line(self) 117 | } 118 | } 119 | 120 | static HTTP : &'static str = "http"; 121 | static HTTPS : &'static str = "https"; 122 | 123 | impl FromStr for Origin { 124 | type Err = ::Error; 125 | 126 | fn from_str(s: &str) -> ::Result { 127 | let idx = match s.find("://") { 128 | Some(idx) => idx, 129 | None => return Err(::Error::Header) 130 | }; 131 | // idx + 3 because that's how long "://" is 132 | let (scheme, etc) = (&s[..idx], &s[idx + 3..]); 133 | let host = Host::from_str(etc)?; 134 | let scheme = match scheme { 135 | "http" => Cow::Borrowed(HTTP), 136 | "https" => Cow::Borrowed(HTTPS), 137 | s => Cow::Owned(s.to_owned()) 138 | }; 139 | 140 | Ok(Origin(OriginOrNull::Origin { 141 | scheme: scheme, 142 | host: host 143 | })) 144 | } 145 | } 146 | 147 | impl fmt::Display for Origin { 148 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 149 | match self.0 { 150 | OriginOrNull::Origin { ref scheme, ref host } => write!(f, "{}://{}", scheme, host), 151 | // Serialized as "null" per ASCII serialization of an origin 152 | // https://html.spec.whatwg.org/multipage/browsers.html#ascii-serialisation-of-an-origin 153 | OriginOrNull::Null => f.write_str("null") 154 | } 155 | } 156 | } 157 | 158 | #[cfg(test)] 159 | mod tests { 160 | use super::Origin; 161 | use header::{Header, Raw}; 162 | use std::borrow::Cow; 163 | 164 | macro_rules! assert_borrowed{ 165 | ($expr : expr) => { 166 | match $expr { 167 | Cow::Owned(ref v) => panic!("assertion failed: `{}` owns {:?}", stringify!($expr), v), 168 | _ => {} 169 | } 170 | } 171 | } 172 | 173 | #[test] 174 | fn test_origin() { 175 | let r: Raw = vec![b"http://foo.com".to_vec()].into(); 176 | let origin : Origin = Header::parse_header(&r).unwrap(); 177 | assert_eq!(&origin, &Origin::new("http", "foo.com", None)); 178 | assert_borrowed!(origin.scheme().unwrap().into()); 179 | 180 | let r: Raw = vec![b"https://foo.com:443".to_vec()].into(); 181 | let origin : Origin = Header::parse_header(&r).unwrap(); 182 | assert_eq!(&origin, &Origin::new("https", "foo.com", Some(443))); 183 | assert_borrowed!(origin.scheme().unwrap().into()); 184 | } 185 | } 186 | 187 | bench_header!(bench, Origin, { vec![b"https://foo.com".to_vec()] }); 188 | 189 | standard_header!(Origin, ORIGIN); 190 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.4.0 (2021-10-29) 2 | 3 | * Minimum supported rust version (MSRV) is now 1.46.0 (_http_ 0.2.5 MSRV is 4 | also 1.46.0). 5 | 6 | * Upgrade to language-tags 0.3.1. (#35) 7 | 8 | * Remove _httparse_ dependency and conversion from `httparse::Error` to 9 | `hyperx::Error`. In the presumed unlikely event that this conversion is used 10 | externally, this is a potential breaking change, risked in a minor 11 | release. (#32) 12 | 13 | * Broaden _httpdate_ dependency to include the 1.0 major release. 14 | 15 | * Add docs to cookie headers recommending use of the _cookie_ crate. (#30) 16 | 17 | ## 1.3.1 (2021-10-28) 18 | 19 | * Broaden _bytes_ dependency to include 1.1. 20 | 21 | * Broaden _httparse_ dependency to include 1.4. 22 | 23 | * Clarify MSRV policy in README.md. 24 | 25 | ## 1.3.0 (2021-1-8) 26 | 27 | * Remove _log_ dependency, which was only used sparsely as a poor workaround 28 | for unspecific `Error::Header` errors. ([#25]) 29 | 30 | * Upgrade to _bytes_ 1.0.0 (paolobarbolini [#31]) 31 | 32 | * Lift prior constraint on _http_ dependency (optimistically hoping for no 33 | further 0.2.z anomalous releases). 34 | 35 | [#25]: https://github.com/dekellum/hyperx/pull/25 36 | [#31]: https://github.com/dekellum/hyperx/pull/31 37 | 38 | ## 1.2.1 (2021-1-7) 39 | 40 | * Constrain _http_ dependency to <0.2.3 for remaining 1.2 series due to dubious 41 | release practices, e.g. forcing duplicates in patch releases. This will be 42 | lifted in 1.3.0, see this [github comment][461]. 43 | 44 | [461]: https://github.com/hyperium/http/pull/461#issuecomment-756298944 45 | 46 | ## 1.2.0 (2020-10-6) 47 | 48 | * Replace use of time crate with httpdate crate for date/time typed 49 | headers (paolobarbolini #24) 50 | 51 | * Broaden _base64_ dependency to include 0.13.0 (dvermd #23) 52 | 53 | ## 1.1.0 (2020-8-29) 54 | 55 | * Fix various compile warnings with rustc 1.44 and later. 56 | 57 | * Broaden _base64_ dependency to include 0.12.0 (paolobarbolini #20) 58 | 59 | ## 1.0.0 (2020-1-3) 60 | 61 | * The default _compat_ feature is no longer optional, as its unlikely that 62 | anyone would be using recent versions without this feature. The feature gate 63 | name is retained for now, but has no effect. 64 | 65 | * Place the legacy `Headers` struct under a new non-default _headers_ feature 66 | gate. Note that use of this type is no longer required nor recommended for 67 | parsing and serialization of typed headers. See the rewritten typed header 68 | doc examples. Consider replacing with the _http_ crate `HeaderMap` and the 69 | `TypedHeaders` extension trait introduced here in 0.15.0. (#18) 70 | 71 | * Upgrade to _http_ 0.2.0 (\w API changes) and _bytes_ 0.5.2 (MSRV 1.39.0) 72 | 73 | * Upgrade (unconstrain) _cfg-if_ dependency to 0.1.10 (MSRV 1.31.0) 74 | 75 | * Upgrade to _unicase_ 2.6.0 76 | 77 | * Upgrade to _percent-encoding_ 2.1.0 (\w API changes, MSRV 1.33.0) (#15) 78 | 79 | * Upgrade to _time_ 0.1.39 to avoid minimal version build failure 80 | 81 | * Broaden _base64_ dependency to include 0.11.0 (MSRV 1.34.0) 82 | 83 | * MSRV is now 1.39.0, based on above upgrades. 84 | 85 | ## 0.15.2 (2019-10-1) 86 | 87 | * Constrain transitive _cfg-if_ dependency to <0.1.10 to preserve MSRV 1.27.2. 88 | 89 | * Narrow various other dependencies for future reliability. We may 90 | subsequently make PATCH releases which _broaden_ private or public 91 | dependencies to include new releases found compatible. 92 | 93 | ## 0.15.1 (2019-6-3) 94 | 95 | * Fix build.rs for `rustc --version` not including git metadata (alyssais #14) 96 | 97 | ## 0.15.0 (2019-5-8) 98 | 99 | * Add a `TypedHeaders` extension trait providing more convenient generic 100 | encode/decode methods to `http::HeaderMap` for _hyperx_ typed headers, 101 | implemented using a new `StandardHeader` trait and `standard_header!` macro, 102 | with an associate function for the `HeaderName` constants of the _http_ 103 | crate. (#13) 104 | 105 | * Add reference based `impl From<&'a Headers> for http::HeaderMap` for symmetry 106 | and performance, e.g. avoiding a `clone`. (#13) 107 | 108 | * Increase MSRV to 1.27.2, which enables us to revert a CI workaround for the 109 | fact that base64 0.10.1 was released with this same MSRV. (#10 #12) 110 | 111 | * Add a build.rs to check MSRV and fail fast with a clear error when older 112 | rustc versions are used. (#12) 113 | 114 | ## 0.14.0 (2019-1-4) 115 | 116 | * Update the signature of `Header::parse_header` to be generic over types 117 | implementing a new `RawLike` trait, which includes the existing local `Raw` 118 | type as well as _http_ crate types `HeaderValue` and (`HeaderMap::get_all`) 119 | `GetAll`. This avoids an allocation when directly parsing from these later 120 | types. 121 | 122 | _Expected Breakage_: Any 3rd-party custom headers directly implementing 123 | `parse_header` will need to change accordingly on upgrade. Also `Into` 124 | conversions to `Raw` now frequently need to be type annotated. (#8) 125 | 126 | * Improve header module rustdoc, including with parsing usage for the above. 127 | 128 | ## 0.13.2 (2019-1-2) 129 | 130 | * Remove un-exported, and unused as of 0.13.1, `uri` module and related code. 131 | 132 | * Broaden base64 dependency to include 0.10.0, passing tests. 133 | 134 | * Silence a deprecation warning for `str::trim_right_matches` until the minimum 135 | rust version is updated to 1.30.0. 136 | 137 | ## 0.13.1 (2018-6-26) 138 | 139 | * Remove `error::UriError` re-export and `error::Canceled` which are unused 140 | internally and where not exported from this crate. (#5) 141 | 142 | ## 0.13.0 (2018-6-18) 143 | 144 | * Remove variants from `hyperx::Error` which are unused by the header 145 | module. Exhaustive matching has been discouraged for this enum, but this is 146 | still a potential breaking change. (dekellum #2) 147 | 148 | * Add an alternative, by reference `From<&http::HeaderMap>` for `Headers`. 149 | (DarrenTsung #3) 150 | 151 | ## 0.12.0 (2018-6-1) 152 | 153 | Forked from hyper 0.11.27, e*x*tracting the typed *header* module 154 | from [hyperium/hyper@76fdbcf2], 0.11.x branch, preserved here as 155 | [76fdbcf2]. 156 | 157 | ## Prior Releases 158 | 159 | See [hyper's CHANGELOG] for prior updates pertaining to the headers 160 | sub-module. 161 | 162 | [hyper's CHANGELOG]: https://github.com/hyperium/hyper/blob/0.11.x/CHANGELOG.md 163 | [hyperium/hyper@76fdbcf2]: https://github.com/hyperium/hyper/commit/76fdbcf2 164 | [76fdbcf2]: https://github.com/dekellum/hyperx/commit/76fdbcf23cd35cebb03bf4c0e3025b671578bd75 165 | -------------------------------------------------------------------------------- /src/header/common/upgrade.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str::FromStr; 3 | use unicase; 4 | 5 | header! { 6 | /// `Upgrade` header, defined in [RFC7230](http://tools.ietf.org/html/rfc7230#section-6.7) 7 | /// 8 | /// The `Upgrade` header field is intended to provide a simple mechanism 9 | /// for transitioning from HTTP/1.1 to some other protocol on the same 10 | /// connection. A client MAY send a list of protocols in the Upgrade 11 | /// header field of a request to invite the server to switch to one or 12 | /// more of those protocols, in order of descending preference, before 13 | /// sending the final response. A server MAY ignore a received Upgrade 14 | /// header field if it wishes to continue using the current protocol on 15 | /// that connection. Upgrade cannot be used to insist on a protocol 16 | /// change. 17 | /// 18 | /// # ABNF 19 | /// 20 | /// ```text 21 | /// Upgrade = 1#protocol 22 | /// 23 | /// protocol = protocol-name ["/" protocol-version] 24 | /// protocol-name = token 25 | /// protocol-version = token 26 | /// ``` 27 | /// 28 | /// # Example values 29 | /// 30 | /// * `HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11` 31 | /// 32 | /// # Examples 33 | /// 34 | /// ``` 35 | /// # extern crate http; 36 | /// use hyperx::header::{Upgrade, Protocol, ProtocolName, TypedHeaders}; 37 | /// 38 | /// let mut headers = http::HeaderMap::new(); 39 | /// headers.encode(&Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)])); 40 | /// ``` 41 | /// 42 | /// ``` 43 | /// # extern crate http; 44 | /// use hyperx::header::{Upgrade, Protocol, ProtocolName, TypedHeaders}; 45 | /// 46 | /// let mut headers = http::HeaderMap::new(); 47 | /// headers.encode( 48 | /// &Upgrade(vec![ 49 | /// Protocol::new(ProtocolName::Http, Some("2.0".to_owned())), 50 | /// Protocol::new(ProtocolName::Unregistered("SHTTP".to_owned()), 51 | /// Some("1.3".to_owned())), 52 | /// Protocol::new(ProtocolName::Unregistered("IRC".to_owned()), 53 | /// Some("6.9".to_owned())), 54 | /// ]) 55 | /// ); 56 | /// ``` 57 | (Upgrade, "Upgrade") => (Protocol)+ 58 | 59 | test_upgrade { 60 | // Testcase from the RFC 61 | test_header!( 62 | test1, 63 | vec![b"HTTP/2.0, SHTTP/1.3, IRC/6.9, RTA/x11"], 64 | Some(Upgrade(vec![ 65 | Protocol::new(ProtocolName::Http, Some("2.0".to_owned())), 66 | Protocol::new(ProtocolName::Unregistered("SHTTP".to_owned()), 67 | Some("1.3".to_owned())), 68 | Protocol::new(ProtocolName::Unregistered("IRC".to_owned()), Some("6.9".to_owned())), 69 | Protocol::new(ProtocolName::Unregistered("RTA".to_owned()), Some("x11".to_owned())), 70 | ]))); 71 | // Own tests 72 | test_header!( 73 | test2, vec![b"websocket"], 74 | Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)]))); 75 | #[test] 76 | fn test3() { 77 | let r: Raw = "WEbSOCKet".into(); 78 | let x: ::Result = Header::parse_header(&r); 79 | assert_eq!(x.ok(), Some(Upgrade(vec![Protocol::new(ProtocolName::WebSocket, None)]))); 80 | } 81 | } 82 | } 83 | 84 | /// A protocol name used to identify a specific protocol. Names are case-sensitive 85 | /// except for the `WebSocket` value. 86 | #[derive(Clone, Debug, Eq, PartialEq)] 87 | pub enum ProtocolName { 88 | /// `HTTP` value, Hypertext Transfer Protocol 89 | Http, 90 | /// `TLS` value, Transport Layer Security [RFC2817](http://tools.ietf.org/html/rfc2817) 91 | Tls, 92 | /// `WebSocket` value, matched case insensitively,Web Socket Protocol 93 | /// [RFC6455](http://tools.ietf.org/html/rfc6455) 94 | WebSocket, 95 | /// `h2c` value, HTTP/2 over cleartext TCP 96 | H2c, 97 | /// Any other protocol name not known to hyper 98 | Unregistered(String), 99 | } 100 | 101 | impl FromStr for ProtocolName { 102 | type Err = (); 103 | fn from_str(s: &str) -> Result { 104 | Ok(match s { 105 | "HTTP" => ProtocolName::Http, 106 | "TLS" => ProtocolName::Tls, 107 | "h2c" => ProtocolName::H2c, 108 | _ => { 109 | if unicase::eq_ascii(s, "websocket") { 110 | ProtocolName::WebSocket 111 | } else { 112 | ProtocolName::Unregistered(s.to_owned()) 113 | } 114 | } 115 | }) 116 | } 117 | } 118 | 119 | impl Display for ProtocolName { 120 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 121 | f.write_str(match *self { 122 | ProtocolName::Http => "HTTP", 123 | ProtocolName::Tls => "TLS", 124 | ProtocolName::WebSocket => "websocket", 125 | ProtocolName::H2c => "h2c", 126 | ProtocolName::Unregistered(ref s) => s, 127 | }) 128 | } 129 | } 130 | 131 | /// Protocols that appear in the `Upgrade` header field 132 | #[derive(Clone, Debug, Eq, PartialEq)] 133 | pub struct Protocol { 134 | /// The protocol identifier 135 | pub name: ProtocolName, 136 | /// The optional version of the protocol, often in the format "DIGIT.DIGIT" (e.g.. "1.2") 137 | pub version: Option, 138 | } 139 | 140 | impl Protocol { 141 | /// Creates a new Protocol with the given name and version 142 | pub fn new(name: ProtocolName, version: Option) -> Protocol { 143 | Protocol { name: name, version: version } 144 | } 145 | } 146 | 147 | impl FromStr for Protocol { 148 | type Err =(); 149 | fn from_str(s: &str) -> Result { 150 | let mut parts = s.splitn(2, '/'); 151 | Ok(Protocol::new( 152 | parts.next().unwrap().parse()?, 153 | parts.next().map(|x| x.to_owned()) 154 | )) 155 | } 156 | } 157 | 158 | impl Display for Protocol { 159 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 160 | fmt::Display::fmt(&self.name, f)?; 161 | if let Some(ref version) = self.version { 162 | write!(f, "/{}", version)?; 163 | } 164 | Ok(()) 165 | } 166 | } 167 | 168 | bench_header!(bench, Upgrade, { vec![b"HTTP/2.0, RTA/x11, websocket".to_vec()] }); 169 | 170 | standard_header!(Upgrade, UPGRADE); 171 | -------------------------------------------------------------------------------- /src/header/common/content_range.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::{self, Display}; 2 | use std::str::FromStr; 3 | 4 | header! { 5 | /// `Content-Range` header, defined in 6 | /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) 7 | (ContentRange, "Content-Range") => [ContentRangeSpec] 8 | 9 | test_content_range { 10 | test_header!(test_bytes, 11 | vec![b"bytes 0-499/500"], 12 | Some(ContentRange(ContentRangeSpec::Bytes { 13 | range: Some((0, 499)), 14 | instance_length: Some(500) 15 | }))); 16 | 17 | test_header!(test_bytes_unknown_len, 18 | vec![b"bytes 0-499/*"], 19 | Some(ContentRange(ContentRangeSpec::Bytes { 20 | range: Some((0, 499)), 21 | instance_length: None 22 | }))); 23 | 24 | test_header!(test_bytes_unknown_range, 25 | vec![b"bytes */500"], 26 | Some(ContentRange(ContentRangeSpec::Bytes { 27 | range: None, 28 | instance_length: Some(500) 29 | }))); 30 | 31 | test_header!(test_unregistered, 32 | vec![b"seconds 1-2"], 33 | Some(ContentRange(ContentRangeSpec::Unregistered { 34 | unit: "seconds".to_owned(), 35 | resp: "1-2".to_owned() 36 | }))); 37 | 38 | test_header!(test_no_len, 39 | vec![b"bytes 0-499"], 40 | None::); 41 | 42 | test_header!(test_only_unit, 43 | vec![b"bytes"], 44 | None::); 45 | 46 | test_header!(test_end_less_than_start, 47 | vec![b"bytes 499-0/500"], 48 | None::); 49 | 50 | test_header!(test_blank, 51 | vec![b""], 52 | None::); 53 | 54 | test_header!(test_bytes_many_spaces, 55 | vec![b"bytes 1-2/500 3"], 56 | None::); 57 | 58 | test_header!(test_bytes_many_slashes, 59 | vec![b"bytes 1-2/500/600"], 60 | None::); 61 | 62 | test_header!(test_bytes_many_dashes, 63 | vec![b"bytes 1-2-3/500"], 64 | None::); 65 | 66 | } 67 | } 68 | 69 | /// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) 70 | /// 71 | /// # ABNF 72 | /// 73 | /// ```text 74 | /// Content-Range = byte-content-range 75 | /// / other-content-range 76 | /// 77 | /// byte-content-range = bytes-unit SP 78 | /// ( byte-range-resp / unsatisfied-range ) 79 | /// 80 | /// byte-range-resp = byte-range "/" ( complete-length / "*" ) 81 | /// byte-range = first-byte-pos "-" last-byte-pos 82 | /// unsatisfied-range = "*/" complete-length 83 | /// 84 | /// complete-length = 1*DIGIT 85 | /// 86 | /// other-content-range = other-range-unit SP other-range-resp 87 | /// other-range-resp = *CHAR 88 | /// ``` 89 | #[derive(PartialEq, Clone, Debug)] 90 | pub enum ContentRangeSpec { 91 | /// Byte range 92 | Bytes { 93 | /// First and last bytes of the range, omitted if request could not be 94 | /// satisfied 95 | range: Option<(u64, u64)>, 96 | 97 | /// Total length of the instance, can be omitted if unknown 98 | instance_length: Option 99 | }, 100 | 101 | /// Custom range, with unit not registered at IANA 102 | Unregistered { 103 | /// other-range-unit 104 | unit: String, 105 | 106 | /// other-range-resp 107 | resp: String 108 | } 109 | } 110 | 111 | fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { 112 | let mut iter = s.splitn(2, separator); 113 | match (iter.next(), iter.next()) { 114 | (Some(a), Some(b)) => Some((a, b)), 115 | _ => None 116 | } 117 | } 118 | 119 | impl FromStr for ContentRangeSpec { 120 | type Err = ::Error; 121 | 122 | fn from_str(s: &str) -> ::Result { 123 | let res = match split_in_two(s, ' ') { 124 | Some(("bytes", resp)) => { 125 | let (range, instance_length) = split_in_two(resp, '/').ok_or(::Error::Header)?; 126 | 127 | let instance_length = if instance_length == "*" { 128 | None 129 | } else { 130 | Some(instance_length.parse().map_err(|_| ::Error::Header)?) 131 | }; 132 | 133 | let range = if range == "*" { 134 | None 135 | } else { 136 | let (first_byte, last_byte) = split_in_two(range, '-').ok_or(::Error::Header)?; 137 | let first_byte = first_byte.parse().map_err(|_| ::Error::Header)?; 138 | let last_byte = last_byte.parse().map_err(|_| ::Error::Header)?; 139 | if last_byte < first_byte { 140 | return Err(::Error::Header); 141 | } 142 | Some((first_byte, last_byte)) 143 | }; 144 | 145 | ContentRangeSpec::Bytes { 146 | range: range, 147 | instance_length: instance_length 148 | } 149 | } 150 | Some((unit, resp)) => { 151 | ContentRangeSpec::Unregistered { 152 | unit: unit.to_owned(), 153 | resp: resp.to_owned() 154 | } 155 | } 156 | _ => return Err(::Error::Header) 157 | }; 158 | Ok(res) 159 | } 160 | } 161 | 162 | impl Display for ContentRangeSpec { 163 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 164 | match *self { 165 | ContentRangeSpec::Bytes { range, instance_length } => { 166 | f.write_str("bytes ")?; 167 | match range { 168 | Some((first_byte, last_byte)) => { 169 | write!(f, "{}-{}", first_byte, last_byte)?; 170 | }, 171 | None => { 172 | f.write_str("*")?; 173 | } 174 | }; 175 | f.write_str("/")?; 176 | if let Some(v) = instance_length { 177 | write!(f, "{}", v) 178 | } else { 179 | f.write_str("*") 180 | } 181 | } 182 | ContentRangeSpec::Unregistered { ref unit, ref resp } => { 183 | f.write_str(&unit)?; 184 | f.write_str(" ")?; 185 | f.write_str(resp) 186 | } 187 | } 188 | } 189 | } 190 | 191 | standard_header!(ContentRange, CONTENT_RANGE); 192 | -------------------------------------------------------------------------------- /src/header/common/retry_after.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 retry-after Developers 2 | // 3 | // This file is dual licensed under MIT and Apache 2.0 4 | // 5 | // ******************************************************* 6 | // 7 | // Permission is hereby granted, free of charge, to any 8 | // person obtaining a copy of this software and associated 9 | // documentation files (the "Software"), to deal in the 10 | // Software without restriction, including without 11 | // limitation the rights to use, copy, modify, merge, 12 | // publish, distribute, sublicense, and/or sell copies of 13 | // the Software, and to permit persons to whom the Software 14 | // is furnished to do so, subject to the following 15 | // 16 | // conditions: 17 | // 18 | // The above copyright notice and this permission notice 19 | // shall be included in all copies or substantial portions 20 | // of the Software. 21 | // 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 23 | // ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 24 | // TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 25 | // PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 26 | // SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 27 | // CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 29 | // IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 30 | // DEALINGS IN THE SOFTWARE. 31 | // 32 | // ******************************************************* 33 | // 34 | // Apache License 35 | // Version 2.0, January 2004 36 | // http://www.apache.org/licenses/ 37 | 38 | use std::fmt; 39 | use std::time::Duration; 40 | 41 | use header::{Header, RawLike}; 42 | use header::shared::HttpDate; 43 | 44 | /// The `Retry-After` header. 45 | /// 46 | /// The `Retry-After` response-header field can be used with a 503 (Service 47 | /// Unavailable) response to indicate how long the service is expected to be 48 | /// unavailable to the requesting client. This field MAY also be used with any 49 | /// 3xx (Redirection) response to indicate the minimum time the user-agent is 50 | /// asked wait before issuing the redirected request. The value of this field 51 | /// can be either an HTTP-date or an integer number of seconds (in decimal) 52 | /// after the time of the response. 53 | /// 54 | /// # Examples 55 | /// ``` 56 | /// # extern crate http; 57 | /// use std::time::Duration; 58 | /// use hyperx::header::{RetryAfter, TypedHeaders}; 59 | /// 60 | /// let mut headers = http::HeaderMap::new(); 61 | /// headers.encode( 62 | /// &RetryAfter::Delay(Duration::from_secs(300)) 63 | /// ); 64 | /// ``` 65 | /// ``` 66 | /// # extern crate http; 67 | /// use std::time::{SystemTime, Duration}; 68 | /// use hyperx::header::{RetryAfter, TypedHeaders}; 69 | /// 70 | /// let mut headers = http::HeaderMap::new(); 71 | /// let date = SystemTime::now() + Duration::from_secs(300); 72 | /// headers.encode( 73 | /// &RetryAfter::DateTime(date.into()) 74 | /// ); 75 | /// ``` 76 | 77 | /// Retry-After header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.3) 78 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 79 | pub enum RetryAfter { 80 | /// Retry after this duration has elapsed 81 | /// 82 | /// This can be coupled with a response time header to produce a DateTime. 83 | Delay(Duration), 84 | 85 | /// Retry after the given DateTime 86 | DateTime(HttpDate), 87 | } 88 | 89 | impl Header for RetryAfter { 90 | fn header_name() -> &'static str { 91 | static NAME: &'static str = "Retry-After"; 92 | NAME 93 | } 94 | 95 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 96 | where T: RawLike<'a> 97 | { 98 | if let Some(ref line) = raw.one() { 99 | let utf8_str = match ::std::str::from_utf8(line) { 100 | Ok(utf8_str) => utf8_str, 101 | Err(_) => return Err(::Error::Header), 102 | }; 103 | 104 | if let Ok(datetime) = utf8_str.parse::() { 105 | return Ok(RetryAfter::DateTime(datetime)) 106 | } 107 | 108 | if let Ok(seconds) = utf8_str.parse::() { 109 | return Ok(RetryAfter::Delay(Duration::from_secs(seconds))); 110 | } 111 | 112 | Err(::Error::Header) 113 | } else { 114 | Err(::Error::Header) 115 | } 116 | } 117 | 118 | fn fmt_header(&self, f: &mut ::header::Formatter) -> ::std::fmt::Result { 119 | f.fmt_line(self) 120 | } 121 | } 122 | 123 | impl fmt::Display for RetryAfter { 124 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 125 | match *self { 126 | RetryAfter::Delay(ref duration) => { 127 | write!(f, "{}", duration.as_secs()) 128 | }, 129 | RetryAfter::DateTime(ref datetime) => { 130 | fmt::Display::fmt(datetime, f) 131 | } 132 | } 133 | } 134 | } 135 | 136 | #[cfg(test)] 137 | mod tests { 138 | use std::time::Duration; 139 | use header::{Header, Raw}; 140 | use header::shared::HttpDate; 141 | 142 | use super::RetryAfter; 143 | 144 | #[test] 145 | fn header_name_regression() { 146 | assert_eq!(RetryAfter::header_name(), "Retry-After"); 147 | } 148 | 149 | #[test] 150 | fn parse_delay() { 151 | let r: Raw = vec![b"1234".to_vec()].into(); 152 | let retry_after = RetryAfter::parse_header(&r).unwrap(); 153 | assert_eq!(RetryAfter::Delay(Duration::from_secs(1234)), retry_after); 154 | } 155 | 156 | macro_rules! test_retry_after_datetime { 157 | ($name:ident, $bytes:expr) => { 158 | #[test] 159 | fn $name() { 160 | let dt = "Sun, 06 Nov 1994 08:49:37 GMT".parse::().unwrap(); 161 | let r: Raw = vec![$bytes.to_vec()].into(); 162 | let retry_after = RetryAfter::parse_header(&r).expect("parse_header ok"); 163 | assert_eq!(RetryAfter::DateTime(dt), retry_after); 164 | } 165 | } 166 | } 167 | 168 | test_retry_after_datetime!(header_parse_rfc1123, b"Sun, 06 Nov 1994 08:49:37 GMT"); 169 | test_retry_after_datetime!(header_parse_rfc850, b"Sunday, 06-Nov-94 08:49:37 GMT"); 170 | test_retry_after_datetime!(header_parse_asctime, b"Sun Nov 6 08:49:37 1994"); 171 | 172 | #[test] 173 | fn hyper_headers_from_raw_delay() { 174 | let r: Raw = b"300".to_vec().into(); 175 | let retry_after = RetryAfter::parse_header(&r).unwrap(); 176 | assert_eq!(retry_after, RetryAfter::Delay(Duration::from_secs(300))); 177 | } 178 | 179 | #[test] 180 | fn hyper_headers_from_raw_datetime() { 181 | let r: Raw = b"Sun, 06 Nov 1994 08:49:37 GMT".to_vec().into(); 182 | let retry_after = RetryAfter::parse_header(&r).unwrap(); 183 | let expected = "Sun, 06 Nov 1994 08:49:37 GMT".parse::().unwrap(); 184 | assert_eq!(retry_after, RetryAfter::DateTime(expected)); 185 | } 186 | } 187 | 188 | standard_header!(RetryAfter, RETRY_AFTER); 189 | -------------------------------------------------------------------------------- /src/header/common/warning.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::{FromStr}; 3 | use header::{Header, HttpDate, RawLike}; 4 | use header::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 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 | /// # extern crate http; 39 | /// use hyperx::header::{TypedHeaders, Warning}; 40 | /// 41 | /// let mut headers = http::HeaderMap::new(); 42 | /// headers.encode( 43 | /// &Warning { 44 | /// code: 299, 45 | /// agent: "api.hyper.rs".to_owned(), 46 | /// text: "Deprecated".to_owned(), 47 | /// date: None 48 | /// } 49 | /// ); 50 | /// ``` 51 | /// 52 | /// ``` 53 | /// # extern crate http; 54 | /// use hyperx::header::{TypedHeaders, HttpDate, Warning}; 55 | /// 56 | /// let mut headers = http::HeaderMap::new(); 57 | /// headers.encode( 58 | /// &Warning { 59 | /// code: 299, 60 | /// agent: "api.hyper.rs".to_owned(), 61 | /// text: "Deprecated".to_owned(), 62 | /// date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() 63 | /// } 64 | /// ); 65 | /// ``` 66 | /// 67 | /// ``` 68 | /// # extern crate http; 69 | /// use std::time::SystemTime; 70 | /// use hyperx::header::{TypedHeaders, Warning}; 71 | /// 72 | /// let mut headers = http::HeaderMap::new(); 73 | /// headers.encode( 74 | /// &Warning { 75 | /// code: 199, 76 | /// agent: "api.hyper.rs".to_owned(), 77 | /// text: "Deprecated".to_owned(), 78 | /// date: Some(SystemTime::now().into()) 79 | /// } 80 | /// ); 81 | /// ``` 82 | #[derive(PartialEq, Clone, Debug)] 83 | pub struct Warning { 84 | /// The 3 digit warn code. 85 | pub code: u16, 86 | /// The name or pseudonym of the server adding this header. 87 | pub agent: String, 88 | /// The warning message describing the error. 89 | pub text: String, 90 | /// An optional warning date. 91 | pub date: Option 92 | } 93 | 94 | impl Header for Warning { 95 | fn header_name() -> &'static str { 96 | static NAME: &'static str = "Warning"; 97 | NAME 98 | } 99 | 100 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 101 | where T: RawLike<'a> 102 | { 103 | from_one_raw_str(raw) 104 | } 105 | 106 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 107 | f.fmt_line(self) 108 | } 109 | } 110 | 111 | impl fmt::Display for Warning { 112 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 113 | match self.date { 114 | Some(date) => write!(f, "{:03} {} \"{}\" \"{}\"", self.code, self.agent, self.text, date), 115 | None => write!(f, "{:03} {} \"{}\"", self.code, self.agent, self.text) 116 | } 117 | } 118 | } 119 | 120 | impl FromStr for Warning { 121 | type Err = ::Error; 122 | 123 | fn from_str(s: &str) -> ::Result { 124 | let mut warning_split = s.split_whitespace(); 125 | let code = match warning_split.next() { 126 | Some(c) => match c.parse::() { 127 | Ok(c) => c, 128 | Err(..) => return Err(::Error::Header) 129 | }, 130 | None => return Err(::Error::Header) 131 | }; 132 | let agent = match warning_split.next() { 133 | Some(a) => a.to_string(), 134 | None => return Err(::Error::Header) 135 | }; 136 | 137 | let mut warning_split = s.split('"').skip(1); 138 | let text = match warning_split.next() { 139 | Some(t) => t.to_string(), 140 | None => return Err(::Error::Header) 141 | }; 142 | let date = match warning_split.skip(1).next() { 143 | Some(d) => d.parse::().ok(), 144 | None => None // Optional 145 | }; 146 | 147 | Ok(Warning { 148 | code: code, 149 | agent: agent, 150 | text: text, 151 | date: date 152 | }) 153 | } 154 | } 155 | 156 | #[cfg(test)] 157 | mod tests { 158 | use super::Warning; 159 | use header::{Header, HttpDate, Raw}; 160 | 161 | #[test] 162 | fn test_parsing() { 163 | let r: Raw = vec![ 164 | b"112 - \"network down\" \"Sat, 25 Aug 2012 23:34:45 GMT\"".to_vec() 165 | ].into(); 166 | let warning = Header::parse_header(&r); 167 | assert_eq!(warning.ok(), Some(Warning { 168 | code: 112, 169 | agent: "-".to_owned(), 170 | text: "network down".to_owned(), 171 | date: "Sat, 25 Aug 2012 23:34:45 GMT".parse::().ok() 172 | })); 173 | 174 | let r: Raw = vec![ 175 | b"299 api.hyper.rs:8080 \"Deprecated API : \ 176 | use newapi.hyper.rs instead.\"".to_vec() 177 | ].into(); 178 | let warning = Header::parse_header(&r); 179 | assert_eq!(warning.ok(), Some(Warning { 180 | code: 299, 181 | agent: "api.hyper.rs:8080".to_owned(), 182 | text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), 183 | date: None 184 | })); 185 | 186 | let r: Raw = vec![ 187 | b"299 api.hyper.rs:8080 \"Deprecated API : \ 188 | use newapi.hyper.rs instead.\" \ 189 | \"Tue, 15 Nov 1994 08:12:31 GMT\"".to_vec() 190 | ].into(); 191 | let warning = Header::parse_header(&r); 192 | assert_eq!(warning.ok(), Some(Warning { 193 | code: 299, 194 | agent: "api.hyper.rs:8080".to_owned(), 195 | text: "Deprecated API : use newapi.hyper.rs instead.".to_owned(), 196 | date: "Tue, 15 Nov 1994 08:12:31 GMT".parse::().ok() 197 | })); 198 | } 199 | } 200 | 201 | standard_header!(Warning, WARNING); 202 | -------------------------------------------------------------------------------- /src/header/internals/cell.rs: -------------------------------------------------------------------------------- 1 | use std::any::{Any, TypeId}; 2 | use std::cell::UnsafeCell; 3 | use std::collections::HashMap; 4 | use std::mem; 5 | use std::ops::Deref; 6 | 7 | pub struct OptCell(UnsafeCell>); 8 | 9 | impl OptCell { 10 | #[inline] 11 | pub fn new(val: Option) -> OptCell { 12 | OptCell(UnsafeCell::new(val)) 13 | } 14 | 15 | #[inline] 16 | pub fn set(&self, val: T) { 17 | unsafe { 18 | let opt = self.0.get(); 19 | debug_assert!((*opt).is_none()); 20 | *opt = Some(val) 21 | } 22 | } 23 | 24 | #[inline] 25 | pub unsafe fn get_mut(&mut self) -> &mut T { 26 | let opt = &mut *self.0.get(); 27 | opt.as_mut().unwrap() 28 | } 29 | } 30 | 31 | impl Deref for OptCell { 32 | type Target = Option; 33 | #[inline] 34 | fn deref(&self) -> &Option { 35 | unsafe { &*self.0.get() } 36 | } 37 | } 38 | 39 | impl Clone for OptCell { 40 | #[inline] 41 | fn clone(&self) -> OptCell { 42 | OptCell::new((**self).clone()) 43 | } 44 | } 45 | 46 | pub struct PtrMapCell(UnsafeCell>>); 47 | 48 | #[derive(Clone, Debug)] 49 | enum PtrMap { 50 | Empty, 51 | One(TypeId, T), 52 | Many(HashMap) 53 | } 54 | 55 | impl PtrMapCell { 56 | #[inline] 57 | pub fn new() -> PtrMapCell { 58 | PtrMapCell(UnsafeCell::new(PtrMap::Empty)) 59 | } 60 | 61 | #[inline] 62 | pub fn with_one(key: TypeId, val: Box) -> PtrMapCell { 63 | PtrMapCell(UnsafeCell::new(PtrMap::One(key, val))) 64 | } 65 | 66 | #[inline] 67 | pub fn get(&self, key: TypeId) -> Option<&V> { 68 | let map = unsafe { &*self.0.get() }; 69 | match *map { 70 | PtrMap::Empty => None, 71 | PtrMap::One(id, ref v) => if id == key { 72 | Some(v) 73 | } else { 74 | None 75 | }, 76 | PtrMap::Many(ref hm) => hm.get(&key) 77 | }.map(|val| &**val) 78 | } 79 | 80 | #[inline] 81 | pub fn get_mut(&mut self, key: TypeId) -> Option<&mut V> { 82 | let map = unsafe { &mut *self.0.get() }; 83 | match *map { 84 | PtrMap::Empty => None, 85 | PtrMap::One(id, ref mut v) => if id == key { 86 | Some(v) 87 | } else { 88 | None 89 | }, 90 | PtrMap::Many(ref mut hm) => hm.get_mut(&key) 91 | }.map(|val| &mut **val) 92 | } 93 | 94 | #[inline] 95 | pub fn into_value(self, key: TypeId) -> Option> { 96 | // UnsafeCell::into_inner was unsafe forever, and 1.25 has removed 97 | // the unsafe modifier, resulting in a warning. This allow can be 98 | // removed when the minimum supported rust version is at least 1.25. 99 | #[allow(unused_unsafe)] 100 | let map = unsafe { self.0.into_inner() }; 101 | match map { 102 | PtrMap::Empty => None, 103 | PtrMap::One(id, v) => if id == key { 104 | Some(v) 105 | } else { 106 | None 107 | }, 108 | PtrMap::Many(mut hm) => hm.remove(&key) 109 | } 110 | } 111 | 112 | #[inline] 113 | pub unsafe fn insert(&self, key: TypeId, val: Box) { 114 | let map = &mut *self.0.get(); 115 | match *map { 116 | PtrMap::Empty => *map = PtrMap::One(key, val), 117 | PtrMap::One(..) => { 118 | let one = mem::replace(map, PtrMap::Empty); 119 | match one { 120 | PtrMap::One(id, one) => { 121 | debug_assert_ne!(id, key); 122 | let mut hm = HashMap::with_capacity(2); 123 | hm.insert(id, one); 124 | hm.insert(key, val); 125 | *map = PtrMap::Many(hm); 126 | }, 127 | _ => unreachable!() 128 | } 129 | }, 130 | PtrMap::Many(ref mut hm) => { hm.insert(key, val); } 131 | } 132 | } 133 | 134 | #[inline] 135 | pub unsafe fn one(&self) -> &V { 136 | let map = &*self.0.get(); 137 | match *map { 138 | PtrMap::One(_, ref one) => one, 139 | _ => panic!("not PtrMap::One value") 140 | } 141 | } 142 | } 143 | 144 | impl Clone for PtrMapCell where Box: Clone { 145 | #[inline] 146 | fn clone(&self) -> PtrMapCell { 147 | let cell = PtrMapCell::new(); 148 | unsafe { 149 | *cell.0.get() = (&*self.0.get()).clone() 150 | } 151 | cell 152 | } 153 | } 154 | 155 | #[cfg(test)] 156 | mod test { 157 | use std::any::TypeId; 158 | use super::*; 159 | 160 | #[test] 161 | fn test_opt_cell_set() { 162 | let one:OptCell = OptCell::new(None); 163 | one.set(1); 164 | assert_eq!(*one,Some(1)); 165 | } 166 | 167 | #[test] 168 | fn test_opt_cell_clone() { 169 | let one:OptCell = OptCell::new(Some(3)); 170 | let stored = *one.clone(); 171 | assert_eq!(stored,Some(3)); 172 | } 173 | 174 | #[test] 175 | fn test_ptr_map_cell_none() { 176 | let type_id = TypeId::of::(); 177 | let pm:PtrMapCell = PtrMapCell::new(); 178 | assert_eq!(pm.get(type_id),None); 179 | } 180 | 181 | #[test] 182 | fn test_ptr_map_cell_one() { 183 | let type_id = TypeId::of::(); 184 | let pm:PtrMapCell = PtrMapCell::new(); 185 | unsafe { pm.insert(type_id, Box::new("a".to_string())); } 186 | assert_eq!(pm.get(type_id), Some(&"a".to_string())); 187 | assert_eq!(unsafe {pm.one()}, "a"); 188 | } 189 | 190 | #[test] 191 | fn test_ptr_map_cell_two() { 192 | let type_id = TypeId::of::(); 193 | let type_id2 = TypeId::of::>(); 194 | let pm:PtrMapCell = PtrMapCell::new(); 195 | unsafe { pm.insert(type_id, Box::new("a".to_string())); } 196 | unsafe { pm.insert(type_id2, Box::new("b".to_string())); } 197 | assert_eq!(pm.get(type_id), Some(&"a".to_string())); 198 | assert_eq!(pm.get(type_id2), Some(&"b".to_string())); 199 | } 200 | 201 | #[test] 202 | fn test_ptr_map_cell_many() { 203 | let id1 = TypeId::of::(); 204 | let id2 = TypeId::of::>(); 205 | let id3 = TypeId::of::>(); 206 | let pm:PtrMapCell = PtrMapCell::new(); 207 | unsafe { pm.insert(id1, Box::new("a".to_string())); } 208 | unsafe { pm.insert(id2, Box::new("b".to_string())); } 209 | unsafe { pm.insert(id3, Box::new("c".to_string())); } 210 | assert_eq!(pm.get(id1), Some(&"a".to_string())); 211 | assert_eq!(pm.get(id2), Some(&"b".to_string())); 212 | assert_eq!(pm.get(id3), Some(&"c".to_string())); 213 | } 214 | 215 | #[test] 216 | fn test_ptr_map_cell_clone() { 217 | let type_id = TypeId::of::(); 218 | let pm:PtrMapCell = PtrMapCell::new(); 219 | unsafe { pm.insert(type_id, Box::new("a".to_string())); } 220 | let cloned = pm.clone(); 221 | assert_eq!(cloned.get(type_id), Some(&"a".to_string())); 222 | } 223 | 224 | } 225 | -------------------------------------------------------------------------------- /src/header/common/proxy_authorization.rs: -------------------------------------------------------------------------------- 1 | use std::any::Any; 2 | use std::fmt; 3 | use std::str::{FromStr, from_utf8}; 4 | use std::ops::{Deref, DerefMut}; 5 | use header::{Header, RawLike, Scheme}; 6 | 7 | /// `Proxy-Authorization` header, defined in [RFC7235](https://tools.ietf.org/html/rfc7235#section-4.4) 8 | /// 9 | /// The `Proxy-Authorization` header field allows a user agent to authenticate 10 | /// itself with an HTTP proxy -- usually, but not necessarily, after 11 | /// receiving a 407 (Proxy Authentication Required) response and the 12 | /// `Proxy-Authenticate` header. Its value consists of credentials containing 13 | /// the authentication information of the user agent for the realm of the 14 | /// resource being requested. 15 | /// 16 | /// # ABNF 17 | /// 18 | /// ```text 19 | /// Authorization = credentials 20 | /// ``` 21 | /// 22 | /// # Example values 23 | /// * `Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==` 24 | /// * `Bearer fpKL54jvWmEGVoRdCNjG` 25 | /// 26 | /// # Examples 27 | /// 28 | /// ``` 29 | /// # extern crate http; 30 | /// use hyperx::header::{ProxyAuthorization, TypedHeaders}; 31 | /// 32 | /// let mut headers = http::HeaderMap::new(); 33 | /// headers.encode(&ProxyAuthorization("let me in".to_owned())); 34 | /// ``` 35 | /// ``` 36 | /// # extern crate http; 37 | /// use hyperx::header::{ProxyAuthorization, Basic, TypedHeaders}; 38 | /// 39 | /// let mut headers = http::HeaderMap::new(); 40 | /// headers.encode( 41 | /// &ProxyAuthorization( 42 | /// Basic { 43 | /// username: "Aladdin".to_owned(), 44 | /// password: Some("open sesame".to_owned()) 45 | /// } 46 | /// ) 47 | /// ); 48 | /// ``` 49 | /// 50 | /// ``` 51 | /// # extern crate http; 52 | /// use hyperx::header::{ProxyAuthorization, Bearer, TypedHeaders}; 53 | /// 54 | /// let mut headers = http::HeaderMap::new(); 55 | /// headers.encode( 56 | /// &ProxyAuthorization( 57 | /// Bearer { 58 | /// token: "QWxhZGRpbjpvcGVuIHNlc2FtZQ".to_owned() 59 | /// } 60 | /// ) 61 | /// ); 62 | /// ``` 63 | #[derive(Clone, PartialEq, Debug)] 64 | pub struct ProxyAuthorization(pub S); 65 | 66 | impl Deref for ProxyAuthorization { 67 | type Target = S; 68 | 69 | fn deref(&self) -> &S { 70 | &self.0 71 | } 72 | } 73 | 74 | impl DerefMut for ProxyAuthorization { 75 | fn deref_mut(&mut self) -> &mut S { 76 | &mut self.0 77 | } 78 | } 79 | 80 | impl Header for ProxyAuthorization where ::Err: 'static { 81 | fn header_name() -> &'static str { 82 | static NAME: &'static str = "Proxy-Authorization"; 83 | NAME 84 | } 85 | 86 | fn parse_header<'a, T>(raw: &'a T) -> ::Result> 87 | where T: RawLike<'a> 88 | { 89 | if let Some(line) = raw.one() { 90 | let header = from_utf8(line)?; 91 | if let Some(scheme) = ::scheme() { 92 | if header.starts_with(scheme) && header.len() > scheme.len() + 1 { 93 | match header[scheme.len() + 1..].parse::().map(ProxyAuthorization) { 94 | Ok(h) => Ok(h), 95 | Err(_) => Err(::Error::Header) 96 | } 97 | } else { 98 | Err(::Error::Header) 99 | } 100 | } else { 101 | match header.parse::().map(ProxyAuthorization) { 102 | Ok(h) => Ok(h), 103 | Err(_) => Err(::Error::Header) 104 | } 105 | } 106 | } else { 107 | Err(::Error::Header) 108 | } 109 | } 110 | 111 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 112 | f.fmt_line(self) 113 | } 114 | } 115 | 116 | impl fmt::Display for ProxyAuthorization { 117 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 118 | if let Some(scheme) = ::scheme() { 119 | write!(f, "{} ", scheme)? 120 | }; 121 | self.0.fmt_scheme(f) 122 | } 123 | } 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::ProxyAuthorization; 128 | use super::super::super::{Header, Raw, Basic, Bearer}; 129 | 130 | #[cfg(feature = "headers")] 131 | use super::super::super::Headers; 132 | 133 | #[cfg(feature = "headers")] 134 | #[test] 135 | fn test_raw_auth() { 136 | let mut headers = Headers::new(); 137 | headers.set(ProxyAuthorization("foo bar baz".to_owned())); 138 | assert_eq!(headers.to_string(), "Proxy-Authorization: foo bar baz\r\n".to_owned()); 139 | } 140 | 141 | #[test] 142 | fn test_raw_auth_parse() { 143 | let r: Raw = b"foo bar baz".as_ref().into(); 144 | let header: ProxyAuthorization = Header::parse_header(&r).unwrap(); 145 | assert_eq!(header.0, "foo bar baz"); 146 | } 147 | 148 | #[cfg(feature = "headers")] 149 | #[test] 150 | fn test_basic_auth() { 151 | let mut headers = Headers::new(); 152 | headers.set(ProxyAuthorization( 153 | Basic { username: "Aladdin".to_owned(), password: Some("open sesame".to_owned()) })); 154 | assert_eq!( 155 | headers.to_string(), 156 | "Proxy-Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==\r\n".to_owned()); 157 | } 158 | 159 | #[cfg(feature = "headers")] 160 | #[test] 161 | fn test_basic_auth_no_password() { 162 | let mut headers = Headers::new(); 163 | headers.set(ProxyAuthorization(Basic { username: "Aladdin".to_owned(), password: None })); 164 | assert_eq!(headers.to_string(), "Proxy-Authorization: Basic QWxhZGRpbjo=\r\n".to_owned()); 165 | } 166 | 167 | #[test] 168 | fn test_basic_auth_parse() { 169 | let r: Raw = b"Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==".as_ref().into(); 170 | let auth: ProxyAuthorization = Header::parse_header(&r).unwrap(); 171 | assert_eq!(auth.0.username, "Aladdin"); 172 | assert_eq!(auth.0.password, Some("open sesame".to_owned())); 173 | } 174 | 175 | #[test] 176 | fn test_basic_auth_parse_no_password() { 177 | let r: Raw = b"Basic QWxhZGRpbjo=".as_ref().into(); 178 | let auth: ProxyAuthorization = Header::parse_header(&r).unwrap(); 179 | assert_eq!(auth.0.username, "Aladdin"); 180 | assert_eq!(auth.0.password, Some("".to_owned())); 181 | } 182 | 183 | #[cfg(feature = "headers")] 184 | #[test] 185 | fn test_bearer_auth() { 186 | let mut headers = Headers::new(); 187 | headers.set(ProxyAuthorization( 188 | Bearer { token: "fpKL54jvWmEGVoRdCNjG".to_owned() })); 189 | assert_eq!( 190 | headers.to_string(), 191 | "Proxy-Authorization: Bearer fpKL54jvWmEGVoRdCNjG\r\n".to_owned()); 192 | } 193 | 194 | #[test] 195 | fn test_bearer_auth_parse() { 196 | let r: Raw = b"Bearer fpKL54jvWmEGVoRdCNjG".as_ref().into(); 197 | let auth: ProxyAuthorization = Header::parse_header(&r).unwrap(); 198 | assert_eq!(auth.0.token, "fpKL54jvWmEGVoRdCNjG"); 199 | } 200 | } 201 | 202 | #[cfg(test)] 203 | #[cfg(feature = "nightly")] 204 | mod benches { 205 | use super::ProxyAuthorization; 206 | use ::header::{Basic, Bearer}; 207 | 208 | bench_header!(raw, ProxyAuthorization, { vec![b"foo bar baz".to_vec()] }); 209 | bench_header!(basic, ProxyAuthorization, { vec![b"Basic QWxhZGRpbjpuIHNlc2FtZQ==".to_vec()] }); 210 | bench_header!(bearer, ProxyAuthorization, { vec![b"Bearer fpKL54jvWmEGVoRdCNjG".to_vec()] }); 211 | } 212 | 213 | impl ::header::StandardHeader for ProxyAuthorization 214 | where S: Scheme + Any 215 | { 216 | #[inline] 217 | fn http_header_name() -> ::http::header::HeaderName { 218 | ::http::header::PROXY_AUTHORIZATION 219 | } 220 | } 221 | -------------------------------------------------------------------------------- /src/header/common/strict_transport_security.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::{self, FromStr}; 3 | 4 | use unicase; 5 | 6 | use header::{Header, RawLike, parsing}; 7 | 8 | /// `StrictTransportSecurity` header, defined in [RFC6797](https://tools.ietf.org/html/rfc6797) 9 | /// 10 | /// This specification defines a mechanism enabling web sites to declare 11 | /// themselves accessible only via secure connections and/or for users to be 12 | /// able to direct their user agent(s) to interact with given sites only over 13 | /// secure connections. This overall policy is referred to as HTTP Strict 14 | /// Transport Security (HSTS). The policy is declared by web sites via the 15 | /// Strict-Transport-Security HTTP response header field and/or by other means, 16 | /// such as user agent configuration, for example. 17 | /// 18 | /// # ABNF 19 | /// 20 | /// ```text 21 | /// [ directive ] *( ";" [ directive ] ) 22 | /// 23 | /// directive = directive-name [ "=" directive-value ] 24 | /// directive-name = token 25 | /// directive-value = token | quoted-string 26 | /// 27 | /// ``` 28 | /// 29 | /// # Example values 30 | /// 31 | /// * `max-age=31536000` 32 | /// * `max-age=15768000 ; includeSubDomains` 33 | /// 34 | /// # Example 35 | /// 36 | /// ``` 37 | /// # extern crate http; 38 | /// # extern crate hyperx; 39 | /// # fn main() { 40 | /// use hyperx::header::{StrictTransportSecurity, TypedHeaders}; 41 | /// 42 | /// let mut headers = http::HeaderMap::new(); 43 | /// 44 | /// headers.encode( 45 | /// &StrictTransportSecurity::including_subdomains(31536000u64) 46 | /// ); 47 | /// # } 48 | /// ``` 49 | #[derive(Clone, PartialEq, Debug)] 50 | pub struct StrictTransportSecurity { 51 | /// Signals the UA that the HSTS Policy applies to this HSTS Host as well as 52 | /// any subdomains of the host's domain name. 53 | pub include_subdomains: bool, 54 | 55 | /// Specifies the number of seconds, after the reception of the STS header 56 | /// field, during which the UA regards the host (from whom the message was 57 | /// received) as a Known HSTS Host. 58 | pub max_age: u64 59 | } 60 | 61 | impl StrictTransportSecurity { 62 | /// Create an STS header that includes subdomains 63 | pub fn including_subdomains(max_age: u64) -> StrictTransportSecurity { 64 | StrictTransportSecurity { 65 | max_age: max_age, 66 | include_subdomains: true 67 | } 68 | } 69 | 70 | /// Create an STS header that excludes subdomains 71 | pub fn excluding_subdomains(max_age: u64) -> StrictTransportSecurity { 72 | StrictTransportSecurity { 73 | max_age: max_age, 74 | include_subdomains: false 75 | } 76 | } 77 | } 78 | 79 | enum Directive { 80 | MaxAge(u64), 81 | IncludeSubdomains, 82 | Unknown 83 | } 84 | 85 | impl FromStr for StrictTransportSecurity { 86 | type Err = ::Error; 87 | 88 | fn from_str(s: &str) -> ::Result { 89 | s.split(';') 90 | .map(str::trim) 91 | .map(|sub| if unicase::eq_ascii(sub, "includeSubdomains") { 92 | Ok(Directive::IncludeSubdomains) 93 | } else { 94 | let mut sub = sub.splitn(2, '='); 95 | match (sub.next(), sub.next()) { 96 | (Some(left), Some(right)) 97 | if unicase::eq_ascii(left.trim(), "max-age") => { 98 | right 99 | .trim() 100 | .trim_matches('"') 101 | .parse() 102 | .map(Directive::MaxAge) 103 | }, 104 | _ => Ok(Directive::Unknown) 105 | } 106 | }) 107 | .fold(Ok((None, None)), |res, dir| match (res, dir) { 108 | (Ok((None, sub)), Ok(Directive::MaxAge(age))) => Ok((Some(age), sub)), 109 | (Ok((age, None)), Ok(Directive::IncludeSubdomains)) => Ok((age, Some(()))), 110 | (Ok((Some(_), _)), Ok(Directive::MaxAge(_))) | 111 | (Ok((_, Some(_))), Ok(Directive::IncludeSubdomains)) | 112 | (_, Err(_)) => Err(::Error::Header), 113 | (res, _) => res 114 | }) 115 | .and_then(|res| match res { 116 | (Some(age), sub) => Ok(StrictTransportSecurity { 117 | max_age: age, 118 | include_subdomains: sub.is_some() 119 | }), 120 | _ => Err(::Error::Header) 121 | }) 122 | } 123 | } 124 | 125 | impl Header for StrictTransportSecurity { 126 | fn header_name() -> &'static str { 127 | static NAME: &'static str = "Strict-Transport-Security"; 128 | NAME 129 | } 130 | 131 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 132 | where T: RawLike<'a> 133 | { 134 | parsing::from_one_raw_str(raw) 135 | } 136 | 137 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 138 | f.fmt_line(self) 139 | } 140 | } 141 | 142 | impl fmt::Display for StrictTransportSecurity { 143 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 144 | if self.include_subdomains { 145 | write!(f, "max-age={}; includeSubdomains", self.max_age) 146 | } else { 147 | write!(f, "max-age={}", self.max_age) 148 | } 149 | } 150 | } 151 | 152 | #[cfg(test)] 153 | mod tests { 154 | use super::StrictTransportSecurity; 155 | use header::{Header, Raw}; 156 | 157 | #[test] 158 | fn test_parse_max_age() { 159 | let r: Raw = "max-age=31536000".into(); 160 | let h = Header::parse_header(&r); 161 | assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); 162 | } 163 | 164 | #[test] 165 | fn test_parse_max_age_no_value() { 166 | let r: Raw = "max-age".into(); 167 | let h: ::Result = Header::parse_header(&r); 168 | assert!(h.is_err()); 169 | } 170 | 171 | #[test] 172 | fn test_parse_quoted_max_age() { 173 | let r: Raw = "max-age=\"31536000\"".into(); 174 | let h = Header::parse_header(&r); 175 | assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); 176 | } 177 | 178 | #[test] 179 | fn test_parse_spaces_max_age() { 180 | let r: Raw = "max-age = 31536000".into(); 181 | let h = Header::parse_header(&r); 182 | assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: false, max_age: 31536000u64 })); 183 | } 184 | 185 | #[test] 186 | fn test_parse_include_subdomains() { 187 | let r: Raw = "max-age=15768000 ; includeSubDomains".into(); 188 | let h = Header::parse_header(&r); 189 | assert_eq!(h.ok(), Some(StrictTransportSecurity { include_subdomains: true, max_age: 15768000u64 })); 190 | } 191 | 192 | #[test] 193 | fn test_parse_no_max_age() { 194 | let r: Raw = "includeSubDomains".into(); 195 | let h: ::Result = Header::parse_header(&r); 196 | assert!(h.is_err()); 197 | } 198 | 199 | #[test] 200 | fn test_parse_max_age_nan() { 201 | let r: Raw = "max-age = derp".into(); 202 | let h: ::Result = Header::parse_header(&r); 203 | assert!(h.is_err()); 204 | } 205 | 206 | #[test] 207 | fn test_parse_duplicate_directives() { 208 | let r: Raw = "max-age=100; max-age=5; max-age=0".into(); 209 | assert!(StrictTransportSecurity::parse_header(&r).is_err()); 210 | } 211 | } 212 | 213 | bench_header!(bench, StrictTransportSecurity, { vec![b"max-age=15768000 ; includeSubDomains".to_vec()] }); 214 | 215 | standard_header!(StrictTransportSecurity, STRICT_TRANSPORT_SECURITY); 216 | -------------------------------------------------------------------------------- /src/header/common/cache_control.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::str::FromStr; 3 | use header::{Header, RawLike}; 4 | use header::parsing::{from_comma_delimited, fmt_comma_delimited}; 5 | 6 | /// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) 7 | /// 8 | /// The `Cache-Control` header field is used to specify directives for 9 | /// caches along the request/response chain. Such cache directives are 10 | /// unidirectional in that the presence of a directive in a request does 11 | /// not imply that the same directive is to be given in the response. 12 | /// 13 | /// # ABNF 14 | /// 15 | /// ```text 16 | /// Cache-Control = 1#cache-directive 17 | /// cache-directive = token [ "=" ( token / quoted-string ) ] 18 | /// ``` 19 | /// 20 | /// # Example values 21 | /// 22 | /// * `no-cache` 23 | /// * `private, community="UCI"` 24 | /// * `max-age=30` 25 | /// 26 | /// # Examples 27 | /// ``` 28 | /// # extern crate http; 29 | /// use hyperx::header::{CacheControl, CacheDirective, TypedHeaders}; 30 | /// 31 | /// let mut headers = http::HeaderMap::new(); 32 | /// headers.encode( 33 | /// &CacheControl(vec![CacheDirective::MaxAge(86400u32)]) 34 | /// ); 35 | /// ``` 36 | /// 37 | /// ``` 38 | /// # extern crate http; 39 | /// use hyperx::header::{CacheControl, CacheDirective, TypedHeaders}; 40 | /// 41 | /// let mut headers = http::HeaderMap::new(); 42 | /// headers.encode( 43 | /// &CacheControl(vec![ 44 | /// CacheDirective::NoCache, 45 | /// CacheDirective::Private, 46 | /// CacheDirective::MaxAge(360u32), 47 | /// CacheDirective::Extension("foo".to_owned(), 48 | /// Some("bar".to_owned())), 49 | /// ]) 50 | /// ); 51 | /// ``` 52 | #[derive(PartialEq, Clone, Debug)] 53 | pub struct CacheControl(pub Vec); 54 | 55 | __hyper__deref!(CacheControl => Vec); 56 | 57 | //TODO: this could just be the header! macro 58 | impl Header for CacheControl { 59 | fn header_name() -> &'static str { 60 | static NAME: &'static str = "Cache-Control"; 61 | NAME 62 | } 63 | 64 | fn parse_header<'a, T>(raw: &'a T) -> ::Result 65 | where T: RawLike<'a> 66 | { 67 | let directives = from_comma_delimited(raw)?; 68 | if !directives.is_empty() { 69 | Ok(CacheControl(directives)) 70 | } else { 71 | Err(::Error::Header) 72 | } 73 | } 74 | 75 | fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { 76 | f.fmt_line(self) 77 | } 78 | } 79 | 80 | impl fmt::Display for CacheControl { 81 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 82 | fmt_comma_delimited(f, &self[..]) 83 | } 84 | } 85 | 86 | /// `CacheControl` contains a list of these directives. 87 | #[derive(PartialEq, Clone, Debug)] 88 | pub enum CacheDirective { 89 | /// "no-cache" 90 | NoCache, 91 | /// "no-store" 92 | NoStore, 93 | /// "no-transform" 94 | NoTransform, 95 | /// "only-if-cached" 96 | OnlyIfCached, 97 | 98 | // request directives 99 | /// "max-age=delta" 100 | MaxAge(u32), 101 | /// "max-stale=delta" 102 | MaxStale(u32), 103 | /// "min-fresh=delta" 104 | MinFresh(u32), 105 | 106 | // response directives 107 | /// "must-revalidate" 108 | MustRevalidate, 109 | /// "public" 110 | Public, 111 | /// "private" 112 | Private, 113 | /// "proxy-revalidate" 114 | ProxyRevalidate, 115 | /// "s-maxage=delta" 116 | SMaxAge(u32), 117 | 118 | /// Extension directives. Optionally include an argument. 119 | Extension(String, Option) 120 | } 121 | 122 | impl fmt::Display for CacheDirective { 123 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 124 | use self::CacheDirective::*; 125 | fmt::Display::fmt(match *self { 126 | NoCache => "no-cache", 127 | NoStore => "no-store", 128 | NoTransform => "no-transform", 129 | OnlyIfCached => "only-if-cached", 130 | 131 | MaxAge(secs) => return write!(f, "max-age={}", secs), 132 | MaxStale(secs) => return write!(f, "max-stale={}", secs), 133 | MinFresh(secs) => return write!(f, "min-fresh={}", secs), 134 | 135 | MustRevalidate => "must-revalidate", 136 | Public => "public", 137 | Private => "private", 138 | ProxyRevalidate => "proxy-revalidate", 139 | SMaxAge(secs) => return write!(f, "s-maxage={}", secs), 140 | 141 | Extension(ref name, None) => &name[..], 142 | Extension(ref name, Some(ref arg)) => return write!(f, "{}={}", name, arg), 143 | 144 | }, f) 145 | } 146 | } 147 | 148 | impl FromStr for CacheDirective { 149 | type Err = Option<::Err>; 150 | fn from_str(s: &str) -> Result::Err>> { 151 | use self::CacheDirective::*; 152 | match s { 153 | "no-cache" => Ok(NoCache), 154 | "no-store" => Ok(NoStore), 155 | "no-transform" => Ok(NoTransform), 156 | "only-if-cached" => Ok(OnlyIfCached), 157 | "must-revalidate" => Ok(MustRevalidate), 158 | "public" => Ok(Public), 159 | "private" => Ok(Private), 160 | "proxy-revalidate" => Ok(ProxyRevalidate), 161 | "" => Err(None), 162 | _ => match s.find('=') { 163 | Some(idx) if idx+1 < s.len() => match (&s[..idx], (&s[idx+1..]).trim_matches('"')) { 164 | ("max-age" , secs) => secs.parse().map(MaxAge).map_err(Some), 165 | ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), 166 | ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), 167 | ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), 168 | (left, right) => Ok(Extension(left.to_owned(), Some(right.to_owned()))) 169 | }, 170 | Some(_) => Err(None), 171 | None => Ok(Extension(s.to_owned(), None)) 172 | } 173 | } 174 | } 175 | } 176 | 177 | #[cfg(test)] 178 | mod tests { 179 | use header::{Header, Raw}; 180 | use super::*; 181 | 182 | #[test] 183 | fn test_parse_multiple_headers() { 184 | let r: Raw = vec![b"no-cache".to_vec(), b"private".to_vec()].into(); 185 | let cache = Header::parse_header(&r); 186 | assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::NoCache, 187 | CacheDirective::Private]))) 188 | } 189 | 190 | #[test] 191 | fn test_parse_argument() { 192 | let r: Raw = vec![b"max-age=100, private".to_vec()].into(); 193 | let cache = Header::parse_header(&r); 194 | assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(100), 195 | CacheDirective::Private]))) 196 | } 197 | 198 | #[test] 199 | fn test_parse_quote_form() { 200 | let r: Raw = vec![b"max-age=\"200\"".to_vec()].into(); 201 | let cache = Header::parse_header(&r); 202 | assert_eq!(cache.ok(), Some(CacheControl(vec![CacheDirective::MaxAge(200)]))) 203 | } 204 | 205 | #[test] 206 | fn test_parse_extension() { 207 | let r: Raw = vec![b"foo, bar=baz".to_vec()].into(); 208 | let cache = Header::parse_header(&r); 209 | assert_eq!(cache.ok(), Some(CacheControl(vec![ 210 | CacheDirective::Extension("foo".to_owned(), None), 211 | CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned()))]))) 212 | } 213 | 214 | #[test] 215 | fn test_parse_bad_syntax() { 216 | let r: Raw = vec![b"foo=".to_vec()].into(); 217 | let cache: ::Result = Header::parse_header(&r); 218 | assert_eq!(cache.ok(), None) 219 | } 220 | } 221 | 222 | bench_header!(normal, 223 | CacheControl, { vec![b"no-cache, private".to_vec(), b"max-age=100".to_vec()] }); 224 | 225 | standard_header!(CacheControl, CACHE_CONTROL); 226 | --------------------------------------------------------------------------------