1use eframe::egui::Context;
12
13#[derive(Debug, PartialEq, Eq)]
15pub enum Target<'a> {
16 External(&'a str),
18 Within(&'a str),
20}
21
22pub fn target(href: &str) -> Option<Target<'_>> {
25 if let Some(place) = href.strip_prefix('#') {
26 return (!place.is_empty()).then_some(Target::Within(place));
27 }
28 let scheme = href.split_once(':')?.0;
29 ["http", "https", "mailto"]
30 .iter()
31 .any(|allowed| scheme.eq_ignore_ascii_case(allowed))
32 .then_some(Target::External(href))
33}
34
35pub fn open(ctx: &Context, address: &str) {
37 ctx.open_url(eframe::egui::OpenUrl::new_tab(address));
38}
39
40#[cfg(test)]
41mod tests {
42 use super::*;
43
44 #[test]
45 fn the_web_and_mail_are_followed() {
46 assert_eq!(
47 target("https://example.com/a"),
48 Some(Target::External("https://example.com/a"))
49 );
50 assert_eq!(
51 target("HTTP://example.com"),
52 Some(Target::External("HTTP://example.com"))
53 );
54 assert_eq!(
55 target("mailto:me@example.com"),
56 Some(Target::External("mailto:me@example.com"))
57 );
58 }
59
60 #[test]
61 fn a_place_in_the_document_is_named_without_its_hash() {
62 assert_eq!(
63 target("#Chapter|outline"),
64 Some(Target::Within("Chapter|outline"))
65 );
66 assert_eq!(target("#"), None);
67 }
68
69 #[test]
70 fn nothing_else_is_followed() {
71 for href in [
72 "file:///etc/passwd",
73 "ms-msdt:/id PCWDiagnostic",
74 "javascript:alert(1)",
75 "smb://host/share",
76 "./other.odt",
77 "example.com",
78 "",
79 ] {
80 assert_eq!(target(href), None, "{href}");
81 }
82 }
83}