From 0c30057c8cc2a0d54637c3ddf13918615b2a3b6c Mon Sep 17 00:00:00 2001
From: Nikolay Kim <fafhrd91@gmail.com>
Date: Mon, 5 Mar 2018 16:45:54 -0800
Subject: [PATCH] move headers to separate module; allow custom HeaderValue
 conversion

---
 src/error.rs           |  15 +++-
 src/header.rs          | 197 +++++++++++++++++++++++++++++++++++++++++
 src/httpresponse.rs    |   6 +-
 src/lib.rs             |  19 +---
 src/server/encoding.rs |  50 +----------
 5 files changed, 219 insertions(+), 68 deletions(-)
 create mode 100644 src/header.rs

diff --git a/src/error.rs b/src/error.rs
index 6abbf7a0..40ecf704 100644
--- a/src/error.rs
+++ b/src/error.rs
@@ -129,8 +129,19 @@ impl ResponseError for io::Error {
     }
 }
 
-/// `InternalServerError` for `InvalidHeaderValue`
-impl ResponseError for header::InvalidHeaderValue {}
+/// `BadRequest` for `InvalidHeaderValue`
+impl ResponseError for header::InvalidHeaderValue {
+    fn error_response(&self) -> HttpResponse {
+        HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
+ }
+}
+
+/// `BadRequest` for `InvalidHeaderValue`
+impl ResponseError for header::InvalidHeaderValueBytes {
+    fn error_response(&self) -> HttpResponse {
+        HttpResponse::new(StatusCode::BAD_REQUEST, Body::Empty)
+    }
+}
 
 /// `InternalServerError` for `futures::Canceled`
 impl ResponseError for Canceled {}
diff --git a/src/header.rs b/src/header.rs
new file mode 100644
index 00000000..3715e1b1
--- /dev/null
+++ b/src/header.rs
@@ -0,0 +1,197 @@
+use std::fmt::{self, Display};
+use std::io::Write;
+use std::str::FromStr;
+use std::time::{Duration, SystemTime, UNIX_EPOCH};
+
+use time;
+use bytes::{Bytes, BytesMut, BufMut};
+use http::{Error as HttpError};
+use http::header::{HeaderValue, InvalidHeaderValue, InvalidHeaderValueBytes};
+
+pub use httpresponse::ConnectionType;
+pub use cookie::{Cookie, CookieBuilder};
+pub use http_range::HttpRange;
+
+use error::ParseError;
+
+
+pub trait IntoHeaderValue: Sized {
+    /// The type returned in the event of a conversion error.
+    type Error: Into<HttpError>;
+
+    /// Cast from PyObject to a concrete Python object type.
+    fn try_into(self) -> Result<HeaderValue, Self::Error>;
+}
+
+impl IntoHeaderValue for HeaderValue {
+    type Error = InvalidHeaderValue;
+
+    #[inline]
+    fn try_into(self) -> Result<HeaderValue, Self::Error> {
+        Ok(self)
+    }
+}
+
+impl<'a> IntoHeaderValue for &'a str {
+    type Error = InvalidHeaderValue;
+
+    #[inline]
+    fn try_into(self) -> Result<HeaderValue, Self::Error> {
+        self.parse()
+    }
+}
+
+impl<'a> IntoHeaderValue for &'a [u8] {
+    type Error = InvalidHeaderValue;
+
+    #[inline]
+    fn try_into(self) -> Result<HeaderValue, Self::Error> {
+        HeaderValue::from_bytes(self)
+    }
+}
+
+impl IntoHeaderValue for Bytes {
+    type Error = InvalidHeaderValueBytes;
+
+    #[inline]
+    fn try_into(self) -> Result<HeaderValue, Self::Error> {
+        HeaderValue::from_shared(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]
+    pub fn is_compression(&self) -> bool {
+        match *self {
+            ContentEncoding::Identity | ContentEncoding::Auto => false,
+            _ => true
+        }
+    }
+
+    #[inline]
+    pub fn as_str(&self) -> &'static str {
+        match *self {
+            ContentEncoding::Br => "br",
+            ContentEncoding::Gzip => "gzip",
+            ContentEncoding::Deflate => "deflate",
+            ContentEncoding::Identity | ContentEncoding::Auto => "identity",
+        }
+    }
+    /// 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 s.trim().to_lowercase().as_ref() {
+            "br" => ContentEncoding::Br,
+            "gzip" => ContentEncoding::Gzip,
+            "deflate" => ContentEncoding::Deflate,
+            "identity" => ContentEncoding::Identity,
+            _ => ContentEncoding::Auto,
+        }
+    }
+}
+
+/// A timestamp with HTTP formatting and parsing
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
+pub struct Date(time::Tm);
+
+impl FromStr for Date {
+    type Err = ParseError;
+
+    fn from_str(s: &str) -> Result<Date, ParseError> {
+        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(Date(t)),
+            Err(_) => Err(ParseError::Header),
+        }
+    }
+}
+
+impl Display for Date {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        fmt::Display::fmt(&self.0.to_utc().rfc822(), f)
+    }
+}
+
+impl From<SystemTime> for Date {
+    fn from(sys: SystemTime) -> Date {
+        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))
+            },
+        };
+        Date(time::at_utc(tmspec))
+    }
+}
+
+impl IntoHeaderValue for Date {
+    type Error = InvalidHeaderValueBytes;
+
+    fn try_into(self) -> Result<HeaderValue, Self::Error> {
+        let mut wrt = BytesMut::with_capacity(29).writer();
+        write!(wrt, "{}", self.0.rfc822()).unwrap();
+        HeaderValue::from_shared(wrt.get_mut().take().freeze())
+    }
+}
+
+impl From<Date> for SystemTime {
+    fn from(date: Date) -> 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 time::Tm;
+    use super::Date;
+
+    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::<Date>().unwrap(), NOV_07);
+        assert_eq!("Sunday, 07-Nov-94 08:48:37 GMT".parse::<Date>().unwrap(), NOV_07);
+        assert_eq!("Sun Nov  7 08:48:37 1994".parse::<Date>().unwrap(), NOV_07);
+        assert!("this-is-no-date".parse::<Date>().is_err());
+    }
+}
diff --git a/src/httpresponse.rs b/src/httpresponse.rs
index 9af932b1..fdb14204 100644
--- a/src/httpresponse.rs
+++ b/src/httpresponse.rs
@@ -14,7 +14,7 @@ use serde::Serialize;
 use body::Body;
 use error::Error;
 use handler::Responder;
-use headers::ContentEncoding;
+use header::{IntoHeaderValue, ContentEncoding};
 use httprequest::HttpRequest;
 
 /// Represents various types of connection
@@ -261,12 +261,12 @@ impl HttpResponseBuilder {
     /// ```
     pub fn header<K, V>(&mut self, key: K, value: V) -> &mut Self
         where HeaderName: HttpTryFrom<K>,
-              HeaderValue: HttpTryFrom<V>
+              V: IntoHeaderValue,
     {
         if let Some(parts) = parts(&mut self.response, &self.err) {
             match HeaderName::try_from(key) {
                 Ok(key) => {
-                    match HeaderValue::try_from(value) {
+                    match value.try_into() {
                         Ok(value) => { parts.headers.append(key, value); }
                         Err(e) => self.err = Some(e.into()),
                     }
diff --git a/src/lib.rs b/src/lib.rs
index f3decb14..07601403 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -120,6 +120,7 @@ pub mod client;
 pub mod fs;
 pub mod ws;
 pub mod error;
+pub mod header;
 pub mod httpcodes;
 pub mod multipart;
 pub mod middleware;
@@ -153,28 +154,14 @@ pub(crate) const HAS_OPENSSL: bool = false;
 // pub(crate) const HAS_TLS: bool = false;
 
 
+#[deprecated(since="0.4.4", note="please use `actix::header` module")]
 pub mod headers {
 //! Headers implementation
 
     pub use httpresponse::ConnectionType;
-
     pub use cookie::{Cookie, CookieBuilder};
     pub use http_range::HttpRange;
-
-    /// 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,
-    }
+    pub use header::ContentEncoding;
 }
 
 pub mod dev {
diff --git a/src/server/encoding.rs b/src/server/encoding.rs
index 23f4aef7..84e48bc8 100644
--- a/src/server/encoding.rs
+++ b/src/server/encoding.rs
@@ -13,60 +13,16 @@ use flate2::write::{GzEncoder, DeflateDecoder, DeflateEncoder};
 use brotli2::write::{BrotliDecoder, BrotliEncoder};
 use bytes::{Bytes, BytesMut, BufMut};
 
-use headers::ContentEncoding;
+use header::ContentEncoding;
 use body::{Body, Binary};
 use error::PayloadError;
+use helpers::convert_usize;
 use httprequest::HttpInnerMessage;
 use httpresponse::HttpResponse;
 use payload::{PayloadSender, PayloadWriter, PayloadStatus};
 
 use super::shared::SharedBytes;
 
-
-impl ContentEncoding {
-
-    #[inline]
-    pub fn is_compression(&self) -> bool {
-        match *self {
-            ContentEncoding::Identity | ContentEncoding::Auto => false,
-            _ => true
-        }
-    }
-
-    #[inline]
-    pub fn as_str(&self) -> &'static str {
-        match *self {
-            ContentEncoding::Br => "br",
-            ContentEncoding::Gzip => "gzip",
-            ContentEncoding::Deflate => "deflate",
-            ContentEncoding::Identity | ContentEncoding::Auto => "identity",
-        }
-    }
-    /// default quality value
-    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 s.trim().to_lowercase().as_ref() {
-            "br" => ContentEncoding::Br,
-            "gzip" => ContentEncoding::Gzip,
-            "deflate" => ContentEncoding::Deflate,
-            "identity" => ContentEncoding::Identity,
-            _ => ContentEncoding::Auto,
-        }
-    }
-}
-
-
 pub(crate) enum PayloadType {
     Sender(PayloadSender),
     Encoding(Box<EncodedPayload>),
@@ -466,7 +422,7 @@ impl ContentEncoder {
                 }
                 if req.method == Method::HEAD {
                     let mut b = BytesMut::new();
-                    let _ = write!(b, "{}", bytes.len());
+                    convert_usize(bytes.len(), &mut b);
                     resp.headers_mut().insert(
                         CONTENT_LENGTH, HeaderValue::try_from(b.freeze()).unwrap());
                 } else {