From c24a8f4c2d121a02e828be52712b9d4b43004f29 Mon Sep 17 00:00:00 2001 From: Nikolay Kim Date: Fri, 5 Oct 2018 07:02:09 -0700 Subject: [PATCH] remove high level apis --- Cargo.toml | 17 +- src/h1/codec.rs | 2 +- src/h1/dispatcher.rs | 7 +- src/h1/mod.rs | 1 + src/{server/output.rs => h1/response.rs} | 0 src/header.rs | 265 +++++++ src/header/common/accept.rs | 159 ---- src/header/common/accept_charset.rs | 69 -- src/header/common/accept_encoding.rs | 72 -- src/header/common/accept_language.rs | 75 -- src/header/common/allow.rs | 82 -- src/header/common/cache_control.rs | 254 ------- src/header/common/content_disposition.rs | 915 ----------------------- src/header/common/content_language.rs | 65 -- src/header/common/content_range.rs | 210 ------ src/header/common/content_type.rs | 122 --- src/header/common/date.rs | 42 -- src/header/common/etag.rs | 96 --- src/header/common/expires.rs | 39 - src/header/common/if_match.rs | 70 -- src/header/common/if_modified_since.rs | 39 - src/header/common/if_none_match.rs | 92 --- src/header/common/if_range.rs | 117 --- src/header/common/if_unmodified_since.rs | 40 - src/header/common/last_modified.rs | 38 - src/header/common/mod.rs | 350 --------- src/header/common/range.rs | 434 ----------- src/header/mod.rs | 471 ------------ src/header/shared/charset.rs | 152 ---- src/header/shared/encoding.rs | 59 -- src/header/shared/entity.rs | 266 ------- src/header/shared/httpdate.rs | 119 --- src/header/shared/mod.rs | 14 - src/header/shared/quality_item.rs | 294 -------- src/httpresponse.rs | 4 +- src/lib.rs | 13 +- src/server/input.rs | 288 ------- src/server/mod.rs | 3 - src/server/service.rs | 273 ------- src/ws/context.rs | 334 --------- 40 files changed, 273 insertions(+), 5689 deletions(-) rename src/{server/output.rs => h1/response.rs} (100%) create mode 100644 src/header.rs delete mode 100644 src/header/common/accept.rs delete mode 100644 src/header/common/accept_charset.rs delete mode 100644 src/header/common/accept_encoding.rs delete mode 100644 src/header/common/accept_language.rs delete mode 100644 src/header/common/allow.rs delete mode 100644 src/header/common/cache_control.rs delete mode 100644 src/header/common/content_disposition.rs delete mode 100644 src/header/common/content_language.rs delete mode 100644 src/header/common/content_range.rs delete mode 100644 src/header/common/content_type.rs delete mode 100644 src/header/common/date.rs delete mode 100644 src/header/common/etag.rs delete mode 100644 src/header/common/expires.rs delete mode 100644 src/header/common/if_match.rs delete mode 100644 src/header/common/if_modified_since.rs delete mode 100644 src/header/common/if_none_match.rs delete mode 100644 src/header/common/if_range.rs delete mode 100644 src/header/common/if_unmodified_since.rs delete mode 100644 src/header/common/last_modified.rs delete mode 100644 src/header/common/mod.rs delete mode 100644 src/header/common/range.rs delete mode 100644 src/header/mod.rs delete mode 100644 src/header/shared/charset.rs delete mode 100644 src/header/shared/encoding.rs delete mode 100644 src/header/shared/entity.rs delete mode 100644 src/header/shared/httpdate.rs delete mode 100644 src/header/shared/mod.rs delete mode 100644 src/header/shared/quality_item.rs delete mode 100644 src/server/input.rs delete mode 100644 src/server/service.rs delete mode 100644 src/ws/context.rs diff --git a/Cargo.toml b/Cargo.toml index 86012175..870f772e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,7 +28,7 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session", "brotli", "flate2-c", "cell"] +default = ["session", "cell"] # tls tls = ["native-tls", "tokio-tls", "actix-net/tls"] @@ -48,15 +48,6 @@ uds = ["tokio-uds"] # sessions feature, session require "ring" crate and c compiler session = ["cookie/secure"] -# brotli encoding, requires c compiler -brotli = ["brotli2"] - -# miniz-sys backend for flate2 crate -flate2-c = ["flate2/miniz-sys"] - -# rust backend for flate2 crate -flate2-rust = ["flate2/rust_backend"] - cell = ["actix-net/cell"] [dependencies] @@ -70,23 +61,17 @@ http = "^0.1.8" httparse = "1.3" log = "0.4" mime = "0.3" -mime_guess = "2.0.0-alpha" percent-encoding = "1.0" rand = "0.5" -regex = "1.0" serde = "1.0" serde_json = "1.0" sha1 = "0.6" -smallvec = "0.6" time = "0.1" encoding = "0.2" -language-tags = "0.2" lazy_static = "1.0" serde_urlencoded = "^0.5.3" url = { version="1.7", features=["query_encoding"] } cookie = { version="0.11", features=["percent-encode"] } -brotli2 = { version="^0.3.2", optional = true } -flate2 = { version="^1.0.2", optional = true, default-features = false } failure = "^0.1.2" diff --git a/src/h1/codec.rs b/src/h1/codec.rs index ac54194a..dd6b26ba 100644 --- a/src/h1/codec.rs +++ b/src/h1/codec.rs @@ -6,6 +6,7 @@ use tokio_codec::{Decoder, Encoder}; use super::decoder::H1Decoder; pub use super::decoder::InMessage; +use super::response::{ResponseInfo, ResponseLength}; use body::Body; use error::ParseError; use helpers; @@ -13,7 +14,6 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use http::{Method, Version}; use httpresponse::HttpResponse; use request::RequestPool; -use server::output::{ResponseInfo, ResponseLength}; /// Http response pub enum OutMessage { diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index f777648e..2b524556 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -12,14 +12,13 @@ use tokio_io::{AsyncRead, AsyncWrite}; use tokio_timer::Delay; use error::{ParseError, PayloadError}; -use payload::{Payload, PayloadStatus, PayloadWriter}; +use payload::{Payload, PayloadSender, PayloadStatus, PayloadWriter}; use body::Body; use config::ServiceConfig; use error::DispatchError; use httpresponse::HttpResponse; use request::Request; -use server::input::PayloadType; use super::codec::{Codec, InMessage, OutMessage}; @@ -50,7 +49,7 @@ where config: ServiceConfig, state: State, - payload: Option, + payload: Option, messages: VecDeque, ka_expire: Instant, @@ -316,7 +315,7 @@ where // payload let (ps, pl) = Payload::new(false); *msg.inner.payload.borrow_mut() = Some(pl); - self.payload = Some(PayloadType::new(&msg.inner.headers, ps)); + self.payload = Some(ps); self.messages.push_back(msg); } diff --git a/src/h1/mod.rs b/src/h1/mod.rs index 1a2bb018..1653227b 100644 --- a/src/h1/mod.rs +++ b/src/h1/mod.rs @@ -2,6 +2,7 @@ mod codec; mod decoder; mod dispatcher; +mod response; mod service; pub use self::codec::{Codec, InMessage, OutMessage}; diff --git a/src/server/output.rs b/src/h1/response.rs similarity index 100% rename from src/server/output.rs rename to src/h1/response.rs diff --git a/src/header.rs b/src/header.rs new file mode 100644 index 00000000..7b32d6c2 --- /dev/null +++ b/src/header.rs @@ -0,0 +1,265 @@ +//! Various http headers + +use std::fmt; +use std::str::FromStr; + +use bytes::{Bytes, BytesMut}; +use mime::Mime; +use modhttp::header::GetAll; +use modhttp::Error as HttpError; +use percent_encoding; + +pub use modhttp::header::*; + +use error::ParseError; +use httpmessage::HttpMessage; + +#[doc(hidden)] +/// A trait for any object that will represent a header field and value. +pub trait Header +where + Self: IntoHeaderValue, +{ + /// Returns the name of the header field + fn name() -> HeaderName; + + /// Parse a header + fn parse(msg: &T) -> Result; +} + +#[doc(hidden)] +/// A trait for any object that can be Converted to a `HeaderValue` +pub trait IntoHeaderValue: Sized { + /// The type returned in the event of a conversion error. + type Error: Into; + + /// Try to convert value to a Header value. + fn try_into(self) -> Result; +} + +impl IntoHeaderValue for HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + Ok(self) + } +} + +impl<'a> IntoHeaderValue for &'a str { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + self.parse() + } +} + +impl<'a> IntoHeaderValue for &'a [u8] { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_bytes(self) + } +} + +impl IntoHeaderValue for Bytes { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(self) + } +} + +impl IntoHeaderValue for Vec { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for String { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(self)) + } +} + +impl IntoHeaderValue for Mime { + type Error = InvalidHeaderValueBytes; + + #[inline] + fn try_into(self) -> Result { + HeaderValue::from_shared(Bytes::from(format!("{}", self))) + } +} + +/// Represents supported types of content encodings +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ContentEncoding { + /// Automatically select encoding based on encoding negotiation + Auto, + /// A format using the Brotli algorithm + Br, + /// A format using the zlib structure with deflate algorithm + Deflate, + /// Gzip algorithm + Gzip, + /// Indicates the identity function (i.e. no compression, nor modification) + Identity, +} + +impl ContentEncoding { + #[inline] + /// Is the content compressed? + pub fn is_compression(self) -> bool { + match self { + ContentEncoding::Identity | ContentEncoding::Auto => false, + _ => true, + } + } + + #[inline] + /// Convert content encoding to string + pub fn as_str(self) -> &'static str { + match self { + ContentEncoding::Br => "br", + ContentEncoding::Gzip => "gzip", + ContentEncoding::Deflate => "deflate", + ContentEncoding::Identity | ContentEncoding::Auto => "identity", + } + } + + #[inline] + /// default quality value + pub fn quality(self) -> f64 { + match self { + ContentEncoding::Br => 1.1, + ContentEncoding::Gzip => 1.0, + ContentEncoding::Deflate => 0.9, + ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + } + } +} + +// TODO: remove memory allocation +impl<'a> From<&'a str> for ContentEncoding { + fn from(s: &'a str) -> ContentEncoding { + match AsRef::::as_ref(&s.trim().to_lowercase()) { + "br" => ContentEncoding::Br, + "gzip" => ContentEncoding::Gzip, + "deflate" => ContentEncoding::Deflate, + _ => ContentEncoding::Identity, + } + } +} + +#[doc(hidden)] +pub(crate) struct Writer { + buf: BytesMut, +} + +impl Writer { + fn new() -> Writer { + Writer { + buf: BytesMut::new(), + } + } + fn take(&mut self) -> Bytes { + self.buf.take().freeze() + } +} + +impl fmt::Write for Writer { + #[inline] + fn write_str(&mut self, s: &str) -> fmt::Result { + self.buf.extend_from_slice(s.as_bytes()); + Ok(()) + } + + #[inline] + fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { + fmt::write(self, args) + } +} + +#[inline] +#[doc(hidden)] +/// Reads a comma-delimited raw header into a Vec. +pub fn from_comma_delimited( + all: GetAll, +) -> Result, ParseError> { + let mut result = Vec::new(); + for h in all { + let s = h.to_str().map_err(|_| ParseError::Header)?; + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }).filter_map(|x| x.trim().parse().ok()), + ) + } + Ok(result) +} + +#[inline] +#[doc(hidden)] +/// Reads a single string when parsing a header. +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { + if let Some(line) = val { + let line = line.to_str().map_err(|_| ParseError::Header)?; + if !line.is_empty() { + return T::from_str(line).or(Err(ParseError::Header)); + } + } + Err(ParseError::Header) +} + +#[inline] +#[doc(hidden)] +/// Format an array into a comma-delimited string. +pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result +where + T: fmt::Display, +{ + let mut iter = parts.iter(); + if let Some(part) = iter.next() { + fmt::Display::fmt(part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(part, f)?; + } + Ok(()) +} + +/// Percent encode a sequence of bytes with a character set defined in +/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] +/// +/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { + let encoded = + percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} +mod percent_encoding_http { + use percent_encoding; + + // internal module because macro is hard-coded to make a public item + // but we don't want to public export this item + define_encode_set! { + // This encode set is used for HTTP header values and is defined at + // https://tools.ietf.org/html/rfc5987#section-3.2 + pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { + ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', + '[', '\\', ']', '{', '}' + } + } +} diff --git a/src/header/common/accept.rs b/src/header/common/accept.rs deleted file mode 100644 index 1ba321ce..00000000 --- a/src/header/common/accept.rs +++ /dev/null @@ -1,159 +0,0 @@ -use header::{qitem, QualityItem}; -use http::header as http; -use mime::{self, Mime}; - -header! { - /// `Accept` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.2) - /// - /// The `Accept` header field can be used by user agents to specify - /// response media types that are acceptable. Accept header fields can - /// be used to indicate that the request is specifically limited to a - /// small set of desired types, as in the case of a request for an - /// in-line image - /// - /// # ABNF - /// - /// ```text - /// Accept = #( media-range [ accept-params ] ) - /// - /// media-range = ( "*/*" - /// / ( type "/" "*" ) - /// / ( type "/" subtype ) - /// ) *( OWS ";" OWS parameter ) - /// accept-params = weight *( accept-ext ) - /// accept-ext = OWS ";" OWS token [ "=" ( token / quoted-string ) ] - /// ``` - /// - /// # Example values - /// * `audio/*; q=0.2, audio/basic` - /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::APPLICATION_JSON), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// - /// builder.set( - /// Accept(vec![ - /// qitem(mime::TEXT_HTML), - /// qitem("application/xhtml+xml".parse().unwrap()), - /// QualityItem::new( - /// mime::TEXT_XML, - /// q(900) - /// ), - /// qitem("image/webp".parse().unwrap()), - /// QualityItem::new( - /// mime::STAR_STAR, - /// q(800) - /// ), - /// ]) - /// ); - /// # } - /// ``` - (Accept, http::ACCEPT) => (QualityItem)+ - - test_accept { - // Tests from the RFC - test_header!( - test1, - vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ - QualityItem::new("audio/*".parse().unwrap(), q(200)), - qitem("audio/basic".parse().unwrap()), - ]))); - test_header!( - test2, - vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ - QualityItem::new(TEXT_PLAIN, q(500)), - qitem(TEXT_HTML), - QualityItem::new( - "text/x-dvi".parse().unwrap(), - q(800)), - qitem("text/x-c".parse().unwrap()), - ]))); - // Custom tests - test_header!( - test3, - vec![b"text/plain; charset=utf-8"], - Some(Accept(vec![ - qitem(TEXT_PLAIN_UTF_8), - ]))); - test_header!( - test4, - vec![b"text/plain; charset=utf-8; q=0.5"], - Some(Accept(vec![ - QualityItem::new(TEXT_PLAIN_UTF_8, - q(500)), - ]))); - - #[test] - fn test_fuzzing1() { - use test::TestRequest; - let req = TestRequest::with_header(super::http::ACCEPT, "chunk#;e").finish(); - let header = Accept::parse(&req); - assert!(header.is_ok()); - } - } -} - -impl Accept { - /// A constructor to easily create `Accept: */*`. - pub fn star() -> Accept { - Accept(vec![qitem(mime::STAR_STAR)]) - } - - /// A constructor to easily create `Accept: application/json`. - pub fn json() -> Accept { - Accept(vec![qitem(mime::APPLICATION_JSON)]) - } - - /// A constructor to easily create `Accept: text/*`. - pub fn text() -> Accept { - Accept(vec![qitem(mime::TEXT_STAR)]) - } - - /// A constructor to easily create `Accept: image/*`. - pub fn image() -> Accept { - Accept(vec![qitem(mime::IMAGE_STAR)]) - } -} diff --git a/src/header/common/accept_charset.rs b/src/header/common/accept_charset.rs deleted file mode 100644 index 49a7237a..00000000 --- a/src/header/common/accept_charset.rs +++ /dev/null @@ -1,69 +0,0 @@ -use header::{Charset, QualityItem, ACCEPT_CHARSET}; - -header! { - /// `Accept-Charset` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.3) - /// - /// The `Accept-Charset` header field can be sent by a user agent to - /// indicate what charsets are acceptable in textual response content. - /// This field allows user agents capable of understanding more - /// comprehensive or special-purpose charsets to signal that capability - /// to an origin server that is capable of representing information in - /// those charsets. - /// - /// # ABNF - /// - /// ```text - /// Accept-Charset = 1#( ( charset / "*" ) [ weight ] ) - /// ``` - /// - /// # Example values - /// * `iso-8859-5, unicode-1-1;q=0.8` - /// - /// # Examples - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![ - /// QualityItem::new(Charset::Us_Ascii, q(900)), - /// QualityItem::new(Charset::Iso_8859_10, q(200)), - /// ]) - /// ); - /// # } - /// ``` - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) - /// ); - /// # } - /// ``` - (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ - - test_accept_charset { - /// Test case from RFC - test_header!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]); - } -} diff --git a/src/header/common/accept_encoding.rs b/src/header/common/accept_encoding.rs deleted file mode 100644 index c90f529b..00000000 --- a/src/header/common/accept_encoding.rs +++ /dev/null @@ -1,72 +0,0 @@ -use header::{Encoding, QualityItem}; - -header! { - /// `Accept-Encoding` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.4) - /// - /// The `Accept-Encoding` header field can be used by user agents to - /// indicate what response content-codings are - /// acceptable in the response. An `identity` token is used as a synonym - /// for "no encoding" in order to communicate when no encoding is - /// preferred. - /// - /// # ABNF - /// - /// ```text - /// Accept-Encoding = #( codings [ weight ] ) - /// codings = content-coding / "identity" / "*" - /// ``` - /// - /// # Example values - /// * `compress, gzip` - /// * `` - /// * `*` - /// * `compress;q=0.5, gzip;q=1` - /// * `gzip;q=1.0, identity; q=0.5, *;q=0` - /// - /// # Examples - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![qitem(Encoding::Chunked)]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// qitem(Encoding::Gzip), - /// qitem(Encoding::Deflate), - /// ]) - /// ); - /// ``` - /// ``` - /// use hyper::header::{Headers, AcceptEncoding, Encoding, QualityItem, q, qitem}; - /// - /// let mut headers = Headers::new(); - /// headers.set( - /// AcceptEncoding(vec![ - /// qitem(Encoding::Chunked), - /// QualityItem::new(Encoding::Gzip, q(600)), - /// QualityItem::new(Encoding::EncodingExt("*".to_owned()), q(0)), - /// ]) - /// ); - /// ``` - (AcceptEncoding, "Accept-Encoding") => (QualityItem)* - - test_accept_encoding { - // From the RFC - test_header!(test1, vec![b"compress, gzip"]); - test_header!(test2, vec![b""], Some(AcceptEncoding(vec![]))); - test_header!(test3, vec![b"*"]); - // Note: Removed quality 1 from gzip - test_header!(test4, vec![b"compress;q=0.5, gzip"]); - // Note: Removed quality 1 from gzip - test_header!(test5, vec![b"gzip, identity; q=0.5, *;q=0"]); - } -} diff --git a/src/header/common/accept_language.rs b/src/header/common/accept_language.rs deleted file mode 100644 index 25fd97df..00000000 --- a/src/header/common/accept_language.rs +++ /dev/null @@ -1,75 +0,0 @@ -use header::{QualityItem, ACCEPT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Accept-Language` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-5.3.5) - /// - /// The `Accept-Language` header field can be used by user agents to - /// indicate the set of natural languages that are preferred in the - /// response. - /// - /// # ABNF - /// - /// ```text - /// Accept-Language = 1#( language-range [ weight ] ) - /// language-range = - /// ``` - /// - /// # Example values - /// * `da, en-gb;q=0.8, en;q=0.7` - /// * `en-us;q=1.0, en;q=0.5, fr` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # extern crate language_tags; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// let mut langtag: LanguageTag = Default::default(); - /// langtag.language = Some("en".to_owned()); - /// langtag.region = Some("US".to_owned()); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// AcceptLanguage(vec![ - /// qitem(langtag!(da)), - /// QualityItem::new(langtag!(en;;;GB), q(800)), - /// QualityItem::new(langtag!(en), q(700)), - /// ]) - /// ); - /// # } - /// ``` - (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ - - test_accept_language { - // From the RFC - test_header!(test1, vec![b"da, en-gb;q=0.8, en;q=0.7"]); - // Own test - test_header!( - test2, vec![b"en-US, en; q=0.5, fr"], - Some(AcceptLanguage(vec![ - qitem("en-US".parse().unwrap()), - QualityItem::new("en".parse().unwrap(), q(500)), - qitem("fr".parse().unwrap()), - ]))); - } -} diff --git a/src/header/common/allow.rs b/src/header/common/allow.rs deleted file mode 100644 index 089c823d..00000000 --- a/src/header/common/allow.rs +++ /dev/null @@ -1,82 +0,0 @@ -use http::header; -use http::Method; - -header! { - /// `Allow` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.4.1) - /// - /// The `Allow` header field lists the set of methods advertised as - /// supported by the target resource. The purpose of this field is - /// strictly to inform the recipient of valid request methods associated - /// with the resource. - /// - /// # ABNF - /// - /// ```text - /// Allow = #method - /// ``` - /// - /// # Example values - /// * `GET, HEAD, PUT` - /// * `OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH, fOObAr` - /// * `` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Allow; - /// use actix_http::http::Method; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![Method::GET]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// use actix_http::HttpResponse; - /// use actix_http::http::{Method, header::Allow}; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// Allow(vec![ - /// Method::GET, - /// Method::POST, - /// Method::PATCH, - /// ]) - /// ); - /// # } - /// ``` - (Allow, header::ALLOW) => (Method)* - - test_allow { - // From the RFC - test_header!( - test1, - vec![b"GET, HEAD, PUT"], - Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT]))); - // Own tests - test_header!( - test2, - vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"], - Some(HeaderField(vec![ - Method::OPTIONS, - Method::GET, - Method::PUT, - Method::POST, - Method::DELETE, - Method::HEAD, - Method::TRACE, - Method::CONNECT, - Method::PATCH]))); - test_header!( - test3, - vec![b""], - Some(HeaderField(Vec::::new()))); - } -} diff --git a/src/header/common/cache_control.rs b/src/header/common/cache_control.rs deleted file mode 100644 index 4379b6f7..00000000 --- a/src/header/common/cache_control.rs +++ /dev/null @@ -1,254 +0,0 @@ -use header::{fmt_comma_delimited, from_comma_delimited}; -use header::{Header, IntoHeaderValue, Writer}; -use http::header; -use std::fmt::{self, Write}; -use std::str::FromStr; - -/// `Cache-Control` header, defined in [RFC7234](https://tools.ietf.org/html/rfc7234#section-5.2) -/// -/// The `Cache-Control` header field is used to specify directives for -/// caches along the request/response chain. Such cache directives are -/// unidirectional in that the presence of a directive in a request does -/// not imply that the same directive is to be given in the response. -/// -/// # ABNF -/// -/// ```text -/// Cache-Control = 1#cache-directive -/// cache-directive = token [ "=" ( token / quoted-string ) ] -/// ``` -/// -/// # Example values -/// -/// * `no-cache` -/// * `private, community="UCI"` -/// * `max-age=30` -/// -/// # Examples -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); -/// ``` -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{CacheControl, CacheDirective}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(CacheControl(vec![ -/// CacheDirective::NoCache, -/// CacheDirective::Private, -/// CacheDirective::MaxAge(360u32), -/// CacheDirective::Extension("foo".to_owned(), Some("bar".to_owned())), -/// ])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub struct CacheControl(pub Vec); - -__hyper__deref!(CacheControl => Vec); - -//TODO: this could just be the header! macro -impl Header for CacheControl { - fn name() -> header::HeaderName { - header::CACHE_CONTROL - } - - #[inline] - fn parse(msg: &T) -> Result - where - T: ::HttpMessage, - { - let directives = from_comma_delimited(msg.headers().get_all(Self::name()))?; - if !directives.is_empty() { - Ok(CacheControl(directives)) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for CacheControl { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt_comma_delimited(f, &self[..]) - } -} - -impl IntoHeaderValue for CacheControl { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -/// `CacheControl` contains a list of these directives. -#[derive(PartialEq, Clone, Debug)] -pub enum CacheDirective { - /// "no-cache" - NoCache, - /// "no-store" - NoStore, - /// "no-transform" - NoTransform, - /// "only-if-cached" - OnlyIfCached, - - // request directives - /// "max-age=delta" - MaxAge(u32), - /// "max-stale=delta" - MaxStale(u32), - /// "min-fresh=delta" - MinFresh(u32), - - // response directives - /// "must-revalidate" - MustRevalidate, - /// "public" - Public, - /// "private" - Private, - /// "proxy-revalidate" - ProxyRevalidate, - /// "s-maxage=delta" - SMaxAge(u32), - - /// Extension directives. Optionally include an argument. - Extension(String, Option), -} - -impl fmt::Display for CacheDirective { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - use self::CacheDirective::*; - fmt::Display::fmt( - match *self { - NoCache => "no-cache", - NoStore => "no-store", - NoTransform => "no-transform", - OnlyIfCached => "only-if-cached", - - MaxAge(secs) => return write!(f, "max-age={}", secs), - MaxStale(secs) => return write!(f, "max-stale={}", secs), - MinFresh(secs) => return write!(f, "min-fresh={}", secs), - - MustRevalidate => "must-revalidate", - Public => "public", - Private => "private", - ProxyRevalidate => "proxy-revalidate", - SMaxAge(secs) => return write!(f, "s-maxage={}", secs), - - Extension(ref name, None) => &name[..], - Extension(ref name, Some(ref arg)) => { - return write!(f, "{}={}", name, arg) - } - }, - f, - ) - } -} - -impl FromStr for CacheDirective { - type Err = Option<::Err>; - fn from_str(s: &str) -> Result::Err>> { - use self::CacheDirective::*; - match s { - "no-cache" => Ok(NoCache), - "no-store" => Ok(NoStore), - "no-transform" => Ok(NoTransform), - "only-if-cached" => Ok(OnlyIfCached), - "must-revalidate" => Ok(MustRevalidate), - "public" => Ok(Public), - "private" => Ok(Private), - "proxy-revalidate" => Ok(ProxyRevalidate), - "" => Err(None), - _ => match s.find('=') { - Some(idx) if idx + 1 < s.len() => { - match (&s[..idx], (&s[idx + 1..]).trim_matches('"')) { - ("max-age", secs) => secs.parse().map(MaxAge).map_err(Some), - ("max-stale", secs) => secs.parse().map(MaxStale).map_err(Some), - ("min-fresh", secs) => secs.parse().map(MinFresh).map_err(Some), - ("s-maxage", secs) => secs.parse().map(SMaxAge).map_err(Some), - (left, right) => { - Ok(Extension(left.to_owned(), Some(right.to_owned()))) - } - } - } - Some(_) => Err(None), - None => Ok(Extension(s.to_owned(), None)), - }, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use header::Header; - use test::TestRequest; - - #[test] - fn test_parse_multiple_headers() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::NoCache, - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_argument() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") - .finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::MaxAge(100), - CacheDirective::Private, - ])) - ) - } - - #[test] - fn test_parse_quote_form() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![CacheDirective::MaxAge(200)])) - ) - } - - #[test] - fn test_parse_extension() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); - let cache = Header::parse(&req); - assert_eq!( - cache.ok(), - Some(CacheControl(vec![ - CacheDirective::Extension("foo".to_owned(), None), - CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())), - ])) - ) - } - - #[test] - fn test_parse_bad_syntax() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); - let cache: Result = Header::parse(&req); - assert_eq!(cache.ok(), None) - } -} diff --git a/src/header/common/content_disposition.rs b/src/header/common/content_disposition.rs deleted file mode 100644 index 0efc4fb0..00000000 --- a/src/header/common/content_disposition.rs +++ /dev/null @@ -1,915 +0,0 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml - -use header; -use header::ExtendedValue; -use header::{Header, IntoHeaderValue, Writer}; -use regex::Regex; - -use std::fmt::{self, Write}; - -/// Split at the index of the first `needle` if it exists or at the end. -fn split_once(haystack: &str, needle: char) -> (&str, &str) { - haystack.find(needle).map_or_else( - || (haystack, ""), - |sc| { - let (first, last) = haystack.split_at(sc); - (first, last.split_at(1).1) - }, - ) -} - -/// Split at the index of the first `needle` if it exists or at the end, trim the right of the -/// first part and the left of the last part. -fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { - let (first, last) = split_once(haystack, needle); - (first.trim_right(), last.trim_left()) -} - -/// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionType { - /// Inline implies default processing - Inline, - /// Attachment implies that the recipient should prompt the user to save the response locally, - /// rather than process it normally (as per its media type). - Attachment, - /// Used in *multipart/form-data* as defined in - /// [RFC7578](https://tools.ietf.org/html/rfc7578) to carry the field name and the file name. - FormData, - /// Extension type. Should be handled by recipients the same way as Attachment - Ext(String), -} - -impl<'a> From<&'a str> for DispositionType { - fn from(origin: &'a str) -> DispositionType { - if origin.eq_ignore_ascii_case("inline") { - DispositionType::Inline - } else if origin.eq_ignore_ascii_case("attachment") { - DispositionType::Attachment - } else if origin.eq_ignore_ascii_case("form-data") { - DispositionType::FormData - } else { - DispositionType::Ext(origin.to_owned()) - } - } -} - -/// Parameter in [`ContentDisposition`]. -/// -/// # Examples -/// ``` -/// use actix_http::http::header::DispositionParam; -/// -/// let param = DispositionParam::Filename(String::from("sample.txt")); -/// assert!(param.is_filename()); -/// assert_eq!(param.as_filename().unwrap(), "sample.txt"); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum DispositionParam { - /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from - /// the form. - Name(String), - /// A plain file name. - Filename(String), - /// An extended file name. It must not exist for `ContentType::Formdata` according to - /// [RFC7578 Section 4.2](https://tools.ietf.org/html/rfc7578#section-4.2). - FilenameExt(ExtendedValue), - /// An unrecognized regular parameter as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *reg-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *token "=" value*. Recipients should - /// ignore unrecognizable parameters. - Unknown(String, String), - /// An unrecognized extended paramater as defined in - /// [RFC5987](https://tools.ietf.org/html/rfc5987) as *ext-parameter*, in - /// [RFC6266](https://tools.ietf.org/html/rfc6266) as *ext-token "=" ext-value*. The single - /// trailling asterisk is not included. Recipients should ignore unrecognizable parameters. - UnknownExt(String, ExtendedValue), -} - -impl DispositionParam { - /// Returns `true` if the paramater is [`Name`](DispositionParam::Name). - #[inline] - pub fn is_name(&self) -> bool { - self.as_name().is_some() - } - - /// Returns `true` if the paramater is [`Filename`](DispositionParam::Filename). - #[inline] - pub fn is_filename(&self) -> bool { - self.as_filename().is_some() - } - - /// Returns `true` if the paramater is [`FilenameExt`](DispositionParam::FilenameExt). - #[inline] - pub fn is_filename_ext(&self) -> bool { - self.as_filename_ext().is_some() - } - - /// Returns `true` if the paramater is [`Unknown`](DispositionParam::Unknown) and the `name` - #[inline] - /// matches. - pub fn is_unknown>(&self, name: T) -> bool { - self.as_unknown(name).is_some() - } - - /// Returns `true` if the paramater is [`UnknownExt`](DispositionParam::UnknownExt) and the - /// `name` matches. - #[inline] - pub fn is_unknown_ext>(&self, name: T) -> bool { - self.as_unknown_ext(name).is_some() - } - - /// Returns the name if applicable. - #[inline] - pub fn as_name(&self) -> Option<&str> { - match self { - DispositionParam::Name(ref name) => Some(name.as_str()), - _ => None, - } - } - - /// Returns the filename if applicable. - #[inline] - pub fn as_filename(&self) -> Option<&str> { - match self { - DispositionParam::Filename(ref filename) => Some(filename.as_str()), - _ => None, - } - } - - /// Returns the filename* if applicable. - #[inline] - pub fn as_filename_ext(&self) -> Option<&ExtendedValue> { - match self { - DispositionParam::FilenameExt(ref value) => Some(value), - _ => None, - } - } - - /// Returns the value of the unrecognized regular parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown>(&self, name: T) -> Option<&str> { - match self { - DispositionParam::Unknown(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value.as_str()) - } - _ => None, - } - } - - /// Returns the value of the unrecognized extended parameter if it is - /// [`Unknown`](DispositionParam::Unknown) and the `name` matches. - #[inline] - pub fn as_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - match self { - DispositionParam::UnknownExt(ref ext_name, ref value) - if ext_name.eq_ignore_ascii_case(name.as_ref()) => - { - Some(value) - } - _ => None, - } - } -} - -/// A *Content-Disposition* header. It is compatible to be used either as -/// [a response header for the main body](https://mdn.io/Content-Disposition#As_a_response_header_for_the_main_body) -/// as (re)defined in [RFC6266](https://tools.ietf.org/html/rfc6266), or as -/// [a header for a multipart body](https://mdn.io/Content-Disposition#As_a_header_for_a_multipart_body) -/// as (re)defined in [RFC7587](https://tools.ietf.org/html/rfc7578). -/// -/// In a regular HTTP response, the *Content-Disposition* response header is a header indicating if -/// the content is expected to be displayed *inline* in the browser, that is, as a Web page or as -/// part of a Web page, or as an attachment, that is downloaded and saved locally, and also can be -/// used to attach additional metadata, such as the filename to use when saving the response payload -/// locally. -/// -/// In a *multipart/form-data* body, the HTTP *Content-Disposition* general header is a header that -/// can be used on the subpart of a multipart body to give information about the field it applies to. -/// The subpart is delimited by the boundary defined in the *Content-Type* header. Used on the body -/// itself, *Content-Disposition* has no effect. -/// -/// # ABNF - -/// ```text -/// content-disposition = "Content-Disposition" ":" -/// disposition-type *( ";" disposition-parm ) -/// -/// disposition-type = "inline" | "attachment" | disp-ext-type -/// ; case-insensitive -/// -/// disp-ext-type = token -/// -/// disposition-parm = filename-parm | disp-ext-parm -/// -/// filename-parm = "filename" "=" value -/// | "filename*" "=" ext-value -/// -/// disp-ext-parm = token "=" value -/// | ext-token "=" ext-value -/// -/// ext-token = -/// ``` -/// -/// **Note**: filename* [must not](https://tools.ietf.org/html/rfc7578#section-4.2) be used within -/// *multipart/form-data*. -/// -/// # Example -/// -/// ``` -/// use actix_http::http::header::{ -/// Charset, ContentDisposition, DispositionParam, DispositionType, -/// ExtendedValue, -/// }; -/// -/// let cd1 = ContentDisposition { -/// disposition: DispositionType::Attachment, -/// parameters: vec![DispositionParam::FilenameExt(ExtendedValue { -/// charset: Charset::Iso_8859_1, // The character set for the bytes of the filename -/// language_tag: None, // The optional language tag (see `language-tag` crate) -/// value: b"\xa9 Copyright 1989.txt".to_vec(), // the actual bytes of the filename -/// })], -/// }; -/// assert!(cd1.is_attachment()); -/// assert!(cd1.get_filename_ext().is_some()); -/// -/// let cd2 = ContentDisposition { -/// disposition: DispositionType::FormData, -/// parameters: vec![ -/// DispositionParam::Name(String::from("file")), -/// DispositionParam::Filename(String::from("bill.odt")), -/// ], -/// }; -/// assert_eq!(cd2.get_name(), Some("file")); // field name -/// assert_eq!(cd2.get_filename(), Some("bill.odt")); -/// ``` -/// -/// # WARN -/// If "filename" parameter is supplied, do not use the file name blindly, check and possibly -/// change to match local file system conventions if applicable, and do not use directory path -/// information that may be present. See [RFC2183](https://tools.ietf.org/html/rfc2183#section-2.3) -/// . -#[derive(Clone, Debug, PartialEq)] -pub struct ContentDisposition { - /// The disposition type - pub disposition: DispositionType, - /// Disposition parameters - pub parameters: Vec, -} - -impl ContentDisposition { - /// Parse a raw Content-Disposition header value. - pub fn from_raw(hv: &header::HeaderValue) -> Result { - // `header::from_one_raw_str` invokes `hv.to_str` which assumes `hv` contains only visible - // ASCII characters. So `hv.as_bytes` is necessary here. - let hv = String::from_utf8(hv.as_bytes().to_vec()) - .map_err(|_| ::error::ParseError::Header)?; - let (disp_type, mut left) = split_once_and_trim(hv.as_str().trim(), ';'); - if disp_type.is_empty() { - return Err(::error::ParseError::Header); - } - let mut cd = ContentDisposition { - disposition: disp_type.into(), - parameters: Vec::new(), - }; - - while !left.is_empty() { - let (param_name, new_left) = split_once_and_trim(left, '='); - if param_name.is_empty() || param_name == "*" || new_left.is_empty() { - return Err(::error::ParseError::Header); - } - left = new_left; - if param_name.ends_with('*') { - // extended parameters - let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk - let (ext_value, new_left) = split_once_and_trim(left, ';'); - left = new_left; - let ext_value = header::parse_extended_value(ext_value)?; - - let param = if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::FilenameExt(ext_value) - } else { - DispositionParam::UnknownExt(param_name.to_owned(), ext_value) - }; - cd.parameters.push(param); - } else { - // regular parameters - let value = if left.starts_with('\"') { - // quoted-string: defined in RFC6266 -> RFC2616 Section 3.6 - let mut escaping = false; - let mut quoted_string = vec![]; - let mut end = None; - // search for closing quote - for (i, &c) in left.as_bytes().iter().skip(1).enumerate() { - if escaping { - escaping = false; - quoted_string.push(c); - } else if c == 0x5c { - // backslash - escaping = true; - } else if c == 0x22 { - // double quote - end = Some(i + 1); // cuz skipped 1 for the leading quote - break; - } else { - quoted_string.push(c); - } - } - left = &left[end.ok_or(::error::ParseError::Header)? + 1..]; - left = split_once(left, ';').1.trim_left(); - // In fact, it should not be Err if the above code is correct. - String::from_utf8(quoted_string) - .map_err(|_| ::error::ParseError::Header)? - } else { - // token: won't contains semicolon according to RFC 2616 Section 2.2 - let (token, new_left) = split_once_and_trim(left, ';'); - left = new_left; - token.to_owned() - }; - if value.is_empty() { - return Err(::error::ParseError::Header); - } - - let param = if param_name.eq_ignore_ascii_case("name") { - DispositionParam::Name(value) - } else if param_name.eq_ignore_ascii_case("filename") { - DispositionParam::Filename(value) - } else { - DispositionParam::Unknown(param_name.to_owned(), value) - }; - cd.parameters.push(param); - } - } - - Ok(cd) - } - - /// Returns `true` if it is [`Inline`](DispositionType::Inline). - pub fn is_inline(&self) -> bool { - match self.disposition { - DispositionType::Inline => true, - _ => false, - } - } - - /// Returns `true` if it is [`Attachment`](DispositionType::Attachment). - pub fn is_attachment(&self) -> bool { - match self.disposition { - DispositionType::Attachment => true, - _ => false, - } - } - - /// Returns `true` if it is [`FormData`](DispositionType::FormData). - pub fn is_form_data(&self) -> bool { - match self.disposition { - DispositionType::FormData => true, - _ => false, - } - } - - /// Returns `true` if it is [`Ext`](DispositionType::Ext) and the `disp_type` matches. - pub fn is_ext>(&self, disp_type: T) -> bool { - match self.disposition { - DispositionType::Ext(ref t) - if t.eq_ignore_ascii_case(disp_type.as_ref()) => - { - true - } - _ => false, - } - } - - /// Return the value of *name* if exists. - pub fn get_name(&self) -> Option<&str> { - self.parameters.iter().filter_map(|p| p.as_name()).nth(0) - } - - /// Return the value of *filename* if exists. - pub fn get_filename(&self) -> Option<&str> { - self.parameters - .iter() - .filter_map(|p| p.as_filename()) - .nth(0) - } - - /// Return the value of *filename\** if exists. - pub fn get_filename_ext(&self) -> Option<&ExtendedValue> { - self.parameters - .iter() - .filter_map(|p| p.as_filename_ext()) - .nth(0) - } - - /// Return the value of the parameter which the `name` matches. - pub fn get_unknown>(&self, name: T) -> Option<&str> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown(name)) - .nth(0) - } - - /// Return the value of the extended parameter which the `name` matches. - pub fn get_unknown_ext>(&self, name: T) -> Option<&ExtendedValue> { - let name = name.as_ref(); - self.parameters - .iter() - .filter_map(|p| p.as_unknown_ext(name)) - .nth(0) - } -} - -impl IntoHeaderValue for ContentDisposition { - type Error = header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - header::HeaderValue::from_shared(writer.take()) - } -} - -impl Header for ContentDisposition { - fn name() -> header::HeaderName { - header::CONTENT_DISPOSITION - } - - fn parse(msg: &T) -> Result { - if let Some(h) = msg.headers().get(Self::name()) { - Self::from_raw(&h) - } else { - Err(::error::ParseError::Header) - } - } -} - -impl fmt::Display for DispositionType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - DispositionType::Inline => write!(f, "inline"), - DispositionType::Attachment => write!(f, "attachment"), - DispositionType::FormData => write!(f, "form-data"), - DispositionType::Ext(ref s) => write!(f, "{}", s), - } - } -} - -impl fmt::Display for DispositionParam { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - // All ASCII control charaters (0-30, 127) excepting horizontal tab, double quote, and - // backslash should be escaped in quoted-string (i.e. "foobar"). - // Ref: RFC6266 S4.1 -> RFC2616 S2.2; RFC 7578 S4.2 -> RFC2183 S2 -> ... . - lazy_static! { - static ref RE: Regex = Regex::new("[\x01-\x08\x10\x1F\x7F\"\\\\]").unwrap(); - } - match self { - DispositionParam::Name(ref value) => write!(f, "name={}", value), - DispositionParam::Filename(ref value) => { - write!(f, "filename=\"{}\"", RE.replace_all(value, "\\$0").as_ref()) - } - DispositionParam::Unknown(ref name, ref value) => write!( - f, - "{}=\"{}\"", - name, - &RE.replace_all(value, "\\$0").as_ref() - ), - DispositionParam::FilenameExt(ref ext_value) => { - write!(f, "filename*={}", ext_value) - } - DispositionParam::UnknownExt(ref name, ref ext_value) => { - write!(f, "{}*={}", name, ext_value) - } - } - } -} - -impl fmt::Display for ContentDisposition { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.disposition)?; - self.parameters - .iter() - .map(|param| write!(f, "; {}", param)) - .collect() - } -} - -#[cfg(test)] -mod tests { - use super::{ContentDisposition, DispositionParam, DispositionType}; - use header::shared::Charset; - use header::{ExtendedValue, HeaderValue}; - #[test] - fn test_from_raw_basic() { - assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err()); - - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"sample.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("attachment; filename=\"image.jpg\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static("inline; filename=image.jpg"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename("image.jpg".to_owned())], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; creation-date=\"Wed, 12 Feb 1997 16:29:51 -0500\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::Unknown( - String::from("creation-date"), - "Wed, 12 Feb 1997 16:29:51 -0500".to_owned(), - )], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extended() { - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "attachment; filename*=UTF-8''%c2%a3%20and%20%e2%82%ac%20rates", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Ext(String::from("UTF-8")), - language_tag: None, - value: vec![ - 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, - b'r', b'a', b't', b'e', b's', - ], - })], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_extra_whitespace() { - let a = HeaderValue::from_static( - "form-data ; du-mmy= 3 ; name =upload ; filename = \"sample.png\" ; ", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("du-mmy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_unordered() { - let a = HeaderValue::from_static( - "form-data; dummy=3; filename=\"sample.png\" ; name=upload;", - // Actually, a trailling semolocon is not compliant. But it is fine to accept. - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - DispositionParam::Name("upload".to_owned()), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_str( - "attachment; filename*=iso-8859-1''foo-%E4.html; filename=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_only_disp() { - let a = ContentDisposition::from_raw(&HeaderValue::from_static("attachment")) - .unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Attachment, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = - ContentDisposition::from_raw(&HeaderValue::from_static("inline ;")).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![], - }; - assert_eq!(a, b); - - let a = ContentDisposition::from_raw(&HeaderValue::from_static( - "unknown-disp-param", - )).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Ext(String::from("unknown-disp-param")), - parameters: vec![], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_mixed_case() { - let a = HeaderValue::from_str( - "InLInE; fIlenAME*=iso-8859-1''foo-%E4.html; filEName=\"foo-ä.html\"", - ).unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![ - DispositionParam::FilenameExt(ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: None, - value: b"foo-\xe4.html".to_vec(), - }), - DispositionParam::Filename("foo-ä.html".to_owned()), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn from_raw_with_unicode() { - /* RFC7578 Section 4.2: - Some commonly deployed systems use multipart/form-data with file names directly encoded - including octets outside the US-ASCII range. The encoding used for the file names is - typically UTF-8, although HTML forms will use the charset associated with the form. - - Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above. - (And now, only UTF-8 is handled by this implementation.) - */ - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"") - .unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from("文件.webp")), - ], - }; - assert_eq!(a, b); - - let a = - HeaderValue::from_str("form-data; name=upload; filename=\"余固知謇謇之為患兮,忍而不能舍也.pptx\"").unwrap(); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name(String::from("upload")), - DispositionParam::Filename(String::from( - "余固知謇謇之為患兮,忍而不能舍也.pptx", - )), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_escape() { - let a = HeaderValue::from_static( - "form-data; dummy=3; name=upload; filename=\"s\\amp\\\"le.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename( - ['s', 'a', 'm', 'p', '\"', 'l', 'e', '.', 'p', 'n', 'g'] - .iter() - .collect(), - ), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_semicolon() { - let a = - HeaderValue::from_static("form-data; filename=\"A semicolon here;.pdf\""); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![DispositionParam::Filename(String::from( - "A semicolon here;.pdf", - ))], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_uncessary_percent_decode() { - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74%2e%70%6e%67\"", // Should not be decoded! - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74%2e%70%6e%67")), - ], - }; - assert_eq!(a, b); - - let a = HeaderValue::from_static( - "form-data; name=photo; filename=\"%74%65%73%74.png\"", - ); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let b = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Name("photo".to_owned()), - DispositionParam::Filename(String::from("%74%65%73%74.png")), - ], - }; - assert_eq!(a, b); - } - - #[test] - fn test_from_raw_param_value_missing() { - let a = HeaderValue::from_static("form-data; name=upload ; filename="); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("attachment; dummy=; filename=invoice.pdf"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; filename= "); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_from_raw_param_name_missing() { - let a = HeaderValue::from_static("inline; =\"test.txt\""); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; =diary.odt"); - assert!(ContentDisposition::from_raw(&a).is_err()); - - let a = HeaderValue::from_static("inline; ="); - assert!(ContentDisposition::from_raw(&a).is_err()); - } - - #[test] - fn test_display_extended() { - let as_string = - "attachment; filename*=UTF-8'en'%C2%A3%20and%20%E2%82%AC%20rates"; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a = HeaderValue::from_static("attachment; filename=colourful.csv"); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"colourful.csv\"".to_owned(), - display_rendered - ); - } - - #[test] - fn test_display_quote() { - let as_string = "form-data; name=upload; filename=\"Quote\\\"here.png\""; - as_string - .find(['\\', '\"'].iter().collect::().as_str()) - .unwrap(); // ensure `\"` is there - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - } - - #[test] - fn test_display_space_tab() { - let as_string = "form-data; name=upload; filename=\"Space here.png\""; - let a = HeaderValue::from_static(as_string); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!(as_string, display_rendered); - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("Tab\there.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"Tab\x09here.png\"", display_rendered); - } - - #[test] - fn test_display_control_characters() { - /* let a = "attachment; filename=\"carriage\rreturn.png\""; - let a = HeaderValue::from_static(a); - let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap(); - let display_rendered = format!("{}", a); - assert_eq!( - "attachment; filename=\"carriage\\\rreturn.png\"", - display_rendered - );*/ - // No way to create a HeaderValue containing a carriage return. - - let a: ContentDisposition = ContentDisposition { - disposition: DispositionType::Inline, - parameters: vec![DispositionParam::Filename(String::from("bell\x07.png"))], - }; - let display_rendered = format!("{}", a); - assert_eq!("inline; filename=\"bell\\\x07.png\"", display_rendered); - } - - #[test] - fn test_param_methods() { - let param = DispositionParam::Filename(String::from("sample.txt")); - assert!(param.is_filename()); - assert_eq!(param.as_filename().unwrap(), "sample.txt"); - - let param = DispositionParam::Unknown(String::from("foo"), String::from("bar")); - assert!(param.is_unknown("foo")); - assert_eq!(param.as_unknown("fOo"), Some("bar")); - } - - #[test] - fn test_disposition_methods() { - let cd = ContentDisposition { - disposition: DispositionType::FormData, - parameters: vec![ - DispositionParam::Unknown("dummy".to_owned(), "3".to_owned()), - DispositionParam::Name("upload".to_owned()), - DispositionParam::Filename("sample.png".to_owned()), - ], - }; - assert_eq!(cd.get_name(), Some("upload")); - assert_eq!(cd.get_unknown("dummy"), Some("3")); - assert_eq!(cd.get_filename(), Some("sample.png")); - assert_eq!(cd.get_unknown_ext("dummy"), None); - assert_eq!(cd.get_unknown("duMMy"), Some("3")); - } -} diff --git a/src/header/common/content_language.rs b/src/header/common/content_language.rs deleted file mode 100644 index c1f87d51..00000000 --- a/src/header/common/content_language.rs +++ /dev/null @@ -1,65 +0,0 @@ -use header::{QualityItem, CONTENT_LANGUAGE}; -use language_tags::LanguageTag; - -header! { - /// `Content-Language` header, defined in - /// [RFC7231](https://tools.ietf.org/html/rfc7231#section-3.1.3.2) - /// - /// The `Content-Language` header field describes the natural language(s) - /// of the intended audience for the representation. Note that this - /// might not be equivalent to all the languages used within the - /// representation. - /// - /// # ABNF - /// - /// ```text - /// Content-Language = 1#language-tag - /// ``` - /// - /// # Example values - /// - /// * `da` - /// * `mi, en` - /// - /// # Examples - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(en)), - /// ]) - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; - /// use actix_http::HttpResponse; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentLanguage(vec![ - /// qitem(langtag!(da)), - /// qitem(langtag!(en;;;GB)), - /// ]) - /// ); - /// # } - /// ``` - (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ - - test_content_language { - test_header!(test1, vec![b"da"]); - test_header!(test2, vec![b"mi, en"]); - } -} diff --git a/src/header/common/content_range.rs b/src/header/common/content_range.rs deleted file mode 100644 index 999307e2..00000000 --- a/src/header/common/content_range.rs +++ /dev/null @@ -1,210 +0,0 @@ -use error::ParseError; -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer, - CONTENT_RANGE}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -header! { - /// `Content-Range` header, defined in - /// [RFC7233](http://tools.ietf.org/html/rfc7233#section-4.2) - (ContentRange, CONTENT_RANGE) => [ContentRangeSpec] - - test_content_range { - test_header!(test_bytes, - vec![b"bytes 0-499/500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: Some(500) - }))); - - test_header!(test_bytes_unknown_len, - vec![b"bytes 0-499/*"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: Some((0, 499)), - instance_length: None - }))); - - test_header!(test_bytes_unknown_range, - vec![b"bytes */500"], - Some(ContentRange(ContentRangeSpec::Bytes { - range: None, - instance_length: Some(500) - }))); - - test_header!(test_unregistered, - vec![b"seconds 1-2"], - Some(ContentRange(ContentRangeSpec::Unregistered { - unit: "seconds".to_owned(), - resp: "1-2".to_owned() - }))); - - test_header!(test_no_len, - vec![b"bytes 0-499"], - None::); - - test_header!(test_only_unit, - vec![b"bytes"], - None::); - - test_header!(test_end_less_than_start, - vec![b"bytes 499-0/500"], - None::); - - test_header!(test_blank, - vec![b""], - None::); - - test_header!(test_bytes_many_spaces, - vec![b"bytes 1-2/500 3"], - None::); - - test_header!(test_bytes_many_slashes, - vec![b"bytes 1-2/500/600"], - None::); - - test_header!(test_bytes_many_dashes, - vec![b"bytes 1-2-3/500"], - None::); - - } -} - -/// Content-Range, described in [RFC7233](https://tools.ietf.org/html/rfc7233#section-4.2) -/// -/// # ABNF -/// -/// ```text -/// Content-Range = byte-content-range -/// / other-content-range -/// -/// byte-content-range = bytes-unit SP -/// ( byte-range-resp / unsatisfied-range ) -/// -/// byte-range-resp = byte-range "/" ( complete-length / "*" ) -/// byte-range = first-byte-pos "-" last-byte-pos -/// unsatisfied-range = "*/" complete-length -/// -/// complete-length = 1*DIGIT -/// -/// other-content-range = other-range-unit SP other-range-resp -/// other-range-resp = *CHAR -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum ContentRangeSpec { - /// Byte range - Bytes { - /// First and last bytes of the range, omitted if request could not be - /// satisfied - range: Option<(u64, u64)>, - - /// Total length of the instance, can be omitted if unknown - instance_length: Option, - }, - - /// Custom range, with unit not registered at IANA - Unregistered { - /// other-range-unit - unit: String, - - /// other-range-resp - resp: String, - }, -} - -fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> { - let mut iter = s.splitn(2, separator); - match (iter.next(), iter.next()) { - (Some(a), Some(b)) => Some((a, b)), - _ => None, - } -} - -impl FromStr for ContentRangeSpec { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - let res = match split_in_two(s, ' ') { - Some(("bytes", resp)) => { - let (range, instance_length) = - split_in_two(resp, '/').ok_or(ParseError::Header)?; - - let instance_length = if instance_length == "*" { - None - } else { - Some(instance_length - .parse() - .map_err(|_| ParseError::Header)?) - }; - - let range = if range == "*" { - None - } else { - let (first_byte, last_byte) = - split_in_two(range, '-').ok_or(ParseError::Header)?; - let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?; - let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?; - if last_byte < first_byte { - return Err(ParseError::Header); - } - Some((first_byte, last_byte)) - }; - - ContentRangeSpec::Bytes { - range, - instance_length, - } - } - Some((unit, resp)) => ContentRangeSpec::Unregistered { - unit: unit.to_owned(), - resp: resp.to_owned(), - }, - _ => return Err(ParseError::Header), - }; - Ok(res) - } -} - -impl Display for ContentRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ContentRangeSpec::Bytes { - range, - instance_length, - } => { - f.write_str("bytes ")?; - match range { - Some((first_byte, last_byte)) => { - write!(f, "{}-{}", first_byte, last_byte)?; - } - None => { - f.write_str("*")?; - } - }; - f.write_str("/")?; - if let Some(v) = instance_length { - write!(f, "{}", v) - } else { - f.write_str("*") - } - } - ContentRangeSpec::Unregistered { - ref unit, - ref resp, - } => { - f.write_str(unit)?; - f.write_str(" ")?; - f.write_str(resp) - } - } - } -} - -impl IntoHeaderValue for ContentRangeSpec { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} diff --git a/src/header/common/content_type.rs b/src/header/common/content_type.rs deleted file mode 100644 index 3286d4ca..00000000 --- a/src/header/common/content_type.rs +++ /dev/null @@ -1,122 +0,0 @@ -use header::CONTENT_TYPE; -use mime::{self, Mime}; - -header! { - /// `Content-Type` header, defined in - /// [RFC7231](http://tools.ietf.org/html/rfc7231#section-3.1.1.5) - /// - /// The `Content-Type` header field indicates the media type of the - /// associated representation: either the representation enclosed in the - /// message payload or the selected representation, as determined by the - /// message semantics. The indicated media type defines both the data - /// format and how that data is intended to be processed by a recipient, - /// within the scope of the received message semantics, after any content - /// codings indicated by Content-Encoding are decoded. - /// - /// Although the `mime` crate allows the mime options to be any slice, this crate - /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If - /// this is an issue, it's possible to implement `Header` on a custom struct. - /// - /// # ABNF - /// - /// ```text - /// Content-Type = media-type - /// ``` - /// - /// # Example values - /// - /// * `text/html; charset=utf-8` - /// * `application/json` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType::json() - /// ); - /// # } - /// ``` - /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// use mime::TEXT_HTML; - /// use actix_http::HttpResponse; - /// use actix_http::http::header::ContentType; - /// - /// # fn main() { - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// ContentType(TEXT_HTML) - /// ); - /// # } - /// ``` - (ContentType, CONTENT_TYPE) => [Mime] - - test_content_type { - test_header!( - test1, - vec![b"text/html"], - Some(HeaderField(TEXT_HTML))); - } -} - -impl ContentType { - /// A constructor to easily create a `Content-Type: application/json` - /// header. - #[inline] - pub fn json() -> ContentType { - ContentType(mime::APPLICATION_JSON) - } - - /// A constructor to easily create a `Content-Type: text/plain; - /// charset=utf-8` header. - #[inline] - pub fn plaintext() -> ContentType { - ContentType(mime::TEXT_PLAIN_UTF_8) - } - - /// A constructor to easily create a `Content-Type: text/html` header. - #[inline] - pub fn html() -> ContentType { - ContentType(mime::TEXT_HTML) - } - - /// A constructor to easily create a `Content-Type: text/xml` header. - #[inline] - pub fn xml() -> ContentType { - ContentType(mime::TEXT_XML) - } - - /// A constructor to easily create a `Content-Type: - /// application/www-form-url-encoded` header. - #[inline] - pub fn form_url_encoded() -> ContentType { - ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) - } - /// A constructor to easily create a `Content-Type: image/jpeg` header. - #[inline] - pub fn jpeg() -> ContentType { - ContentType(mime::IMAGE_JPEG) - } - - /// A constructor to easily create a `Content-Type: image/png` header. - #[inline] - pub fn png() -> ContentType { - ContentType(mime::IMAGE_PNG) - } - - /// A constructor to easily create a `Content-Type: - /// application/octet-stream` header. - #[inline] - pub fn octet_stream() -> ContentType { - ContentType(mime::APPLICATION_OCTET_STREAM) - } -} - -impl Eq for ContentType {} diff --git a/src/header/common/date.rs b/src/header/common/date.rs deleted file mode 100644 index 9ce2bd65..00000000 --- a/src/header/common/date.rs +++ /dev/null @@ -1,42 +0,0 @@ -use header::{HttpDate, DATE}; -use std::time::SystemTime; - -header! { - /// `Date` header, defined in [RFC7231](http://tools.ietf.org/html/rfc7231#section-7.1.1.2) - /// - /// The `Date` header field represents the date and time at which the - /// message was originated. - /// - /// # ABNF - /// - /// ```text - /// Date = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Tue, 15 Nov 1994 08:12:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Date; - /// use std::time::SystemTime; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(Date(SystemTime::now().into())); - /// ``` - (Date, DATE) => [HttpDate] - - test_date { - test_header!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]); - } -} - -impl Date { - /// Create a date instance set to the current system time - pub fn now() -> Date { - Date(SystemTime::now().into()) - } -} diff --git a/src/header/common/etag.rs b/src/header/common/etag.rs deleted file mode 100644 index ea4be2a7..00000000 --- a/src/header/common/etag.rs +++ /dev/null @@ -1,96 +0,0 @@ -use header::{EntityTag, ETAG}; - -header! { - /// `ETag` header, defined in [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.3) - /// - /// The `ETag` header field in a response provides the current entity-tag - /// for the selected representation, as determined at the conclusion of - /// handling the request. An entity-tag is an opaque validator for - /// differentiating between multiple representations of the same - /// resource, regardless of whether those multiple representations are - /// due to resource state changes over time, content negotiation - /// resulting in multiple representations being valid at the same time, - /// or both. An entity-tag consists of an opaque quoted string, possibly - /// prefixed by a weakness indicator. - /// - /// # ABNF - /// - /// ```text - /// ETag = entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `""` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{ETag, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); - /// ``` - (ETag, ETAG) => [EntityTag] - - test_etag { - // From the RFC - test_header!(test1, - vec![b"\"xyzzy\""], - Some(ETag(EntityTag::new(false, "xyzzy".to_owned())))); - test_header!(test2, - vec![b"W/\"xyzzy\""], - Some(ETag(EntityTag::new(true, "xyzzy".to_owned())))); - test_header!(test3, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - // Own tests - test_header!(test4, - vec![b"\"foobar\""], - Some(ETag(EntityTag::new(false, "foobar".to_owned())))); - test_header!(test5, - vec![b"\"\""], - Some(ETag(EntityTag::new(false, "".to_owned())))); - test_header!(test6, - vec![b"W/\"weak-etag\""], - Some(ETag(EntityTag::new(true, "weak-etag".to_owned())))); - test_header!(test7, - vec![b"W/\"\x65\x62\""], - Some(ETag(EntityTag::new(true, "\u{0065}\u{0062}".to_owned())))); - test_header!(test8, - vec![b"W/\"\""], - Some(ETag(EntityTag::new(true, "".to_owned())))); - test_header!(test9, - vec![b"no-dquotes"], - None::); - test_header!(test10, - vec![b"w/\"the-first-w-is-case-sensitive\""], - None::); - test_header!(test11, - vec![b""], - None::); - test_header!(test12, - vec![b"\"unmatched-dquotes1"], - None::); - test_header!(test13, - vec![b"unmatched-dquotes2\""], - None::); - test_header!(test14, - vec![b"matched-\"dquotes\""], - None::); - test_header!(test15, - vec![b"\""], - None::); - } -} diff --git a/src/header/common/expires.rs b/src/header/common/expires.rs deleted file mode 100644 index bdd25fdb..00000000 --- a/src/header/common/expires.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, EXPIRES}; - -header! { - /// `Expires` header, defined in [RFC7234](http://tools.ietf.org/html/rfc7234#section-5.3) - /// - /// The `Expires` header field gives the date/time after which the - /// response is considered stale. - /// - /// The presence of an Expires field does not imply that the original - /// resource will change or cease to exist at, before, or after that - /// time. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// * `Thu, 01 Dec 1994 16:00:00 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::Expires; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// builder.set(Expires(expiration.into())); - /// ``` - (Expires, EXPIRES) => [HttpDate] - - test_expires { - // Test case from RFC - test_header!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]); - } -} diff --git a/src/header/common/if_match.rs b/src/header/common/if_match.rs deleted file mode 100644 index 5f7976a4..00000000 --- a/src/header/common/if_match.rs +++ /dev/null @@ -1,70 +0,0 @@ -use header::{EntityTag, IF_MATCH}; - -header! { - /// `If-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.1) - /// - /// The `If-Match` header field makes the request method conditional on - /// the recipient origin server either having at least one current - /// representation of the target resource, when the field-value is "*", - /// or having a current representation of the target resource that has an - /// entity-tag matching a member of the list of entity-tags provided in - /// the field-value. - /// - /// An origin server MUST use the strong comparison function when - /// comparing entity-tags for `If-Match`, since the client - /// intends this precondition to prevent the method from being applied if - /// there have been any changes to the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * "xyzzy", "r2d2xxxx", "c3piozzzz" - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{IfMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfMatch, IF_MATCH) => {Any / (EntityTag)+} - - test_if_match { - test_header!( - test1, - vec![b"\"xyzzy\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned())]))); - test_header!( - test2, - vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""], - Some(HeaderField::Items( - vec![EntityTag::new(false, "xyzzy".to_owned()), - EntityTag::new(false, "r2d2xxxx".to_owned()), - EntityTag::new(false, "c3piozzzz".to_owned())]))); - test_header!(test3, vec![b"*"], Some(IfMatch::Any)); - } -} diff --git a/src/header/common/if_modified_since.rs b/src/header/common/if_modified_since.rs deleted file mode 100644 index 41d6fba2..00000000 --- a/src/header/common/if_modified_since.rs +++ /dev/null @@ -1,39 +0,0 @@ -use header::{HttpDate, IF_MODIFIED_SINCE}; - -header! { - /// `If-Modified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.3) - /// - /// The `If-Modified-Since` header field makes a GET or HEAD request - /// method conditional on the selected representation's modification date - /// being more recent than the date provided in the field-value. - /// Transfer of the selected representation's data is avoided if that - /// data has not changed. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfModifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfModifiedSince(modified.into())); - /// ``` - (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] - - test_if_modified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/if_none_match.rs b/src/header/common/if_none_match.rs deleted file mode 100644 index 8b3905ba..00000000 --- a/src/header/common/if_none_match.rs +++ /dev/null @@ -1,92 +0,0 @@ -use header::{EntityTag, IF_NONE_MATCH}; - -header! { - /// `If-None-Match` header, defined in - /// [RFC7232](https://tools.ietf.org/html/rfc7232#section-3.2) - /// - /// The `If-None-Match` header field makes the request method conditional - /// on a recipient cache or origin server either not having any current - /// representation of the target resource, when the field-value is "*", - /// or having a selected representation with an entity-tag that does not - /// match any of those listed in the field-value. - /// - /// A recipient MUST use the weak comparison function when comparing - /// entity-tags for If-None-Match (Section 2.3.2), since weak entity-tags - /// can be used for cache validation even if there have been changes to - /// the representation data. - /// - /// # ABNF - /// - /// ```text - /// If-None-Match = "*" / 1#entity-tag - /// ``` - /// - /// # Example values - /// - /// * `"xyzzy"` - /// * `W/"xyzzy"` - /// * `"xyzzy", "r2d2xxxx", "c3piozzzz"` - /// * `W/"xyzzy", W/"r2d2xxxx", W/"c3piozzzz"` - /// * `*` - /// - /// # Examples - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfNoneMatch; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set(IfNoneMatch::Any); - /// ``` - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::{IfNoneMatch, EntityTag}; - /// - /// let mut builder = HttpResponse::Ok(); - /// builder.set( - /// IfNoneMatch::Items(vec![ - /// EntityTag::new(false, "xyzzy".to_owned()), - /// EntityTag::new(false, "foobar".to_owned()), - /// EntityTag::new(false, "bazquux".to_owned()), - /// ]) - /// ); - /// ``` - (IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+} - - test_if_none_match { - test_header!(test1, vec![b"\"xyzzy\""]); - test_header!(test2, vec![b"W/\"xyzzy\""]); - test_header!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]); - test_header!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]); - test_header!(test5, vec![b"*"]); - } -} - -#[cfg(test)] -mod tests { - use super::IfNoneMatch; - use header::{EntityTag, Header, IF_NONE_MATCH}; - use test::TestRequest; - - #[test] - fn test_if_none_match() { - let mut if_none_match: Result; - - let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); - if_none_match = Header::parse(&req); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - - let req = - TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) - .finish(); - - if_none_match = Header::parse(&req); - let mut entities: Vec = Vec::new(); - let foobar_etag = EntityTag::new(false, "foobar".to_owned()); - let weak_etag = EntityTag::new(true, "weak-etag".to_owned()); - entities.push(foobar_etag); - entities.push(weak_etag); - assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Items(entities))); - } -} diff --git a/src/header/common/if_range.rs b/src/header/common/if_range.rs deleted file mode 100644 index 8cbb8c89..00000000 --- a/src/header/common/if_range.rs +++ /dev/null @@ -1,117 +0,0 @@ -use error::ParseError; -use header::from_one_raw_str; -use header::{ - EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, - InvalidHeaderValueBytes, Writer, -}; -use http::header; -use httpmessage::HttpMessage; -use std::fmt::{self, Display, Write}; - -/// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) -/// -/// If a client has a partial copy of a representation and wishes to have -/// an up-to-date copy of the entire representation, it could use the -/// Range header field with a conditional GET (using either or both of -/// If-Unmodified-Since and If-Match.) However, if the precondition -/// fails because the representation has been modified, the client would -/// then have to make a second request to obtain the entire current -/// representation. -/// -/// The `If-Range` header field allows a client to \"short-circuit\" the -/// second request. Informally, its meaning is as follows: if the -/// representation is unchanged, send me the part(s) that I am requesting -/// in Range; otherwise, send me the entire representation. -/// -/// # ABNF -/// -/// ```text -/// If-Range = entity-tag / HTTP-date -/// ``` -/// -/// # Example values -/// -/// * `Sat, 29 Oct 1994 19:43:31 GMT` -/// * `\"xyzzy\"` -/// -/// # Examples -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::{EntityTag, IfRange}; -/// -/// let mut builder = HttpResponse::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new( -/// false, -/// "xyzzy".to_owned(), -/// ))); -/// ``` -/// -/// ```rust -/// use actix_http::HttpResponse; -/// use actix_http::http::header::IfRange; -/// use std::time::{Duration, SystemTime}; -/// -/// let mut builder = HttpResponse::Ok(); -/// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// builder.set(IfRange::Date(fetched.into())); -/// ``` -#[derive(Clone, Debug, PartialEq)] -pub enum IfRange { - /// The entity-tag the client has of the resource - EntityTag(EntityTag), - /// The date when the client retrieved the resource - Date(HttpDate), -} - -impl Header for IfRange { - fn name() -> HeaderName { - header::IF_RANGE - } - #[inline] - fn parse(msg: &T) -> Result - where - T: HttpMessage, - { - let etag: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(etag) = etag { - return Ok(IfRange::EntityTag(etag)); - } - let date: Result = - from_one_raw_str(msg.headers().get(header::IF_RANGE)); - if let Ok(date) = date { - return Ok(IfRange::Date(date)); - } - Err(ParseError::Header) - } -} - -impl Display for IfRange { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - IfRange::EntityTag(ref x) => Display::fmt(x, f), - IfRange::Date(ref x) => Display::fmt(x, f), - } - } -} - -impl IntoHeaderValue for IfRange { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut writer = Writer::new(); - let _ = write!(&mut writer, "{}", self); - HeaderValue::from_shared(writer.take()) - } -} - -#[cfg(test)] -mod test_if_range { - use super::IfRange as HeaderField; - use header::*; - use std::str; - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); - test_header!(test3, vec![b"this-is-invalid"], None::); -} diff --git a/src/header/common/if_unmodified_since.rs b/src/header/common/if_unmodified_since.rs deleted file mode 100644 index 02f9252e..00000000 --- a/src/header/common/if_unmodified_since.rs +++ /dev/null @@ -1,40 +0,0 @@ -use header::{HttpDate, IF_UNMODIFIED_SINCE}; - -header! { - /// `If-Unmodified-Since` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-3.4) - /// - /// The `If-Unmodified-Since` header field makes the request method - /// conditional on the selected representation's last modification date - /// being earlier than or equal to the date provided in the field-value. - /// This field accomplishes the same purpose as If-Match for cases where - /// the user agent does not have an entity-tag for the representation. - /// - /// # ABNF - /// - /// ```text - /// If-Unmodified-Since = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::IfUnmodifiedSince; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfUnmodifiedSince(modified.into())); - /// ``` - (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] - - test_if_unmodified_since { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - } -} diff --git a/src/header/common/last_modified.rs b/src/header/common/last_modified.rs deleted file mode 100644 index 608f4313..00000000 --- a/src/header/common/last_modified.rs +++ /dev/null @@ -1,38 +0,0 @@ -use header::{HttpDate, LAST_MODIFIED}; - -header! { - /// `Last-Modified` header, defined in - /// [RFC7232](http://tools.ietf.org/html/rfc7232#section-2.2) - /// - /// The `Last-Modified` header field in a response provides a timestamp - /// indicating the date and time at which the origin server believes the - /// selected representation was last modified, as determined at the - /// conclusion of handling the request. - /// - /// # ABNF - /// - /// ```text - /// Expires = HTTP-date - /// ``` - /// - /// # Example values - /// - /// * `Sat, 29 Oct 1994 19:43:31 GMT` - /// - /// # Example - /// - /// ```rust - /// use actix_http::HttpResponse; - /// use actix_http::http::header::LastModified; - /// use std::time::{SystemTime, Duration}; - /// - /// let mut builder = HttpResponse::Ok(); - /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(LastModified(modified.into())); - /// ``` - (LastModified, LAST_MODIFIED) => [HttpDate] - - test_last_modified { - // Test case from RFC - test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);} -} diff --git a/src/header/common/mod.rs b/src/header/common/mod.rs deleted file mode 100644 index e6185b5a..00000000 --- a/src/header/common/mod.rs +++ /dev/null @@ -1,350 +0,0 @@ -//! A Collection of Header implementations for common HTTP Headers. -//! -//! ## Mime -//! -//! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate -//! is used, such as `ContentType(pub Mime)`. -#![cfg_attr(rustfmt, rustfmt_skip)] - -pub use self::accept_charset::AcceptCharset; -//pub use self::accept_encoding::AcceptEncoding; -pub use self::accept_language::AcceptLanguage; -pub use self::accept::Accept; -pub use self::allow::Allow; -pub use self::cache_control::{CacheControl, CacheDirective}; -pub use self::content_disposition::{ContentDisposition, DispositionType, DispositionParam}; -pub use self::content_language::ContentLanguage; -pub use self::content_range::{ContentRange, ContentRangeSpec}; -pub use self::content_type::ContentType; -pub use self::date::Date; -pub use self::etag::ETag; -pub use self::expires::Expires; -pub use self::if_match::IfMatch; -pub use self::if_modified_since::IfModifiedSince; -pub use self::if_none_match::IfNoneMatch; -pub use self::if_range::IfRange; -pub use self::if_unmodified_since::IfUnmodifiedSince; -pub use self::last_modified::LastModified; -//pub use self::range::{Range, ByteRangeSpec}; - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__deref { - ($from:ty => $to:ty) => { - impl ::std::ops::Deref for $from { - type Target = $to; - - #[inline] - fn deref(&self) -> &$to { - &self.0 - } - } - - impl ::std::ops::DerefMut for $from { - #[inline] - fn deref_mut(&mut self) -> &mut $to { - &mut self.0 - } - } - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! __hyper__tm { - ($id:ident, $tm:ident{$($tf:item)*}) => { - #[allow(unused_imports)] - #[cfg(test)] - mod $tm{ - use std::str; - use http::Method; - use $crate::header::*; - use $crate::mime::*; - use super::$id as HeaderField; - $($tf)* - } - - } -} - -#[doc(hidden)] -#[macro_export] -macro_rules! test_header { - ($id:ident, $raw:expr) => { - #[test] - fn $id() { - use test; - let raw = $raw; - let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let value = HeaderField::parse(&req); - let result = format!("{}", value.unwrap()); - let expected = String::from_utf8(raw[0].to_vec()).unwrap(); - let result_cmp: Vec = result - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - let expected_cmp: Vec = expected - .to_ascii_lowercase() - .split(' ') - .map(|x| x.to_owned()) - .collect(); - assert_eq!(result_cmp.concat(), expected_cmp.concat()); - } - }; - ($id:ident, $raw:expr, $typed:expr) => { - #[test] - fn $id() { - use $crate::test; - let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); - let mut req = test::TestRequest::default(); - for item in a { - req = req.header(HeaderField::name(), item); - } - let req = req.finish(); - let val = HeaderField::parse(&req); - let typed: Option = $typed; - // Test parsing - assert_eq!(val.ok(), typed); - // Test formatting - if typed.is_some() { - let raw = &($raw)[..]; - let mut iter = raw.iter().map(|b|str::from_utf8(&b[..]).unwrap()); - let mut joined = String::new(); - joined.push_str(iter.next().unwrap()); - for s in iter { - joined.push_str(", "); - joined.push_str(s); - } - assert_eq!(format!("{}", typed.unwrap()), joined); - } - } - } -} - -#[macro_export] -macro_rules! header { - // $a:meta: Attributes associated with the header item (usually docs) - // $id:ident: Identifier of the header - // $n:expr: Lowercase name of the header - // $nn:expr: Nice name of the header - - // List header, zero or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)*) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // List header, one or more items - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)+) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub Vec<$item>); - __hyper__deref!($id => Vec<$item>); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - $crate::header::fmt_comma_delimited(f, &self.0[..]) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - // Single value header - ($(#[$a:meta])*($id:ident, $name:expr) => [$value:ty]) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub struct $id(pub $value); - __hyper__deref!($id => $value); - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - $crate::header::from_one_raw_str( - msg.headers().get(Self::name())).map($id) - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - ::std::fmt::Display::fmt(&self.0, f) - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - self.0.try_into() - } - } - }; - // List header, one or more items with "*" option - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { - $(#[$a])* - #[derive(Clone, Debug, PartialEq)] - pub enum $id { - /// Any value is a match - Any, - /// Only the listed items are a match - Items(Vec<$item>), - } - impl $crate::header::Header for $id { - #[inline] - fn name() -> $crate::header::HeaderName { - $name - } - #[inline] - fn parse(msg: &T) -> Result - where T: $crate::HttpMessage - { - let any = msg.headers().get(Self::name()).and_then(|hdr| { - hdr.to_str().ok().and_then(|hdr| Some(hdr.trim() == "*"))}); - - if let Some(true) = any { - Ok($id::Any) - } else { - Ok($id::Items( - $crate::header::from_comma_delimited( - msg.headers().get_all(Self::name()))?)) - } - } - } - impl ::std::fmt::Display for $id { - #[inline] - fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { - match *self { - $id::Any => f.write_str("*"), - $id::Items(ref fields) => $crate::header::fmt_comma_delimited( - f, &fields[..]) - } - } - } - impl $crate::header::IntoHeaderValue for $id { - type Error = $crate::header::InvalidHeaderValueBytes; - - fn try_into(self) -> Result<$crate::header::HeaderValue, Self::Error> { - use std::fmt::Write; - let mut writer = $crate::header::Writer::new(); - let _ = write!(&mut writer, "{}", self); - $crate::header::HeaderValue::from_shared(writer.take()) - } - } - }; - - // optional test module - ($(#[$a:meta])*($id:ident, $name:expr) => ($item:ty)* $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => ($item)* - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $n:expr) => ($item:ty)+ $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $n) => ($item)+ - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => [$item:ty] $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* ($id, $name) => [$item] - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; - ($(#[$a:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+} $tm:ident{$($tf:item)*}) => { - header! { - $(#[$a])* - ($id, $name) => {Any / ($item)+} - } - - __hyper__tm! { $id, $tm { $($tf)* }} - }; -} - - -mod accept_charset; -//mod accept_encoding; -mod accept_language; -mod accept; -mod allow; -mod cache_control; -mod content_disposition; -mod content_language; -mod content_range; -mod content_type; -mod date; -mod etag; -mod expires; -mod if_match; -mod if_modified_since; -mod if_none_match; -mod if_range; -mod if_unmodified_since; -mod last_modified; -//mod range; diff --git a/src/header/common/range.rs b/src/header/common/range.rs deleted file mode 100644 index 71718fc7..00000000 --- a/src/header/common/range.rs +++ /dev/null @@ -1,434 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use header::parsing::from_one_raw_str; -use header::{Header, Raw}; - -/// `Range` header, defined in [RFC7233](https://tools.ietf.org/html/rfc7233#section-3.1) -/// -/// The "Range" header field on a GET request modifies the method -/// semantics to request transfer of only one or more subranges of the -/// selected representation data, rather than the entire selected -/// representation data. -/// -/// # ABNF -/// -/// ```text -/// Range = byte-ranges-specifier / other-ranges-specifier -/// other-ranges-specifier = other-range-unit "=" other-range-set -/// other-range-set = 1*VCHAR -/// -/// bytes-unit = "bytes" -/// -/// byte-ranges-specifier = bytes-unit "=" byte-range-set -/// byte-range-set = 1#(byte-range-spec / suffix-byte-range-spec) -/// byte-range-spec = first-byte-pos "-" [last-byte-pos] -/// first-byte-pos = 1*DIGIT -/// last-byte-pos = 1*DIGIT -/// ``` -/// -/// # Example values -/// -/// * `bytes=1000-` -/// * `bytes=-2000` -/// * `bytes=0-1,30-40` -/// * `bytes=0-10,20-90,-100` -/// * `custom_unit=0-123` -/// * `custom_unit=xxx-yyy` -/// -/// # Examples -/// -/// ``` -/// use hyper::header::{Headers, Range, ByteRangeSpec}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::Bytes( -/// vec![ByteRangeSpec::FromTo(1, 100), ByteRangeSpec::AllFrom(200)] -/// )); -/// -/// headers.clear(); -/// headers.set(Range::Unregistered("letters".to_owned(), "a-f".to_owned())); -/// ``` -/// -/// ``` -/// use hyper::header::{Headers, Range}; -/// -/// let mut headers = Headers::new(); -/// headers.set(Range::bytes(1, 100)); -/// -/// headers.clear(); -/// headers.set(Range::bytes_multi(vec![(1, 100), (200, 300)])); -/// ``` -#[derive(PartialEq, Clone, Debug)] -pub enum Range { - /// Byte range - Bytes(Vec), - /// Custom range, with unit not registered at IANA - /// (`other-range-unit`: String , `other-range-set`: String) - Unregistered(String, String), -} - -/// Each `Range::Bytes` header can contain one or more `ByteRangeSpecs`. -/// Each `ByteRangeSpec` defines a range of bytes to fetch -#[derive(PartialEq, Clone, Debug)] -pub enum ByteRangeSpec { - /// Get all bytes between x and y ("x-y") - FromTo(u64, u64), - /// Get all bytes starting from x ("x-") - AllFrom(u64), - /// Get last x bytes ("-x") - Last(u64), -} - -impl ByteRangeSpec { - /// Given the full length of the entity, attempt to normalize the byte range - /// into an satisfiable end-inclusive (from, to) range. - /// - /// The resulting range is guaranteed to be a satisfiable range within the - /// bounds of `0 <= from <= to < full_length`. - /// - /// If the byte range is deemed unsatisfiable, `None` is returned. - /// An unsatisfiable range is generally cause for a server to either reject - /// the client request with a `416 Range Not Satisfiable` status code, or to - /// simply ignore the range header and serve the full entity using a `200 - /// OK` status code. - /// - /// This function closely follows [RFC 7233][1] section 2.1. - /// As such, it considers ranges to be satisfiable if they meet the - /// following conditions: - /// - /// > If a valid byte-range-set includes at least one byte-range-spec with - /// a first-byte-pos that is less than the current length of the - /// representation, or at least one suffix-byte-range-spec with a - /// non-zero suffix-length, then the byte-range-set is satisfiable. - /// Otherwise, the byte-range-set is unsatisfiable. - /// - /// The function also computes remainder ranges based on the RFC: - /// - /// > If the last-byte-pos value is - /// absent, or if the value is greater than or equal to the current - /// length of the representation data, the byte range is interpreted as - /// the remainder of the representation (i.e., the server replaces the - /// value of last-byte-pos with a value that is one less than the current - /// length of the selected representation). - /// - /// [1]: https://tools.ietf.org/html/rfc7233 - pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { - // If the full length is zero, there is no satisfiable end-inclusive range. - if full_length == 0 { - return None; - } - match self { - &ByteRangeSpec::FromTo(from, to) => { - if from < full_length && from <= to { - Some((from, ::std::cmp::min(to, full_length - 1))) - } else { - None - } - } - &ByteRangeSpec::AllFrom(from) => { - if from < full_length { - Some((from, full_length - 1)) - } else { - None - } - } - &ByteRangeSpec::Last(last) => { - if last > 0 { - // From the RFC: If the selected representation is shorter - // than the specified suffix-length, - // the entire representation is used. - if last > full_length { - Some((0, full_length - 1)) - } else { - Some((full_length - last, full_length - 1)) - } - } else { - None - } - } - } - } -} - -impl Range { - /// Get the most common byte range header ("bytes=from-to") - pub fn bytes(from: u64, to: u64) -> Range { - Range::Bytes(vec![ByteRangeSpec::FromTo(from, to)]) - } - - /// Get byte range header with multiple subranges - /// ("bytes=from1-to1,from2-to2,fromX-toX") - pub fn bytes_multi(ranges: Vec<(u64, u64)>) -> Range { - Range::Bytes( - ranges - .iter() - .map(|r| ByteRangeSpec::FromTo(r.0, r.1)) - .collect(), - ) - } -} - -impl fmt::Display for ByteRangeSpec { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ByteRangeSpec::FromTo(from, to) => write!(f, "{}-{}", from, to), - ByteRangeSpec::Last(pos) => write!(f, "-{}", pos), - ByteRangeSpec::AllFrom(pos) => write!(f, "{}-", pos), - } - } -} - -impl fmt::Display for Range { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - Range::Bytes(ref ranges) => { - try!(write!(f, "bytes=")); - - for (i, range) in ranges.iter().enumerate() { - if i != 0 { - try!(f.write_str(",")); - } - try!(Display::fmt(range, f)); - } - Ok(()) - } - Range::Unregistered(ref unit, ref range_str) => { - write!(f, "{}={}", unit, range_str) - } - } - } -} - -impl FromStr for Range { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut iter = s.splitn(2, '='); - - match (iter.next(), iter.next()) { - (Some("bytes"), Some(ranges)) => { - let ranges = from_comma_delimited(ranges); - if ranges.is_empty() { - return Err(::Error::Header); - } - Ok(Range::Bytes(ranges)) - } - (Some(unit), Some(range_str)) if unit != "" && range_str != "" => Ok( - Range::Unregistered(unit.to_owned(), range_str.to_owned()), - ), - _ => Err(::Error::Header), - } - } -} - -impl FromStr for ByteRangeSpec { - type Err = ::Error; - - fn from_str(s: &str) -> ::Result { - let mut parts = s.splitn(2, '-'); - - match (parts.next(), parts.next()) { - (Some(""), Some(end)) => end.parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::Last), - (Some(start), Some("")) => start - .parse() - .or(Err(::Error::Header)) - .map(ByteRangeSpec::AllFrom), - (Some(start), Some(end)) => match (start.parse(), end.parse()) { - (Ok(start), Ok(end)) if start <= end => { - Ok(ByteRangeSpec::FromTo(start, end)) - } - _ => Err(::Error::Header), - }, - _ => Err(::Error::Header), - } - } -} - -fn from_comma_delimited(s: &str) -> Vec { - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.parse().ok()) - .collect() -} - -impl Header for Range { - fn header_name() -> &'static str { - static NAME: &'static str = "Range"; - NAME - } - - fn parse_header(raw: &Raw) -> ::Result { - from_one_raw_str(raw) - } - - fn fmt_header(&self, f: &mut ::header::Formatter) -> fmt::Result { - f.fmt_line(self) - } -} - -#[test] -fn test_parse_bytes_range_valid() { - let r: Range = Header::parse_header(&"bytes=1-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100,-".into()).unwrap(); - let r3 = Range::bytes(1, 100); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,200-".into()).unwrap(); - let r2: Range = - Header::parse_header(&"bytes= 1-100 , 101-xxx, 200- ".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::AllFrom(200), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"bytes=1-100,-100".into()).unwrap(); - let r2: Range = Header::parse_header(&"bytes=1-100, ,,-100".into()).unwrap(); - let r3 = Range::Bytes(vec![ - ByteRangeSpec::FromTo(1, 100), - ByteRangeSpec::Last(100), - ]); - assert_eq!(r, r2); - assert_eq!(r2, r3); - - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_unregistered_range_valid() { - let r: Range = Header::parse_header(&"custom=1-100,-100".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "1-100,-100".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=abcd".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "abcd".to_owned()); - assert_eq!(r, r2); - - let r: Range = Header::parse_header(&"custom=xxx-yyy".into()).unwrap(); - let r2 = Range::Unregistered("custom".to_owned(), "xxx-yyy".to_owned()); - assert_eq!(r, r2); -} - -#[test] -fn test_parse_invalid() { - let r: ::Result = Header::parse_header(&"bytes=1-a,-".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-2-3".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"abc".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=1-100=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"bytes=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"custom=".into()); - assert_eq!(r.ok(), None); - - let r: ::Result = Header::parse_header(&"=1-100".into()); - assert_eq!(r.ok(), None); -} - -#[test] -fn test_fmt() { - use header::Headers; - - let mut headers = Headers::new(); - - headers.set(Range::Bytes(vec![ - ByteRangeSpec::FromTo(0, 1000), - ByteRangeSpec::AllFrom(2000), - ])); - assert_eq!(&headers.to_string(), "Range: bytes=0-1000,2000-\r\n"); - - headers.clear(); - headers.set(Range::Bytes(vec![])); - - assert_eq!(&headers.to_string(), "Range: bytes=\r\n"); - - headers.clear(); - headers.set(Range::Unregistered( - "custom".to_owned(), - "1-xxx".to_owned(), - )); - - assert_eq!(&headers.to_string(), "Range: custom=1-xxx\r\n"); -} - -#[test] -fn test_byte_range_spec_to_satisfiable_range() { - assert_eq!( - Some((0, 0)), - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 2).to_satisfiable_range(3) - ); - assert_eq!( - Some((1, 2)), - ByteRangeSpec::FromTo(1, 5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(3, 3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(2, 1).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::FromTo(0, 0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((0, 2)), - ByteRangeSpec::AllFrom(0).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::AllFrom(2).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(3).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(5).to_satisfiable_range(3) - ); - assert_eq!( - None, - ByteRangeSpec::AllFrom(0).to_satisfiable_range(0) - ); - - assert_eq!( - Some((1, 2)), - ByteRangeSpec::Last(2).to_satisfiable_range(3) - ); - assert_eq!( - Some((2, 2)), - ByteRangeSpec::Last(1).to_satisfiable_range(3) - ); - assert_eq!( - Some((0, 2)), - ByteRangeSpec::Last(5).to_satisfiable_range(3) - ); - assert_eq!(None, ByteRangeSpec::Last(0).to_satisfiable_range(3)); - assert_eq!(None, ByteRangeSpec::Last(2).to_satisfiable_range(0)); -} diff --git a/src/header/mod.rs b/src/header/mod.rs deleted file mode 100644 index 74e4b03e..00000000 --- a/src/header/mod.rs +++ /dev/null @@ -1,471 +0,0 @@ -//! Various http headers -// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) - -use std::fmt; -use std::str::FromStr; - -use bytes::{Bytes, BytesMut}; -use mime::Mime; -use modhttp::header::GetAll; -use modhttp::Error as HttpError; -use percent_encoding; - -pub use modhttp::header::*; - -use error::ParseError; -use httpmessage::HttpMessage; - -mod common; -mod shared; -#[doc(hidden)] -pub use self::common::*; -#[doc(hidden)] -pub use self::shared::*; - -#[doc(hidden)] -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ - /// Returns the name of the header field - fn name() -> HeaderName; - - /// Parse a header - fn parse(msg: &T) -> Result; -} - -#[doc(hidden)] -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(self)) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValueBytes; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_shared(Bytes::from(format!("{}", self))) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - #[cfg(feature = "brotli")] - Br, - /// A format using the zlib structure with deflate algorithm - #[cfg(feature = "flate2")] - Deflate, - /// Gzip algorithm - #[cfg(feature = "flate2")] - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - match self { - ContentEncoding::Identity | ContentEncoding::Auto => false, - _ => true, - } - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => "br", - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => "gzip", - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - #[cfg(feature = "brotli")] - ContentEncoding::Br => 1.1, - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => 1.0, - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -// TODO: remove memory allocation -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - match AsRef::::as_ref(&s.trim().to_lowercase()) { - #[cfg(feature = "brotli")] - "br" => ContentEncoding::Br, - #[cfg(feature = "flate2")] - "gzip" => ContentEncoding::Gzip, - #[cfg(feature = "flate2")] - "deflate" => ContentEncoding::Deflate, - _ => ContentEncoding::Identity, - } - } -} - -#[doc(hidden)] -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl fmt::Write for Writer { - #[inline] - fn write_str(&mut self, s: &str) -> fmt::Result { - self.buf.extend_from_slice(s.as_bytes()); - Ok(()) - } - - #[inline] - fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result { - fmt::write(self, args) - } -} - -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited( - all: GetAll, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }).filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -// From hyper v0.11.27 src/header/parsing.rs - -/// The value part of an extended parameter consisting of three parts: -/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), -/// and a character sequence representing the actual value (`value`), separated by single quote -/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value(val: &str) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3, '\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(n) => FromStr::from_str(n).map_err(|_| ::error::ParseError::Header)?, - }; - - // Interpret the second piece as a language tag - let language_tag: Option = match parts.next() { - None => return Err(::error::ParseError::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(::error::ParseError::Header), - }, - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(::error::ParseError::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - value, - charset, - language_tag, - }) -} - -impl fmt::Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - let encoded_value = percent_encoding::percent_encode( - &self.value[..], - self::percent_encoding_http::HTTP_VALUE, - ); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 -pub fn http_percent_encode(f: &mut fmt::Formatter, bytes: &[u8]) -> fmt::Result { - let encoded = - percent_encoding::percent_encode(bytes, self::percent_encoding_http::HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} -mod percent_encoding_http { - use percent_encoding; - - // internal module because macro is hard-coded to make a public item - // but we don't want to public export this item - define_encode_set! { - // This encode set is used for HTTP header values and is defined at - // https://tools.ietf.org/html/rfc5987#section-3.2 - pub HTTP_VALUE = [percent_encoding::SIMPLE_ENCODE_SET] | { - ' ', '"', '%', '\'', '(', ')', '*', ',', '/', ':', ';', '<', '-', '>', '?', - '[', '\\', ']', '{', '}' - } - } -} - -#[cfg(test)] -mod tests { - use super::{parse_extended_value, ExtendedValue}; - use header::shared::Charset; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!( - vec![163, b' ', b'r', b'a', b't', b'e', b's'], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!( - vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - }; - assert_eq!( - "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value) - ); - } -} diff --git a/src/header/shared/charset.rs b/src/header/shared/charset.rs deleted file mode 100644 index b679971b..00000000 --- a/src/header/shared/charset.rs +++ /dev/null @@ -1,152 +0,0 @@ -use std::fmt::{self, Display}; -use std::str::FromStr; - -use self::Charset::*; - -/// A Mime charset. -/// -/// The string representation is normalized to upper case. -/// -/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. -/// -/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml -#[derive(Clone, Debug, PartialEq)] -#[allow(non_camel_case_types)] -pub enum Charset { - /// US ASCII - Us_Ascii, - /// ISO-8859-1 - Iso_8859_1, - /// ISO-8859-2 - Iso_8859_2, - /// ISO-8859-3 - Iso_8859_3, - /// ISO-8859-4 - Iso_8859_4, - /// ISO-8859-5 - Iso_8859_5, - /// ISO-8859-6 - Iso_8859_6, - /// ISO-8859-7 - Iso_8859_7, - /// ISO-8859-8 - Iso_8859_8, - /// ISO-8859-9 - Iso_8859_9, - /// ISO-8859-10 - Iso_8859_10, - /// Shift_JIS - Shift_Jis, - /// EUC-JP - Euc_Jp, - /// ISO-2022-KR - Iso_2022_Kr, - /// EUC-KR - Euc_Kr, - /// ISO-2022-JP - Iso_2022_Jp, - /// ISO-2022-JP-2 - Iso_2022_Jp_2, - /// ISO-8859-6-E - Iso_8859_6_E, - /// ISO-8859-6-I - Iso_8859_6_I, - /// ISO-8859-8-E - Iso_8859_8_E, - /// ISO-8859-8-I - Iso_8859_8_I, - /// GB2312 - Gb2312, - /// Big5 - Big5, - /// KOI8-R - Koi8_R, - /// An arbitrary charset specified as a string - Ext(String), -} - -impl Charset { - fn label(&self) -> &str { - match *self { - Us_Ascii => "US-ASCII", - Iso_8859_1 => "ISO-8859-1", - Iso_8859_2 => "ISO-8859-2", - Iso_8859_3 => "ISO-8859-3", - Iso_8859_4 => "ISO-8859-4", - Iso_8859_5 => "ISO-8859-5", - Iso_8859_6 => "ISO-8859-6", - Iso_8859_7 => "ISO-8859-7", - Iso_8859_8 => "ISO-8859-8", - Iso_8859_9 => "ISO-8859-9", - Iso_8859_10 => "ISO-8859-10", - Shift_Jis => "Shift-JIS", - Euc_Jp => "EUC-JP", - Iso_2022_Kr => "ISO-2022-KR", - Euc_Kr => "EUC-KR", - Iso_2022_Jp => "ISO-2022-JP", - Iso_2022_Jp_2 => "ISO-2022-JP-2", - Iso_8859_6_E => "ISO-8859-6-E", - Iso_8859_6_I => "ISO-8859-6-I", - Iso_8859_8_E => "ISO-8859-8-E", - Iso_8859_8_I => "ISO-8859-8-I", - Gb2312 => "GB2312", - Big5 => "big5", - Koi8_R => "KOI8-R", - Ext(ref s) => s, - } - } -} - -impl Display for Charset { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(self.label()) - } -} - -impl FromStr for Charset { - type Err = ::Error; - fn from_str(s: &str) -> ::Result { - Ok(match s.to_ascii_uppercase().as_ref() { - "US-ASCII" => Us_Ascii, - "ISO-8859-1" => Iso_8859_1, - "ISO-8859-2" => Iso_8859_2, - "ISO-8859-3" => Iso_8859_3, - "ISO-8859-4" => Iso_8859_4, - "ISO-8859-5" => Iso_8859_5, - "ISO-8859-6" => Iso_8859_6, - "ISO-8859-7" => Iso_8859_7, - "ISO-8859-8" => Iso_8859_8, - "ISO-8859-9" => Iso_8859_9, - "ISO-8859-10" => Iso_8859_10, - "SHIFT-JIS" => Shift_Jis, - "EUC-JP" => Euc_Jp, - "ISO-2022-KR" => Iso_2022_Kr, - "EUC-KR" => Euc_Kr, - "ISO-2022-JP" => Iso_2022_Jp, - "ISO-2022-JP-2" => Iso_2022_Jp_2, - "ISO-8859-6-E" => Iso_8859_6_E, - "ISO-8859-6-I" => Iso_8859_6_I, - "ISO-8859-8-E" => Iso_8859_8_E, - "ISO-8859-8-I" => Iso_8859_8_I, - "GB2312" => Gb2312, - "big5" => Big5, - "KOI8-R" => Koi8_R, - s => Ext(s.to_owned()), - }) - } -} - -#[test] -fn test_parse() { - assert_eq!(Us_Ascii, "us-ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-Ascii".parse().unwrap()); - assert_eq!(Us_Ascii, "US-ASCII".parse().unwrap()); - assert_eq!(Shift_Jis, "Shift-JIS".parse().unwrap()); - assert_eq!(Ext("ABCD".to_owned()), "abcd".parse().unwrap()); -} - -#[test] -fn test_display() { - assert_eq!("US-ASCII", format!("{}", Us_Ascii)); - assert_eq!("ABCD", format!("{}", Ext("ABCD".to_owned()))); -} diff --git a/src/header/shared/encoding.rs b/src/header/shared/encoding.rs deleted file mode 100644 index 64027d8a..00000000 --- a/src/header/shared/encoding.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::fmt; -use std::str; - -pub use self::Encoding::{ - Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, -}; - -/// A value to represent an encoding used in `Transfer-Encoding` -/// or `Accept-Encoding` header. -#[derive(Clone, PartialEq, Debug)] -pub enum Encoding { - /// The `chunked` encoding. - Chunked, - /// The `br` encoding. - Brotli, - /// The `gzip` encoding. - Gzip, - /// The `deflate` encoding. - Deflate, - /// The `compress` encoding. - Compress, - /// The `identity` encoding. - Identity, - /// The `trailers` encoding. - Trailers, - /// Some other encoding that is less common, can be any String. - EncodingExt(String), -} - -impl fmt::Display for Encoding { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str(match *self { - Chunked => "chunked", - Brotli => "br", - Gzip => "gzip", - Deflate => "deflate", - Compress => "compress", - Identity => "identity", - Trailers => "trailers", - EncodingExt(ref s) => s.as_ref(), - }) - } -} - -impl str::FromStr for Encoding { - type Err = ::error::ParseError; - fn from_str(s: &str) -> Result { - match s { - "chunked" => Ok(Chunked), - "br" => Ok(Brotli), - "deflate" => Ok(Deflate), - "gzip" => Ok(Gzip), - "compress" => Ok(Compress), - "identity" => Ok(Identity), - "trailers" => Ok(Trailers), - _ => Ok(EncodingExt(s.to_owned())), - } - } -} diff --git a/src/header/shared/entity.rs b/src/header/shared/entity.rs deleted file mode 100644 index 0d3b0a4e..00000000 --- a/src/header/shared/entity.rs +++ /dev/null @@ -1,266 +0,0 @@ -use header::{HeaderValue, IntoHeaderValue, InvalidHeaderValueBytes, Writer}; -use std::fmt::{self, Display, Write}; -use std::str::FromStr; - -/// check that each char in the slice is either: -/// 1. `%x21`, or -/// 2. in the range `%x23` to `%x7E`, or -/// 3. above `%x80` -fn check_slice_validity(slice: &str) -> bool { - slice - .bytes() - .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) -} - -/// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) -/// -/// An entity tag consists of a string enclosed by two literal double quotes. -/// Preceding the first double quote is an optional weakness indicator, -/// which always looks like `W/`. Examples for valid tags are `"xyzzy"` and -/// `W/"xyzzy"`. -/// -/// # ABNF -/// -/// ```text -/// entity-tag = [ weak ] opaque-tag -/// weak = %x57.2F ; "W/", case-sensitive -/// opaque-tag = DQUOTE *etagc DQUOTE -/// etagc = %x21 / %x23-7E / obs-text -/// ; VCHAR except double quotes, plus obs-text -/// ``` -/// -/// # Comparison -/// To check if two entity tags are equivalent in an application always use the -/// `strong_eq` or `weak_eq` methods based on the context of the Tag. Only use -/// `==` to check if two tags are identical. -/// -/// The example below shows the results for a set of entity-tag pairs and -/// both the weak and strong comparison function results: -/// -/// | `ETag 1`| `ETag 2`| Strong Comparison | Weak Comparison | -/// |---------|---------|-------------------|-----------------| -/// | `W/"1"` | `W/"1"` | no match | match | -/// | `W/"1"` | `W/"2"` | no match | no match | -/// | `W/"1"` | `"1"` | no match | match | -/// | `"1"` | `"1"` | match | match | -#[derive(Clone, Debug, Eq, PartialEq)] -pub struct EntityTag { - /// Weakness indicator for the tag - pub weak: bool, - /// The opaque string in between the DQUOTEs - tag: String, -} - -impl EntityTag { - /// Constructs a new EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn new(weak: bool, tag: String) -> EntityTag { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - EntityTag { weak, tag } - } - - /// Constructs a new weak EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn weak(tag: String) -> EntityTag { - EntityTag::new(true, tag) - } - - /// Constructs a new strong EntityTag. - /// # Panics - /// If the tag contains invalid characters. - pub fn strong(tag: String) -> EntityTag { - EntityTag::new(false, tag) - } - - /// Get the tag. - pub fn tag(&self) -> &str { - self.tag.as_ref() - } - - /// Set the tag. - /// # Panics - /// If the tag contains invalid characters. - pub fn set_tag(&mut self, tag: String) { - assert!(check_slice_validity(&tag), "Invalid tag: {:?}", tag); - self.tag = tag - } - - /// For strong comparison two entity-tags are equivalent if both are not - /// weak and their opaque-tags match character-by-character. - pub fn strong_eq(&self, other: &EntityTag) -> bool { - !self.weak && !other.weak && self.tag == other.tag - } - - /// For weak comparison two entity-tags are equivalent if their - /// opaque-tags match character-by-character, regardless of either or - /// both being tagged as "weak". - pub fn weak_eq(&self, other: &EntityTag) -> bool { - self.tag == other.tag - } - - /// The inverse of `EntityTag.strong_eq()`. - pub fn strong_ne(&self, other: &EntityTag) -> bool { - !self.strong_eq(other) - } - - /// The inverse of `EntityTag.weak_eq()`. - pub fn weak_ne(&self, other: &EntityTag) -> bool { - !self.weak_eq(other) - } -} - -impl Display for EntityTag { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if self.weak { - write!(f, "W/\"{}\"", self.tag) - } else { - write!(f, "\"{}\"", self.tag) - } - } -} - -impl FromStr for EntityTag { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result { - let length: usize = s.len(); - let slice = &s[..]; - // Early exits if it doesn't terminate in a DQUOTE. - if !slice.ends_with('"') || slice.len() < 2 { - return Err(::error::ParseError::Header); - } - // The etag is weak if its first char is not a DQUOTE. - if slice.len() >= 2 - && slice.starts_with('"') - && check_slice_validity(&slice[1..length - 1]) - { - // No need to check if the last char is a DQUOTE, - // we already did that above. - return Ok(EntityTag { - weak: false, - tag: slice[1..length - 1].to_owned(), - }); - } else if slice.len() >= 4 - && slice.starts_with("W/\"") - && check_slice_validity(&slice[3..length - 1]) - { - return Ok(EntityTag { - weak: true, - tag: slice[3..length - 1].to_owned(), - }); - } - Err(::error::ParseError::Header) - } -} - -impl IntoHeaderValue for EntityTag { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = Writer::new(); - write!(wrt, "{}", self).unwrap(); - HeaderValue::from_shared(wrt.take()) - } -} - -#[cfg(test)] -mod tests { - use super::EntityTag; - - #[test] - fn test_etag_parse_success() { - // Expected success - assert_eq!( - "\"foobar\"".parse::().unwrap(), - EntityTag::strong("foobar".to_owned()) - ); - assert_eq!( - "\"\"".parse::().unwrap(), - EntityTag::strong("".to_owned()) - ); - assert_eq!( - "W/\"weaktag\"".parse::().unwrap(), - EntityTag::weak("weaktag".to_owned()) - ); - assert_eq!( - "W/\"\x65\x62\"".parse::().unwrap(), - EntityTag::weak("\x65\x62".to_owned()) - ); - assert_eq!( - "W/\"\"".parse::().unwrap(), - EntityTag::weak("".to_owned()) - ); - } - - #[test] - fn test_etag_parse_failures() { - // Expected failures - assert!("no-dquotes".parse::().is_err()); - assert!( - "w/\"the-first-w-is-case-sensitive\"" - .parse::() - .is_err() - ); - assert!("".parse::().is_err()); - assert!("\"unmatched-dquotes1".parse::().is_err()); - assert!("unmatched-dquotes2\"".parse::().is_err()); - assert!("matched-\"dquotes\"".parse::().is_err()); - } - - #[test] - fn test_etag_fmt() { - assert_eq!( - format!("{}", EntityTag::strong("foobar".to_owned())), - "\"foobar\"" - ); - assert_eq!(format!("{}", EntityTag::strong("".to_owned())), "\"\""); - assert_eq!( - format!("{}", EntityTag::weak("weak-etag".to_owned())), - "W/\"weak-etag\"" - ); - assert_eq!( - format!("{}", EntityTag::weak("\u{0065}".to_owned())), - "W/\"\x65\"" - ); - assert_eq!(format!("{}", EntityTag::weak("".to_owned())), "W/\"\""); - } - - #[test] - fn test_cmp() { - // | ETag 1 | ETag 2 | Strong Comparison | Weak Comparison | - // |---------|---------|-------------------|-----------------| - // | `W/"1"` | `W/"1"` | no match | match | - // | `W/"1"` | `W/"2"` | no match | no match | - // | `W/"1"` | `"1"` | no match | match | - // | `"1"` | `"1"` | match | match | - let mut etag1 = EntityTag::weak("1".to_owned()); - let mut etag2 = EntityTag::weak("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::weak("2".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(!etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(etag1.weak_ne(&etag2)); - - etag1 = EntityTag::weak("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(!etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - - etag1 = EntityTag::strong("1".to_owned()); - etag2 = EntityTag::strong("1".to_owned()); - assert!(etag1.strong_eq(&etag2)); - assert!(etag1.weak_eq(&etag2)); - assert!(!etag1.strong_ne(&etag2)); - assert!(!etag1.weak_ne(&etag2)); - } -} diff --git a/src/header/shared/httpdate.rs b/src/header/shared/httpdate.rs deleted file mode 100644 index 7fd26b12..00000000 --- a/src/header/shared/httpdate.rs +++ /dev/null @@ -1,119 +0,0 @@ -use std::fmt::{self, Display}; -use std::io::Write; -use std::str::FromStr; -use std::time::{Duration, SystemTime, UNIX_EPOCH}; - -use bytes::{BufMut, BytesMut}; -use http::header::{HeaderValue, InvalidHeaderValueBytes}; -use time; - -use error::ParseError; -use header::IntoHeaderValue; - -/// A timestamp with HTTP formatting and parsing -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -pub struct HttpDate(time::Tm); - -impl FromStr for HttpDate { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - match time::strptime(s, "%a, %d %b %Y %T %Z") - .or_else(|_| time::strptime(s, "%A, %d-%b-%y %T %Z")) - .or_else(|_| time::strptime(s, "%c")) - { - Ok(t) => Ok(HttpDate(t)), - Err(_) => Err(ParseError::Header), - } - } -} - -impl Display for HttpDate { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.0.to_utc().rfc822(), f) - } -} - -impl From for HttpDate { - fn from(tm: time::Tm) -> HttpDate { - HttpDate(tm) - } -} - -impl From for HttpDate { - fn from(sys: SystemTime) -> HttpDate { - let tmspec = match sys.duration_since(UNIX_EPOCH) { - Ok(dur) => { - time::Timespec::new(dur.as_secs() as i64, dur.subsec_nanos() as i32) - } - Err(err) => { - let neg = err.duration(); - time::Timespec::new( - -(neg.as_secs() as i64), - -(neg.subsec_nanos() as i32), - ) - } - }; - HttpDate(time::at_utc(tmspec)) - } -} - -impl IntoHeaderValue for HttpDate { - type Error = InvalidHeaderValueBytes; - - fn try_into(self) -> Result { - let mut wrt = BytesMut::with_capacity(29).writer(); - write!(wrt, "{}", self.0.rfc822()).unwrap(); - HeaderValue::from_shared(wrt.get_mut().take().freeze()) - } -} - -impl From for SystemTime { - fn from(date: HttpDate) -> SystemTime { - let spec = date.0.to_timespec(); - if spec.sec >= 0 { - UNIX_EPOCH + Duration::new(spec.sec as u64, spec.nsec as u32) - } else { - UNIX_EPOCH - Duration::new(spec.sec as u64, spec.nsec as u32) - } - } -} - -#[cfg(test)] -mod tests { - use super::HttpDate; - use time::Tm; - - const NOV_07: HttpDate = HttpDate(Tm { - tm_nsec: 0, - tm_sec: 37, - tm_min: 48, - tm_hour: 8, - tm_mday: 7, - tm_mon: 10, - tm_year: 94, - tm_wday: 0, - tm_isdst: 0, - tm_yday: 0, - tm_utcoff: 0, - }); - - #[test] - fn test_date() { - assert_eq!( - "Sun, 07 Nov 1994 08:48:37 GMT".parse::().unwrap(), - NOV_07 - ); - assert_eq!( - "Sunday, 07-Nov-94 08:48:37 GMT" - .parse::() - .unwrap(), - NOV_07 - ); - assert_eq!( - "Sun Nov 7 08:48:37 1994".parse::().unwrap(), - NOV_07 - ); - assert!("this-is-no-date".parse::().is_err()); - } -} diff --git a/src/header/shared/mod.rs b/src/header/shared/mod.rs deleted file mode 100644 index f2bc9163..00000000 --- a/src/header/shared/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -//! Copied for `hyper::header::shared`; - -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; -pub use language_tags::LanguageTag; - -mod charset; -mod encoding; -mod entity; -mod httpdate; -mod quality_item; diff --git a/src/header/shared/quality_item.rs b/src/header/shared/quality_item.rs deleted file mode 100644 index 80bd7e1c..00000000 --- a/src/header/shared/quality_item.rs +++ /dev/null @@ -1,294 +0,0 @@ -use std::cmp; -use std::default::Default; -use std::fmt; -use std::str; - -use self::internal::IntoQuality; - -/// Represents a quality used in quality values. -/// -/// Can be created with the `q` function. -/// -/// # Implementation notes -/// -/// The quality value is defined as a number between 0 and 1 with three decimal -/// places. This means there are 1001 possible values. Since floating point -/// numbers are not exact and the smallest floating point data type (`f32`) -/// consumes four bytes, hyper uses an `u16` value to store the -/// quality internally. For performance reasons you may set quality directly to -/// a value between 0 and 1000 e.g. `Quality(532)` matches the quality -/// `q=0.532`. -/// -/// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) -/// gives more information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] -pub struct Quality(u16); - -impl Default for Quality { - fn default() -> Quality { - Quality(1000) - } -} - -/// Represents an item with a quality value as defined in -/// [RFC7231](https://tools.ietf.org/html/rfc7231#section-5.3.1). -#[derive(Clone, PartialEq, Debug)] -pub struct QualityItem { - /// The actual contents of the field. - pub item: T, - /// The quality (client or server preference) for the value. - pub quality: Quality, -} - -impl QualityItem { - /// Creates a new `QualityItem` from an item and a quality. - /// The item can be of any type. - /// The quality should be a value in the range [0, 1]. - pub fn new(item: T, quality: Quality) -> QualityItem { - QualityItem { item, quality } - } -} - -impl cmp::PartialOrd for QualityItem { - fn partial_cmp(&self, other: &QualityItem) -> Option { - self.quality.partial_cmp(&other.quality) - } -} - -impl fmt::Display for QualityItem { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - fmt::Display::fmt(&self.item, f)?; - match self.quality.0 { - 1000 => Ok(()), - 0 => f.write_str("; q=0"), - x => write!(f, "; q=0.{}", format!("{:03}", x).trim_right_matches('0')), - } - } -} - -impl str::FromStr for QualityItem { - type Err = ::error::ParseError; - - fn from_str(s: &str) -> Result, ::error::ParseError> { - if !s.is_ascii() { - return Err(::error::ParseError::Header); - } - // Set defaults used if parsing fails. - let mut raw_item = s; - let mut quality = 1f32; - - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); - if parts.len() == 2 { - if parts[0].len() < 2 { - return Err(::error::ParseError::Header); - } - let start = &parts[0][0..2]; - if start == "q=" || start == "Q=" { - let q_part = &parts[0][2..parts[0].len()]; - if q_part.len() > 5 { - return Err(::error::ParseError::Header); - } - match q_part.parse::() { - Ok(q_value) => { - if 0f32 <= q_value && q_value <= 1f32 { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(::error::ParseError::Header); - } - } - Err(_) => return Err(::error::ParseError::Header), - } - } - } - match raw_item.parse::() { - // we already checked above that the quality is within range - Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), - Err(_) => Err(::error::ParseError::Header), - } - } -} - -#[inline] -fn from_f32(f: f32) -> Quality { - // this function is only used internally. A check that `f` is within range - // should be done before calling this method. Just in case, this - // debug_assert should catch if we were forgetful - debug_assert!( - f >= 0f32 && f <= 1f32, - "q value must be between 0.0 and 1.0" - ); - Quality((f * 1000f32) as u16) -} - -/// Convenience function to wrap a value in a `QualityItem` -/// Sets `q` to the default 1.0 -pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Default::default()) -} - -/// Convenience function to create a `Quality` from a float or integer. -/// -/// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality { - val.into_quality() -} - -mod internal { - use super::Quality; - - // TryFrom is probably better, but it's not stable. For now, we want to - // keep the functionality of the `q` function, while allowing it to be - // generic over `f32` and `u16`. - // - // `q` would panic before, so keep that behavior. `TryFrom` can be - // introduced later for a non-panicking conversion. - - pub trait IntoQuality: Sealed + Sized { - fn into_quality(self) -> Quality; - } - - impl IntoQuality for f32 { - fn into_quality(self) -> Quality { - assert!( - self >= 0f32 && self <= 1f32, - "float must be between 0.0 and 1.0" - ); - super::from_f32(self) - } - } - - impl IntoQuality for u16 { - fn into_quality(self) -> Quality { - assert!(self <= 1000, "u16 must be between 0 and 1000"); - Quality(self) - } - } - - pub trait Sealed {} - impl Sealed for u16 {} - impl Sealed for f32 {} -} - -#[cfg(test)] -mod tests { - use super::super::encoding::*; - use super::*; - - #[test] - fn test_quality_item_fmt_q_1() { - let x = qitem(Chunked); - assert_eq!(format!("{}", x), "chunked"); - } - #[test] - fn test_quality_item_fmt_q_0001() { - let x = QualityItem::new(Chunked, Quality(1)); - assert_eq!(format!("{}", x), "chunked; q=0.001"); - } - #[test] - fn test_quality_item_fmt_q_05() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(500), - }; - assert_eq!(format!("{}", x), "identity; q=0.5"); - } - - #[test] - fn test_quality_item_fmt_q_0() { - // Custom value - let x = QualityItem { - item: EncodingExt("identity".to_owned()), - quality: Quality(0), - }; - assert_eq!(x.to_string(), "identity; q=0"); - } - - #[test] - fn test_quality_item_from_str1() { - let x: Result, _> = "chunked".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str2() { - let x: Result, _> = "chunked; q=1".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Chunked, - quality: Quality(1000), - } - ); - } - #[test] - fn test_quality_item_from_str3() { - let x: Result, _> = "gzip; q=0.5".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(500), - } - ); - } - #[test] - fn test_quality_item_from_str4() { - let x: Result, _> = "gzip; q=0.273".parse(); - assert_eq!( - x.unwrap(), - QualityItem { - item: Gzip, - quality: Quality(273), - } - ); - } - #[test] - fn test_quality_item_from_str5() { - let x: Result, _> = "gzip; q=0.2739999".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_from_str6() { - let x: Result, _> = "gzip; q=2".parse(); - assert!(x.is_err()); - } - #[test] - fn test_quality_item_ordering() { - let x: QualityItem = "gzip; q=0.5".parse().ok().unwrap(); - let y: QualityItem = "gzip; q=0.273".parse().ok().unwrap(); - let comparision_result: bool = x.gt(&y); - assert!(comparision_result) - } - - #[test] - fn test_quality() { - assert_eq!(q(0.5), Quality(500)); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid() { - q(-1.0); - } - - #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] - fn test_quality_invalid2() { - q(2.0); - } - - #[test] - fn test_fuzzing_bugs() { - assert!("99999;".parse::>().is_err()); - assert!("\x0d;;;=\u{d6aa}==".parse::>().is_err()) - } -} diff --git a/src/httpresponse.rs b/src/httpresponse.rs index cdcdea94..3c034fae 100644 --- a/src/httpresponse.rs +++ b/src/httpresponse.rs @@ -15,8 +15,6 @@ use serde_json; use body::Body; use error::Error; use header::{ContentEncoding, Header, IntoHeaderValue}; -// use httpmessage::HttpMessage; -// use httprequest::HttpRequest; /// max write buffer size 64k pub(crate) const MAX_WRITE_BUFFER_SIZE: usize = 65_536; @@ -942,8 +940,8 @@ mod tests { use body::Binary; use http; use http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - use time::Duration; + use header::ContentEncoding; use test::TestRequest; #[test] diff --git a/src/lib.rs b/src/lib.rs index 6215bc4f..7efb4dea 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -88,7 +88,6 @@ extern crate log; extern crate base64; extern crate byteorder; extern crate bytes; -extern crate regex; extern crate sha1; extern crate time; #[macro_use] @@ -99,21 +98,16 @@ extern crate failure; extern crate lazy_static; #[macro_use] extern crate futures; -#[cfg(feature = "brotli")] -extern crate brotli2; extern crate cookie; extern crate encoding; -#[cfg(feature = "flate2")] -extern crate flate2; extern crate http as modhttp; extern crate httparse; -extern crate language_tags; extern crate mime; -extern crate mime_guess; extern crate net2; extern crate rand; extern crate serde; extern crate serde_urlencoded; +extern crate tokio; extern crate tokio_codec; extern crate tokio_current_thread; extern crate tokio_io; @@ -126,8 +120,6 @@ extern crate url; #[macro_use] extern crate percent_encoding; extern crate serde_json; -extern crate smallvec; -extern crate tokio; #[cfg(test)] #[macro_use] @@ -193,9 +185,6 @@ pub mod http { /// Various http headers pub mod header { pub use header::*; - pub use header::{ - Charset, ContentDisposition, DispositionParam, DispositionType, LanguageTag, - }; } pub use header::ContentEncoding; pub use httpresponse::ConnectionType; diff --git a/src/server/input.rs b/src/server/input.rs deleted file mode 100644 index d23d1e99..00000000 --- a/src/server/input.rs +++ /dev/null @@ -1,288 +0,0 @@ -use std::io::{self, Write}; - -#[cfg(feature = "brotli")] -use brotli2::write::BrotliDecoder; -use bytes::{Bytes, BytesMut}; -use error::PayloadError; -#[cfg(feature = "flate2")] -use flate2::write::{GzDecoder, ZlibDecoder}; -use header::ContentEncoding; -use http::header::{HeaderMap, CONTENT_ENCODING}; -use payload::{PayloadSender, PayloadStatus, PayloadWriter}; - -pub(crate) enum PayloadType { - Sender(PayloadSender), - Encoding(Box), -} - -impl PayloadType { - #[cfg(any(feature = "brotli", feature = "flate2"))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - // check content-encoding - let enc = if let Some(enc) = headers.get(CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Auto - } - } else { - ContentEncoding::Auto - }; - - match enc { - ContentEncoding::Auto | ContentEncoding::Identity => { - PayloadType::Sender(sender) - } - _ => PayloadType::Encoding(Box::new(EncodedPayload::new(sender, enc))), - } - } - - #[cfg(not(any(feature = "brotli", feature = "flate2")))] - pub fn new(headers: &HeaderMap, sender: PayloadSender) -> PayloadType { - PayloadType::Sender(sender) - } -} - -impl PayloadWriter for PayloadType { - #[inline] - fn set_error(&mut self, err: PayloadError) { - match *self { - PayloadType::Sender(ref mut sender) => sender.set_error(err), - PayloadType::Encoding(ref mut enc) => enc.set_error(err), - } - } - - #[inline] - fn feed_eof(&mut self) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_eof(), - PayloadType::Encoding(ref mut enc) => enc.feed_eof(), - } - } - - #[inline] - fn feed_data(&mut self, data: Bytes) { - match *self { - PayloadType::Sender(ref mut sender) => sender.feed_data(data), - PayloadType::Encoding(ref mut enc) => enc.feed_data(data), - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - match *self { - PayloadType::Sender(ref sender) => sender.need_read(), - PayloadType::Encoding(ref enc) => enc.need_read(), - } - } -} - -/// Payload wrapper with content decompression support -pub(crate) struct EncodedPayload { - inner: PayloadSender, - error: bool, - payload: PayloadStream, -} - -impl EncodedPayload { - pub fn new(inner: PayloadSender, enc: ContentEncoding) -> EncodedPayload { - EncodedPayload { - inner, - error: false, - payload: PayloadStream::new(enc), - } - } -} - -impl PayloadWriter for EncodedPayload { - fn set_error(&mut self, err: PayloadError) { - self.inner.set_error(err) - } - - fn feed_eof(&mut self) { - if !self.error { - match self.payload.feed_eof() { - Err(err) => { - self.error = true; - self.set_error(PayloadError::Io(err)); - } - Ok(value) => { - if let Some(b) = value { - self.inner.feed_data(b); - } - self.inner.feed_eof(); - } - } - } - } - - fn feed_data(&mut self, data: Bytes) { - if self.error { - return; - } - - match self.payload.feed_data(data) { - Ok(Some(b)) => self.inner.feed_data(b), - Ok(None) => (), - Err(e) => { - self.error = true; - self.set_error(e.into()); - } - } - } - - #[inline] - fn need_read(&self) -> PayloadStatus { - self.inner.need_read() - } -} - -pub(crate) enum Decoder { - #[cfg(feature = "flate2")] - Deflate(Box>), - #[cfg(feature = "flate2")] - Gzip(Box>), - #[cfg(feature = "brotli")] - Br(Box>), - Identity, -} - -pub(crate) struct Writer { - buf: BytesMut, -} - -impl Writer { - fn new() -> Writer { - Writer { - buf: BytesMut::with_capacity(8192), - } - } - fn take(&mut self) -> Bytes { - self.buf.take().freeze() - } -} - -impl io::Write for Writer { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.buf.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -/// Payload stream with decompression support -pub(crate) struct PayloadStream { - decoder: Decoder, -} - -impl PayloadStream { - pub fn new(enc: ContentEncoding) -> PayloadStream { - let decoder = match enc { - #[cfg(feature = "brotli")] - ContentEncoding::Br => { - Decoder::Br(Box::new(BrotliDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Deflate => { - Decoder::Deflate(Box::new(ZlibDecoder::new(Writer::new()))) - } - #[cfg(feature = "flate2")] - ContentEncoding::Gzip => { - Decoder::Gzip(Box::new(GzDecoder::new(Writer::new()))) - } - _ => Decoder::Identity, - }; - PayloadStream { decoder } - } -} - -impl PayloadStream { - pub fn feed_eof(&mut self) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.finish() { - Ok(mut writer) => { - let b = writer.take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.try_finish() { - Ok(_) => { - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(None), - } - } - - pub fn feed_data(&mut self, data: Bytes) -> io::Result> { - match self.decoder { - #[cfg(feature = "brotli")] - Decoder::Br(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - #[cfg(feature = "flate2")] - Decoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { - Ok(_) => { - decoder.flush()?; - let b = decoder.get_mut().take(); - if !b.is_empty() { - Ok(Some(b)) - } else { - Ok(None) - } - } - Err(e) => Err(e), - }, - Decoder::Identity => Ok(Some(data)), - } - } -} diff --git a/src/server/mod.rs b/src/server/mod.rs index 0abd7c21..972fbf3f 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -114,9 +114,6 @@ use tokio_tcp::TcpStream; pub use actix_net::server::{PauseServer, ResumeServer, StopServer}; -pub(crate) mod input; -pub(crate) mod output; - #[doc(hidden)] pub use super::helpers::write_content_length; diff --git a/src/server/service.rs b/src/server/service.rs deleted file mode 100644 index a55c33f7..00000000 --- a/src/server/service.rs +++ /dev/null @@ -1,273 +0,0 @@ -use std::marker::PhantomData; -use std::time::Duration; - -use actix_net::service::{NewService, Service}; -use futures::future::{ok, FutureResult}; -use futures::{Async, Poll}; - -use super::channel::{H1Channel, HttpChannel}; -use super::error::HttpDispatchError; -use super::handler::HttpHandler; -use super::settings::ServiceConfig; -use super::IoStream; -use error::Error; - -/// `NewService` implementation for HTTP1/HTTP2 transports -pub struct HttpService -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpService -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - HttpService { - settings, - _t: PhantomData, - } - } -} - -impl NewService for HttpService -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = HttpServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(HttpServiceHandler::new(self.settings.clone())) - } -} - -pub struct HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> HttpServiceHandler { - HttpServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for HttpServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = HttpChannel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - HttpChannel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for HTTP1 transport -pub struct H1Service -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1Service -where - H: HttpHandler, - Io: IoStream, -{ - /// Create new `HttpService` instance. - pub fn new(settings: ServiceConfig) -> Self { - H1Service { - settings, - _t: PhantomData, - } - } -} - -impl NewService for H1Service -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type InitError = (); - type Service = H1ServiceHandler; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(H1ServiceHandler::new(self.settings.clone())) - } -} - -/// `Service` implementation for HTTP1 transport -pub struct H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - settings: ServiceConfig, - _t: PhantomData, -} - -impl H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - fn new(settings: ServiceConfig) -> H1ServiceHandler { - H1ServiceHandler { - settings, - _t: PhantomData, - } - } -} - -impl Service for H1ServiceHandler -where - H: HttpHandler, - Io: IoStream, -{ - type Request = Io; - type Response = (); - type Error = HttpDispatchError; - type Future = H1Channel; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - H1Channel::new(self.settings.clone(), req) - } -} - -/// `NewService` implementation for stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfiguration { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Default for StreamConfiguration { - fn default() -> Self { - Self::new() - } -} - -impl StreamConfiguration { - /// Create new `StreamConfigurationService` instance. - pub fn new() -> Self { - Self { - no_delay: None, - tcp_ka: None, - _t: PhantomData, - } - } - - /// Sets the value of the `TCP_NODELAY` option on this socket. - pub fn nodelay(mut self, nodelay: bool) -> Self { - self.no_delay = Some(nodelay); - self - } - - /// Sets whether keepalive messages are enabled to be sent on this socket. - pub fn tcp_keepalive(mut self, keepalive: Option) -> Self { - self.tcp_ka = Some(keepalive); - self - } -} - -impl NewService for StreamConfiguration { - type Request = T; - type Response = T; - type Error = E; - type InitError = (); - type Service = StreamConfigurationService; - type Future = FutureResult; - - fn new_service(&self) -> Self::Future { - ok(StreamConfigurationService { - no_delay: self.no_delay, - tcp_ka: self.tcp_ka, - _t: PhantomData, - }) - } -} - -/// Stream configuration service -/// -/// Stream configuration service allows to change some socket level -/// parameters. for example `tcp nodelay` or `tcp keep-alive`. -pub struct StreamConfigurationService { - no_delay: Option, - tcp_ka: Option>, - _t: PhantomData<(T, E)>, -} - -impl Service for StreamConfigurationService -where - T: IoStream, -{ - type Request = T; - type Response = T; - type Error = E; - type Future = FutureResult; - - fn poll_ready(&mut self) -> Poll<(), Self::Error> { - Ok(Async::Ready(())) - } - - fn call(&mut self, mut req: Self::Request) -> Self::Future { - if let Some(no_delay) = self.no_delay { - if req.set_nodelay(no_delay).is_err() { - error!("Can not set socket no-delay option"); - } - } - if let Some(keepalive) = self.tcp_ka { - if req.set_keepalive(keepalive).is_err() { - error!("Can not set socket keep-alive option"); - } - } - - ok(req) - } -} diff --git a/src/ws/context.rs b/src/ws/context.rs deleted file mode 100644 index 4db83df5..00000000 --- a/src/ws/context.rs +++ /dev/null @@ -1,334 +0,0 @@ -extern crate actix; - -use bytes::Bytes; -use futures::sync::oneshot::{self, Sender}; -use futures::{Async, Future, Poll, Stream}; -use smallvec::SmallVec; - -use self::actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, - ToEnvelope, -}; -use self::actix::fut::ActorFuture; -use self::actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, - Message as ActixMessage, SpawnHandle, -}; - -use body::{Binary, Body}; -use context::{ActorHttpContext, Drain, Frame as ContextFrame}; -use error::{Error, ErrorInternalServerError, PayloadError}; -use httprequest::HttpRequest; - -use ws::frame::{Frame, FramedMessage}; -use ws::proto::{CloseReason, OpCode}; -use ws::{Message, ProtocolError, WsStream, WsWriter}; - -/// Execution context for `WebSockets` actors -pub struct WebsocketContext -where - A: Actor>, -{ - inner: ContextParts, - stream: Option>, - request: HttpRequest, - disconnected: bool, -} - -impl ActorContext for WebsocketContext -where - A: Actor, -{ - fn stop(&mut self) { - self.inner.stop(); - } - fn terminate(&mut self) { - self.inner.terminate() - } - fn state(&self) -> ActorState { - self.inner.state() - } -} - -impl AsyncContext for WebsocketContext -where - A: Actor, -{ - fn spawn(&mut self, fut: F) -> SpawnHandle - where - F: ActorFuture + 'static, - { - self.inner.spawn(fut) - } - - fn wait(&mut self, fut: F) - where - F: ActorFuture + 'static, - { - self.inner.wait(fut) - } - - #[doc(hidden)] - #[inline] - fn waiting(&self) -> bool { - self.inner.waiting() - || self.inner.state() == ActorState::Stopping - || self.inner.state() == ActorState::Stopped - } - - fn cancel_future(&mut self, handle: SpawnHandle) -> bool { - self.inner.cancel_future(handle) - } - - #[inline] - fn address(&self) -> Addr { - self.inner.address() - } -} - -impl WebsocketContext -where - A: Actor, -{ - #[inline] - /// Create a new Websocket context from a request and an actor - pub fn create

(req: HttpRequest, actor: A, stream: WsStream

) -> Body - where - A: StreamHandler, - P: Stream + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - ctx.add_stream(stream); - - Body::Actor(Box::new(WebsocketContextFut::new(ctx, actor, mb))) - } - - /// Create a new Websocket context - pub fn with_factory(req: HttpRequest, f: F) -> Body - where - F: FnOnce(&mut Self) -> A + 'static, - { - let mb = Mailbox::default(); - let mut ctx = WebsocketContext { - inner: ContextParts::new(mb.sender_producer()), - stream: None, - request: req, - disconnected: false, - }; - - let act = f(&mut ctx); - Body::Actor(Box::new(WebsocketContextFut::new(ctx, act, mb))) - } -} - -impl WebsocketContext -where - A: Actor, -{ - /// Write payload - /// - /// This is a low-level function that accepts framed messages that should - /// be created using `Frame::message()`. If you want to send text or binary - /// data you should prefer the `text()` or `binary()` convenience functions - /// that handle the framing for you. - #[inline] - pub fn write_raw(&mut self, data: FramedMessage) { - if !self.disconnected { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - let stream = self.stream.as_mut().unwrap(); - stream.push(ContextFrame::Chunk(Some(data.0))); - } else { - warn!("Trying to write to disconnected response"); - } - } - - /// Shared application state - #[inline] - pub fn state(&self) -> &S { - self.request.state() - } - - /// Incoming request - #[inline] - pub fn request(&mut self) -> &mut HttpRequest { - &mut self.request - } - - /// Returns drain future - pub fn drain(&mut self) -> Drain { - let (tx, rx) = oneshot::channel(); - self.add_frame(ContextFrame::Drain(tx)); - Drain::new(rx) - } - - /// Send text frame - #[inline] - pub fn text>(&mut self, text: T) { - self.write_raw(Frame::message(text.into(), OpCode::Text, true, false)); - } - - /// Send binary frame - #[inline] - pub fn binary>(&mut self, data: B) { - self.write_raw(Frame::message(data, OpCode::Binary, true, false)); - } - - /// Send ping frame - #[inline] - pub fn ping(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Ping, - true, - false, - )); - } - - /// Send pong frame - #[inline] - pub fn pong(&mut self, message: &str) { - self.write_raw(Frame::message( - Vec::from(message), - OpCode::Pong, - true, - false, - )); - } - - /// Send close frame - #[inline] - pub fn close(&mut self, reason: Option) { - self.write_raw(Frame::close(reason, false)); - } - - /// Check if connection still open - #[inline] - pub fn connected(&self) -> bool { - !self.disconnected - } - - #[inline] - fn add_frame(&mut self, frame: ContextFrame) { - if self.stream.is_none() { - self.stream = Some(SmallVec::new()); - } - if let Some(s) = self.stream.as_mut() { - s.push(frame) - } - } - - /// Handle of the running future - /// - /// SpawnHandle is the handle returned by `AsyncContext::spawn()` method. - pub fn handle(&self) -> SpawnHandle { - self.inner.curr_handle() - } -} - -impl WsWriter for WebsocketContext -where - A: Actor, - S: 'static, -{ - /// Send text frame - #[inline] - fn send_text>(&mut self, text: T) { - self.text(text) - } - - /// Send binary frame - #[inline] - fn send_binary>(&mut self, data: B) { - self.binary(data) - } - - /// Send ping frame - #[inline] - fn send_ping(&mut self, message: &str) { - self.ping(message) - } - - /// Send pong frame - #[inline] - fn send_pong(&mut self, message: &str) { - self.pong(message) - } - - /// Send close frame - #[inline] - fn send_close(&mut self, reason: Option) { - self.close(reason) - } -} - -impl AsyncContextParts for WebsocketContext -where - A: Actor, -{ - fn parts(&mut self) -> &mut ContextParts { - &mut self.inner - } -} - -struct WebsocketContextFut -where - A: Actor>, -{ - fut: ContextFut>, -} - -impl WebsocketContextFut -where - A: Actor>, -{ - fn new(ctx: WebsocketContext, act: A, mailbox: Mailbox) -> Self { - let fut = ContextFut::new(ctx, act, mailbox); - WebsocketContextFut { fut } - } -} - -impl ActorHttpContext for WebsocketContextFut -where - A: Actor>, - S: 'static, -{ - #[inline] - fn disconnected(&mut self) { - self.fut.ctx().disconnected = true; - self.fut.ctx().stop(); - } - - fn poll(&mut self) -> Poll>, Error> { - if self.fut.alive() && self.fut.poll().is_err() { - return Err(ErrorInternalServerError("error")); - } - - // frames - if let Some(data) = self.fut.ctx().stream.take() { - Ok(Async::Ready(Some(data))) - } else if self.fut.alive() { - Ok(Async::NotReady) - } else { - Ok(Async::Ready(None)) - } - } -} - -impl ToEnvelope for WebsocketContext -where - A: Actor> + Handler, - M: ActixMessage + Send + 'static, - M::Result: Send, -{ - fn pack(msg: M, tx: Option>) -> Envelope { - Envelope::new(msg, tx) - } -}