atlas_http/
cookie_jar.rs

1
2use super::HttpHeaders;
3use crate::cookie::Cookie;
4use crate::error::{Error, FileNotCreatedError};
5use std::collections::HashMap;
6use std::fs;
7use std::fs::File;
8use std::io::prelude::*;
9use std::path::Path;
10use std::time::{SystemTime, UNIX_EPOCH};
11use url::Url;
12
13#[derive(Clone, Debug)]
14pub struct CookieJar {
15    jar_file: String,
16    auto_update: bool,
17    cookies: HashMap<String, Cookie>,
18}
19
20impl CookieJar {
21    /// Instantiate a new, empty cookie jar
22    pub fn new() -> Self {
23        Self {
24            jar_file: String::new(),
25            auto_update: false,
26            cookies: HashMap::new(),
27        }
28    }
29
30    /// Instantiate a new cookie jar from a Netscape formatted cookies.txt file
31    pub fn from_file(jar_file: &str, _auto_update: bool) -> Result<Self, Error> {
32        // Check file exists
33        if !Path::new(jar_file).exists() {
34            return Err(Error::FileNotExists(jar_file.to_string()));
35        }
36
37        // Get file contents
38        let contents = fs::read_to_string(jar_file).unwrap();
39        let mut jar = Self::from_string(&contents);
40        jar.jar_file = jar_file.to_string();
41        jar.auto_update = true;
42
43        Ok(jar)
44    }
45
46    /// Instantiate cookie jar from a string of a Netscape formatted cookies.txt file
47    pub fn from_string(contents: &String) -> Self {
48        // Go through lines
49        let mut cookies: HashMap<String, Cookie> = HashMap::new();
50        for line in contents.split('\n') {
51            if line.starts_with('#') {
52                continue;
53            }
54
55            if let Some(cookie) = Cookie::from_line(line) {
56                cookies.insert(cookie.name.clone(), cookie.clone());
57            }
58        }
59
60        Self {
61            jar_file: String::new(),
62            auto_update: false,
63            cookies,
64        }
65    }
66
67    /// Update jar filename
68    pub fn set_jar_file(&mut self, jar_file: &str) {
69        self.jar_file = jar_file.to_string();
70    }
71
72    /// Change auto_update
73    pub fn set_auto_update(&mut self, auto_update: bool) {
74        self.auto_update = auto_update;
75    }
76
77    /// Get individual cookie
78    pub fn get(&self, name: &str) -> Option<Cookie> {
79        if let Some(cookie) = self.cookies.get(name) {
80            return Some(cookie.clone());
81        }
82        None
83    }
84
85    // Set a cookie.  Will insert new cookie, or update if cookie already exists within jar.
86    pub fn set(&mut self, cookie: &Cookie) {
87        let name = cookie.name.clone();
88        *self.cookies.entry(name.clone()).or_insert(cookie.clone()) = cookie.clone();
89    }
90
91    /// Delete a cookie within jar
92    pub fn delete(&mut self, name: &str) {
93        self.cookies.remove(name);
94    }
95
96    /// Clear all cookies within jar
97    pub fn clear(&mut self) {
98        self.cookies.clear();
99    }
100
101    /// Get http header for host
102    pub fn get_http_header(&self, uri: &Url) -> Option<String> {
103        // Initialize
104        let mut pairs = Vec::new();
105        let host = uri.host_str().unwrap();
106        let host_chk = format!(".{}", host);
107
108        // Iterate through cookies
109        for (_name, cookie) in self.iter() {
110            if (cookie.host != host && cookie.host != host_chk)
111                || (!uri.path().starts_with(&cookie.path))
112                || (cookie.secure && uri.scheme() != "https")
113            {
114                continue;
115            }
116
117            // Add to pairs
118            let line = format!("{}={}", cookie.name, cookie.value);
119            pairs.push(line);
120        }
121
122        if pairs.is_empty() {
123            return None;
124        }
125        Some(pairs.join("; ").to_string())
126    }
127
128    /// Iterate over all cookies
129    pub fn iter(&self) -> Box<dyn Iterator<Item = (String, Cookie)>> {
130        Box::new(self.cookies.clone().into_iter())
131    }
132
133    /// Update cookie jar from response http headers
134    pub fn update_jar(&mut self, headers: &HttpHeaders) {
135        // GO through headers
136        for line in headers.get_lower_vec("set-cookie") {
137            // Get name and value
138            let eq_index = line.find('=').unwrap_or(0);
139            let sc_index = line.find(';').unwrap_or(0);
140            if eq_index == 0 || sc_index == 0 || eq_index >= sc_index {
141                continue;
142            }
143            let name = line[..eq_index].to_string();
144            let value = line[eq_index + 1..sc_index].trim().to_string();
145
146            if value.is_empty() {
147                self.delete(name.as_str());
148                continue;
149            }
150
151            let elem: HashMap<String, String> = line[sc_index + 1..]
152                .split(';')
153                .map(|e| {
154                    let (mut ekey, mut evalue) = (e.to_string(), "".to_string());
155                    if let Some(eindex) = e.find('=') {
156                        ekey = e[..eindex].to_lowercase().trim().to_string();
157                        evalue = e[eindex + 1..].trim().to_string();
158                    }
159                    (ekey, evalue)
160                })
161                .collect();
162
163            let expires: u64 = 0;
164            if let Some(_max_age) = elem.get(&"max-age".to_string()) {
165                let _secs = SystemTime::now()
166                    .duration_since(UNIX_EPOCH)
167                    .unwrap()
168                    .as_secs();
169                //expires = secs as u64 + max_age.parse::<u64>().unwrap();
170            }
171
172            let cookie = Cookie {
173                host: elem
174                    .get(&"domain".to_string())
175                    .unwrap_or(&"".to_string())
176                    .clone(),
177                path: elem
178                    .get(&"path".to_string())
179                    .unwrap_or(&"/".to_string())
180                    .clone(),
181                http_only: elem.contains_key(&"httponly".to_string()),
182                secure: elem.contains_key(&"secure".to_string()),
183                expires,
184                name: name.to_string(),
185                value: line[eq_index + 1..sc_index].trim().to_string(),
186            };
187            self.set(&cookie);
188        }
189
190        // Save jar file
191        if self.auto_update {
192            self.save_jar();
193        }
194    }
195
196    /// Save jar file
197    pub fn save_jar(&mut self) -> Result<(), Error> {
198        if self.jar_file.is_empty() {
199            return Ok(());
200        }
201
202        let mut file = match File::create(&self.jar_file) {
203            Ok(r) => r,
204            Err(e) => {
205                return Err(Error::FileNotCreated(FileNotCreatedError {
206                    filename: self.jar_file.clone(),
207                    error: e.to_string(),
208                }));
209            }
210        };
211        writeln!(
212            file,
213            "# Auto-generated by atlas-http (https://crates.io/crates/atlas-http)\n"
214        );
215
216        // Go through all cookies
217        for (_name, cookie) in self.iter() {
218            writeln!(file, "{}", Cookie::to_line(&cookie));
219        }
220
221        Ok(())
222    }
223}