slicc-cdp-client 1.42.2

Shared CDP connection for slicc-cdp CLIs
Documentation
struct RedirectScopeTransport;
impl Transport for RedirectScopeTransport {
    fn send_text(&mut self, _: &str) -> Result<(), String> { Ok(()) }
    fn recv_text(&mut self, _: Instant) -> Result<Option<String>, String> { Ok(None) }
}

fn scope_request(cdp: &mut Cdp, id: &str) {
    cdp.absorb(json!({"method":"Network.requestWillBeSent","sessionId":"S","params":{"requestId":id,"type":"Document","request":{"url":"https://ordinary.invalid/redirect","method":"GET","headers":{}}}}));
}

#[test]
fn blocked_session_redirects_never_publish_carriers_or_opaque_headers() {
    for seen in [false, true] {
        let mut cdp = Cdp::from_transport(Box::new(RedirectScopeTransport));
        if seen { scope_request(&mut cdp, "N"); }
        cdp.blocked.insert("S".into());
        cdp.absorb(json!({"method":"Fetch.requestPaused","sessionId":"S","params":{"requestId":"F","networkId":"N","frameId":"FR","resourceType":"XHR","request":{"url":"https://seven.sliccy.ai/oauth","method":"GET","headers":{}},"responseStatusCode":302,"responseHeaders":[{"name":"Location","value":"https://vault.sliccy.ai/callback"},{"name":"X-Private","value":"BLOCKED_SCOPE_OPAQUE"}]}}));
        cdp.absorb(json!({"method":"Network.responseReceivedExtraInfo","sessionId":"S","params":{"requestId":"N","statusCode":302,"headers":{"location":"https://ordinary.invalid/cb?code=BLOCKED_SCOPE_LOCATION","x-csrf":"BLOCKED_SCOPE_CSRF"},"headersText":"HTTP/1.1 302\r\nlocation: https://seven.sliccy.ai/x\r\n\r\n"}}));
        cdp.absorb(json!({"method":"Network.responseReceived","sessionId":"S","params":{"requestId":"N","response":{"url":"https://seven.sliccy.ai/oauth","status":302,"headers":{"Location":"https://ordinary.invalid/cb?code=BLOCKED_SCOPE_LOCATION","X-Private":"BLOCKED_SCOPE_OPAQUE"}}}}));
        let events = cdp.events();
        assert!(!events.iter().any(|event| matches!(event["method"].as_str(), Some("Network.responseReceivedExtraInfo" | "Network.responseReceived" | "Fetch.requestPaused"))));
        assert_eq!(events.iter().filter(|event| event["method"] == "Network.loadingFailed").count(), usize::from(seen));
        let output = serde_json::to_string(events).unwrap();
        for secret in ["BLOCKED_SCOPE_OPAQUE", "BLOCKED_SCOPE_LOCATION", "BLOCKED_SCOPE_CSRF"] { assert!(!output.contains(secret)); }
    }
}

#[test]
fn protected_page_or_response_origin_redirects_never_publish_headers() {
    for page in [false, true] {
        for method in ["Network.responseReceived", "Network.requestWillBeSent", "Fetch.requestPaused"] {
            let mut cdp = Cdp::from_transport(Box::new(RedirectScopeTransport));
            if page { cdp.absorb(json!({"method":"Page.frameNavigated","sessionId":"S","params":{"frame":{"id":"FR","url":"https://seven.sliccy.ai/private","securityOrigin":"https://seven.sliccy.ai"}}})); }
            let source = if page { "https://ordinary.invalid/redirect" } else { "https://seven.sliccy.ai/private" };
            let params = match method {
                "Network.responseReceived" => json!({"requestId":"N","response":{"url":source,"status":302,"headers":{"Location":"https://vault.sliccy.ai/callback","X-Private-Token":"PROTECTED_SOURCE_RESPONSE"}}}),
                "Network.requestWillBeSent" => json!({"requestId":"N","request":{"url":"https://vault.sliccy.ai/callback","headers":{}},"redirectResponse":{"url":source,"status":302,"headers":{"Location":"https://vault.sliccy.ai/callback","X-Private-Token":"PROTECTED_SOURCE_RESPONSE"}}}),
                _ => json!({"requestId":"F","networkId":"N","frameId":"FR","resourceType":"Document","request":{"url":source,"method":"GET","headers":{}},"responseStatusCode":302,"responseHeaders":[{"name":"Location","value":"https://vault.sliccy.ai/callback"},{"name":"X-Private-Token","value":"PROTECTED_SOURCE_RESPONSE"}]}),
            };
            if method == "Network.requestWillBeSent" && !page { scope_request(&mut cdp, "N"); }
            let before = cdp.events.len();
            cdp.absorb(json!({"method":method,"sessionId":"S","params":params}));
            assert!(cdp.events()[before..].iter().all(|event| event["method"] == "Network.loadingFailed"), "{method} {page}");
            assert!(!serde_json::to_string(cdp.events()).unwrap().contains("PROTECTED_SOURCE_RESPONSE"));
        }
    }
}

#[test]
fn protected_request_page_and_held_blocked_events_cannot_reenter_delivery() {
    let mut cdp = Cdp::from_transport(Box::new(RedirectScopeTransport));
    cdp.absorb(json!({"method":"Network.requestWillBeSent","sessionId":"S","params":{"requestId":"P","documentURL":"https://seven.sliccy.ai/private","request":{"url":"https://ordinary.invalid/hop","headers":{}}}}));
    cdp.absorb(json!({"method":"Network.responseReceivedExtraInfo","sessionId":"S","params":{"requestId":"P","statusCode":302,"headers":{"Location":"https://vault.sliccy.ai/private","X-CSRF":"PRIVATE_PAGE_EXTRA_SECRET"}}}));
    assert!(cdp.events().is_empty());
    let mut cdp = Cdp::from_transport(Box::new(RedirectScopeTransport));
    cdp.absorb(json!({"method":"Network.responseReceivedExtraInfo","sessionId":"S","params":{"requestId":"HELD","statusCode":302,"headers":{"Location":"https://ordinary.invalid/cb?secret=HELD_BLOCKED_SECRET"}}}));
    cdp.blocked.insert("S".into());
    cdp.absorb(json!({"method":"Network.requestWillBeSent","sessionId":"OTHER","params":{"requestId":"HELD","request":{"url":"https://ordinary.invalid/other","headers":{}}}}));
    assert!(!cdp.events().iter().any(|event| event["sessionId"] == "S"));
    assert!(!serde_json::to_string(cdp.events()).unwrap().contains("HELD_BLOCKED_SECRET"));
}

#[test]
fn redacted_redirect_hops_keep_only_four_carriers_and_rebuild_headers_text() {
    let headers = json!({"Location":"https://seven.sliccy.ai/private?code=ALLOWLIST_DESTINATION","Content-Location":"https://seven.sliccy.ai/private","Refresh":"0;url=https://seven.sliccy.ai/private","Link":"<https://seven.sliccy.ai/private>; rel=preconnect","X-CSRF":"ALLOWLIST_OPAQUE_SECRET","ETag":"ALLOWLIST_OPAQUE_SECRET","Content-Type":"ALLOWLIST_OPAQUE_SECRET","Set-Cookie":"ALLOWLIST_OPAQUE_SECRET"});
    for method in ["Network.responseReceivedExtraInfo", "Network.responseReceived", "Fetch.requestPaused"] {
        let mut cdp = Cdp::from_transport(Box::new(RedirectScopeTransport));
        scope_request(&mut cdp, "N");
        let params = match method {
            "Network.responseReceivedExtraInfo" => json!({"requestId":"N","statusCode":302,"headers":headers,"headersText":"HTTP/1.1 302\r\nX-CSRF: ALLOWLIST_OPAQUE_SECRET\r\nLocation: https://seven.sliccy.ai/private?code=ALLOWLIST_DESTINATION\r\n\r\n"}),
            "Network.responseReceived" => json!({"requestId":"N","response":{"url":"https://ordinary.invalid/redirect","status":302,"headers":headers}}),
            _ => json!({"requestId":"F","networkId":"N","resourceType":"Document","request":{"url":"https://ordinary.invalid/redirect","method":"GET","headers":{}},"responseStatusCode":302,"responseHeaders":headers.as_object().unwrap().iter().map(|(name,value)|json!({"name":name,"value":value,"extra":"ALLOWLIST_OPAQUE_SECRET"})).collect::<Vec<_>>()}),
        };
        cdp.absorb(json!({"method":method,"sessionId":"S","params":params}));
        let event = cdp.events().iter().find(|event| event["method"] == method).unwrap();
        let delivered = match method {
            "Network.responseReceivedExtraInfo" => event["params"]["headers"].clone(),
            "Network.responseReceived" => event["params"]["response"]["headers"].clone(),
            _ => Value::Object(event["params"]["responseHeaders"].as_array().unwrap().iter().map(|field|(field["name"].as_str().unwrap().into(),field["value"].clone())).collect()),
        };
        assert_eq!(delivered.as_object().unwrap().len(),4);
        assert_eq!(delivered["Location"],"https://seven.sliccy.ai");
        assert_eq!(delivered["Content-Location"],"https://seven.sliccy.ai");
        assert_eq!(delivered["Refresh"],"0;url=https://seven.sliccy.ai");
        assert_eq!(delivered["Link"],"<https://seven.sliccy.ai>; rel=preconnect");
        if method == "Network.responseReceivedExtraInfo" {
            for (name, value) in delivered.as_object().unwrap() { assert!(event["params"]["headersText"].as_str().unwrap().contains(&format!("{name}: {}\r\n",value.as_str().unwrap()))); }
        }
        let output=serde_json::to_string(cdp.events()).unwrap();
        assert!(!output.contains("ALLOWLIST_OPAQUE_SECRET"));
        assert!(!output.contains("ALLOWLIST_DESTINATION"));
    }
    let mut cdp = Cdp::from_transport(Box::new(RedirectScopeTransport));
    scope_request(&mut cdp,"CONTROL");
    let headers=json!({"Location":"https://ordinary.invalid/plain?positive=kept","X-Control":"ordinary-public-bytes","Content-Type":"text/html"});
    cdp.absorb(json!({"method":"Network.responseReceived","sessionId":"S","params":{"requestId":"CONTROL","response":{"url":"https://ordinary.invalid/redirect","status":302,"headers":headers}}}));
    assert_eq!(cdp.events().last().unwrap()["params"]["response"]["headers"],headers);
}