1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-06-25 06:39:22 +02:00

optional cookies features (#1981)

This commit is contained in:
Rob Ede
2021-02-13 15:08:43 +00:00
committed by GitHub
parent b37669cb3b
commit 3279070f9f
20 changed files with 205 additions and 89 deletions

View File

@ -19,10 +19,12 @@ use serde_json::error::Error as JsonError;
use serde_urlencoded::ser::Error as FormError;
use crate::body::Body;
pub use crate::cookie::ParseError as CookieParseError;
use crate::helpers::Writer;
use crate::response::{Response, ResponseBuilder};
#[cfg(feature = "cookies")]
pub use crate::cookie::ParseError as CookieParseError;
/// A specialized [`std::result::Result`]
/// for actix web operations
///
@ -397,6 +399,7 @@ impl ResponseError for PayloadError {
}
/// Return `BadRequest` for `cookie::ParseError`
#[cfg(feature = "cookies")]
impl ResponseError for crate::cookie::ParseError {
fn status_code(&self) -> StatusCode {
StatusCode::BAD_REQUEST

View File

@ -549,7 +549,6 @@ mod tests {
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
eprintln!("{}", &data);
assert!(data.contains("Content-Length: 0\r\n"));
assert!(data.contains("Connection: close\r\n"));

View File

@ -5,12 +5,14 @@ use encoding_rs::{Encoding, UTF_8};
use http::header;
use mime::Mime;
use crate::cookie::Cookie;
use crate::error::{ContentTypeError, CookieParseError, ParseError};
use crate::error::{ContentTypeError, ParseError};
use crate::extensions::Extensions;
use crate::header::{Header, HeaderMap};
use crate::payload::Payload;
#[cfg(feature = "cookies")]
use crate::{cookie::Cookie, error::CookieParseError};
#[cfg(feature = "cookies")]
struct Cookies(Vec<Cookie<'static>>);
/// Trait that implements general purpose operations on HTTP messages.
@ -104,7 +106,7 @@ pub trait HttpMessage: Sized {
}
/// Load request cookies.
#[inline]
#[cfg(feature = "cookies")]
fn cookies(&self) -> Result<Ref<'_, Vec<Cookie<'static>>>, CookieParseError> {
if self.extensions().get::<Cookies>().is_none() {
let mut cookies = Vec::new();
@ -119,12 +121,14 @@ pub trait HttpMessage: Sized {
}
self.extensions_mut().insert(Cookies(cookies));
}
Ok(Ref::map(self.extensions(), |ext| {
&ext.get::<Cookies>().unwrap().0
}))
}
/// Return request cookie.
#[cfg(feature = "cookies")]
fn cookie(&self, name: &str) -> Option<Cookie<'static>> {
if let Ok(cookies) = self.cookies() {
for cookie in cookies.iter() {

View File

@ -1,4 +1,19 @@
//! HTTP primitives for the Actix ecosystem.
//!
//! ## Crate Features
//! | Feature | Functionality |
//! | ---------------- | ----------------------------------------------------- |
//! | `openssl` | TLS support via [OpenSSL]. |
//! | `rustls` | TLS support via [rustls]. |
//! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) |
//! | `cookies` | Support for cookies backed by the [cookie] crate. |
//! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. |
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
//!
//! [OpenSSL]: https://crates.io/crates/openssl
//! [rustls]: https://crates.io/crates/rustls
//! [cookie]: https://crates.io/crates/cookie
//! [trust-dns]: https://crates.io/crates/trust-dns
#![deny(rust_2018_idioms, nonstandard_style)]
#![allow(
@ -34,13 +49,15 @@ mod response;
mod service;
mod time_parser;
pub use cookie;
pub mod error;
pub mod h1;
pub mod h2;
pub mod test;
pub mod ws;
#[cfg(feature = "cookies")]
pub use cookie;
pub use self::builder::HttpServiceBuilder;
pub use self::config::{KeepAlive, ServiceConfig};
pub use self::error::{Error, ResponseError, Result};
@ -61,6 +78,7 @@ pub mod http {
pub use http::{uri, Error, Uri};
pub use http::{Method, StatusCode, Version};
#[cfg(feature = "cookies")]
pub use crate::cookie::{Cookie, CookieBuilder};
pub use crate::header::HeaderMap;

View File

@ -16,13 +16,17 @@ use futures_core::Stream;
use serde::Serialize;
use crate::body::{Body, BodyStream, MessageBody, ResponseBody};
use crate::cookie::{Cookie, CookieJar};
use crate::error::Error;
use crate::extensions::Extensions;
use crate::header::{IntoHeaderPair, IntoHeaderValue};
use crate::http::header::{self, HeaderName, HeaderValue};
use crate::http::header::{self, HeaderName};
use crate::http::{Error as HttpError, HeaderMap, StatusCode};
use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead};
#[cfg(feature = "cookies")]
use crate::{
cookie::{Cookie, CookieJar},
http::header::HeaderValue,
};
/// An HTTP Response
pub struct Response<B = Body> {
@ -133,6 +137,7 @@ impl<B> Response<B> {
}
/// Get an iterator for the cookies set by this response
#[cfg(feature = "cookies")]
#[inline]
pub fn cookies(&self) -> CookieIter<'_> {
CookieIter {
@ -141,6 +146,7 @@ impl<B> Response<B> {
}
/// Add a cookie to this response
#[cfg(feature = "cookies")]
#[inline]
pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> {
let h = &mut self.head.headers;
@ -153,6 +159,7 @@ impl<B> Response<B> {
/// Remove all cookies with the given name from this response. Returns
/// the number of cookies removed.
#[cfg(feature = "cookies")]
#[inline]
pub fn del_cookie(&mut self, name: &str) -> usize {
let h = &mut self.head.headers;
@ -298,10 +305,12 @@ impl Future for Response {
}
}
#[cfg(feature = "cookies")]
pub struct CookieIter<'a> {
iter: header::GetAll<'a>,
}
#[cfg(feature = "cookies")]
impl<'a> Iterator for CookieIter<'a> {
type Item = Cookie<'a>;
@ -316,13 +325,13 @@ impl<'a> Iterator for CookieIter<'a> {
}
}
/// An HTTP response builder
/// An HTTP response builder.
///
/// This type can be used to construct an instance of `Response` through a
/// builder-like pattern.
/// This type can be used to construct an instance of `Response` through a builder-like pattern.
pub struct ResponseBuilder {
head: Option<BoxedResponseHead>,
err: Option<HttpError>,
#[cfg(feature = "cookies")]
cookies: Option<CookieJar>,
}
@ -333,6 +342,7 @@ impl ResponseBuilder {
ResponseBuilder {
head: Some(BoxedResponseHead::new(status)),
err: None,
#[cfg(feature = "cookies")]
cookies: None,
}
}
@ -531,6 +541,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();
@ -557,6 +568,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() {
self.cookies = Some(CookieJar::new())
@ -624,8 +636,11 @@ impl ResponseBuilder {
return Response::from(Error::from(e)).into_body();
}
// allow unused mut when cookies feature is disabled
#[allow(unused_mut)]
let mut response = self.head.take().expect("cannot reuse response builder");
#[cfg(feature = "cookies")]
if let Some(ref jar) = self.cookies {
for cookie in jar.delta() {
match HeaderValue::from_str(&cookie.to_string()) {
@ -693,6 +708,7 @@ impl ResponseBuilder {
ResponseBuilder {
head: self.head.take(),
err: self.err.take(),
#[cfg(feature = "cookies")]
cookies: self.cookies.take(),
}
}
@ -712,21 +728,28 @@ fn parts<'a>(
/// Convert `Response` to a `ResponseBuilder`. Body get dropped.
impl<B> From<Response<B>> for ResponseBuilder {
fn from(res: Response<B>) -> ResponseBuilder {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
for c in res.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 jar = {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
for c in res.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);
}
}
}
jar
};
ResponseBuilder {
head: Some(res.head),
err: None,
#[cfg(feature = "cookies")]
cookies: jar,
}
}
@ -735,22 +758,6 @@ impl<B> From<Response<B>> for ResponseBuilder {
/// Convert `ResponseHead` to a `ResponseBuilder`
impl<'a> From<&'a ResponseHead> for ResponseBuilder {
fn from(head: &'a ResponseHead) -> ResponseBuilder {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
let cookies = CookieIter {
iter: head.headers.get_all(header::SET_COOKIE),
};
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);
}
}
let mut msg = BoxedResponseHead::new(head.status);
msg.version = head.version;
msg.reason = head.reason;
@ -761,9 +768,32 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder {
msg.no_chunking(!head.chunked());
#[cfg(feature = "cookies")]
let jar = {
// If this response has cookies, load them into a jar
let mut jar: Option<CookieJar> = None;
let cookies = CookieIter {
iter: head.headers.get_all(header::SET_COOKIE),
};
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);
}
}
jar
};
ResponseBuilder {
head: Some(msg),
err: None,
#[cfg(feature = "cookies")]
cookies: jar,
}
}

View File

@ -11,13 +11,14 @@ use std::{
use actix_codec::{AsyncRead, AsyncWrite, ReadBuf};
use bytes::{Bytes, BytesMut};
use http::{
header::{self, HeaderValue},
Method, Uri, Version,
};
use http::{Method, Uri, Version};
#[cfg(feature = "cookies")]
use crate::{
cookie::{Cookie, CookieJar},
header::{self, HeaderValue},
};
use crate::{
header::{HeaderMap, IntoHeaderPair},
payload::Payload,
Request,
@ -53,6 +54,7 @@ struct Inner {
method: Method,
uri: Uri,
headers: HeaderMap,
#[cfg(feature = "cookies")]
cookies: CookieJar,
payload: Option<Payload>,
}
@ -64,6 +66,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,
}))
@ -132,6 +135,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
@ -165,17 +169,20 @@ impl TestRequest {
head.version = inner.version;
head.headers = inner.headers;
let cookie: String = inner
.cookies
.delta()
// ensure only name=value is written to cookie header
.map(|c| Cookie::new(c.name(), c.value()).encoded().to_string())
.collect::<Vec<_>>()
.join("; ");
#[cfg(feature = "cookies")]
{
let cookie: String = inner
.cookies
.delta()
// ensure only name=value is written to cookie header
.map(|c| Cookie::new(c.name(), c.value()).encoded().to_string())
.collect::<Vec<_>>()
.join("; ");
if !cookie.is_empty() {
head.headers
.insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap());
if !cookie.is_empty() {
head.headers
.insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap());
}
}
req