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 pub fn new() -> Self {
23 Self {
24 jar_file: String::new(),
25 auto_update: false,
26 cookies: HashMap::new(),
27 }
28 }
29
30 pub fn from_file(jar_file: &str, _auto_update: bool) -> Result<Self, Error> {
32 if !Path::new(jar_file).exists() {
34 return Err(Error::FileNotExists(jar_file.to_string()));
35 }
36
37 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 pub fn from_string(contents: &String) -> Self {
48 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 pub fn set_jar_file(&mut self, jar_file: &str) {
69 self.jar_file = jar_file.to_string();
70 }
71
72 pub fn set_auto_update(&mut self, auto_update: bool) {
74 self.auto_update = auto_update;
75 }
76
77 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 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 pub fn delete(&mut self, name: &str) {
93 self.cookies.remove(name);
94 }
95
96 pub fn clear(&mut self) {
98 self.cookies.clear();
99 }
100
101 pub fn get_http_header(&self, uri: &Url) -> Option<String> {
103 let mut pairs = Vec::new();
105 let host = uri.host_str().unwrap();
106 let host_chk = format!(".{}", host);
107
108 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 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 pub fn iter(&self) -> Box<dyn Iterator<Item = (String, Cookie)>> {
130 Box::new(self.cookies.clone().into_iter())
131 }
132
133 pub fn update_jar(&mut self, headers: &HttpHeaders) {
135 for line in headers.get_lower_vec("set-cookie") {
137 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 }
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 if self.auto_update {
192 self.save_jar();
193 }
194 }
195
196 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 for (_name, cookie) in self.iter() {
218 writeln!(file, "{}", Cookie::to_line(&cookie));
219 }
220
221 Ok(())
222 }
223}