tidy_browser/
cli.rs

1#[cfg(target_os = "macos")]
2use decrypt_cookies::SafariGetter;
3use decrypt_cookies::{Browser, ChromiumBuilder, ChromiumGetter, FirefoxBuilder, FirefoxGetter};
4use miette::{IntoDiagnostic, Result};
5use strum::IntoEnumIterator;
6use tokio::{
7    fs::{create_dir_all, File, OpenOptions},
8    io::{AsyncWriteExt, BufWriter},
9};
10
11const BASE_DIR: &str = "./results";
12
13async fn open_file(browser: Browser, item: &str) -> Result<BufWriter<File>> {
14    Ok(BufWriter::new(
15        OpenOptions::new()
16            .create(true)
17            .write(true)
18            .open(format!("{BASE_DIR}/{browser}-{item}.csv"))
19            .await
20            .into_diagnostic()?,
21    ))
22}
23
24async fn write_chromium_password(getter: &ChromiumGetter) -> Result<()> {
25    println!("{} password", getter.browser());
26    let getter = ChromiumBuilder::new(getter.browser())
27        .build()
28        .await?;
29    let all_passwords = getter.get_logins_all().await?;
30    let head = b"Url,Username,Password,CreateDate,LastUsedDate,PasswordModifiedDate\n";
31    let mut buf_writer = open_file(getter.browser(), "password").await?;
32    buf_writer
33        .write_all(head)
34        .await
35        .into_diagnostic()?;
36
37    for ck in all_passwords {
38        let pass_str = format!(
39            "{},{},{},{},{},{}\n",
40            ck.origin_url,
41            ck.username_value
42                .unwrap_or_default(),
43            ck.password_value
44                .unwrap_or_default(),
45            ck.date_created.unwrap_or_default(),
46            ck.date_last_used
47                .unwrap_or_default(),
48            ck.date_password_modified
49                .unwrap_or_default()
50        );
51        buf_writer
52            .write_all(pass_str.as_bytes())
53            .await
54            .into_diagnostic()?;
55    }
56    buf_writer
57        .flush()
58        .await
59        .into_diagnostic()?;
60    buf_writer
61        .get_ref()
62        .sync_all()
63        .await
64        .into_diagnostic()?;
65    println!("{} password done", getter.browser());
66
67    Ok(())
68}
69
70async fn write_chromium_cookies(getter: &ChromiumGetter) -> Result<()> {
71    println!("{} cookies", getter.browser());
72    let getter = ChromiumBuilder::new(getter.browser())
73        .build()
74        .await?;
75    let cks = getter.get_cookies_all().await?;
76    let mut buf_writer = open_file(getter.browser(), "cookies").await?;
77    let head  = b"Url,Name,Path,Value,DecryptedValue,IsSecure,IsHttponly,SourcePort,CreationUtc,ExpiresUtc,LastAccessUtc,LastUpdateUtc,HasExpires,IsPersistent\n";
78    buf_writer
79        .write_all(head)
80        .await
81        .into_diagnostic()?;
82    for ck in cks {
83        let ck_str = format!(
84            "{},{},{},{},{},{},{},{},{},{},{},{},{},{}\n",
85            ck.host_key,
86            ck.name,
87            ck.path,
88            ck.value,
89            ck.decrypted_value
90                .unwrap_or_default(),
91            ck.is_secure,
92            ck.is_httponly,
93            ck.source_port,
94            ck.creation_utc.unwrap_or_default(),
95            ck.expires_utc.unwrap_or_default(),
96            ck.last_access_utc
97                .unwrap_or_default(),
98            ck.last_update_utc
99                .unwrap_or_default(),
100            ck.has_expires,
101            ck.is_persistent,
102        );
103        buf_writer
104            .write_all(ck_str.as_bytes())
105            .await
106            .into_diagnostic()?;
107    }
108
109    buf_writer
110        .flush()
111        .await
112        .into_diagnostic()?;
113    buf_writer
114        .get_ref()
115        .sync_all()
116        .await
117        .into_diagnostic()?;
118    println!("{} cookies done", getter.browser());
119
120    Ok(())
121}
122
123async fn write_firefox_cookies(getter: &FirefoxGetter) -> Result<()> {
124    println!("{} cookies", getter.browser());
125    let head  = b"Host,Name,Path,Value,CreationTime,LastAccessed,Expiry,IsSecure,IsHttpOnly,OriginAttributes";
126    let mut buf_writer = open_file(getter.browser(), "cookies").await?;
127    buf_writer
128        .write_all(head)
129        .await
130        .into_diagnostic()?;
131    let cks = getter.get_cookies_all().await?;
132    for ck in cks {
133        let ck_str = format!(
134            "{},{},{},{},{},{},{},{},{},{}\n",
135            ck.host,
136            ck.name,
137            ck.path,
138            ck.value,
139            ck.creation_time
140                .unwrap_or_default(),
141            ck.last_accessed
142                .unwrap_or_default(),
143            ck.expiry.unwrap_or_default(),
144            ck.is_secure,
145            ck.is_http_only,
146            ck.origin_attributes
147        );
148        buf_writer
149            .write_all(ck_str.as_bytes())
150            .await
151            .into_diagnostic()?;
152    }
153
154    buf_writer
155        .flush()
156        .await
157        .into_diagnostic()?;
158    buf_writer
159        .get_ref()
160        .sync_all()
161        .await
162        .into_diagnostic()?;
163    println!("{} cookies done", getter.browser());
164    Ok(())
165}
166
167#[cfg(target_os = "macos")]
168async fn write_safari_cookies(getter: &SafariGetter) -> Result<()> {
169    println!("{} cookies", getter.browser());
170    let head = b"Domain,Name,Path,Value,Creation,Expires,IsSecure,IsHttpOnly,Comment\n";
171    let mut buf_writer = open_file(getter.browser(), "cookies").await?;
172    buf_writer
173        .write_all(head)
174        .await
175        .into_diagnostic()?;
176    let cks = getter.all_cookies();
177    for ck in cks {
178        let ck_str = format!(
179            "{},{},{},{},{},{},{},{},{}\n",
180            ck.domain,
181            ck.name,
182            ck.path,
183            ck.value,
184            ck.creation.unwrap_or_default(),
185            ck.expires.unwrap_or_default(),
186            ck.is_secure,
187            ck.is_httponly,
188            ck.comment,
189        );
190        buf_writer
191            .write_all(ck_str.as_bytes())
192            .await
193            .into_diagnostic()?;
194    }
195
196    buf_writer
197        .flush()
198        .await
199        .into_diagnostic()?;
200    buf_writer
201        .get_ref()
202        .sync_all()
203        .await
204        .into_diagnostic()?;
205    println!("{} cookies done", getter.browser());
206    Ok(())
207}
208
209pub async fn run() -> Result<()> {
210    create_dir_all(BASE_DIR)
211        .await
212        .into_diagnostic()?;
213    let mut jds = vec![];
214
215    for browser in Browser::iter() {
216        match browser {
217            Browser::Firefox | Browser::Librewolf => {
218                let hd = tokio::spawn(async move {
219                    let getter = match FirefoxBuilder::new(browser)
220                        .build()
221                        .await
222                    {
223                        Ok(it) => it,
224                        Err(err) => {
225                            tracing::warn!("Firefox Getter wrong: {err}");
226                            return;
227                        },
228                    };
229                    match write_firefox_cookies(&getter).await {
230                        Ok(()) => {},
231                        Err(err) => tracing::warn!("{err}"),
232                    }
233                });
234                jds.push(hd);
235            },
236            #[cfg(target_os = "macos")]
237            Browser::Safari => {
238                use decrypt_cookies::SafariBuilder;
239                let hd = tokio::spawn(async move {
240                    let getter = match SafariBuilder::new().build().await {
241                        Ok(it) => it,
242                        Err(err) => {
243                            tracing::warn!("{browser} wrong: {err}");
244                            return;
245                        },
246                    };
247                    match write_safari_cookies(&getter).await {
248                        Ok(()) => {},
249                        Err(err) => tracing::warn!("{browser} wrong: {err}"),
250                    };
251                });
252                jds.push(hd);
253            },
254
255            browser => {
256                let hd = tokio::spawn(async move {
257                    let getter = match ChromiumBuilder::new(browser)
258                        .build()
259                        .await
260                    {
261                        Ok(it) => it,
262                        Err(err) => {
263                            tracing::warn!("{browser} wrong: {err}");
264                            return;
265                        },
266                    };
267                    match write_chromium_cookies(&getter).await {
268                        Ok(()) => {},
269                        Err(err) => tracing::warn!("{browser} Cookies wrong: {err}"),
270                    };
271                    match write_chromium_password(&getter).await {
272                        Ok(()) => {},
273                        Err(err) => tracing::warn!("{browser} Cookies wrong: {err}"),
274                    };
275                });
276                jds.push(hd);
277            },
278        }
279    }
280    for ele in jds {
281        match ele.await {
282            Ok(()) => {},
283            Err(err) => tracing::warn!("{err}"),
284        }
285    }
286
287    Ok(())
288}