decrypt_cookies/chromium/
mod.rs1pub mod builder;
2pub(crate) mod items;
3use std::{
4 fmt::Display,
5 marker::{PhantomData, Sync},
6};
7
8use chromium_crypto::{Decrypter, Which};
9use chrono::prelude::Utc;
10use items::cookie::cookie_entities::cookies;
11pub use items::{
12 cookie::{
13 cookie_entities::cookies::{
14 Column as ChromiumCookieCol, ColumnIter as ChromiumCookieColIter,
15 },
16 ChromiumCookie,
17 },
18 passwd::{
19 login_data_entities::logins::{Column as ChromiumLoginCol, Column as ChromiumLoginColIter},
20 LoginData,
21 },
22};
23use rayon::prelude::*;
24use sea_orm::{sea_query::IntoCondition, ColumnTrait, DbErr};
25use snafu::{Location, ResultExt, Snafu};
26use tokio::task::{self, JoinError};
27
28use crate::{
29 browser::{cookies::LeetCodeCookies, ChromiumPath},
30 chromium::items::{
31 cookie::cookie_dao::CookiesQuery,
32 passwd::{login_data_dao::LoginDataQuery, login_data_entities::logins},
33 I64ToChromiumDateTime,
34 },
35};
36
37#[derive(Debug)]
38#[derive(Snafu)]
39#[snafu(visibility(pub))]
40pub enum ChromiumError {
41 #[snafu(display("{source}\n@:{location}"))]
42 Task {
43 source: JoinError,
44 #[snafu(implicit)]
45 location: Location,
46 },
47 #[snafu(display("{source}\n@:{location}"))]
48 Db {
49 source: DbErr,
50 #[snafu(implicit)]
51 location: Location,
52 },
53 #[snafu(display("{source}\n@:{location}"))]
54 Decrypt {
55 source: chromium_crypto::error::CryptoError,
56 #[snafu(implicit)]
57 location: Location,
58 },
59}
60
61type Result<T> = std::result::Result<T, ChromiumError>;
62
63#[derive(Clone)]
77#[derive(Debug)]
78#[derive(Default)]
79pub struct ChromiumGetter<T> {
80 pub(crate) cookies_query: CookiesQuery,
81 pub(crate) login_data_query: LoginDataQuery,
82 pub(crate) login_data_for_account_query: Option<LoginDataQuery>,
83 pub(crate) crypto: Decrypter,
84 pub(crate) __browser: PhantomData<T>,
85}
86
87#[derive(Clone)]
88#[derive(Debug)]
89#[derive(Default)]
90pub struct ChromiumCookieGetter<T> {
91 pub(crate) cookies_query: CookiesQuery,
92 pub(crate) crypto: Decrypter,
93 pub(crate) __browser: PhantomData<T>,
94}
95
96#[derive(Clone)]
97#[derive(Debug)]
98#[derive(Default)]
99pub struct ChromiumLoginGetter<T> {
100 pub(crate) login_data_query: LoginDataQuery,
101 pub(crate) login_data_for_account_query: Option<LoginDataQuery>,
102 pub(crate) crypto: Decrypter,
103 pub(crate) __browser: PhantomData<T>,
104}
105
106macro_rules! impl_display {
107 ($($browser:ident),* $(,)?) => {
108 $(
109 impl<B: ChromiumPath> Display for $browser<B> {
110 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
111 f.write_str(B::NAME)
112 }
113 }
114 )*
115 };
116}
117impl_display![ChromiumGetter, ChromiumCookieGetter, ChromiumLoginGetter,];
118
119impl<B> SealedCrypto for ChromiumGetter<B> {
120 fn crypto(&self) -> &Decrypter {
121 &self.crypto
122 }
123}
124impl<B> SealedCrypto for ChromiumCookieGetter<B> {
125 fn crypto(&self) -> &Decrypter {
126 &self.crypto
127 }
128}
129impl<B> SealedCrypto for ChromiumLoginGetter<B> {
130 fn crypto(&self) -> &Decrypter {
131 &self.crypto
132 }
133}
134
135impl<B> SealedCookies for ChromiumCookieGetter<B> {
136 fn cookies_query(&self) -> &CookiesQuery {
137 &self.cookies_query
138 }
139}
140
141impl<B> SealedCookies for ChromiumGetter<B> {
142 fn cookies_query(&self) -> &CookiesQuery {
143 &self.cookies_query
144 }
145}
146
147impl<B> SealedLogins for ChromiumGetter<B> {
148 fn login_data_query(&self) -> &LoginDataQuery {
149 &self.login_data_query
150 }
151
152 fn login_data_for_account_query(&self) -> Option<&LoginDataQuery> {
153 self.login_data_for_account_query
154 .as_ref()
155 }
156}
157
158impl<B> SealedLogins for ChromiumLoginGetter<B> {
159 fn login_data_query(&self) -> &LoginDataQuery {
160 &self.login_data_query
161 }
162
163 fn login_data_for_account_query(&self) -> Option<&LoginDataQuery> {
164 self.login_data_for_account_query
165 .as_ref()
166 }
167}
168
169impl<B> GetCookies for ChromiumGetter<B> {}
170impl<B> GetCookies for ChromiumCookieGetter<B> {}
171
172impl<B> GetLogins for ChromiumGetter<B> {}
173impl<B> GetLogins for ChromiumLoginGetter<B> {}
174
175trait SealedCrypto {
176 fn crypto(&self) -> &Decrypter;
177 fn par_decrypt_logins(
178 &self,
179 raw: Vec<logins::Model>,
180 ) -> impl std::future::Future<Output = Result<Vec<LoginData>>> + std::marker::Send
181 where
182 Self: Sync,
183 {
184 async {
185 let crypto = self.crypto().clone();
186
187 task::spawn_blocking(move || {
188 raw.into_par_iter()
189 .map(|mut v| {
190 let res = v
191 .password_value
192 .as_mut()
193 .and_then(|v| {
194 crypto
195 .decrypt(v, Which::Login)
196 .ok()
197 });
198
199 let mut login_data = LoginData::from(v);
200 login_data.password_value = res;
201 login_data
202 })
203 .collect()
204 })
205 .await
206 .context(TaskSnafu)
207 }
208 }
209
210 fn par_decrypt_ck(
213 &self,
214 raw: Vec<cookies::Model>,
215 ) -> impl std::future::Future<Output = Result<Vec<ChromiumCookie>>> + Send + Sync
216 where
217 Self: Sync,
218 {
219 async {
220 let crypto = self.crypto().clone();
221
222 let decrypted_ck = task::spawn_blocking(move || {
223 raw.into_par_iter()
224 .map(|mut v| {
225 let res = crypto
226 .decrypt(&mut v.encrypted_value, Which::Cookie)
227 .ok();
228 let mut cookies = ChromiumCookie::from(v);
229 cookies.decrypted_value = res;
230 cookies
231 })
232 .collect()
233 })
234 .await
235 .context(TaskSnafu)?;
236 Ok(decrypted_ck)
237 }
238 }
239}
240
241trait SealedCookies {
242 fn cookies_query(&self) -> &CookiesQuery;
243}
244
245trait SealedLogins {
246 fn login_data_query(&self) -> &LoginDataQuery;
247 fn login_data_for_account_query(&self) -> Option<&LoginDataQuery>;
248}
249
250#[expect(private_bounds, reason = "impl details")]
251pub trait GetLogins: SealedCrypto + SealedLogins {
252 fn logins_filter<F>(
273 &self,
274 filter: F,
275 ) -> impl std::future::Future<Output = Result<Vec<LoginData>>> + Send
276 where
277 F: IntoCondition + Send + Clone,
278 Self: Sync,
279 {
280 async {
281 let mut raw_login = self
282 .login_data_query()
283 .query_login_dt_filter(filter.clone())
284 .await
285 .context(DbSnafu)?;
286 if raw_login.is_empty() {
287 if let Some(query) = &self.login_data_for_account_query() {
288 raw_login = query
289 .query_login_dt_filter(filter)
290 .await
291 .context(DbSnafu)?;
292 }
293 }
294 self.par_decrypt_logins(raw_login)
295 .await
296 }
297 }
298
299 fn logins_by_host<H>(
301 &self,
302 host: H,
303 ) -> impl std::future::Future<Output = Result<Vec<LoginData>>> + Send
304 where
305 Self: Sync,
306 H: AsRef<str> + Send + Sync,
307 {
308 async move {
309 let mut raw_login = self
310 .login_data_query()
311 .query_login_dt_filter(ChromiumLoginCol::OriginUrl.contains(host.as_ref()))
312 .await
313 .context(DbSnafu)?;
314 if raw_login.is_empty() {
315 if let Some(query) = &self.login_data_for_account_query() {
316 raw_login = query
317 .query_login_dt_filter(ChromiumLoginCol::OriginUrl.contains(host.as_ref()))
318 .await
319 .context(DbSnafu)?;
320 }
321 }
322 self.par_decrypt_logins(raw_login)
323 .await
324 }
325 }
326
327 fn logins_all(&self) -> impl std::future::Future<Output = Result<Vec<LoginData>>> + Send
329 where
330 Self: Sync,
331 {
332 async {
333 let mut raw_login = self
334 .login_data_query()
335 .query_all_login_dt()
336 .await
337 .context(DbSnafu)?;
338 if raw_login.is_empty() {
339 if let Some(query) = &self.login_data_for_account_query() {
340 raw_login = query
341 .query_all_login_dt()
342 .await
343 .context(DbSnafu)?;
344 }
345 }
346 self.par_decrypt_logins(raw_login)
347 .await
348 }
349 }
350}
351
352#[expect(private_bounds, reason = "impl details")]
353pub trait GetCookies: SealedCrypto + SealedCookies {
354 fn cookies_filter<F>(
375 &self,
376 filter: F,
377 ) -> impl std::future::Future<Output = Result<Vec<ChromiumCookie>>> + Send
378 where
379 F: IntoCondition + Send,
380 Self: Sync,
381 {
382 async {
383 let raw_ck = self
384 .cookies_query()
385 .cookies_filter(filter)
386 .await
387 .context(DbSnafu)?;
388 self.par_decrypt_ck(raw_ck).await
389 }
390 }
391
392 #[doc(alias = "cookies_by_domain", alias = "cookies_by_url")]
394 fn cookies_by_host<H>(
395 &self,
396 host: H,
397 ) -> impl std::future::Future<Output = Result<Vec<ChromiumCookie>>> + Send
398 where
399 Self: Sync,
400 H: AsRef<str> + Send + Sync,
401 {
402 async move {
403 let raw_ck = self
404 .cookies_query()
405 .cookies_by_host(host.as_ref())
406 .await
407 .context(DbSnafu)?;
408 self.par_decrypt_ck(raw_ck).await
409 }
410 }
411
412 fn cookies_all(&self) -> impl std::future::Future<Output = Result<Vec<ChromiumCookie>>> + Send
414 where
415 Self: Sync,
416 {
417 async {
418 let raw_ck = self
419 .cookies_query()
420 .cookies_all()
421 .await
422 .context(DbSnafu)?;
423 self.par_decrypt_ck(raw_ck).await
424 }
425 }
426
427 fn get_session_csrf<H>(
429 &self,
430 host: H,
431 ) -> impl std::future::Future<Output = Result<LeetCodeCookies>> + Send
432 where
433 Self: Sync,
434 H: AsRef<str> + Send + Sync,
435 {
436 async move {
437 let cookies = self
438 .cookies_query()
439 .cookies_filter(
440 ChromiumCookieCol::HostKey
441 .contains(host.as_ref())
442 .and(
443 ChromiumCookieCol::Name
444 .eq("csrftoken")
445 .or(ChromiumCookieCol::Name.eq("LEETCODE_SESSION")),
446 ),
447 )
448 .await
449 .context(DbSnafu)?;
450
451 let mut csrf_token = LeetCodeCookies::default();
452 let mut hds = Vec::with_capacity(2);
453
454 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
455 enum CsrfSession {
456 Csrf,
457 Session,
458 }
459
460 let cy =
462 unsafe { std::mem::transmute::<&Decrypter, &'static Decrypter>(self.crypto()) };
463
464 for mut cookie in cookies {
465 if cookie.name == "csrftoken" {
466 let expir = cookie
467 .expires_utc
468 .micros_to_chromium_utc();
469 if let Some(expir) = expir {
470 if Utc::now() > expir {
471 csrf_token.expiry = true;
472 break;
473 }
474 }
475
476 let csrf_hd = task::spawn_blocking(move || {
477 cy.decrypt(&mut cookie.encrypted_value, Which::Cookie)
478 .inspect_err(|_e| {
479 #[cfg(feature = "tracing")]
480 tracing::warn!("decrypt csrf failed: {_e}");
481 })
482 .unwrap_or_default()
483 });
484 hds.push((csrf_hd, CsrfSession::Csrf));
485 }
486 else if cookie.name == "LEETCODE_SESSION" {
487 let expir = cookie
488 .expires_utc
489 .micros_to_chromium_utc();
490 if let Some(expir) = expir {
491 if Utc::now() > expir {
492 csrf_token.expiry = true;
493 break;
494 }
495 }
496
497 let session_hd = task::spawn_blocking(move || {
498 cy.decrypt(&mut cookie.encrypted_value, Which::Cookie)
499 .inspect_err(|_e| {
500 #[cfg(feature = "tracing")]
501 tracing::warn!("decrypt session failed: {_e}");
502 })
503 .unwrap_or_default()
504 });
505 hds.push((session_hd, CsrfSession::Session));
506 }
507 }
508
509 for (handle, flag) in hds {
510 let res = handle.await.context(TaskSnafu)?;
511 match flag {
512 CsrfSession::Csrf => csrf_token.csrf = res,
513 CsrfSession::Session => csrf_token.session = res,
514 }
515 }
516 Ok(csrf_token)
517 }
518 }
519}