mirror of
https://github.com/storytold/figcraft.git
synced 2026-11-13 13:52:34 +00:00
webbrowser on WSL hands an address to cmd.exe /c start, escaping only & and ^, so a document link holding | (or < > " `) ran what followed it with the viewer's authority; and an address its parser can't read is reopened as a file path. Characters never valid in a URL unencoded are now refused, and an http(s) authority must be made of host characters.
110 lines
3.6 KiB
Rust
110 lines
3.6 KiB
Rust
//! A URL stored in a document reaches the browser or the system opener, so only the schemes a
|
|
//! browser follows on a plain link are allowed ([`crate::doc_urls`]).
|
|
|
|
use crate::doc_urls::openable;
|
|
|
|
#[test]
|
|
fn web_links_are_opened() {
|
|
for url in [
|
|
"http://example.com",
|
|
"https://example.com",
|
|
"https://example.com/a/b?c=d#e",
|
|
"HTTPS://EXAMPLE.COM",
|
|
"HtTp://example.com",
|
|
"mailto:a@b.c",
|
|
"mailto:a@b.c?subject=hi",
|
|
" https://example.com ",
|
|
"https://user:pw@example.com:8443/p",
|
|
] {
|
|
assert!(openable(url), "{url:?} should open");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn script_and_handler_schemes_are_refused() {
|
|
for url in [
|
|
// Runs in the app's own origin on the web build: a saved document would be stored XSS.
|
|
"javascript:alert(1)",
|
|
"JavaScript:alert(1)",
|
|
"jAvAsCrIpT:alert(document.cookie)",
|
|
"vbscript:msgbox(1)",
|
|
"data:text/html,<script>alert(1)</script>",
|
|
"blob:https://example.com/x",
|
|
// Reach the file system; a UNC host also sends Windows credentials.
|
|
"file:///etc/passwd",
|
|
"file://attacker.example/share/x",
|
|
"smb://attacker.example/share/x",
|
|
"webdav://attacker.example/x",
|
|
// Windows application handlers.
|
|
"ms-msdt:/id PCWDiagnostic",
|
|
"ms-officecmd:x",
|
|
"search-ms:query=x",
|
|
"shell:startup",
|
|
// Anything else a platform may have registered.
|
|
"tel:+1234",
|
|
"sms:+1234",
|
|
"ssh://host",
|
|
"ftp://host/x",
|
|
"figcraft://join?host=127.0.0.1",
|
|
"zoommtg://zoom.us/join?x",
|
|
"view-source:https://example.com",
|
|
"jar:https://example.com/x!/y",
|
|
] {
|
|
assert!(!openable(url), "{url:?} must be refused");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn a_scheme_is_required_and_control_characters_are_refused() {
|
|
for url in [
|
|
// No scheme: the web opener would resolve these against the app's own page.
|
|
"",
|
|
" ",
|
|
"example.com",
|
|
"/etc/passwd",
|
|
"//example.com",
|
|
"\\\\host\\share\\x",
|
|
"about",
|
|
// Browsers ignore some control characters inside a scheme, so a smuggled one must not
|
|
// leave `javascript` looking like an unknown scheme that we then refuse by luck.
|
|
"java\tscript:alert(1)",
|
|
"java\nscript:alert(1)",
|
|
"java\rscript:alert(1)",
|
|
"javascript\u{0}:alert(1)",
|
|
"https://example.com/\u{0}",
|
|
// A colon in ordinary text is not a scheme.
|
|
"see page 2: the diagram",
|
|
"1https://example.com",
|
|
// The allowed schemes still need their own part.
|
|
"mailto:",
|
|
"http:",
|
|
"http:example.com",
|
|
"http:///p",
|
|
"https://",
|
|
] {
|
|
assert!(!openable(url), "{url:?} must be refused");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn shell_syntax_and_malformed_authorities_are_refused() {
|
|
for url in [
|
|
// Never valid unencoded, and command syntax to the `cmd.exe /c start` an opener may use.
|
|
"https://example.com/?a=b|calc",
|
|
"https://example.com/<x>",
|
|
"https://example.com/\"x\"",
|
|
"https://example.com/a b",
|
|
"https://example.com/`x`",
|
|
"https://example.com/x^y",
|
|
"https://example.com\\x",
|
|
// An authority an opener can't parse may be reopened as a file path.
|
|
"http://exam ple.com/",
|
|
"http://a{b}/",
|
|
] {
|
|
assert!(!openable(url), "{url:?} must be refused");
|
|
}
|
|
for url in ["https://example.com/?a=b&c=d%20e", "https://[::1]:8080/x", "https://bücher.example/", "https://a.b-c_d.example:443/"] {
|
|
assert!(openable(url), "{url:?} should open");
|
|
}
|
|
}
|