diff --git a/Cargo.toml b/Cargo.toml index 1705479bd..6ab1b0903 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,10 +28,7 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["session"] - -# sessions feature, session require "ring" crate and c compiler -session = ["cookie/secure"] +default = [] # openssl ssl = ["openssl", "actix-connector/ssl"] diff --git a/src/client/response.rs b/src/client/response.rs index 104d28ed7..236a63382 100644 --- a/src/client/response.rs +++ b/src/client/response.rs @@ -1,3 +1,4 @@ +use std::cell::{Ref, RefMut}; use std::fmt; use bytes::Bytes; @@ -5,6 +6,7 @@ use futures::{Poll, Stream}; use http::{HeaderMap, StatusCode, Version}; use crate::error::PayloadError; +use crate::extensions::Extensions; use crate::httpmessage::HttpMessage; use crate::message::{Head, Message, ResponseHead}; use crate::payload::{Payload, PayloadStream}; @@ -22,6 +24,18 @@ impl HttpMessage for ClientResponse { &self.head.headers } + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head.headers + } + + fn extensions(&self) -> Ref { + self.head.extensions() + } + + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + fn take_payload(&mut self) -> Payload { std::mem::replace(&mut self.payload, Payload::None) } @@ -30,8 +44,11 @@ impl HttpMessage for ClientResponse { impl ClientResponse { /// Create new Request instance pub fn new() -> ClientResponse { + let head: Message = Message::new(); + head.extensions_mut().clear(); + ClientResponse { - head: Message::new(), + head, payload: Payload::None, } } diff --git a/src/h1/decoder.rs b/src/h1/decoder.rs index 80bca94c5..77b76c242 100644 --- a/src/h1/decoder.rs +++ b/src/h1/decoder.rs @@ -163,7 +163,7 @@ impl MessageType for Request { } fn headers_mut(&mut self) -> &mut HeaderMap { - self.headers_mut() + &mut self.head_mut().headers } fn decode(src: &mut BytesMut) -> Result, ParseError> { @@ -832,6 +832,7 @@ mod tests { "GET /test HTTP/1.0\r\n\ connection: close\r\n\r\n", ); + let req = parse_ready!(&mut buf); assert_eq!(req.head().ctype, Some(ConnectionType::Close)); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 79f29a723..17447af63 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,6 +1,8 @@ +use std::cell::{Ref, RefMut}; use std::str; use bytes::{Bytes, BytesMut}; +use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::types::{DecoderTrap, Encoding}; @@ -12,12 +14,16 @@ use serde::de::DeserializeOwned; use serde_urlencoded; use crate::error::{ - ContentTypeError, ParseError, PayloadError, ReadlinesError, UrlencodedError, + ContentTypeError, CookieParseError, ParseError, PayloadError, ReadlinesError, + UrlencodedError, }; +use crate::extensions::Extensions; use crate::header::Header; use crate::json::JsonBody; use crate::payload::Payload; +struct Cookies(Vec>); + /// Trait that implements general purpose operations on http messages pub trait HttpMessage: Sized { /// Type of message payload stream @@ -26,9 +32,18 @@ pub trait HttpMessage: Sized { /// Read the message headers. fn headers(&self) -> &HeaderMap; + /// Mutable reference to the message's headers. + fn headers_mut(&mut self) -> &mut HeaderMap; + /// Message payload stream fn take_payload(&mut self) -> Payload; + /// Request's extensions container + fn extensions(&self) -> Ref; + + /// Mutable reference to a the request's extensions container + fn extensions_mut(&self) -> RefMut; + #[doc(hidden)] /// Get a header fn get_header(&self) -> Option @@ -100,6 +115,39 @@ pub trait HttpMessage: Sized { } } + /// Load request cookies. + #[inline] + fn cookies(&self) -> Result>>, CookieParseError> { + if self.extensions().get::().is_none() { + let mut cookies = Vec::new(); + for hdr in self.headers().get_all(header::COOKIE) { + let s = + str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; + for cookie_str in s.split(';').map(|s| s.trim()) { + if !cookie_str.is_empty() { + cookies.push(Cookie::parse_encoded(cookie_str)?.into_owned()); + } + } + } + self.extensions_mut().insert(Cookies(cookies)); + } + Ok(Ref::map(self.extensions(), |ext| { + &ext.get::().unwrap().0 + })) + } + + /// Return request cookie. + fn cookie(&self, name: &str) -> Option> { + if let Ok(cookies) = self.cookies() { + for cookie in cookies.iter() { + if cookie.name() == name { + return Some(cookie.to_owned()); + } + } + } + None + } + /// Load http message body. /// /// By default only 256Kb payload reads to a memory, then diff --git a/src/message.rs b/src/message.rs index 3a1ac1306..f9dfe9736 100644 --- a/src/message.rs +++ b/src/message.rs @@ -125,6 +125,7 @@ pub struct ResponseHead { pub reason: Option<&'static str>, pub no_chunking: bool, pub(crate) ctype: Option, + pub(crate) extensions: RefCell, } impl Default for ResponseHead { @@ -136,10 +137,25 @@ impl Default for ResponseHead { reason: None, no_chunking: false, ctype: None, + extensions: RefCell::new(Extensions::new()), } } } +impl ResponseHead { + /// Message extensions + #[inline] + pub fn extensions(&self) -> Ref { + self.extensions.borrow() + } + + /// Mutable reference to a the message's extensions + #[inline] + pub fn extensions_mut(&self) -> RefMut { + self.extensions.borrow_mut() + } +} + impl Head for ResponseHead { fn clear(&mut self) { self.ctype = None; diff --git a/src/request.rs b/src/request.rs index e1b893f95..761a159d8 100644 --- a/src/request.rs +++ b/src/request.rs @@ -17,10 +17,28 @@ pub struct Request

{ impl

HttpMessage for Request

{ type Stream = P; + #[inline] fn headers(&self) -> &HeaderMap { &self.head().headers } + #[inline] + fn headers_mut(&mut self) -> &mut HeaderMap { + &mut self.head_mut().headers + } + + /// Request extensions + #[inline] + fn extensions(&self) -> Ref { + self.head.extensions() + } + + /// Mutable reference to a the request's extensions + #[inline] + fn extensions_mut(&self) -> RefMut { + self.head.extensions_mut() + } + fn take_payload(&mut self) -> Payload

{ std::mem::replace(&mut self.payload, Payload::None) } @@ -119,30 +137,6 @@ impl

Request

{ self.head().uri.path() } - #[inline] - /// Returns Request's headers. - pub fn headers(&self) -> &HeaderMap { - &self.head().headers - } - - #[inline] - /// Returns mutable Request's headers. - pub fn headers_mut(&mut self) -> &mut HeaderMap { - &mut self.head_mut().headers - } - - /// Request extensions - #[inline] - pub fn extensions(&self) -> Ref { - self.head.extensions() - } - - /// Mutable reference to a the request's extensions - #[inline] - pub fn extensions_mut(&self) -> RefMut { - self.head.extensions_mut() - } - /// Check if request requires connection upgrade pub fn upgrade(&self) -> bool { if let Some(conn) = self.head().headers.get(header::CONNECTION) { diff --git a/src/response.rs b/src/response.rs index 9b503de1b..649e7fd88 100644 --- a/src/response.rs +++ b/src/response.rs @@ -766,12 +766,14 @@ impl From for Response { #[cfg(test)] mod tests { + use time::Duration; + use super::*; use crate::body::Body; use crate::http; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE}; - - // use test::TestRequest; + use crate::httpmessage::HttpMessage; + use crate::test::TestRequest; #[test] fn test_debug() { @@ -783,38 +785,39 @@ mod tests { assert!(dbg.contains("Response")); } - // #[test] - // fn test_response_cookies() { - // let req = TestRequest::default() - // .header(COOKIE, "cookie1=value1") - // .header(COOKIE, "cookie2=value2") - // .finish(); - // let cookies = req.cookies().unwrap(); + #[test] + fn test_response_cookies() { + let req = TestRequest::default() + .header(COOKIE, "cookie1=value1") + .header(COOKIE, "cookie2=value2") + .finish(); + let cookies = req.cookies().unwrap(); - // let resp = Response::Ok() - // .cookie( - // http::Cookie::build("name", "value") - // .domain("www.rust-lang.org") - // .path("/test") - // .http_only(true) - // .max_age(Duration::days(1)) - // .finish(), - // ).del_cookie(&cookies[0]) - // .finish(); + let resp = Response::Ok() + .cookie( + http::Cookie::build("name", "value") + .domain("www.rust-lang.org") + .path("/test") + .http_only(true) + .max_age(Duration::days(1)) + .finish(), + ) + .del_cookie(&cookies[0]) + .finish(); - // let mut val: Vec<_> = resp - // .headers() - // .get_all("Set-Cookie") - // .iter() - // .map(|v| v.to_str().unwrap().to_owned()) - // .collect(); - // val.sort(); - // assert!(val[0].starts_with("cookie1=; Max-Age=0;")); - // assert_eq!( - // val[1], - // "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" - // ); - // } + let mut val: Vec<_> = resp + .headers() + .get_all("Set-Cookie") + .iter() + .map(|v| v.to_str().unwrap().to_owned()) + .collect(); + val.sort(); + assert!(val[0].starts_with("cookie1=; Max-Age=0;")); + assert_eq!( + val[1], + "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" + ); + } #[test] fn test_update_response_cookies() { @@ -871,25 +874,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "text/plain") } - // #[test] - // fn test_content_encoding() { - // let resp = Response::build(StatusCode::OK).finish(); - // assert_eq!(resp.content_encoding(), None); - - // #[cfg(feature = "brotli")] - // { - // let resp = Response::build(StatusCode::OK) - // .content_encoding(ContentEncoding::Br) - // .finish(); - // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Br)); - // } - - // let resp = Response::build(StatusCode::OK) - // .content_encoding(ContentEncoding::Gzip) - // .finish(); - // assert_eq!(resp.content_encoding(), Some(ContentEncoding::Gzip)); - // } - #[test] fn test_json() { let resp = Response::build(StatusCode::OK).json(vec!["v1", "v2", "v3"]); diff --git a/src/ws/mod.rs b/src/ws/mod.rs index 2d629c73b..a8de59dd4 100644 --- a/src/ws/mod.rs +++ b/src/ws/mod.rs @@ -9,6 +9,7 @@ use derive_more::{Display, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; +use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::{Response, ResponseBuilder};