codetether_browser/browser/offline/
explain_cors.rs1use anyhow::Result;
4use reqwest::blocking::Client;
5use std::collections::BTreeMap;
6
7use super::explain_cors_analyse::header_reasons;
8
9#[derive(Debug, serde::Serialize)]
10pub struct CorsExplanation {
11 pub allowed: bool,
12 pub target: String,
13 pub origin: String,
14 pub method: String,
15 pub preflight_status: u16,
16 pub reasons: Vec<String>,
17 pub response_headers: BTreeMap<String, String>,
18}
19
20pub fn run(url: &str, origin: &str, method: &str) -> Result<String> {
21 let client = Client::builder().build()?;
22 let resp = client
23 .request(reqwest::Method::OPTIONS, url)
24 .header("origin", origin)
25 .header("access-control-request-method", method)
26 .send()?;
27 let preflight_status = resp.status().as_u16();
28 let headers: BTreeMap<String, String> = resp
29 .headers()
30 .iter()
31 .map(|(k, v)| {
32 (
33 k.as_str().to_ascii_lowercase(),
34 String::from_utf8_lossy(v.as_bytes()).to_string(),
35 )
36 })
37 .collect();
38 let mut reasons = Vec::new();
39 if !(200..300).contains(&preflight_status) {
40 reasons.push(format!(
41 "preflight returned non-2xx status {preflight_status}"
42 ));
43 }
44 reasons.extend(header_reasons(&headers, origin, method));
45 Ok(serde_json::to_string_pretty(&CorsExplanation {
46 allowed: reasons.is_empty(),
47 target: url.into(),
48 origin: origin.into(),
49 method: method.into(),
50 preflight_status,
51 reasons,
52 response_headers: headers,
53 })?)
54}