decrypt_cookies/chromium/
mod.rs

1pub 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/// Chromium based, get cookies, etc. and decrypt
64///
65/// Initialize it with `ChromiumBuilder`
66///
67/// # Example
68/// ```rust, ignore
69/// let getter = ChromiumBuilder::new(Chromium::new())
70///     .build()
71///     .await?;
72/// getter
73///     .get_cookies_session_csrf(host)
74///     .await?
75/// ```
76#[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    /// parallel decrypt cookies
211    /// and not blocking scheduling
212    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    /// contains passwords
253    ///
254    /// # Example:
255    ///
256    /// ```rust
257    /// use decrypt_cookies::prelude::*;
258    ///
259    /// #[tokio::main]
260    /// async fn main() {
261    ///     let edge_getter = ChromiumBuilder::<Chrome>::new()
262    ///         .build()
263    ///         .await
264    ///         .unwrap();
265    ///     let res = edge_getter
266    ///         .logins_filter(ChromiumLoginCol::OriginUrl.contains("google.com"))
267    ///         .await
268    ///         .unwrap_or_default();
269    ///     dbg!(res);
270    /// }
271    /// ```
272    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    /// Filter by host
300    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    /// Return all login data
328    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    /// filter cookies
355    ///
356    /// # Example:
357    ///
358    /// ```rust
359    /// use decrypt_cookies::{chromium::GetCookies, prelude::*};
360    ///
361    /// #[tokio::main]
362    /// async fn main() {
363    ///     let edge_getter = ChromiumBuilder::<Chrome>::new()
364    ///         .build()
365    ///         .await
366    ///         .unwrap();
367    ///     let res = edge_getter
368    ///         .cookies_filter(ChromiumCookieCol::HostKey.contains("google.com"))
369    ///         .await
370    ///         .unwrap_or_default();
371    ///     dbg!(res);
372    /// }
373    /// ```
374    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    /// Filter by host
393    #[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    /// Return all cookies
413    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    /// get `LEETCODE_SESSION` and `csrftoken` for leetcode
428    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            // # Safety: scope task
461            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}