Skip to content

Commit

Permalink
Create CookieBuilder for constructing cookies, expose adding to jar
Browse files Browse the repository at this point in the history
  • Loading branch information
jacobmischka committed Oct 10, 2021
1 parent e3e8b60 commit 5f8f9dd
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 48 deletions.
13 changes: 10 additions & 3 deletions examples/cookies.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
//! A simple example program that sends a GET request and then prints out all
//! the cookies in the cookie jar.

use isahc::{cookies::CookieJar, prelude::*, Request};
use isahc::{
cookies::{Cookie, CookieJar},
prelude::*,
Request,
};
use std::error::Error;

fn main() -> Result<(), Box<dyn Error>> {
Expand All @@ -21,8 +25,11 @@ fn main() -> Result<(), Box<dyn Error>> {
println!("Cookie set: {} = {}", cookie.name(), cookie.value());
}

// Send another request. The cookies previously set by the server will be
// returned to it.
// Replace a cookie
let cookie = Cookie::builder("foo", "rab").path("/").build()?;
cookie_jar.set(cookie, &uri)?;

// Send another request. The cookies previously set will be sent.
let mut response = Request::get("http:https://httpbin.org/cookies")
.cookie_jar(cookie_jar.clone())
.body(())?
Expand Down
148 changes: 146 additions & 2 deletions src/cookies/cookie.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::{error::Error, fmt, str};

/// An error which can occur when attempting to parse a cookie string.
#[derive(Debug)]
pub(crate) struct ParseError(());
pub struct ParseError(());

impl fmt::Display for ParseError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
Expand All @@ -13,6 +13,119 @@ impl fmt::Display for ParseError {

impl Error for ParseError {}

/// Builder for a [`Cookie`].
///
/// ```rust
/// use chrono::{Utc, Duration};
/// use isahc::cookies::Cookie;
///
/// let cookie: Cookie = Cookie::builder("name", "value") // or CookieBuilder::new("name", "value")
/// .domain("example.com")
/// .path("/")
/// .secure(true)
/// .expiration(Utc::now() + Duration::minutes(30))
/// .build()
/// .unwrap();
/// ```
#[derive(Clone, Debug)]
pub struct CookieBuilder {
/// The name of the cookie.
name: String,

/// The cookie value.
value: String,

/// The domain the cookie belongs to.
domain: Option<String>,

/// A path prefix that this cookie belongs to.
path: Option<String>,

/// True if the cookie is marked as secure (limited in scope to HTTPS).
secure: Option<bool>,

/// Time when this cookie expires. If not present, then this is a session
/// cookie that expires when the current client session ends.
expiration: Option<DateTime<Utc>>,
}

impl CookieBuilder {
/// Create a new cookie builder with a given name and value.
#[allow(unused)]
pub fn new<N, V>(name: N, value: V) -> Self
where
N: Into<String>,
V: Into<String>,
{
Self {
name: name.into(),
value: value.into(),
domain: None,
path: None,
secure: None,
expiration: None,
}
}

/// Sets the domain the cookie belongs to.
pub fn domain<S>(mut self, domain: S) -> Self
where
S: Into<String>,
{
self.domain = Some(domain.into());
self
}

/// Sets the path prefix that this cookie belongs to.
pub fn path<S>(mut self, path: S) -> Self
where
S: Into<String>,
{
self.path = Some(path.into());
self
}

/// True if the cookie is marked as secure (limited in scope to HTTPS).
pub fn secure(mut self, secure: bool) -> Self {
self.secure = Some(secure);
self
}

/// Time when this cookie expires. If not present, then this is a session
/// cookie that expires when the current client session ends.
pub fn expiration(mut self, expiration: DateTime<Utc>) -> Self {
self.expiration = Some(expiration);
self
}

/// Builds the cookie.
///
/// Returns an error if either the name or value given contains illegal
/// characters. In practice, only a subset of US-ASCII characters are
/// allowed in cookies for maximum compatibility with most web servers.
pub fn build(self) -> Result<Cookie, ParseError> {
let Self {
name,
value,
domain,
path,
secure,
expiration,
} = self;

let mut cookie = Cookie::new(name, value)?;
cookie.domain = domain;
cookie.path = path;
cookie.expiration = expiration;

if let Some(secure) = secure {
cookie.secure = secure;
}

Ok(cookie)
}
}

/// Information stored about an HTTP cookie.
///
/// # Comparison operators
Expand Down Expand Up @@ -57,7 +170,7 @@ impl Cookie {
/// characters. In practice, only a subset of US-ASCII characters are
/// allowed in cookies for maximum compatibility with most web servers.
#[allow(unused)]
pub(crate) fn new<N, V>(name: N, value: V) -> Result<Self, ParseError>
fn new<N, V>(name: N, value: V) -> Result<Self, ParseError>
where
N: Into<String>,
V: Into<String>,
Expand All @@ -80,6 +193,17 @@ impl Cookie {
}
}

/// Create a new cookie builder with a given name and value.
/// See [`CookieBuilder::new`] for an example.
#[allow(unused)]
pub fn builder<N, V>(name: N, value: V) -> CookieBuilder
where
N: Into<String>,
V: Into<String>,
{
CookieBuilder::new(name, value)
}

/// Parse a cookie from a cookie string, as defined in [RFC 6265, section
/// 4.2.1](https://tools.ietf.org/html/rfc6265#section-4.2.1). This can be
/// used to parse `Set-Cookie` header values, but not `Cookie` header
Expand Down Expand Up @@ -341,4 +465,24 @@ mod tests {
Some(1_445_412_480)
);
}

#[test]
fn create_cookie() {
let exp = Utc::now();

let cookie = Cookie::builder("foo", "bar")
.domain("baz.com")
.path("/sub")
.secure(true)
.expiration(exp)
.build()
.unwrap();

assert_eq!(cookie.name(), "foo");
assert_eq!(cookie.value(), "bar");
assert_eq!(cookie.path(), Some("/sub"));
assert_eq!(cookie.domain.as_deref(), Some("baz.com"));
assert!(cookie.is_secure());
assert_eq!(cookie.expiration, Some(exp));
}
}
6 changes: 2 additions & 4 deletions src/cookies/interceptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ pub(crate) struct CookieInterceptor {

impl CookieInterceptor {
pub(crate) fn new(cookie_jar: Option<CookieJar>) -> Self {
Self {
cookie_jar,
}
Self { cookie_jar }
}
}

Expand Down Expand Up @@ -97,7 +95,7 @@ impl Interceptor for CookieInterceptor {
});

for cookie in cookies {
jar.set(cookie, request_uri);
let _ = jar.set(cookie, request_uri);
}
}

Expand Down
Loading

0 comments on commit 5f8f9dd

Please sign in to comment.