decrypt_cookies/chromium/
builder.rs1#![expect(clippy::exhaustive_structs, reason = "Allow error")]
2
3use std::{
4 fmt::{Debug, Display},
5 marker::PhantomData,
6 path::{Path, PathBuf},
7};
8
9use chromium_crypto::Decrypter;
10use snafu::{ensure, Location, OptionExt, ResultExt, Snafu};
11use tokio::{fs, join};
12
13use super::{ChromiumCookieGetter, ChromiumGetter, ChromiumLoginGetter};
14use crate::{
15 browser::ChromiumPath,
16 chromium::items::{cookie::cookie_dao::CookiesQuery, passwd::login_data_dao::LoginDataQuery},
17};
18
19#[derive(Debug)]
21#[derive(Snafu)]
22#[snafu(visibility(pub))]
23#[non_exhaustive]
24pub enum ChromiumBuilderError {
25 #[snafu(display(r#"Not found {}
26The browser is not installed or started with `--user-data-dir` arg
27@:{location}"#, path.display()))]
28 NotFoundBase {
29 path: PathBuf,
30 #[snafu(implicit)]
31 location: Location,
32 },
33 #[snafu(display("{source}\n@:{location}"))]
34 Decrypter {
35 source: chromium_crypto::error::CryptoError,
36 #[snafu(implicit)]
37 location: Location,
38 },
39 #[snafu(display("{source}\n@:{location}"))]
40 Db {
41 source: sea_orm::DbErr,
42 #[snafu(implicit)]
43 location: Location,
44 },
45 #[snafu(display("{source}, path: {}\n@:{location}",path.display()))]
46 Io {
47 source: std::io::Error,
48 path: PathBuf,
49 #[snafu(implicit)]
50 location: Location,
51 },
52 #[cfg(target_os = "windows")]
53 #[snafu(display("{source}\n@:{location}"))]
54 Rawcopy {
55 source: anyhow::Error,
56 #[snafu(implicit)]
57 location: Location,
58 },
59 #[snafu(display("{source}\n@:{location}"))]
60 TokioJoin {
61 source: tokio::task::JoinError,
62 #[snafu(implicit)]
63 location: Location,
64 },
65 #[snafu(display("Can not found home dir\n@:{location}"))]
66 Home {
67 #[snafu(implicit)]
68 location: Location,
69 },
70}
71
72pub type Result<T> = std::result::Result<T, ChromiumBuilderError>;
73
74async fn copy<A, A0>(from: A, to: A0) -> Result<()>
76where
77 A: AsRef<Path> + Send,
78 A0: AsRef<Path> + Send,
79{
80 let parent = to
81 .as_ref()
82 .parent()
83 .expect("Get parent dir failed");
84 fs::create_dir_all(parent)
85 .await
86 .with_context(|_| IoSnafu { path: parent.to_owned() })?;
87
88 #[cfg(not(target_os = "windows"))]
89 fs::copy(from.as_ref(), to.as_ref())
90 .await
91 .with_context(|_| IoSnafu { path: from.as_ref().to_owned() })?;
92 #[cfg(target_os = "windows")]
93 {
94 let from_ = from.as_ref().to_owned();
95 let to = to.as_ref().to_owned();
96 tokio::task::spawn_blocking(move || crate::utils::shadow_copy(&from_, &to))
97 .await
98 .context(TokioJoinSnafu)??;
99 };
100
101 Ok(())
102}
103
104#[derive(Clone)]
105#[derive(Debug)]
106#[derive(Default)]
107#[derive(PartialEq, Eq)]
108pub struct ChromiumBuilder<T> {
109 pub(crate) base: Option<PathBuf>,
110 pub(crate) __browser: PhantomData<T>,
111}
112
113impl<B: ChromiumPath> Display for ChromiumBuilder<B> {
114 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
115 f.write_fmt(format_args!("{}Builder", B::NAME))
116 }
117}
118
119impl<B: ChromiumPath> ChromiumBuilder<B> {
120 pub const fn new() -> Self {
121 Self {
122 base: None,
123 __browser: PhantomData::<B>,
124 }
125 }
126
127 pub const fn with_user_data_dir(base: PathBuf) -> Self {
129 Self {
130 base: Some(base),
131 __browser: PhantomData::<B>,
132 }
133 }
134}
135
136impl<B: ChromiumPath + Send + Sync> ChromiumBuilder<B> {
137 fn ensure_base(self) -> Result<PathBuf> {
138 let base = if let Some(base) = self.base {
139 base
140 }
141 else {
142 let mut base = dirs::home_dir().context(HomeSnafu)?;
143
144 base.push(B::BASE);
145 base
146 };
147
148 ensure!(base.exists(), NotFoundBaseSnafu { path: base });
149
150 Ok(base)
151 }
152
153 #[cfg_attr(
154 feature = "tracing",
155 tracing::instrument(name = "Chromium build", skip(self), fields(browser), level = "debug")
156 )]
157 pub async fn build(self) -> Result<ChromiumGetter<B>> {
158 let __browser = self.__browser;
159 let base = self.ensure_base()?;
160
161 #[cfg(feature = "tracing")]
162 {
163 tracing::Span::current().record("browser", B::NAME);
164 tracing::debug!(base = %base.display());
165 };
166
167 let crypto = Self::gen_crypto(&base);
168
169 let (crypto, cookies_query, logins) = join!(
170 crypto,
171 Self::cache_cookies(base.clone()),
172 Self::cache_login(base.clone())
173 );
174
175 let (login_data_query, lfa) = logins?;
176
177 Ok(ChromiumGetter {
178 cookies_query: cookies_query?,
179 login_data_query,
180 login_data_for_account_query: lfa,
181 crypto: crypto?,
182 __browser,
183 })
184 }
185
186 #[cfg_attr(
187 feature = "tracing",
188 tracing::instrument(
189 name = "Chromium Login build",
190 skip(self),
191 fields(browser),
192 level = "debug"
193 )
194 )]
195 pub async fn build_login(self) -> Result<ChromiumLoginGetter<B>> {
196 let __browser = self.__browser;
197 let base = self.ensure_base()?;
198
199 #[cfg(feature = "tracing")]
200 {
201 tracing::Span::current().record("browser", B::NAME);
202 tracing::debug!(base = %base.display());
203 };
204
205 let (crypto, logins) = join!(Self::gen_crypto(&base), Self::cache_login(base.clone()));
206
207 let (login_data_query, lfa) = logins?;
208
209 Ok(ChromiumLoginGetter {
210 login_data_query,
211 login_data_for_account_query: lfa,
212 crypto: crypto?,
213 __browser,
214 })
215 }
216
217 #[cfg_attr(
218 feature = "tracing",
219 tracing::instrument(
220 name = "Chromium Cookie build",
221 skip(self),
222 fields(browser),
223 level = "debug"
224 )
225 )]
226 pub async fn build_cookie(self) -> Result<ChromiumCookieGetter<B>> {
227 let __browser = self.__browser;
228 let base = self.ensure_base()?;
229
230 #[cfg(feature = "tracing")]
231 {
232 tracing::Span::current().record("browser", B::NAME);
233 tracing::debug!(base = %base.display());
234 };
235
236 let crypto = Self::gen_crypto(&base);
237
238 let (crypto, cookies_query) = join!(crypto, Self::cache_cookies(base.clone()));
239
240 Ok(ChromiumCookieGetter {
241 cookies_query: cookies_query?,
242 crypto: crypto?,
243 __browser,
244 })
245 }
246
247 #[cfg_attr(
248 not(target_os = "windows"),
249 expect(unused_variables, reason = "for windows")
250 )]
251 async fn gen_crypto(base: &Path) -> Result<Decrypter> {
252 #[cfg(target_os = "linux")]
253 let crypto = Decrypter::build(B::SAFE_STORAGE, crate::browser::need_safe_storage);
254
255 #[cfg(target_os = "macos")]
256 let crypto = Decrypter::build(B::SAFE_STORAGE, B::SAFE_NAME);
257
258 #[cfg(target_os = "windows")]
259 let crypto = {
260 let key_path = Self::cache_key(base.to_owned()).await?;
261 Decrypter::build(key_path)
262 };
263
264 crypto
265 .await
266 .context(DecrypterSnafu)
267 }
268
269 async fn cache_login(base: PathBuf) -> Result<(LoginDataQuery, Option<LoginDataQuery>)> {
271 let login_data = B::login_data(base.clone());
272 let login_data_temp = B::login_data_temp().context(HomeSnafu)?;
273
274 let login_data_for_account = B::login_data_for_account(base.clone());
275 let login_data_for_account_temp = B::login_data_for_account_temp().context(HomeSnafu)?;
276
277 let (lg, lfac) = join!(
278 copy(&login_data, &login_data_temp),
279 copy(&login_data_for_account, &login_data_for_account_temp)
280 );
281 lg?;
282
283 Ok((
284 LoginDataQuery::new(login_data_temp)
285 .await
286 .context(DbSnafu)?,
287 if lfac.is_ok() {
288 LoginDataQuery::new(login_data_for_account_temp)
289 .await
290 .ok()
291 }
292 else {
293 None
294 },
295 ))
296 }
297
298 async fn cache_cookies(base: PathBuf) -> Result<CookiesQuery> {
299 let cookies = B::cookies(base.clone());
300 let cookies_temp = B::cookies_temp().context(HomeSnafu)?;
301
302 copy(&cookies, &cookies_temp).await?;
303 CookiesQuery::new(cookies_temp)
304 .await
305 .context(DbSnafu)
306 }
307
308 #[cfg(target_os = "windows")]
309 async fn cache_key(base: PathBuf) -> Result<PathBuf> {
310 let key = B::key(base.clone());
311 let key_temp = B::key_temp().context(HomeSnafu)?;
312
313 copy(&key, &key_temp).await?;
314
315 Ok(key_temp)
316 }
317}