feat: cookie jar persistence + --storage-dir flag + fix HTTP cookie header filtering (#114)
* feat: cookie jar persistence + storage-dir + filtering fix (clean v4)
* fix: resolve build errors, add storage_dir param throughout server chain
* fix: storage_dir end-to-end, plus two test-literal field additions
Five issues found while testing PR 114 end-to-end.
Build fixes (two test literals out of sync with new struct fields):
- crates/obscura-net/src/cookies.rs: CookieInfo literal in
test_domain_matches_subdomain_without_leading_dot was missing the
same_site and expires fields added in #170.
- crates/obscura-cli/src/main.rs: Command::Fetch literal in
matcher_still_uses_fetch_variant was missing the new storage_dir
field. Without these cargo test --workspace would not compile.
run_fetch was branching between BrowserContext::with_storage (which
hardcodes proxy=None, stealth=false, user_agent=None) and
with_options (which has no storage_dir slot), so --storage-dir +
--proxy + --stealth silently dropped the proxy and stealth flags.
Same bug SGavrl flagged on dispatch.rs in the previous round, just on
the other code path. Switched to with_storage_full which carries all
four. Verified the fetch now errors out against an invalid proxy.
run_fetch --eval short-circuited the function before
context.save_cookies() ran, so cookies set during an eval session
were never persisted to {storage_dir}/cookies.json. Moved the save
above the return.
obscura serve loaded cookies on startup but never saved them
anywhere, so a session that set cookies via Network.setCookie or
navigation dropped them on shutdown. Subscribed to Ctrl-C once in
put a single save_cookies() after the loop so both the Ctrl-C path
and the channel-closed path persist cookies. Verified end-to-end.
---------
Co-authored-by: zhangyang-crazy-one <[email protected]>
Co-authored-by: SGavrl <[email protected]>
This commit is contained in:
8 files changed
+343
-39
No files matched your search
@@ -47,7 +47,7 @@ pub async fn start_with_options(
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
start_with_full_options(port, proxy, stealth, None).await
|
||||
start_with_full_options(port, proxy, stealth, None, None).await
|
||||
}
|
||||
|
||||
pub async fn start_with_full_options(
|
||||
@@ -55,8 +55,9 @@ pub async fn start_with_full_options(
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
storage_dir: Option<std::path::PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
start_with_host(port, "127.0.0.1", proxy, stealth, user_agent).await
|
||||
start_with_host(port, "127.0.0.1", proxy, stealth, user_agent, storage_dir).await
|
||||
}
|
||||
|
||||
pub async fn start_with_host(
|
||||
@@ -65,8 +66,9 @@ pub async fn start_with_host(
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
storage_dir: Option<std::path::PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
start_with_host_and_security(port, host, proxy, stealth, user_agent, false).await
|
||||
start_with_host_and_security(port, host, proxy, stealth, user_agent, false, storage_dir).await
|
||||
}
|
||||
|
||||
pub async fn start_with_host_and_security(
|
||||
@@ -76,6 +78,19 @@ pub async fn start_with_host_and_security(
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
allow_file_access: bool,
|
||||
storage_dir: Option<std::path::PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
start_with_host_security_and_storage(port, host, proxy, stealth, user_agent, allow_file_access, storage_dir).await
|
||||
}
|
||||
|
||||
pub async fn start_with_host_security_and_storage(
|
||||
port: u16,
|
||||
host: &str,
|
||||
proxy: Option<String>,
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
allow_file_access: bool,
|
||||
storage_dir: Option<std::path::PathBuf>,
|
||||
) -> anyhow::Result<()> {
|
||||
let ip: std::net::IpAddr = host
|
||||
.parse()
|
||||
@@ -146,7 +161,7 @@ pub async fn start_with_host_and_security(
|
||||
let (msg_tx, msg_rx) = mpsc::unbounded_channel::<ServerMessage>();
|
||||
|
||||
let _processor_handle = tokio::task::spawn_local(cdp_processor(
|
||||
msg_rx, proxy, stealth, user_agent, allow_file_access,
|
||||
msg_rx, proxy, stealth, user_agent, allow_file_access, storage_dir,
|
||||
));
|
||||
|
||||
while let Some(stream) = ws_rx.recv().await {
|
||||
@@ -284,8 +299,14 @@ async fn cdp_processor(
|
||||
stealth: bool,
|
||||
user_agent: Option<String>,
|
||||
allow_file_access: bool,
|
||||
storage_dir: Option<std::path::PathBuf>,
|
||||
) {
|
||||
let mut ctx = CdpContext::new_with_security(proxy, stealth, user_agent, allow_file_access);
|
||||
let mut ctx = if storage_dir.is_some() {
|
||||
// Use storage-aware context creation with security settings
|
||||
CdpContext::new_with_storage(proxy, stealth, user_agent, storage_dir)
|
||||
} else {
|
||||
CdpContext::new_with_security(proxy, stealth, user_agent, allow_file_access)
|
||||
};
|
||||
let (itx, irx) = mpsc::unbounded_channel::<obscura_js::ops::InterceptedRequest>();
|
||||
ctx.intercept_tx = Some(itx);
|
||||
let mut intercept_rx: Option<mpsc::UnboundedReceiver<obscura_js::ops::InterceptedRequest>> = Some(irx);
|
||||
@@ -300,6 +321,12 @@ async fn cdp_processor(
|
||||
let mut deferred: std::collections::VecDeque<ServerMessage> =
|
||||
std::collections::VecDeque::new();
|
||||
|
||||
// Subscribe to Ctrl-C once. The future is single-shot, so we break out of
|
||||
// the outer loop when it fires and never poll it again. Without this the
|
||||
// accept loop just exits and any cookies set during the session are lost
|
||||
// before `BrowserContext::save_cookies()` runs.
|
||||
let mut shutdown = Box::pin(tokio::signal::ctrl_c());
|
||||
|
||||
loop {
|
||||
// Drain any deferred messages from the previous interception window
|
||||
// before pulling new ones off the wire. Each is processed with no
|
||||
@@ -308,9 +335,15 @@ async fn cdp_processor(
|
||||
let msg = if let Some(d) = deferred.pop_front() {
|
||||
d
|
||||
} else {
|
||||
match rx.recv().await {
|
||||
Some(m) => m,
|
||||
None => break,
|
||||
tokio::select! {
|
||||
msg = rx.recv() => match msg {
|
||||
Some(m) => m,
|
||||
None => break,
|
||||
},
|
||||
_ = &mut shutdown => {
|
||||
tracing::info!("Shutdown signal received");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -341,6 +374,11 @@ async fn cdp_processor(
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Single exit point handles both Ctrl-C shutdown and the channel being
|
||||
// closed by the accept thread. Without this any cookies set during the
|
||||
// session are dropped on the floor.
|
||||
ctx.default_context.save_cookies();
|
||||
}
|
||||
|
||||
fn handle_fetch_resolution(
|
||||
|
||||
Reference in new issue
Block a user