Make Cookie header RFC6265 compliant (#130)

This commit is contained in:
cazgp
2020-08-07 18:04:15 +01:00
committed by GitHub
parent 75d5e52a45
commit 22d815e5e7
2 changed files with 77 additions and 36 deletions

View File

@@ -1,7 +1,7 @@
use crate::error::Error; use crate::error::Error;
use std::str::FromStr; use std::str::FromStr;
#[derive(Clone)] #[derive(Clone, PartialEq)]
/// Wrapper type for a header field. /// Wrapper type for a header field.
/// https://tools.ietf.org/html/rfc7230#section-3.2 /// https://tools.ietf.org/html/rfc7230#section-3.2
pub struct Header { pub struct Header {

View File

@@ -61,7 +61,7 @@ impl Unit {
let query_string = combine_query(&url, &req.query, mix_queries); let query_string = combine_query(&url, &req.query, mix_queries);
let cookie_headers: Vec<_> = extract_cookies(&req.agent, &url); let cookie_header: Option<Header> = extract_cookies(&req.agent, &url);
let extra_headers = { let extra_headers = {
let mut extra = vec![]; let mut extra = vec![];
@@ -86,7 +86,7 @@ impl Unit {
let headers: Vec<_> = req let headers: Vec<_> = req
.headers .headers
.iter() .iter()
.chain(cookie_headers.iter()) .chain(cookie_header.iter())
.chain(extra_headers.iter()) .chain(extra_headers.iter())
.cloned() .cloned()
.collect(); .collect();
@@ -239,51 +239,56 @@ pub(crate) fn connect(
} }
#[cfg(feature = "cookie")] #[cfg(feature = "cookie")]
fn extract_cookies(state: &std::sync::Mutex<Option<AgentState>>, url: &Url) -> Vec<Header> { fn extract_cookies(state: &std::sync::Mutex<Option<AgentState>>, url: &Url) -> Option<Header> {
let state = state.lock().unwrap(); let state = state.lock().unwrap();
let is_secure = url.scheme().eq_ignore_ascii_case("https"); let is_secure = url.scheme().eq_ignore_ascii_case("https");
let hostname = url.host_str().unwrap_or(DEFAULT_HOST).to_string(); let hostname = url.host_str().unwrap_or(DEFAULT_HOST).to_string();
match state.as_ref().map(|state| &state.jar) { state
None => vec![], .as_ref()
Some(jar) => match_cookies(jar, &hostname, url.path(), is_secure), .map(|state| &state.jar)
} .and_then(|jar| match_cookies(jar, &hostname, url.path(), is_secure))
} }
#[cfg(not(feature = "cookie"))] #[cfg(not(feature = "cookie"))]
fn extract_cookies(_state: &std::sync::Mutex<Option<AgentState>>, _url: &Url) -> Vec<Header> { fn extract_cookies(_state: &std::sync::Mutex<Option<AgentState>>, _url: &Url) -> Option<Header> {
vec![] None
} }
// TODO check so cookies can't be set for tld:s // TODO check so cookies can't be set for tld:s
#[cfg(feature = "cookie")] #[cfg(feature = "cookie")]
fn match_cookies(jar: &CookieJar, domain: &str, path: &str, is_secure: bool) -> Vec<Header> { fn match_cookies(jar: &CookieJar, domain: &str, path: &str, is_secure: bool) -> Option<Header> {
jar.iter() Some(
.filter(|c| { jar.iter()
// if there is a domain, it must be matched. .filter(|c| {
// if there is no domain, then ignore cookie // if there is a domain, it must be matched.
let domain_ok = c // if there is no domain, then ignore cookie
.domain() let domain_ok = c
.map(|cdom| domain.contains(cdom)) .domain()
.unwrap_or(false); .map(|cdom| domain.contains(cdom))
// a path must match the beginning of request path. .unwrap_or(false);
// no cookie path, we say is ok. is it?! // a path must match the beginning of request path.
let path_ok = c // no cookie path, we say is ok. is it?!
.path() let path_ok = c
.map(|cpath| path.find(cpath).map(|pos| pos == 0).unwrap_or(false)) .path()
.unwrap_or(true); .map(|cpath| path.find(cpath).map(|pos| pos == 0).unwrap_or(false))
// either the cookie isnt secure, or we're not doing a secure request. .unwrap_or(true);
let secure_ok = !c.secure().unwrap_or(false) || is_secure; // either the cookie isnt secure, or we're not doing a secure request.
let secure_ok = !c.secure().unwrap_or(false) || is_secure;
domain_ok && path_ok && secure_ok domain_ok && path_ok && secure_ok
}) })
.map(|c| { .map(|c| {
let name = c.name().to_string(); let name = c.name().to_string();
let value = c.value().to_string(); let value = c.value().to_string();
let nameval = Cookie::new(name, value).encoded().to_string(); let nameval = Cookie::new(name, value).encoded().to_string();
Header::new("Cookie", &nameval) nameval
}) })
.collect() .collect::<Vec<_>>()
.join(";"),
)
.filter(|x| !x.is_empty())
.map(|s| Header::new("Cookie", &s))
} }
/// Combine the query of the url and the query options set on the request object. /// Combine the query of the url and the query options set on the request object.
@@ -419,3 +424,39 @@ fn save_cookies(unit: &Unit, resp: &Response) {
} }
} }
} }
#[cfg(test)]
#[cfg(feature = "cookies")]
mod tests {
use super::*;
///////////////////// COOKIE TESTS //////////////////////////////
#[test]
fn match_cookies_returns_nothing_when_no_cookies() {
let jar = CookieJar::new();
let result = match_cookies(&jar, "crates.io", "/", false);
assert_eq!(result, None);
}
#[test]
fn match_cookies_returns_one_header() {
let mut jar = CookieJar::new();
let cookie1 = Cookie::parse("cookie1=value1; Domain=crates.io").unwrap();
let cookie2 = Cookie::parse("cookie2=value2; Domain=crates.io").unwrap();
jar.add(cookie1);
jar.add(cookie2);
// There's no guarantee to the order in which cookies are defined.
// Ensure that they're either in one order or the other.
let result = match_cookies(&jar, "crates.io", "/", false);
let order1 = "cookie1=value1;cookie2=value2";
let order2 = "cookie2=value2;cookie1=value1";
assert!(
result == Some(Header::new("Cookie", order1))
|| result == Some(Header::new("Cookie", order2))
);
}
}