1use reqwest::cookie::Jar;
2use std::{
3 fs::File,
4 io::{BufRead, BufReader},
5 path::Path,
6};
7
8pub trait JarExt {
9 fn load_netscape(path: &impl AsRef<Path>) -> crate::Result<Jar>;
15}
16
17impl JarExt for Jar {
18 fn load_netscape(path: &impl AsRef<Path>) -> crate::Result<Self> {
19 let jar = Self::default();
20 let file = File::open(path)?;
21 let mut reader = BufReader::new(file);
22 let mut line = String::new();
23
24 while reader.read_line(&mut line)? > 0 {
25 let trimmed = line.trim();
26
27 if trimmed.starts_with('#') || trimmed.is_empty() {
28 line.clear();
29 continue;
30 }
31
32 let mut parts = trimmed.splitn(7, '\t');
33 let domain = parts
34 .next()
35 .ok_or_else(|| crate::Error::NotFound("malformed cookie: missing domain".into()))?;
36 let key = parts
37 .nth(4)
38 .ok_or_else(|| crate::Error::NotFound("malformed cookie: missing name".into()))?;
39 let value = parts
40 .next()
41 .ok_or_else(|| crate::Error::NotFound("malformed cookie: missing value".into()))?;
42
43 let mut cookie = String::with_capacity(key.len() + value.len() + domain.len() + 10);
44 cookie.push_str(key);
45 cookie.push('=');
46 cookie.push_str(value);
47 cookie.push_str("; Domain=");
48 cookie.push_str(domain);
49
50 let domain = domain.trim_start_matches('.');
51 let mut url_str = String::with_capacity(8 + domain.len());
52 url_str.push_str("https://");
53 url_str.push_str(domain);
54
55 jar.add_cookie_str(
56 &cookie,
57 &reqwest::Url::parse(&url_str)
58 .map_err(|e| crate::Error::NotFound(format!("invalid cookie domain '{domain}': {e}")))?,
59 );
60
61 line.clear();
62 }
63
64 Ok(jar)
65 }
66}