1pub fn url_origin(url: &str) -> Option<String> {
11 #[cfg(target_arch = "wasm32")]
13 let url = &web_sys::Url::new(url).ok()?.href();
14 let url = url.trim();
15 if url
16 .chars()
17 .any(|c| c == '\\' || c.is_control() || c.is_whitespace())
18 {
19 return None;
20 }
21 let (scheme, rest) = url.split_once("://")?;
22 let scheme = match scheme.to_ascii_lowercase().as_str() {
23 "http" | "ws" => "http",
24 "https" | "wss" => "https",
25 _ => return None,
26 };
27 let authority = &rest[..rest.find(['/', '?', '#']).unwrap_or(rest.len())];
28 let host = authority.rsplit_once('@').map_or(authority, |(_, h)| h);
29 if host.is_empty() {
30 return None;
31 }
32 let host = host.to_ascii_lowercase();
33 let default = if scheme == "http" { ":80" } else { ":443" };
34 let host = host.strip_suffix(default).unwrap_or(&host);
35 Some(format!("{scheme}://{host}"))
36}
37
38pub fn same_origin(a: &str, b: &str) -> bool {
40 url_origin(a).is_some_and(|a| Some(a) == url_origin(b))
41}
42
43#[cfg(test)]
44mod tests {
45 use super::*;
46
47 #[test]
48 fn origins() {
49 let o = |u| url_origin(u);
50 assert_eq!(
51 o("http://Host:8080/a.bob?x").as_deref(),
52 Some("http://host:8080")
53 );
54 assert_eq!(o("https://host:443/").as_deref(), Some("https://host"));
55 assert_eq!(o("wss://host/pvws/pv").as_deref(), Some("https://host"));
56 assert_eq!(o("ws://u:p@host:80").as_deref(), Some("http://host"));
57 assert_eq!(
58 o("http://[::1]:8080/x").as_deref(),
59 Some("http://[::1]:8080")
60 );
61 assert_eq!(o("file:///x"), None);
62 assert_eq!(o("a.bob"), None);
63 assert_eq!(o("http:///x"), None);
64 }
65
66 #[test]
67 fn same_origins() {
68 assert!(same_origin(
69 "http://server:8080/?display=a.bob",
70 "ws://server:8080/pvws/pv"
71 ));
72 assert!(same_origin("https://server/x", "wss://server:443/pvws/pv"));
73 assert!(!same_origin("https://server/x", "ws://server/pvws/pv"));
74 assert!(!same_origin(
75 "http://server:8080/",
76 "ws://evil:8080/pvws/pv"
77 ));
78 assert!(!same_origin("http://server:8080/", "https://evil/x.bob"));
79 assert!(!same_origin("http://server/", "http://server@evil/x.bob"));
81 assert!(!same_origin("x", "x"));
82 let page = "http://server:8080/";
84 for socket in [
85 r"ws://evil\@server:8080/pvws/pv",
86 r"ws://evil\\@server:8080/pvws/pv",
87 "ws://evil\t@server:8080/pvws/pv",
88 "ws://evil\n@server:8080/pvws/pv",
89 "ws://evil @server:8080/pvws/pv",
90 r"ws:\\server:8080/pvws/pv",
91 ] {
92 assert!(!same_origin(page, socket), "{socket}");
93 assert_eq!(url_origin(socket), None, "{socket}");
94 }
95 assert!(same_origin(page, "ws://evil%5C@server:8080/pvws/pv"));
97 }
98}