pub mod crypto;
mod items;
pub mod local_state;
use std::path::PathBuf;
use chrono::prelude::Utc;
use items::cookie::{cookie_dao::CookiesQuery, cookie_entities::cookies};
pub use items::{
cookie::{
cookie_entities::cookies::{Column as ChromCkColumn, ColumnIter as ChromCkColumnIter},
DecryptedCookies,
},
passwd::{
login_data_entities::logins::{Column as ChromLoginColumn, Column as ChromLoginColumnIter},
LoginData,
},
};
use miette::{IntoDiagnostic, Result};
use rayon::prelude::*;
use sea_orm::{prelude::ColumnTrait, sea_query::IntoCondition};
use tokio::{fs, task};
use self::items::passwd::{login_data_dao::LoginDataQuery, login_data_entities::logins};
use crate::{
browser::info::ChromiumInfo, chromium::items::I64ToChromiumDateTime, Browser, LeetCodeCookies,
};
cfg_if::cfg_if!(
if #[cfg(target_os="linux")] {
use crypto::linux::Decrypter;
use crate::browser::info::linux::LinuxChromiumBase;
} else if #[cfg(target_os="macos")] {
use crypto::macos::Decrypter;
use crate::browser::info::macos::MacChromiumBase;
} else if #[cfg(target_os="windows")] {
use crypto::win::Decrypter;
use crate::browser::info::win::WinChromiumBase;
}
);
#[derive(Clone)]
#[derive(Debug)]
#[derive(Default)]
pub struct ChromiumGetter {
browser: Browser,
cookies_query: CookiesQuery,
login_data_query: LoginDataQuery,
crypto: Decrypter,
#[cfg(target_os = "linux")]
pub info: LinuxChromiumBase,
#[cfg(target_os = "windows")]
pub info: WinChromiumBase,
#[cfg(target_os = "macos")]
pub info: MacChromiumBase,
}
#[derive(Clone)]
#[derive(Debug)]
#[derive(Default)]
#[derive(PartialEq, Eq)]
pub struct ChromiumBuilder {
browser: Browser,
cookies_path: Option<PathBuf>,
local_state_path: Option<PathBuf>,
login_data_path: Option<PathBuf>,
}
impl ChromiumBuilder {
pub fn new(browser: Browser) -> Self {
assert!(
browser.is_chromium_base(),
"Chromium based not support: {browser}"
);
Self {
browser,
cookies_path: None,
local_state_path: None,
login_data_path: None,
}
}
pub fn login_data_path<P>(&mut self, path: P) -> &mut Self
where
P: Into<PathBuf>,
{
self.login_data_path = Some(path.into());
self
}
pub fn cookies_path<P>(&mut self, path: P) -> &mut Self
where
P: Into<PathBuf>,
{
self.cookies_path = Some(path.into());
self
}
pub fn local_state_path<P>(&mut self, path: P) -> &mut Self
where
P: Into<PathBuf>,
{
self.local_state_path = Some(path.into());
self
}
pub async fn build(self) -> Result<ChromiumGetter> {
cfg_if::cfg_if!(
if #[cfg(target_os = "linux")] {
let info = LinuxChromiumBase::new(self.browser);
let crypto = Decrypter::build(self.browser, info.safe_storage()).await?;
} else if #[cfg(target_os = "macos")] {
let info = MacChromiumBase::new(self.browser);
let crypto = Decrypter::build(
self.browser,
info.safe_storage(),
info.safe_name(),
)?;
} else if #[cfg(target_os = "windows")] {
let info = WinChromiumBase::new(self.browser);
let temp_key_path = info.local_state_temp();
fs::copy(
self.local_state_path
.unwrap_or_else(|| info.local_state()),
&temp_key_path,
)
.await
.into_diagnostic()?;
let crypto = Decrypter::build(self.browser, temp_key_path).await?;
}
);
let (temp_cookies_path, temp_login_data_path) =
(info.cookies_temp(), info.logindata_temp());
let cp_login = fs::copy(
self.login_data_path
.unwrap_or_else(|| info.logindata()),
&temp_login_data_path,
);
let cp_cookies = fs::copy(
self.cookies_path
.unwrap_or_else(|| info.cookies()),
&temp_cookies_path,
);
_ = tokio::join!(cp_login, cp_cookies);
let (cookies_query, login_data_query) = (
CookiesQuery::new(temp_cookies_path),
LoginDataQuery::new(temp_login_data_path),
);
let (cookies_query, login_data_query) = tokio::join!(cookies_query, login_data_query);
let (cookies_query, login_data_query) = (cookies_query?, login_data_query?);
Ok(ChromiumGetter {
browser: self.browser,
cookies_query,
login_data_query,
crypto,
info,
})
}
}
impl ChromiumGetter {
async fn par_decrypt_logins(&self, raw: Vec<logins::Model>) -> Result<Vec<LoginData>> {
let crypto = self.crypto.clone();
task::spawn_blocking(move || {
raw.into_par_iter()
.map(|mut v| {
let res = v
.password_value
.as_mut()
.map_or_else(String::new, |passwd| {
crypto
.decrypt(passwd)
.unwrap_or_default()
});
let mut cookies = LoginData::from(v);
cookies.set_password_value(res);
cookies
})
.collect()
})
.await
.into_diagnostic()
}
pub async fn get_logins_filter<F>(&self, filter: F) -> Result<Vec<LoginData>>
where
F: IntoCondition,
{
let raw_login = self
.login_data_query
.query_login_dt_filter(filter)
.await?;
self.par_decrypt_logins(raw_login)
.await
}
pub async fn get_logins_by_host<F>(&self, host: F) -> Result<Vec<LoginData>>
where
F: AsRef<str>,
{
let raw_login = self
.login_data_query
.query_login_dt_filter(ChromLoginColumn::OriginUrl.contains(host.as_ref()))
.await?;
self.par_decrypt_logins(raw_login)
.await
}
pub async fn get_logins_all(&self) -> Result<Vec<LoginData>> {
let raw_login = self
.login_data_query
.query_all_login_dt()
.await?;
self.par_decrypt_logins(raw_login)
.await
}
}
impl ChromiumGetter {
pub async fn get_cookies_filter<F>(&self, filter: F) -> Result<Vec<DecryptedCookies>>
where
F: IntoCondition,
{
let raw_ck = self
.cookies_query
.query_cookie_filter(filter)
.await?;
self.par_decrypt_ck(raw_ck).await
}
pub async fn get_cookies_by_host<A: AsRef<str>>(
&self,
host: A,
) -> Result<Vec<DecryptedCookies>> {
let raw_ck = self
.cookies_query
.query_cookie_by_host(host.as_ref())
.await?;
self.par_decrypt_ck(raw_ck).await
}
pub async fn get_cookies_all(&self) -> Result<Vec<DecryptedCookies>> {
let raw_ck = self
.cookies_query
.query_all_cookie()
.await?;
self.par_decrypt_ck(raw_ck).await
}
pub async fn get_cookies_session_csrf<A: AsRef<str>>(
&self,
host: A,
) -> Result<LeetCodeCookies> {
let cookies = self
.cookies_query
.query_cookie_filter(
ChromCkColumn::HostKey
.contains(host.as_ref())
.and(
ChromCkColumn::Name
.eq("csrftoken")
.or(ChromCkColumn::Name.eq("LEETCODE_SESSION")),
),
)
.await?;
let mut csrf_token = LeetCodeCookies::default();
let mut hds = Vec::with_capacity(2);
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum CsrfSession {
Csrf,
Session,
}
for mut cookie in cookies {
if cookie.name == "csrftoken" {
let expir = cookie
.expires_utc
.micros_to_chromium_utc();
if Utc::now() > expir {
csrf_token.expiry = true;
break;
}
let cy = self.crypto.clone();
let csrf_hd =
task::spawn_blocking(move || match cy.decrypt(&mut cookie.encrypted_value) {
Ok(it) => it,
Err(err) => {
tracing::warn!("decrypt csrf failed: {err}");
String::new()
},
});
hds.push((csrf_hd, CsrfSession::Csrf));
}
else if cookie.name == "LEETCODE_SESSION" {
let expir = cookie
.expires_utc
.micros_to_chromium_utc();
if Utc::now() > expir {
csrf_token.expiry = true;
break;
}
let cy = self.crypto.clone();
let session_hd =
task::spawn_blocking(move || match cy.decrypt(&mut cookie.encrypted_value) {
Ok(it) => it,
Err(err) => {
tracing::warn!("decrypt session failed: {err}");
String::new()
},
});
hds.push((session_hd, CsrfSession::Session));
}
}
for (handle, flag) in hds {
let res = handle.await.into_diagnostic()?;
match flag {
CsrfSession::Csrf => csrf_token.csrf = res,
CsrfSession::Session => csrf_token.session = res,
}
}
Ok(csrf_token)
}
pub fn par_decrypt_cookies(&self, raw: Vec<cookies::Model>) -> Vec<DecryptedCookies> {
raw.into_par_iter()
.map(|mut v| {
let res = self
.crypto
.decrypt(&mut v.encrypted_value)
.unwrap_or_default();
let mut cookies = DecryptedCookies::from(v);
cookies.set_encrypted_value(res);
cookies
})
.collect()
}
async fn par_decrypt_ck(&self, raw: Vec<cookies::Model>) -> Result<Vec<DecryptedCookies>> {
let crypto = self.crypto.clone();
task::spawn_blocking(move || {
raw.into_par_iter()
.map(|mut v| {
let res = crypto
.decrypt(&mut v.encrypted_value)
.unwrap_or_default();
let mut cookies = DecryptedCookies::from(v);
cookies.set_encrypted_value(res);
cookies
})
.collect()
})
.await
.into_diagnostic()
}
}
impl ChromiumGetter {
pub fn decrypt(&self, ciphertext: &mut [u8]) -> Result<String> {
self.crypto.decrypt(ciphertext)
}
pub const fn browser(&self) -> Browser {
self.browser
}
pub fn info(&self) -> &impl crate::browser::info::ChromiumInfo {
&self.info
}
}