diff --git a/.travis.yml b/.travis.yml index ff8815059..02fbd42c7 100644 --- a/.travis.yml +++ b/.travis.yml @@ -31,8 +31,8 @@ before_cache: | script: - cargo clean -- cargo build --features="ssl" -- cargo test --features="ssl" +- cargo build --all-features +- cargo test --all-features # Upload docs after_success: diff --git a/Cargo.toml b/Cargo.toml index a68489f51..84b974be5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,20 +7,20 @@ readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" repository = "https://github.com/actix/actix-http.git" -documentation = "https://actix.rs/api/actix-http/stable/actix_http/" +documentation = "https://docs.rs/actix-http/" categories = ["network-programming", "asynchronous", "web-programming::http-server", "web-programming::websocket"] -license = "Apache-2.0" +license = "MIT/Apache-2.0" exclude = [".gitignore", ".travis.yml", ".cargo/config", "appveyor.yml"] edition = "2018" [package.metadata.docs.rs] -features = ["session"] +features = ["ssl", "fail", "cookie"] [badges] travis-ci = { repository = "actix/actix-http", branch = "master" } -appveyor = { repository = "fafhrd91/actix-http-b1qsn" } +# appveyor = { repository = "fafhrd91/actix-http-b1qsn" } codecov = { repository = "actix/actix-http", branch = "master", service = "github" } [lib] @@ -28,13 +28,15 @@ name = "actix_http" path = "src/lib.rs" [features] -default = ["fail"] +default = [] # openssl ssl = ["openssl", "actix-connect/ssl"] -# failure integration. it is on by default, it will be off in future versions -# actix itself does not use failure anymore +# cookies integration +cookies = ["cookie"] + +# failure integration. actix does not use failure anymore fail = ["failure"] [dependencies] @@ -49,7 +51,6 @@ backtrace = "0.3" bitflags = "1.0" bytes = "0.4" byteorder = "1.2" -cookie = { version="0.11", features=["percent-encode"] } derive_more = "0.14" encoding = "0.2" futures = "0.1" @@ -76,11 +77,10 @@ tokio-timer = "0.2" tokio-current-thread = "0.1" trust-dns-resolver = { version="0.11.0-alpha.2", default-features = false } -# openssl -openssl = { version="0.10", optional = true } - -# failure is optional +# optional deps +cookie = { version="0.11", features=["percent-encode"], optional = true } failure = { version = "0.1.5", optional = true } +openssl = { version="0.10", optional = true } [dev-dependencies] actix-rt = "0.2.0" diff --git a/src/client/request.rs b/src/client/request.rs index 7c7079fb7..26713aa46 100644 --- a/src/client/request.rs +++ b/src/client/request.rs @@ -1,13 +1,12 @@ use std::fmt; -use std::fmt::Write as FmtWrite; use std::io::Write; use actix_service::Service; use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; use futures::future::{err, Either}; use futures::{Future, Stream}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use serde::Serialize; use serde_json; @@ -58,6 +57,7 @@ impl ClientRequest<()> { ClientRequestBuilder { head: Some(RequestHead::default()), err: None, + #[cfg(feature = "cookies")] cookies: None, default_headers: true, } @@ -235,6 +235,7 @@ where pub struct ClientRequestBuilder { head: Option, err: Option, + #[cfg(feature = "cookies")] cookies: Option, default_headers: bool, } @@ -441,6 +442,7 @@ impl ClientRequestBuilder { self.header(header::CONTENT_LENGTH, wrt.get_mut().take().freeze()) } + #[cfg(feature = "cookies")] /// Set a cookie /// /// ```rust @@ -560,20 +562,28 @@ impl ClientRequestBuilder { ); } + #[allow(unused_mut)] let mut head = self.head.take().expect("cannot reuse request builder"); - // set cookies - if let Some(ref mut jar) = self.cookies { - let mut cookie = String::new(); - for c in jar.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); + #[cfg(feature = "cookies")] + { + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + use std::fmt::Write; + + // set cookies + if let Some(ref mut jar) = self.cookies { + let mut cookie = String::new(); + for c in jar.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = + percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); } - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); } Ok(ClientRequest { head, body }) } @@ -646,6 +656,7 @@ impl ClientRequestBuilder { ClientRequestBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), default_headers: self.default_headers, } diff --git a/src/error.rs b/src/error.rs index 6bc401332..820071b1e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -8,6 +8,7 @@ use std::{fmt, io, result}; // use actix::MailboxError; use actix_utils::timeout::TimeoutError; use backtrace::Backtrace; +#[cfg(feature = "cookies")] use cookie; use derive_more::{Display, From}; use futures::Canceled; @@ -19,7 +20,8 @@ use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; use tokio_timer::Error as TimerError; -// re-exports +// re-export for convinience +#[cfg(feature = "cookies")] pub use cookie::ParseError as CookieParseError; use crate::body::Body; @@ -322,6 +324,7 @@ impl ResponseError for PayloadError { } /// Return `BadRequest` for `cookie::ParseError` +#[cfg(feature = "cookies")] impl ResponseError for cookie::ParseError { fn error_response(&self) -> Response { Response::new(StatusCode::BAD_REQUEST) @@ -889,7 +892,6 @@ mod failure_integration { #[cfg(test)] mod tests { use super::*; - use cookie::ParseError as CookieParseError; use http::{Error as HttpError, StatusCode}; use httparse; use std::error::Error as StdError; @@ -900,14 +902,19 @@ mod tests { let resp: Response = ParseError::Incomplete.error_response(); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let resp: Response = CookieParseError::EmptyName.error_response(); - assert_eq!(resp.status(), StatusCode::BAD_REQUEST); - let err: HttpError = StatusCode::from_u16(10000).err().unwrap().into(); let resp: Response = err.error_response(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } + #[cfg(feature = "cookies")] + #[test] + fn test_cookie_parse() { + use cookie::ParseError as CookieParseError; + let resp: Response = CookieParseError::EmptyName.error_response(); + assert_eq!(resp.status(), StatusCode::BAD_REQUEST); + } + #[test] fn test_as_response() { let orig = io::Error::new(io::ErrorKind::Other, "other"); diff --git a/src/h1/dispatcher.rs b/src/h1/dispatcher.rs index 8e21e9b09..afeabc825 100644 --- a/src/h1/dispatcher.rs +++ b/src/h1/dispatcher.rs @@ -613,13 +613,12 @@ mod tests { let mut sys = actix_rt::System::new("test"); let _ = sys.block_on(lazy(|| { let buf = Buffer::new("GET /test HTTP/1\r\n\r\n"); - let readbuf = BytesMut::new(); let mut h1 = Dispatcher::new( buf, ServiceConfig::default(), CloneableService::new( - (|req| ok::<_, Error>(Response::Ok().finish())).into_service(), + (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), ), ); assert!(h1.poll().is_ok()); diff --git a/src/httpmessage.rs b/src/httpmessage.rs index 117e10a81..60821d300 100644 --- a/src/httpmessage.rs +++ b/src/httpmessage.rs @@ -1,18 +1,23 @@ use std::cell::{Ref, RefMut}; use std::str; -use cookie::Cookie; use encoding::all::UTF_8; use encoding::label::encoding_from_whatwg_label; use encoding::EncodingRef; use http::{header, HeaderMap}; use mime::Mime; -use crate::error::{ContentTypeError, CookieParseError, ParseError}; +use crate::error::{ContentTypeError, ParseError}; use crate::extensions::Extensions; use crate::header::Header; use crate::payload::Payload; +#[cfg(feature = "cookies")] +use crate::error::CookieParseError; +#[cfg(feature = "cookies")] +use cookie::Cookie; + +#[cfg(feature = "cookies")] struct Cookies(Vec>); /// Trait that implements general purpose operations on http messages @@ -105,6 +110,7 @@ pub trait HttpMessage: Sized { /// Load request cookies. #[inline] + #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); @@ -125,6 +131,7 @@ pub trait HttpMessage: Sized { } /// Return request cookie. + #[cfg(feature = "cookies")] fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { diff --git a/src/lib.rs b/src/lib.rs index 9a87b77f1..85efe5e44 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -113,6 +113,7 @@ pub mod http { #[doc(hidden)] pub use http::uri::PathAndQuery; + #[cfg(feature = "cookies")] pub use cookie::{Cookie, CookieBuilder}; /// Various http headers diff --git a/src/response.rs b/src/response.rs index 34ac54ea7..5281c2d95 100644 --- a/src/response.rs +++ b/src/response.rs @@ -3,6 +3,7 @@ use std::io::Write; use std::{fmt, str}; use bytes::{BufMut, Bytes, BytesMut}; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; use futures::future::{ok, FutureResult, IntoFuture}; use futures::Stream; @@ -128,6 +129,7 @@ impl Response { /// Get an iterator for the cookies set by this response #[inline] + #[cfg(feature = "cookies")] pub fn cookies(&self) -> CookieIter { CookieIter { iter: self.head.headers.get_all(header::SET_COOKIE).iter(), @@ -136,6 +138,7 @@ impl Response { /// Add a cookie to this response #[inline] + #[cfg(feature = "cookies")] pub fn add_cookie(&mut self, cookie: &Cookie) -> Result<(), HttpError> { let h = &mut self.head.headers; HeaderValue::from_str(&cookie.to_string()) @@ -148,6 +151,7 @@ impl Response { /// Remove all cookies with the given name from this response. Returns /// the number of cookies removed. #[inline] + #[cfg(feature = "cookies")] pub fn del_cookie(&mut self, name: &str) -> usize { let h = &mut self.head.headers; let vals: Vec = h @@ -267,10 +271,12 @@ impl IntoFuture for Response { } } +#[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: header::ValueIter<'a, HeaderValue>, } +#[cfg(feature = "cookies")] impl<'a> Iterator for CookieIter<'a> { type Item = Cookie<'a>; @@ -292,6 +298,7 @@ impl<'a> Iterator for CookieIter<'a> { pub struct ResponseBuilder { head: Option>, err: Option, + #[cfg(feature = "cookies")] cookies: Option, } @@ -304,6 +311,7 @@ impl ResponseBuilder { ResponseBuilder { head: Some(head), err: None, + #[cfg(feature = "cookies")] cookies: None, } } @@ -503,6 +511,7 @@ impl ResponseBuilder { /// .finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -530,6 +539,7 @@ impl ResponseBuilder { /// builder.finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { { if self.cookies.is_none() { @@ -582,15 +592,20 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } + #[allow(unused_mut)] let mut response = self.head.take().expect("cannot reuse response builder"); - if let Some(ref jar) = self.cookies { - for cookie in jar.delta() { - match HeaderValue::from_str(&cookie.to_string()) { - Ok(val) => { - let _ = response.headers.append(header::SET_COOKIE, val); - } - Err(e) => return Response::from(Error::from(e)).into_body(), - }; + + #[cfg(feature = "cookies")] + { + if let Some(ref jar) = self.cookies { + for cookie in jar.delta() { + match HeaderValue::from_str(&cookie.to_string()) { + Ok(val) => { + let _ = response.headers.append(header::SET_COOKIE, val); + } + Err(e) => return Response::from(Error::from(e)).into_body(), + }; + } } } @@ -654,6 +669,7 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), } } @@ -674,7 +690,9 @@ fn parts<'a>( impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { // If this response has cookies, load them into a jar + #[cfg(feature = "cookies")] let mut jar: Option = None; + #[cfg(feature = "cookies")] for c in res.cookies() { if let Some(ref mut j) = jar { j.add_original(c.into_owned()); @@ -688,6 +706,7 @@ impl From> for ResponseBuilder { ResponseBuilder { head: Some(res.head), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -697,17 +716,22 @@ impl From> for ResponseBuilder { impl<'a> From<&'a ResponseHead> for ResponseBuilder { fn from(head: &'a ResponseHead) -> ResponseBuilder { // If this response has cookies, load them into a jar + #[cfg(feature = "cookies")] let mut jar: Option = None; - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE).iter(), - }; - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); + + #[cfg(feature = "cookies")] + { + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE).iter(), + }; + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } } } @@ -721,6 +745,7 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { ResponseBuilder { head: Some(msg), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -802,14 +827,9 @@ 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 crate::httpmessage::HttpMessage; - use crate::test::TestRequest; #[test] fn test_debug() { @@ -822,8 +842,11 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_response_cookies() { - let req = TestRequest::default() + use crate::httpmessage::HttpMessage; + + let req = crate::test::TestRequest::default() .header(COOKIE, "cookie1=value1") .header(COOKIE, "cookie2=value2") .finish(); @@ -831,11 +854,11 @@ mod tests { let resp = Response::Ok() .cookie( - http::Cookie::build("name", "value") + crate::http::Cookie::build("name", "value") .domain("www.rust-lang.org") .path("/test") .http_only(true) - .max_age(Duration::days(1)) + .max_age(time::Duration::days(1)) .finish(), ) .del_cookie(&cookies[0]) @@ -856,16 +879,17 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_update_response_cookies() { let mut r = Response::Ok() - .cookie(http::Cookie::new("original", "val100")) + .cookie(crate::http::Cookie::new("original", "val100")) .finish(); - r.add_cookie(&http::Cookie::new("cookie2", "val200")) + r.add_cookie(&crate::http::Cookie::new("cookie2", "val200")) .unwrap(); - r.add_cookie(&http::Cookie::new("cookie2", "val250")) + r.add_cookie(&crate::http::Cookie::new("cookie2", "val250")) .unwrap(); - r.add_cookie(&http::Cookie::new("cookie3", "val300")) + r.add_cookie(&crate::http::Cookie::new("cookie3", "val300")) .unwrap(); assert_eq!(r.cookies().count(), 4); @@ -1016,11 +1040,14 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_into_builder() { + use crate::httpmessage::HttpMessage; + let mut resp: Response = "test".into(); assert_eq!(resp.status(), StatusCode::OK); - resp.add_cookie(&http::Cookie::new("cookie1", "val100")) + resp.add_cookie(&crate::http::Cookie::new("cookie1", "val100")) .unwrap(); let mut builder: ResponseBuilder = resp.into(); diff --git a/src/test.rs b/src/test.rs index c60d2d01c..2d4b3d0f9 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,12 +1,11 @@ //! Test Various helpers for Actix applications to use during testing. -use std::fmt::Write as FmtWrite; use std::str::FromStr; use bytes::Bytes; +#[cfg(feature = "cookies")] use cookie::{Cookie, CookieJar}; -use http::header::{self, HeaderName, HeaderValue}; +use http::header::HeaderName; use http::{HeaderMap, HttpTryFrom, Method, Uri, Version}; -use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; use crate::header::{Header, IntoHeaderValue}; use crate::payload::Payload; @@ -46,6 +45,7 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, + #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -57,6 +57,7 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, })) @@ -126,6 +127,7 @@ impl TestRequest { } /// Set cookie for this request + #[cfg(feature = "cookies")] pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self @@ -145,38 +147,39 @@ impl TestRequest { /// Complete request creation and generate `Request` instance pub fn finish(&mut self) -> Request { - let Inner { - method, - uri, - version, - headers, - payload, - cookies, - } = self.0.take().expect("cannot reuse test request builder");; + let inner = self.0.take().expect("cannot reuse test request builder");; - let mut req = if let Some(pl) = payload { + let mut req = if let Some(pl) = inner.payload { Request::with_payload(pl) } else { Request::with_payload(crate::h1::Payload::empty().into()) }; let head = req.head_mut(); - head.uri = uri; - head.method = method; - head.version = version; - head.headers = headers; + head.uri = inner.uri; + head.method = inner.method; + head.version = inner.version; + head.headers = inner.headers; - let mut cookie = String::new(); - for c in cookies.delta() { - let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); - let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); - let _ = write!(&mut cookie, "; {}={}", name, value); - } - if !cookie.is_empty() { - head.headers.insert( - header::COOKIE, - HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), - ); + #[cfg(feature = "cookies")] + { + use std::fmt::Write as FmtWrite; + + use http::header::{self, HeaderValue}; + use percent_encoding::{percent_encode, USERINFO_ENCODE_SET}; + + let mut cookie = String::new(); + for c in inner.cookies.delta() { + let name = percent_encode(c.name().as_bytes(), USERINFO_ENCODE_SET); + let value = percent_encode(c.value().as_bytes(), USERINFO_ENCODE_SET); + let _ = write!(&mut cookie, "; {}={}", name, value); + } + if !cookie.is_empty() { + head.headers.insert( + header::COOKIE, + HeaderValue::from_str(&cookie.as_str()[2..]).unwrap(), + ); + } } req diff --git a/src/ws/client/connect.rs b/src/ws/client/connect.rs index 09d025631..5e877a645 100644 --- a/src/ws/client/connect.rs +++ b/src/ws/client/connect.rs @@ -1,6 +1,7 @@ //! Http client request use std::str; +#[cfg(feature = "cookies")] use cookie::Cookie; use http::header::{HeaderName, HeaderValue}; use http::{Error as HttpError, HttpTryFrom}; @@ -50,6 +51,7 @@ impl Connect { self } + #[cfg(feature = "cookies")] /// Set cookie for handshake request pub fn cookie(mut self, cookie: Cookie) -> Self { self.request.cookie(cookie);