mirror of https://github.com/Y2Z/monolith
add support for using cookie file
parent
20c56a5440
commit
78c37958dc
@ -0,0 +1,119 @@
|
|||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
|
pub struct Cookie {
|
||||||
|
pub domain: String,
|
||||||
|
pub include_subdomains: bool,
|
||||||
|
pub path: String,
|
||||||
|
pub https_only: bool,
|
||||||
|
pub expires: u64,
|
||||||
|
pub name: String,
|
||||||
|
pub value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum CookieFileContentsParseError {
|
||||||
|
InvalidHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Cookie {
|
||||||
|
pub fn is_expired(&self) -> bool {
|
||||||
|
if self.expires == 0 {
|
||||||
|
return false; // Session, never expires
|
||||||
|
}
|
||||||
|
|
||||||
|
let start = SystemTime::now();
|
||||||
|
let since_the_epoch = start
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.expect("Time went backwards");
|
||||||
|
|
||||||
|
self.expires < since_the_epoch.as_secs()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn matches_url(&self, url: &str) -> bool {
|
||||||
|
match Url::parse(&url) {
|
||||||
|
Ok(url) => {
|
||||||
|
// Check protocol scheme
|
||||||
|
match url.scheme() {
|
||||||
|
"http" => {
|
||||||
|
if self.https_only {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"https" => {}
|
||||||
|
_ => {
|
||||||
|
// Should never match URLs of protocols other than HTTP(S)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check host
|
||||||
|
if let Some(url_host) = url.host_str() {
|
||||||
|
if self.domain.starts_with(".") && self.include_subdomains {
|
||||||
|
if !url_host.to_lowercase().ends_with(&self.domain)
|
||||||
|
&& !url_host
|
||||||
|
.eq_ignore_ascii_case(&self.domain[1..self.domain.len() - 1])
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if !url_host.eq_ignore_ascii_case(&self.domain) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check path
|
||||||
|
if !url.path().eq_ignore_ascii_case(&self.path)
|
||||||
|
&& !url.path().starts_with(&self.path)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_cookie_file_contents(
|
||||||
|
cookie_file_contents: &str,
|
||||||
|
) -> Result<Vec<Cookie>, CookieFileContentsParseError> {
|
||||||
|
let mut cookies: Vec<Cookie> = Vec::new();
|
||||||
|
|
||||||
|
for (i, line) in cookie_file_contents.lines().enumerate() {
|
||||||
|
if i == 0 {
|
||||||
|
// Parsing first line
|
||||||
|
if !line.eq("# HTTP Cookie File") && !line.eq("# Netscape HTTP Cookie File") {
|
||||||
|
return Err(CookieFileContentsParseError::InvalidHeader);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Ignore comment lines
|
||||||
|
if line.starts_with("#") {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Attempt to parse values
|
||||||
|
let mut fields = line.split("\t");
|
||||||
|
if fields.clone().count() != 7 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
cookies.push(Cookie {
|
||||||
|
domain: fields.next().unwrap().to_string().to_lowercase(),
|
||||||
|
include_subdomains: fields.next().unwrap().to_string() == "TRUE",
|
||||||
|
path: fields.next().unwrap().to_string(),
|
||||||
|
https_only: fields.next().unwrap().to_string() == "TRUE",
|
||||||
|
expires: fields.next().unwrap().parse::<u64>().unwrap(),
|
||||||
|
name: fields.next().unwrap().to_string(),
|
||||||
|
value: fields.next().unwrap().to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(cookies)
|
||||||
|
}
|
@ -0,0 +1,68 @@
|
|||||||
|
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
|
||||||
|
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
|
||||||
|
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
|
||||||
|
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
|
||||||
|
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
|
||||||
|
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod passing {
|
||||||
|
use monolith::cookies;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn never_expires() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: false,
|
||||||
|
expires: 0,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(!cookie.is_expired());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expires_long_from_now() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: false,
|
||||||
|
expires: 9999999999,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(!cookie.is_expired());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗
|
||||||
|
// ██╔════╝██╔══██╗██║██║ ██║████╗ ██║██╔════╝
|
||||||
|
// █████╗ ███████║██║██║ ██║██╔██╗ ██║██║ ███╗
|
||||||
|
// ██╔══╝ ██╔══██║██║██║ ██║██║╚██╗██║██║ ██║
|
||||||
|
// ██║ ██║ ██║██║███████╗██║██║ ╚████║╚██████╔╝
|
||||||
|
// ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod failing {
|
||||||
|
use monolith::cookies;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn expired() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: false,
|
||||||
|
expires: 1,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert!(cookie.is_expired());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,107 @@
|
|||||||
|
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
|
||||||
|
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
|
||||||
|
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
|
||||||
|
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
|
||||||
|
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
|
||||||
|
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod passing {
|
||||||
|
use monolith::cookies;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn secure_url() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: true,
|
||||||
|
expires: 0,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
assert!(cookie.matches_url("https://127.0.0.1/something"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn non_secure_url() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: false,
|
||||||
|
expires: 0,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
assert!(cookie.matches_url("http://127.0.0.1/something"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn subdomain() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from(".somethingsomething.com"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: true,
|
||||||
|
expires: 0,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
assert!(cookie.matches_url("https://cdn.somethingsomething.com/something"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗
|
||||||
|
// ██╔════╝██╔══██╗██║██║ ██║████╗ ██║██╔════╝
|
||||||
|
// █████╗ ███████║██║██║ ██║██╔██╗ ██║██║ ███╗
|
||||||
|
// ██╔══╝ ██╔══██║██║██║ ██║██║╚██╗██║██║ ██║
|
||||||
|
// ██║ ██║ ██║██║███████╗██║██║ ╚████║╚██████╔╝
|
||||||
|
// ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod failing {
|
||||||
|
use monolith::cookies;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty_url() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: false,
|
||||||
|
expires: 0,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
assert!(!cookie.matches_url(""));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrong_hostname() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: true,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: false,
|
||||||
|
expires: 0,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
assert!(!cookie.matches_url("http://0.0.0.0/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn wrong_path() {
|
||||||
|
let cookie = cookies::Cookie {
|
||||||
|
domain: String::from("127.0.0.1"),
|
||||||
|
include_subdomains: false,
|
||||||
|
path: String::from("/"),
|
||||||
|
https_only: false,
|
||||||
|
expires: 0,
|
||||||
|
name: String::from(""),
|
||||||
|
value: String::from(""),
|
||||||
|
};
|
||||||
|
assert!(!cookie.matches_url("http://0.0.0.0/path"));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
mod is_expired;
|
||||||
|
mod matches_url;
|
@ -0,0 +1,2 @@
|
|||||||
|
mod cookie;
|
||||||
|
mod parse_cookie_file_contents;
|
@ -0,0 +1,87 @@
|
|||||||
|
// ██████╗ █████╗ ███████╗███████╗██╗███╗ ██╗ ██████╗
|
||||||
|
// ██╔══██╗██╔══██╗██╔════╝██╔════╝██║████╗ ██║██╔════╝
|
||||||
|
// ██████╔╝███████║███████╗███████╗██║██╔██╗ ██║██║ ███╗
|
||||||
|
// ██╔═══╝ ██╔══██║╚════██║╚════██║██║██║╚██╗██║██║ ██║
|
||||||
|
// ██║ ██║ ██║███████║███████║██║██║ ╚████║╚██████╔╝
|
||||||
|
// ╚═╝ ╚═╝ ╚═╝╚══════╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod passing {
|
||||||
|
use monolith::cookies;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_file() {
|
||||||
|
let file_contents =
|
||||||
|
"# Netscape HTTP Cookie File\n127.0.0.1\tFALSE\t/\tFALSE\t0\tUSER_TOKEN\tin";
|
||||||
|
let result = cookies::parse_cookie_file_contents(&file_contents).unwrap();
|
||||||
|
assert_eq!(result.len(), 1);
|
||||||
|
assert_eq!(result[0].domain, "127.0.0.1");
|
||||||
|
assert_eq!(result[0].include_subdomains, false);
|
||||||
|
assert_eq!(result[0].path, "/");
|
||||||
|
assert_eq!(result[0].https_only, false);
|
||||||
|
assert_eq!(result[0].expires, 0);
|
||||||
|
assert_eq!(result[0].name, "USER_TOKEN");
|
||||||
|
assert_eq!(result[0].value, "in");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_multiline_file() {
|
||||||
|
let file_contents = "# HTTP Cookie File\n127.0.0.1\tFALSE\t/\tFALSE\t0\tUSER_TOKEN\tin\n127.0.0.1\tTRUE\t/\tTRUE\t9\tUSER_TOKEN\tout\n\n";
|
||||||
|
let result = cookies::parse_cookie_file_contents(&file_contents).unwrap();
|
||||||
|
assert_eq!(result.len(), 2);
|
||||||
|
assert_eq!(result[0].domain, "127.0.0.1");
|
||||||
|
assert_eq!(result[0].include_subdomains, false);
|
||||||
|
assert_eq!(result[0].path, "/");
|
||||||
|
assert_eq!(result[0].https_only, false);
|
||||||
|
assert_eq!(result[0].expires, 0);
|
||||||
|
assert_eq!(result[0].name, "USER_TOKEN");
|
||||||
|
assert_eq!(result[0].value, "in");
|
||||||
|
assert_eq!(result[1].domain, "127.0.0.1");
|
||||||
|
assert_eq!(result[1].include_subdomains, true);
|
||||||
|
assert_eq!(result[1].path, "/");
|
||||||
|
assert_eq!(result[1].https_only, true);
|
||||||
|
assert_eq!(result[1].expires, 9);
|
||||||
|
assert_eq!(result[1].name, "USER_TOKEN");
|
||||||
|
assert_eq!(result[1].value, "out");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ███████╗ █████╗ ██╗██╗ ██╗███╗ ██╗ ██████╗
|
||||||
|
// ██╔════╝██╔══██╗██║██║ ██║████╗ ██║██╔════╝
|
||||||
|
// █████╗ ███████║██║██║ ██║██╔██╗ ██║██║ ███╗
|
||||||
|
// ██╔══╝ ██╔══██║██║██║ ██║██║╚██╗██║██║ ██║
|
||||||
|
// ██║ ██║ ██║██║███████╗██║██║ ╚████║╚██████╔╝
|
||||||
|
// ╚═╝ ╚═╝ ╚═╝╚═╝╚══════╝╚═╝╚═╝ ╚═══╝ ╚═════╝
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod failing {
|
||||||
|
use monolith::cookies;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn empty() {
|
||||||
|
let file_contents = "";
|
||||||
|
let result = cookies::parse_cookie_file_contents(&file_contents).unwrap();
|
||||||
|
assert_eq!(result.len(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_header() {
|
||||||
|
let file_contents = "127.0.0.1 FALSE / FALSE 0 USER_TOKEN in";
|
||||||
|
match cookies::parse_cookie_file_contents(&file_contents) {
|
||||||
|
Ok(_result) => {
|
||||||
|
assert!(false);
|
||||||
|
}
|
||||||
|
Err(_e) => {
|
||||||
|
assert!(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn spaces_instead_of_tabs() {
|
||||||
|
let file_contents =
|
||||||
|
"# HTTP Cookie File\n127.0.0.1 FALSE / FALSE 0 USER_TOKEN in";
|
||||||
|
let result = cookies::parse_cookie_file_contents(&file_contents).unwrap();
|
||||||
|
assert_eq!(result.len(), 0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue