api: honor interception Continue overrides and fix pre-navigation enable
op_fetch_url ignored the url/method/headers/body fields on InterceptResolution::Continue, so the interception channel could pass, mock, or block a request but not rewrite it. Apply them: shadow url/method/body and derive custom_headers from the override after the channel resolves Continue. Also fix enable_interception() called before the first navigation: init_js propagated intercept_tx to the new runtime but not intercept_enabled, so the runtime started with interception disabled and op_fetch_url never intercepted. Propagate intercept_enabled in init_js as well. Add a test that rewrites a fetch's url via Continue and asserts the rewritten response is captured.
This commit is contained in:
1 parent
1f5066fc30
commit
c19a21c142
3 files changed
+88
-3
No files matched your search
@@ -327,6 +327,11 @@ impl Page {
|
||||
if let Some(tx) = &self.intercept_tx {
|
||||
rt.set_intercept_tx(tx.clone());
|
||||
}
|
||||
// Re-apply intercept_enabled: enable_interception()/enable_intercept()
|
||||
// called before the first navigation sets this on the Page while the
|
||||
// runtime does not exist yet, so the new runtime would otherwise start
|
||||
// with interception disabled and op_fetch_url would never intercept.
|
||||
rt.set_intercept_enabled(self.intercept_enabled);
|
||||
|
||||
if let Some(dom) = self.dom.take() {
|
||||
rt.set_dom(dom);
|
||||
|
||||
@@ -654,6 +654,13 @@ async fn op_fetch_url(
|
||||
(jar, in_flight, itx, proxy_url, gs.http_client.clone())
|
||||
};
|
||||
|
||||
// Slots the interception channel can override via Continue so a consumer
|
||||
// can rewrite url/method/headers/body before the request goes out.
|
||||
let mut override_url: Option<String> = None;
|
||||
let mut override_method: Option<String> = None;
|
||||
let mut override_headers: Option<HashMap<String, String>> = None;
|
||||
let mut override_body: Option<String> = None;
|
||||
|
||||
if let Some((tx, request_id)) = intercept_tx {
|
||||
let custom_headers: HashMap<String, String> = serde_json::from_str(&headers_json).unwrap_or_default();
|
||||
let (resolve_tx, resolve_rx) = tokio::sync::oneshot::channel();
|
||||
@@ -686,8 +693,16 @@ async fn op_fetch_url(
|
||||
"error": reason,
|
||||
}).to_string());
|
||||
}
|
||||
Ok(InterceptResolution::Continue { url: _new_url, method: _new_method, headers: _new_headers, body: _new_body }) => {
|
||||
tracing::debug!("Interception: continue request {}", url);
|
||||
Ok(InterceptResolution::Continue { url, method, headers, body }) => {
|
||||
override_url = url;
|
||||
override_method = method;
|
||||
override_headers = headers;
|
||||
override_body = body;
|
||||
tracing::debug!(
|
||||
"Interception: continue (overrides url={} method={} headers={} body={})",
|
||||
override_url.is_some(), override_method.is_some(),
|
||||
override_headers.is_some(), override_body.is_some()
|
||||
);
|
||||
}
|
||||
Err(_) => {
|
||||
}
|
||||
@@ -695,6 +710,11 @@ async fn op_fetch_url(
|
||||
}
|
||||
}
|
||||
|
||||
// Apply interception overrides (shadow the params for the rest of the op).
|
||||
let url = override_url.unwrap_or(url);
|
||||
let method = override_method.unwrap_or(method);
|
||||
let body = override_body.unwrap_or(body);
|
||||
|
||||
let client = cached_request_client(proxy_url.as_deref())
|
||||
.map_err(deno_error::JsErrorBox::generic)?;
|
||||
|
||||
@@ -714,7 +734,7 @@ async fn op_fetch_url(
|
||||
let req_method: reqwest::Method = method.parse().unwrap_or(reqwest::Method::GET);
|
||||
|
||||
let custom_headers: std::collections::HashMap<String, String> =
|
||||
serde_json::from_str(&headers_json).unwrap_or_default();
|
||||
override_headers.unwrap_or_else(|| serde_json::from_str(&headers_json).unwrap_or_default());
|
||||
|
||||
// Passive request observation (non-blocking). Fires for every request that
|
||||
// reaches the network (Fulfill/Fail from the interception channel short-
|
||||
|
||||
@@ -25,6 +25,8 @@ fn spawn_echo_server() -> String {
|
||||
let path = req.split_whitespace().nth(1).unwrap_or("/");
|
||||
let (ct, body) = if path.starts_with("/api") {
|
||||
("application/json", "{\"hello\":\"world\"}".to_string())
|
||||
} else if path.starts_with("/modified") {
|
||||
("text/plain", "REWRITTEN".to_string())
|
||||
} else {
|
||||
("text/html", "<script>fetch('/api');</script>".to_string())
|
||||
};
|
||||
@@ -105,3 +107,61 @@ async fn page_intercepts_and_observes_js_fetch() {
|
||||
body
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn page_rewrites_request_url_via_interception() {
|
||||
std::env::set_var("OBSCURA_ALLOW_PRIVATE_NETWORK", "1");
|
||||
let base = spawn_echo_server();
|
||||
let modified = format!("{}/modified", base);
|
||||
|
||||
let browser = Browser::new().unwrap();
|
||||
let mut page = browser.new_page().await.unwrap();
|
||||
|
||||
let captured = Arc::new(Mutex::new(String::new()));
|
||||
let cap = captured.clone();
|
||||
page.on_response(Arc::new(move |info, resp| {
|
||||
if info.resource_type == ResourceType::Fetch {
|
||||
*cap.lock().unwrap() = String::from_utf8_lossy(&resp.body).into_owned();
|
||||
}
|
||||
}));
|
||||
|
||||
// Intercept and rewrite the /api request to /modified via Continue.
|
||||
let mut rx = page.enable_interception();
|
||||
let modified_for_task = modified.clone();
|
||||
tokio::spawn(async move {
|
||||
while let Some(req) = rx.recv().await {
|
||||
let new_url = if req.url.contains("/api") {
|
||||
Some(modified_for_task.clone())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if req
|
||||
.resolver
|
||||
.send(InterceptResolution::Continue {
|
||||
url: new_url,
|
||||
method: None,
|
||||
headers: None,
|
||||
body: None,
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
page.goto(&base).await.unwrap();
|
||||
for _ in 0..20 {
|
||||
page.settle(500).await;
|
||||
if captured.lock().unwrap().contains("REWRITTEN") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let body = captured.lock().unwrap().clone();
|
||||
assert!(
|
||||
body.contains("REWRITTEN"),
|
||||
"interception Continue url-rewrite did not take effect; captured: {:?}",
|
||||
body
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user