decrypt_cookies/browser/
info.rs

1use std::{fs::create_dir_all, path::PathBuf};
2
3use chrono::{DateTime, Utc};
4use miette::{IntoDiagnostic, Result};
5use tokio::fs::read_to_string;
6
7use crate::Browser;
8
9#[cfg(any(target_os = "linux", target_os = "macos"))]
10const CHROMIUM_SAFE: &str = "Chromium Safe Storage";
11#[cfg(any(target_os = "linux", target_os = "macos"))]
12const CHROME_SAFE: &str = "Chrome Safe Storage";
13#[cfg(any(target_os = "linux", target_os = "macos"))]
14const EDGE_SAFE: &str = "Microsoft Edge Safe Storage";
15#[cfg(any(target_os = "linux", target_os = "macos"))]
16const BRAVE_SAFE: &str = "Brave Safe Storage";
17#[cfg(any(target_os = "linux", target_os = "macos"))]
18const YANDEX_SAFE: &str = "Yandex Safe Storage";
19#[cfg(any(target_os = "linux", target_os = "macos"))]
20const VIVALDI_SAFE: &str = "Vivaldi Safe Storage";
21#[cfg(any(target_os = "linux", target_os = "macos"))]
22const OPERA_SAFE: &str = "Opera Safe Storage";
23#[cfg(target_os = "macos")]
24const OPERAGX_SAFE: &str = "Opera Safe Storage";
25#[cfg(target_os = "macos")]
26const COCCOC_SAFE: &str = "CocCoc Safe Storage";
27#[cfg(target_os = "macos")]
28const ARC_SAFE: &str = "Arc Safe Storage";
29
30pub trait BrowserTime {
31    const MAX_TIME: DateTime<Utc> = chrono::DateTime::<Utc>::MAX_UTC;
32    const MIN_TIME: DateTime<Utc> = chrono::DateTime::<Utc>::MIN_UTC;
33}
34
35impl BrowserTime for i64 {}
36
37/// just impl `browser` method
38pub trait TempPath {
39    fn browser(&self) -> Browser;
40
41    /// for gen temp path
42    fn temp_path_prefix(&self) -> PathBuf {
43        let mut temp_path = dirs::cache_dir().expect("get cache_dir failed");
44        temp_path.push(format!("tidy_browser/{}", self.browser(),));
45        create_dir_all(&temp_path).expect("create temp path failed");
46
47        temp_path
48    }
49}
50
51/// just impl the `base` method
52pub trait ChromiumInfo: TempPath {
53    const BOOKMARKS: &'static str = "Bookmarks"; // json
54    const COOKIES: &'static str = "Cookies"; // sqlite3
55
56    // const PROFILE_PICTURE: &'static str = "Edge Profile Picture.png";
57    const EXTENSION_COOKIES: &'static str = "Extension Cookies";
58    // const FAVICONS: &'static str = "Favicons"; // sqlite3
59    const HISTORY: &'static str = "History"; // sqlite3
60    const LOAD_STATISTICS: &'static str = "load_statistics.db"; // sqlite3
61    const LOGIN_DATA: &'static str = "Login Data"; // sqlite3
62    const MEDIA_DEVICE_SALTS: &'static str = "MediaDeviceSalts"; // sqlite3, https://source.chromium.org/chromium/chromium/src/+/main:components/media_device_salt/README.md
63    const NETWORK_ACTION_PREDICTOR: &'static str = "Network Action Predictor"; // sqlite3
64    const LOCAL_STORAGE: &'static str = "Local Storage/leveldb";
65    const EXTENSIONS: &'static str = "Extensions"; // a directory
66    const SESSION_STORAGE: &'static str = "Session Storage"; // leveldb
67    /// The webdata component manages the "web database", a `SQLite` database stored in the user's profile
68    /// containing various webpage-related metadata such as autofill and web search engine data.
69    const WEB_DATA: &'static str = "Web Data"; // sqlite3, https://source.chromium.org/chromium/chromium/src/+/main:components/webdata/README.md
70    /// This directory contains shared files for the implementation of the <chrome://local-state> `WebUI` page.
71    const LOCAL_STATE: &'static str = "Local State"; // key, json, https://source.chromium.org/chromium/chromium/src/+/main:components/local_state/README.md
72
73    fn base(&self) -> &PathBuf;
74
75    /// json, for windows fetch password
76    fn local_state(&self) -> PathBuf {
77        let mut path = self.base().clone();
78        path.pop();
79        path.push(Self::LOCAL_STATE);
80        path
81    }
82    fn local_state_temp(&self) -> PathBuf {
83        self.temp_path_prefix()
84            .join(Self::LOCAL_STATE)
85    }
86
87    /// sqlite3
88    fn credit(&self) -> PathBuf {
89        self.base().join(Self::WEB_DATA)
90    }
91    fn credit_temp(&self) -> PathBuf {
92        self.temp_path_prefix()
93            .join(Self::WEB_DATA)
94    }
95
96    /// leveldb
97    fn session(&self) -> PathBuf {
98        self.base()
99            .join(Self::SESSION_STORAGE)
100    }
101    fn session_temp(&self) -> PathBuf {
102        self.temp_path_prefix()
103            .join(Self::SESSION_STORAGE)
104    }
105
106    /// a directory
107    fn extensions(&self) -> PathBuf {
108        self.base().join(Self::EXTENSIONS)
109    }
110    fn extensions_temp(&self) -> PathBuf {
111        self.temp_path_prefix()
112            .join(Self::EXTENSIONS)
113    }
114
115    /// sqlite3
116    fn logindata(&self) -> PathBuf {
117        match self.browser() {
118            Browser::Yandex => self.base().join("Ya Passman Data"),
119            _ => self.base().join(Self::LOGIN_DATA),
120        }
121    }
122    fn logindata_temp(&self) -> PathBuf {
123        self.temp_path_prefix()
124            .join(Self::LOGIN_DATA)
125    }
126
127    /// leveldb
128    fn storage(&self) -> PathBuf {
129        self.base()
130            .join(Self::LOCAL_STORAGE)
131    }
132    fn storage_temp(&self) -> PathBuf {
133        self.temp_path_prefix()
134            .join(Self::LOCAL_STORAGE)
135    }
136
137    /// json
138    fn bookmarks(&self) -> PathBuf {
139        self.base().join(Self::BOOKMARKS)
140    }
141    fn bookmarks_temp(&self) -> PathBuf {
142        self.temp_path_prefix()
143            .join(Self::BOOKMARKS)
144    }
145
146    /// sqlite3
147    fn history(&self) -> PathBuf {
148        self.base().join(Self::HISTORY)
149    }
150    fn history_temp(&self) -> PathBuf {
151        self.temp_path_prefix()
152            .join(Self::HISTORY)
153    }
154
155    /// sqlite3
156    fn cookies(&self) -> PathBuf {
157        self.base().join(Self::COOKIES)
158    }
159    fn cookies_temp(&self) -> PathBuf {
160        self.temp_path_prefix()
161            .join(Self::COOKIES)
162    }
163
164    /// for fetch password
165    #[cfg(target_os = "macos")]
166    fn safe_name(&self) -> &str {
167        match self.browser() {
168            Browser::Chromium => "Chromium",
169            Browser::Chrome => "Chrome",
170            Browser::Edge => "Microsoft Edge",
171            Browser::Brave => "Brave",
172            Browser::Yandex => "Yandex",
173            Browser::Vivaldi => "Vivaldi",
174            Browser::Opera => "Opera",
175            #[cfg(not(target_os = "linux"))]
176            Browser::OperaGX => "Opera",
177            #[cfg(not(target_os = "linux"))]
178            Browser::CocCoc => "CocCoc",
179            #[cfg(not(target_os = "linux"))]
180            Browser::Arc => "Arc",
181            _ => panic!("safe storage not support: {}", self.browser()),
182        }
183    }
184
185    /// for fetch password
186    #[cfg(not(target_os = "windows"))]
187    fn safe_storage(&self) -> &str {
188        match self.browser() {
189            Browser::Chromium => CHROMIUM_SAFE,
190            Browser::Chrome => CHROME_SAFE,
191            Browser::Edge => EDGE_SAFE,
192            Browser::Brave => BRAVE_SAFE,
193            Browser::Yandex => YANDEX_SAFE,
194            Browser::Vivaldi => VIVALDI_SAFE,
195            Browser::Opera => OPERA_SAFE,
196            #[cfg(not(target_os = "linux"))]
197            Browser::OperaGX => OPERAGX_SAFE,
198            #[cfg(not(target_os = "linux"))]
199            Browser::CocCoc => COCCOC_SAFE,
200            #[cfg(not(target_os = "linux"))]
201            Browser::Arc => ARC_SAFE,
202            _ => panic!("safe storage not support: {}", self.browser()),
203        }
204    }
205}
206
207/// on Linux cache this
208#[cfg(target_os = "linux")]
209pub(crate) fn need_safe_storage(lab: &str) -> bool {
210    matches!(
211        lab,
212        CHROMIUM_SAFE
213            | CHROME_SAFE
214            | EDGE_SAFE
215            | BRAVE_SAFE
216            | YANDEX_SAFE
217            | VIVALDI_SAFE
218            | OPERA_SAFE
219    )
220}
221
222pub trait FfInfo: TempPath {
223    const COOKIES: &'static str = "cookies.sqlite";
224    const DATAS: &'static str = "places.sqlite"; // Bookmarks, Downloads and Browsing History:
225    const BOOKMARKBACKUPS: &'static str = "bookmarkbackups/bookmarks-date.jsonlz4";
226    const FAVICONS: &'static str = "favicons.sqlite"; // sqlite3, This file contains all of the favicons for your Firefox bookmarks.
227    const KEY: &'static str = "key4.db"; // key sqlite3
228    const PASSWD: &'static str = "logins.json"; // passwd
229    const SEARCH: &'static str = "search.json.mozlz4"; // This file stores user-installed search engines.
230    const STORAGE: &'static str = "webappsstore.sqlite"; // web storage data
231    const EXTENSIONS: &'static str = "extensions.json";
232    const CERT9: &'static str = "cert9.db"; // This file stores all your security certificate settings and any SSL certificates you have imported into Firefox.
233
234    fn base(&self) -> &PathBuf;
235
236    /// json
237    fn extensions(&self) -> PathBuf {
238        self.base().join(Self::EXTENSIONS)
239    }
240    fn extensions_temp(&self) -> PathBuf {
241        self.temp_path_prefix()
242            .join(Self::EXTENSIONS)
243    }
244
245    /// json
246    fn passwd(&self) -> PathBuf {
247        self.base().join(Self::PASSWD)
248    }
249    fn passwd_temp(&self) -> PathBuf {
250        self.temp_path_prefix()
251            .join(Self::PASSWD)
252    }
253
254    /// sqlite3
255    fn storage(&self) -> PathBuf {
256        self.base().join(Self::STORAGE)
257    }
258    fn storage_temp(&self) -> PathBuf {
259        self.temp_path_prefix()
260            .join(Self::STORAGE)
261    }
262
263    /// sqlite3
264    fn key(&self) -> PathBuf {
265        self.base().join(Self::KEY)
266    }
267    fn key_temp(&self) -> PathBuf {
268        self.temp_path_prefix()
269            .join(Self::KEY)
270    }
271
272    /// sqlite3
273    fn datas(&self) -> PathBuf {
274        self.base().join(Self::DATAS)
275    }
276    fn datas_temp(&self) -> PathBuf {
277        self.temp_path_prefix()
278            .join(Self::DATAS)
279    }
280
281    /// sqlite3
282    fn cookies(&self) -> PathBuf {
283        self.base().join(Self::COOKIES)
284    }
285    fn cookies_temp(&self) -> PathBuf {
286        self.temp_path_prefix()
287            .join(Self::COOKIES)
288    }
289
290    fn helper(
291        init_path: PathBuf,
292        base: &str,
293    ) -> impl std::future::Future<Output = Result<PathBuf>> + Send {
294        let mut ini_path = init_path.clone();
295        ini_path.push(format!("{}/profiles.ini", base));
296        async move {
297            if !ini_path.exists() {
298                miette::bail!(
299                    "{} not exists",
300                    ini_path
301                        .to_str()
302                        .unwrap_or_default()
303                );
304            }
305            let str = read_to_string(ini_path)
306                .await
307                .into_diagnostic()?;
308            let ini_file = ini::Ini::load_from_str(&str).into_diagnostic()?;
309            let mut section = String::new();
310            for (sec, prop) in ini_file {
311                let Some(sec) = sec
312                else {
313                    continue;
314                };
315                if sec.starts_with("Install") {
316                    prop.get("Default")
317                        .unwrap_or_default()
318                        .clone_into(&mut section);
319                    break;
320                }
321            }
322
323            tracing::debug!("section: {}", section);
324
325            let mut res = init_path;
326            res.push(format!("{}/{}", base, section));
327            tracing::debug!("path: {:?}", res);
328
329            Ok(res)
330        }
331    }
332}
333
334#[cfg(target_os = "linux")]
335pub mod linux {
336    use std::path::PathBuf;
337
338    use miette::Result;
339
340    use super::{ChromiumInfo, FfInfo, TempPath};
341    use crate::Browser;
342
343    #[allow(clippy::exhaustive_structs)]
344    #[derive(Clone)]
345    #[derive(Debug)]
346    #[derive(Default)]
347    #[derive(PartialEq, Eq)]
348    pub struct LinuxChromiumBase {
349        pub base: PathBuf,
350        pub browser: Browser,
351    }
352
353    impl TempPath for LinuxChromiumBase {
354        fn browser(&self) -> Browser {
355            self.browser
356        }
357    }
358
359    impl ChromiumInfo for LinuxChromiumBase {
360        fn base(&self) -> &PathBuf {
361            &self.base
362        }
363    }
364
365    // TODO: add dev,nightly .etc channel
366    impl LinuxChromiumBase {
367        pub const EDGE_LINUX: &'static str = "microsoft-edge/Default";
368        pub const CHROME_LINUX: &'static str = "google-chrome/Default";
369        pub const OPERA_LINUX: &'static str = "opera/Default";
370        pub const BRAVE_LINUX: &'static str = "BraveSoftware/Brave-Browser/Default";
371        pub const CHROMIUM_LINUX: &'static str = "chromium/Default";
372        pub const YANDEX_LINUX: &'static str = "yandex-browser/Default";
373        pub const VIVALDI_LINUX: &'static str = "vivaldi/Default";
374
375        pub fn new(browser: Browser) -> Self {
376            let base = match browser {
377                Browser::Edge => Self::EDGE_LINUX,
378                Browser::Chromium => Self::CHROMIUM_LINUX,
379                Browser::Chrome => Self::CHROME_LINUX,
380                Browser::Brave => Self::BRAVE_LINUX,
381                Browser::Yandex => Self::YANDEX_LINUX,
382                Browser::Vivaldi => Self::VIVALDI_LINUX,
383                Browser::Opera => Self::OPERA_LINUX,
384                _ => panic!("Linux Chromium base not support: {browser}"),
385            };
386            let mut res = dirs::config_dir().expect("get config dir failed");
387            res.push(base);
388
389            Self { base: res, browser }
390        }
391    }
392
393    #[derive(Clone)]
394    #[derive(Debug)]
395    #[derive(Default)]
396    #[derive(PartialEq, Eq)]
397    pub struct LinuxFFBase {
398        base: PathBuf,
399        browser: Browser,
400    }
401
402    impl TempPath for LinuxFFBase {
403        fn browser(&self) -> Browser {
404            self.browser
405        }
406    }
407
408    impl FfInfo for LinuxFFBase {
409        fn base(&self) -> &PathBuf {
410            &self.base
411        }
412    }
413
414    impl LinuxFFBase {
415        const FF_BASE: &'static str = ".mozilla/firefox";
416        const LIBREWOLF_BASE: &'static str = ".librewolf";
417
418        pub async fn new(browser: Browser) -> Result<Self> {
419            let init = dirs::home_dir().ok_or_else(|| miette::miette!("get home dir failed"))?;
420            let base = match browser {
421                Browser::Librewolf => Self::LIBREWOLF_BASE,
422                Browser::Firefox => Self::FF_BASE,
423                _ => panic!("Linux Firefox base not support: {browser}"),
424            };
425            let base = Self::helper(init, base).await?;
426
427            Ok(Self { base, browser })
428        }
429    }
430}
431
432#[cfg(target_os = "macos")]
433pub mod macos {
434    use std::path::PathBuf;
435
436    use miette::Result;
437
438    use super::{ChromiumInfo, FfInfo, TempPath};
439    use crate::Browser;
440
441    #[allow(clippy::exhaustive_structs)]
442    #[derive(Clone)]
443    #[derive(Debug)]
444    #[derive(Default)]
445    #[derive(PartialEq, Eq)]
446    pub struct MacChromiumBase {
447        pub base: PathBuf,
448        pub browser: Browser,
449    }
450
451    impl TempPath for MacChromiumBase {
452        fn browser(&self) -> Browser {
453            self.browser
454        }
455    }
456
457    impl MacChromiumBase {
458        pub const EDGE_MAC: &'static str = "Microsoft Edge/Default";
459        pub const CHROME_MAC: &'static str = "Google/Chrome/Default";
460        pub const CHROMIUM_MAC: &'static str = "Chromium/Default";
461        pub const BRAVE_MAC: &'static str = "BraveSoftware/Brave-Browser/Default";
462        pub const YANDEX_MAC: &'static str = "Yandex/YandexBrowser/Default";
463        pub const VIVALDI_MAC: &'static str = "Vivaldi/Default";
464        pub const OPERA_MAC: &'static str = "com.operasoftware.Opera/Default";
465        pub const OPERAGX_MAC: &'static str = "com.operasoftware.OperaGX";
466        pub const COCCOC_MAC: &'static str = "Coccoc/Default";
467        pub const ARC_MAC: &'static str = "Arc/User Data/Default";
468
469        pub fn new(browser: Browser) -> Self {
470            let mut cookie_dir = dirs::config_local_dir().expect("get config dir failed");
471            let v = match browser {
472                Browser::Chrome => Self::CHROME_MAC,
473                Browser::Edge => Self::EDGE_MAC,
474                Browser::Chromium => Self::CHROMIUM_MAC,
475                Browser::Brave => Self::BRAVE_MAC,
476                Browser::Yandex => Self::YANDEX_MAC,
477                Browser::Vivaldi => Self::VIVALDI_MAC,
478                Browser::Opera => Self::OPERA_MAC,
479                Browser::OperaGX => Self::OPERAGX_MAC,
480                Browser::CocCoc => Self::COCCOC_MAC,
481                Browser::Arc => Self::ARC_MAC,
482                _ => panic!("MacOs Chromium base not support: {browser}"),
483            };
484            cookie_dir.push(v);
485            Self { base: cookie_dir, browser }
486        }
487    }
488
489    impl ChromiumInfo for MacChromiumBase {
490        fn base(&self) -> &PathBuf {
491            &self.base
492        }
493    }
494
495    #[derive(Clone)]
496    #[derive(Debug)]
497    #[derive(Default)]
498    #[derive(PartialEq, Eq)]
499    pub struct MacFFBase {
500        pub base: PathBuf,
501        browser: Browser,
502    }
503
504    impl TempPath for MacFFBase {
505        fn browser(&self) -> Browser {
506            self.browser
507        }
508    }
509
510    impl FfInfo for MacFFBase {
511        fn base(&self) -> &PathBuf {
512            &self.base
513        }
514    }
515
516    impl MacFFBase {
517        const FIREFOX_BASE: &'static str = "Firefox";
518        const LIBREWOLF_BASE: &'static str = "librewolf";
519
520        pub async fn new(browser: Browser) -> Result<Self> {
521            let init = dirs::config_local_dir()
522                .ok_or_else(|| miette::miette!("get config local dir failed"))?;
523            let base = match browser {
524                Browser::Librewolf => Self::LIBREWOLF_BASE,
525                Browser::Firefox => Self::FIREFOX_BASE,
526                _ => panic!("MacOs Firefox base not support: {browser}"),
527            };
528            let base = Self::helper(init, base).await?;
529
530            Ok(Self { base, browser })
531        }
532    }
533}
534
535#[cfg(target_os = "windows")]
536pub mod win {
537    use std::path::PathBuf;
538
539    use miette::Result;
540
541    use super::{ChromiumInfo, FfInfo, TempPath};
542    use crate::Browser;
543
544    #[derive(Clone)]
545    #[derive(Debug)]
546    #[derive(Default)]
547    #[derive(PartialEq, Eq)]
548    pub struct WinChromiumBase {
549        base: PathBuf,
550        browser: Browser,
551    }
552
553    impl TempPath for WinChromiumBase {
554        fn browser(&self) -> Browser {
555            self.browser
556        }
557    }
558
559    impl WinChromiumBase {
560        /// consume self
561        pub fn into_key(mut self) -> PathBuf {
562            self.base.push(Self::LOCAL_STATE);
563            self.base
564        }
565    }
566
567    impl WinChromiumBase {
568        const EDGE_WIN: &'static str = "Microsoft/Edge/User Data/Default";
569        const CHROME_WIN: &'static str = "Google/Chrome/User Data/Default";
570        const CHROMIUM_WIN: &'static str = "Chromium/User Data/Default";
571        const BRAVE_WIN: &'static str = "BraveSoftware/Brave-Browser/User Data/Default";
572        const VIVALDI_WIN: &'static str = "Vivaldi/User Data/Default";
573        const COCCOC_WIN: &'static str = "CocCoc/Browser/User Data/Default";
574        const YANDEX_WIN: &'static str = "Yandex/YandexBrowser/User Data/Default";
575        const OPERA_WIN: &'static str = "Opera Software/Opera Stable/Default";
576        const OPERAGX_WIN: &'static str = "Opera Software/Opera GX Stable";
577        // const ARC_WIN: &'static str = r#"Yandex/YandexBrowser/User Data/Default"#;
578
579        pub fn new(browser: Browser) -> Self {
580            let mut cookie_dir = if matches!(browser, Browser::Opera | Browser::OperaGX) {
581                dirs::data_dir().expect("get config dir failed")
582            }
583            else {
584                dirs::data_local_dir().expect("get config dir failed")
585            };
586
587            let path_base = match browser {
588                Browser::Edge => Self::EDGE_WIN,
589                Browser::Chromium => Self::CHROMIUM_WIN,
590                Browser::Chrome => Self::CHROME_WIN,
591                Browser::Brave => Self::BRAVE_WIN,
592                Browser::Yandex => Self::YANDEX_WIN,
593                Browser::Vivaldi => Self::VIVALDI_WIN,
594                Browser::Opera => Self::OPERA_WIN,
595                Browser::OperaGX => Self::OPERAGX_WIN,
596                Browser::CocCoc => Self::COCCOC_WIN,
597                // Browser::Arc => Self::ARC_WIN,
598                _ => panic!("Windows Chromium base not support: {browser}."),
599            };
600            cookie_dir.push(path_base);
601
602            Self { base: cookie_dir, browser }
603        }
604    }
605
606    impl ChromiumInfo for WinChromiumBase {
607        const COOKIES: &'static str = "Network/Cookies"; // sqlite3
608
609        fn base(&self) -> &PathBuf {
610            &self.base
611        }
612        fn local_state(&self) -> PathBuf {
613            // shit, quirky
614            if self.browser == Browser::OperaGX {
615                self.base().join(Self::LOCAL_STATE)
616            }
617            else {
618                let mut path = self.base().clone();
619                path.pop();
620                path.push(Self::LOCAL_STATE);
621                path
622            }
623        }
624        fn cookies_temp(&self) -> PathBuf {
625            self.temp_path_prefix()
626                .join("Cookies")
627        }
628    }
629
630    #[derive(Clone)]
631    #[derive(Debug)]
632    #[derive(Default)]
633    #[derive(PartialEq, Eq)]
634    pub struct WinFFBase {
635        base: PathBuf,
636        browser: Browser,
637    }
638
639    impl TempPath for WinFFBase {
640        fn browser(&self) -> Browser {
641            self.browser
642        }
643    }
644
645    impl WinFFBase {
646        const FIREFOX_BASE: &'static str = r"Mozilla\Firefox";
647        const LIBREWOLF_BASE: &'static str = "librewolf";
648
649        pub async fn new(browser: Browser) -> Result<Self> {
650            let base = match browser {
651                Browser::Librewolf => Self::LIBREWOLF_BASE,
652                Browser::Firefox => Self::FIREFOX_BASE,
653                _ => panic!("Windows Firefox base not support: {browser}"),
654            };
655            let init =
656                dirs::data_dir().ok_or_else(|| miette::miette!("get data local dir failed"))?;
657            let base = Self::helper(init, base).await?;
658
659            Ok(Self { base, browser })
660        }
661    }
662
663    impl FfInfo for WinFFBase {
664        fn base(&self) -> &PathBuf {
665            &self.base
666        }
667    }
668}