decrypt_cookies/firefox/
builder.rs1use std::{fmt::Display, marker::PhantomData, path::PathBuf};
2
3use tokio::{fs, join};
4
5use crate::{
6 firefox::{items::cookie::dao::CookiesQuery, FirefoxGetter},
7 prelude::FirefoxPath,
8};
9
10#[derive(Debug)]
12#[derive(thiserror::Error)]
13pub enum FirefoxBuilderError {
14 #[error(transparent)]
15 Ini(#[from] ini::Error),
16 #[error(transparent)]
17 IniParser(#[from] ini::ParseError),
18 #[error("Profile {0} missing `Name` properties")]
19 ProfilePath(String),
20 #[error("Install {0} missing `Default` properties")]
21 InstallPath(String),
22 #[error(transparent)]
23 Db(#[from] sea_orm::DbErr),
24 #[error("Io: {source}, path: {path}")]
25 Io {
26 source: std::io::Error,
27 path: PathBuf,
28 },
29}
30
31pub type Result<T> = std::result::Result<T, FirefoxBuilderError>;
32
33#[derive(Clone)]
34#[derive(Debug)]
35#[derive(Default)]
36#[derive(PartialEq, Eq, PartialOrd, Ord)]
37pub(crate) struct TempPaths {
38 pub(crate) cookies_temp: PathBuf,
39 pub(crate) login_data_temp: PathBuf,
40 pub(crate) key_temp: PathBuf,
41}
42
43#[derive(Clone)]
44#[derive(Debug)]
45#[derive(Default)]
46pub struct FirefoxBuilder<'a, T> {
47 pub(crate) init: Option<PathBuf>,
48 pub(crate) profile: Option<&'a str>,
49 pub(crate) profile_path: Option<&'a str>,
50 pub(crate) __browser: PhantomData<T>,
51}
52
53impl<B: FirefoxPath> Display for FirefoxGetter<B> {
54 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 f.write_str(B::NAME)
56 }
57}
58
59impl<B: FirefoxPath> Display for FirefoxBuilder<'_, B> {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 f.write_fmt(format_args!("{}Builder", B::NAME))
62 }
63}
64
65impl<'b, B: FirefoxPath> FirefoxBuilder<'b, B> {
66 pub const fn new() -> Self {
67 Self {
68 init: None,
69 profile: None,
70 profile_path: None,
71 __browser: core::marker::PhantomData::<B>,
72 }
73 }
74
75 pub fn init() -> PathBuf {
77 dirs::home_dir()
78 .expect("Get home dir failed")
79 .join(B::BASE)
80 }
81
82 pub fn with_profile_path<P>(profile_path: P) -> Result<Self>
84 where
85 P: Into<Option<&'b str>>,
86 {
87 Ok(Self {
88 init: None,
89 profile: None,
90 profile_path: profile_path.into(),
91 __browser: core::marker::PhantomData::<B>,
92 })
93 }
94
95 pub fn with_base_profile<I, P>(init: I, profile: P) -> Self
98 where
99 I: Into<Option<PathBuf>>,
100 P: Into<Option<&'b str>>,
101 {
102 Self {
103 init: init.into(),
104 profile: profile.into(),
105 profile_path: None,
106 __browser: core::marker::PhantomData::<B>,
107 }
108 }
109
110 async fn cache_data(profile_path: PathBuf) -> Result<TempPaths> {
111 let cookies = B::cookies(profile_path.clone());
112 let cookies_temp = B::cookies_temp();
113
114 let login_data = B::login_data(profile_path.clone());
115 let login_data_temp = B::login_data_temp();
116
117 let key = B::key(profile_path.clone());
118 let key_temp = B::key_temp();
119
120 let ck_temp_p = cookies_temp
121 .parent()
122 .expect("Get parent dir failed");
123 let cd_ck = fs::create_dir_all(ck_temp_p);
124 let lg_temp_p = login_data_temp
125 .parent()
126 .expect("Get parent dir failed");
127 let cd_lg = fs::create_dir_all(lg_temp_p);
128 let k_temp_p = key_temp
129 .parent()
130 .expect("Get parent dir failed");
131 let cd_k = fs::create_dir_all(k_temp_p);
132 let (cd_ck, cd_lg, cd_k) = join!(cd_ck, cd_lg, cd_k);
133 cd_ck.map_err(|e| FirefoxBuilderError::Io {
134 source: e,
135 path: ck_temp_p.to_owned(),
136 })?;
137 cd_lg.map_err(|e| FirefoxBuilderError::Io {
138 source: e,
139 path: lg_temp_p.to_owned(),
140 })?;
141 cd_k.map_err(|e| FirefoxBuilderError::Io {
142 source: e,
143 path: k_temp_p.to_owned(),
144 })?;
145
146 let cookies_cp = fs::copy(&cookies, &cookies_temp);
147 let login_cp = fs::copy(&login_data, &login_data_temp);
148 let key_cp = fs::copy(&key, &key_temp);
149
150 let (ck, lg, k) = join!(cookies_cp, login_cp, key_cp);
151 ck.map_err(|e| FirefoxBuilderError::Io { source: e, path: cookies })?;
152 lg.map_err(|e| FirefoxBuilderError::Io { source: e, path: login_data })?;
153 k.map_err(|e| FirefoxBuilderError::Io { source: e, path: key })?;
154
155 Ok(TempPaths {
156 cookies_temp,
157 login_data_temp,
158 key_temp,
159 })
160 }
161}
162
163impl<'b, B: FirefoxPath + Send + Sync> FirefoxBuilder<'b, B> {
164 pub async fn get_profile_path(&self) -> Result<PathBuf> {
166 let mut base = self
167 .init
168 .clone()
169 .unwrap_or_else(Self::init);
170 let ini_path = base.join("profiles.ini");
171
172 let ini_str = fs::read_to_string(&ini_path)
173 .await
174 .map_err(|e| FirefoxBuilderError::Io { source: e, path: ini_path })?;
175
176 let ini_file = ini::Ini::load_from_str(&ini_str)?;
177 for (section, prop) in ini_file {
178 let Some(section) = section
179 else {
180 continue;
181 };
182 if let Some(profile) = self.profile {
183 if !section.starts_with("Profile") {
184 continue;
185 }
186 let Some(profile_name) = prop.get("Name")
187 else {
188 continue;
189 };
190 if profile_name == profile {
191 let Some(var) = prop.get("Path")
192 else {
193 return Err(FirefoxBuilderError::ProfilePath(profile_name.to_owned()));
194 };
195 base.push(var);
196 break;
197 }
198 }
199 else {
200 if !section.starts_with("Install") {
201 continue;
202 }
203 let Some(default) = prop.get("Default")
204 else {
205 return Err(FirefoxBuilderError::InstallPath(section));
206 };
207 base.push(default);
208 break;
209 }
210 }
211
212 tracing::debug!("path: {:?}", base);
213
214 Ok(base)
215 }
216
217 pub async fn build(self) -> Result<FirefoxGetter<B>> {
218 let profile_path = if let Some(path) = self.profile_path {
219 path.into()
220 }
221 else {
222 self.get_profile_path().await?
223 };
224 let temp_paths = Self::cache_data(profile_path).await?;
225
226 let query = CookiesQuery::new(temp_paths.cookies_temp).await?;
227
228 Ok(FirefoxGetter {
229 cookies_query: query,
230 __browser: core::marker::PhantomData::<B>,
231 })
232 }
233}