decrypt_cookies/firefox/
builder.rs1use std::{
2 fmt::Display,
3 marker::PhantomData,
4 path::{Path, PathBuf},
5};
6
7use snafu::{Location, OptionExt, ResultExt, Snafu};
8use tokio::fs;
9
10use super::FirefoxCookieGetter;
11use crate::{
12 firefox::{items::cookie::dao::CookiesQuery, FirefoxGetter},
13 prelude::FirefoxPath,
14};
15
16#[derive(Debug)]
18#[derive(Snafu)]
19#[snafu(visibility(pub))]
20pub enum FirefoxBuilderError {
21 #[snafu(display(r#"Not found {}
22The browser is not installed or started with `-P`/`-profile` arg
23@:{location}"#, path.display()))]
24 NotFoundBase {
25 path: PathBuf,
26 #[snafu(implicit)]
27 location: Location,
28 },
29 #[snafu(display("{source}\n@:{location}"))]
30 Ini {
31 source: ini::Error,
32 #[snafu(implicit)]
33 location: Location,
34 },
35 #[snafu(display("{source}\n@:{location}"))]
36 IniParser {
37 source: ini::ParseError,
38 #[snafu(implicit)]
39 location: Location,
40 },
41 #[snafu(display("Profile {profile} missing `Name` properties\n@:{location}"))]
42 ProfilePath {
43 profile: String,
44 #[snafu(implicit)]
45 location: Location,
46 },
47 #[snafu(display("Install {install} missing `Default` properties\n@:{location}"))]
48 InstallPath {
49 install: String,
50 #[snafu(implicit)]
51 location: Location,
52 },
53 #[snafu(display("{source}\n@:{location}"))]
54 Db {
55 source: sea_orm::DbErr,
56 #[snafu(implicit)]
57 location: Location,
58 },
59 #[snafu(display("Io: {source}, path: {}\n@:{location}",path.display()))]
60 Io {
61 source: std::io::Error,
62 path: PathBuf,
63 #[snafu(implicit)]
64 location: Location,
65 },
66 #[snafu(display("Can not found home dir\n@:{location}"))]
67 Home {
68 #[snafu(implicit)]
69 location: Location,
70 },
71}
72
73pub type Result<T> = std::result::Result<T, FirefoxBuilderError>;
74
75async fn copy<A, A0>(from: A, to: A0) -> Result<()>
77where
78 A: AsRef<Path> + Send,
79 A0: AsRef<Path> + Send,
80{
81 let parent = to
82 .as_ref()
83 .parent()
84 .expect("Get parent dir failed");
85 fs::create_dir_all(parent)
86 .await
87 .with_context(|_| IoSnafu { path: parent.to_owned() })?;
88
89 let from = from.as_ref();
90 fs::copy(from, to.as_ref())
91 .await
92 .with_context(|_| IoSnafu { path: from.to_owned() })?;
93
94 Ok(())
95}
96
97#[derive(Clone)]
98#[derive(Debug)]
99#[derive(Default)]
100pub struct FirefoxBuilder<'a, T> {
101 pub(crate) base: Option<PathBuf>,
102 pub(crate) profile: Option<&'a str>,
103 pub(crate) profile_path: Option<PathBuf>,
104 pub(crate) __browser: PhantomData<T>,
105}
106
107impl<B: FirefoxPath> Display for FirefoxBuilder<'_, B> {
108 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109 f.write_fmt(format_args!("{}Builder", B::NAME))
110 }
111}
112
113impl<'b, B: FirefoxPath> FirefoxBuilder<'b, B> {
114 pub const fn new() -> Self {
115 Self {
116 base: None,
117 profile: None,
118 profile_path: None,
119 __browser: core::marker::PhantomData::<B>,
120 }
121 }
122
123 pub fn with_profile_path(profile_path: PathBuf) -> Self {
126 Self {
127 base: None,
128 profile: None,
129 profile_path: profile_path.into(),
130 __browser: core::marker::PhantomData::<B>,
131 }
132 }
133
134 pub fn base(&mut self, base: PathBuf) -> &mut Self {
136 self.base = base.into();
137 self
138 }
139
140 pub fn profile(&mut self, profile: &'b str) -> &mut Self {
142 self.profile = profile.into();
143 self
144 }
145
146 async fn cache_cookies(profile_path: PathBuf) -> Result<CookiesQuery> {
173 let cookies = B::cookies(profile_path.clone());
174 let cookies_temp = B::cookies_temp().context(HomeSnafu)?;
175
176 copy(&cookies, &cookies_temp).await?;
177 CookiesQuery::new(cookies_temp)
178 .await
179 .context(DbSnafu)
180 }
181}
182
183impl<'b, B: FirefoxPath + Send + Sync> FirefoxBuilder<'b, B> {
184 pub async fn get_profile_path(self) -> Result<PathBuf> {
186 let mut base = if let Some(base) = self.base {
187 base
188 }
189 else {
190 let mut home = dirs::home_dir().context(HomeSnafu)?;
191 home.push(B::BASE);
192 home
193 };
194 let ini_path = base.join("profiles.ini");
195
196 let ini_str = fs::read_to_string(&ini_path)
197 .await
198 .context(IoSnafu { path: ini_path })?;
199
200 let ini_file = ini::Ini::load_from_str(&ini_str).context(IniParserSnafu)?;
201 for (section, prop) in ini_file {
202 let Some(section) = section
203 else {
204 continue;
205 };
206 if let Some(profile) = self.profile {
207 if !section.starts_with("Profile") {
208 continue;
209 }
210 let Some(profile_name) = prop.get("Name")
211 else {
212 continue;
213 };
214 if profile_name == profile {
215 let Some(var) = prop.get("Path")
216 else {
217 return Err(ProfilePathSnafu { profile: profile_name.to_owned() }.build());
218 };
219 base.push(var);
220 break;
221 }
222 }
223 else if section.starts_with("Install") {
224 let Some(default) = prop.get("Default")
225 else {
226 return Err(InstallPathSnafu { install: section }.build());
227 };
228 base.push(default);
229 break;
230 }
231 }
232
233 Ok(base)
234 }
235
236 #[cfg_attr(
237 feature = "tracing",
238 tracing::instrument(name = "Firefox build", skip(self), fields(browser), level = "debug")
239 )]
240 pub async fn build(self) -> Result<FirefoxGetter<B>> {
241 let profile_path = if let Some(path) = self.profile_path {
242 path
243 }
244 else {
245 self.get_profile_path().await?
246 };
247
248 #[cfg(feature = "tracing")]
249 {
250 tracing::Span::current().record("browser", B::NAME);
251 tracing::debug!(profile_path = %profile_path.display());
252 };
253
254 let cookies_query = Self::cache_cookies(profile_path).await?;
255
256 Ok(FirefoxGetter {
257 cookies_query,
258 __browser: core::marker::PhantomData::<B>,
259 })
260 }
261
262 #[cfg_attr(
263 feature = "tracing",
264 tracing::instrument(
265 name = "Firefox Cookie build",
266 skip(self),
267 fields(browser),
268 level = "debug"
269 )
270 )]
271 pub async fn build_cookie(self) -> Result<FirefoxCookieGetter<B>> {
272 let profile_path = if let Some(path) = self.profile_path {
273 path
274 }
275 else {
276 self.get_profile_path().await?
277 };
278
279 #[cfg(feature = "tracing")]
280 {
281 tracing::Span::current().record("browser", B::NAME);
282 tracing::debug!(profile_path = %profile_path.display());
283 };
284
285 let cookies_query = Self::cache_cookies(profile_path).await?;
286
287 Ok(FirefoxCookieGetter {
288 cookies_query,
289 __browser: core::marker::PhantomData::<B>,
290 })
291 }
292
293 #[deprecated(note = "use build_cookie")]
294 pub async fn build_cookies(self) -> Result<FirefoxCookieGetter<B>> {
295 self.build_cookie().await
296 }
297}