char/avril

(wip) why'd you have to go and make things so complicaaaaated?

git clone https://git.t4t.associates/char/avril

Charlotte Somport URL extension to typescriptbf382b3

main
5.5 KiB141 linesraw
1use std::convert::Infallible;
2use std::io;
3
4use ::url::{Position, Url, form_urlencoded, quirks};
5use avril_core::{Extension, Op};
6
7fn parts(url: Url) -> Vec<String> {
8    // Blob origins inherit only HTTP(S), not every scheme accepted by rust-url's origin API.
9    let origin = if url.scheme() == "blob" {
10        Url::parse(url.path())
11            .ok()
12            .filter(|inner| matches!(inner.scheme(), "http" | "https"))
13            .map_or_else(|| "null".into(), |inner| quirks::origin(&inner))
14    } else {
15        quirks::origin(&url)
16    };
17    // Order is shared with the component table in url.js.
18    vec![
19        quirks::href(&url).into(),
20        origin,
21        quirks::protocol(&url).into(),
22        quirks::username(&url).into(),
23        quirks::password(&url).into(),
24        quirks::host(&url).into(),
25        quirks::hostname(&url).into(),
26        quirks::port(&url).into(),
27        quirks::pathname(&url).into(),
28        quirks::search(&url).into(),
29        quirks::hash(&url).into(),
30    ]
31}
32
33pub fn init() -> Extension {
34    Extension {
35        name: "ext:url",
36        dependencies: &["ext:web"],
37        ops: vec![
38            Op::new("op_url_parse", |(input, base): (String, Option<String>)| {
39                let parsed = (|| {
40                    let base = base.as_deref().map(Url::parse).transpose().ok()?;
41                    Url::options()
42                        .base_url(base.as_ref())
43                        .parse(&input)
44                        .ok()
45                        .map(parts)
46                })();
47                Ok::<_, Infallible>(parsed)
48            }),
49            Op::new(
50                "op_url_set",
51                |(href, component, value): (String, String, String)| {
52                    let mut url = Url::parse(&href).map_err(io::Error::other)?;
53                    let original = url.clone();
54                    match component.as_str() {
55                        "href" => {
56                            if quirks::set_href(&mut url, &value).is_err() {
57                                return Ok(None);
58                            }
59                        }
60                        "protocol" => {
61                            let _ = quirks::set_protocol(&mut url, &value);
62                        }
63                        "username" => {
64                            let _ = quirks::set_username(&mut url, &value);
65                        }
66                        "password" => {
67                            let _ = quirks::set_password(&mut url, &value);
68                        }
69                        "host" => {
70                            let _ = quirks::set_host(&mut url, &value);
71                        }
72                        "hostname" => {
73                            let _ = quirks::set_hostname(&mut url, &value);
74                        }
75                        "port" => {
76                            let _ = quirks::set_port(&mut url, &value);
77                        }
78                        "pathname" => quirks::set_pathname(&mut url, &value),
79                        "search" => quirks::set_search(&mut url, &value),
80                        "hash" => quirks::set_hash(&mut url, &value),
81                        _ => {
82                            return Err(io::Error::new(
83                                io::ErrorKind::InvalidInput,
84                                "Unknown URL component",
85                            ));
86                        }
87                    }
88                    // rust-url setters can omit the /. prefix that disambiguates a hostless // path.
89                    let href = if url.host_str().is_none()
90                        && !url.cannot_be_a_base()
91                        && url.path().starts_with("//")
92                    {
93                        format!("{}:/.{}", url.scheme(), &url[Position::BeforePath..])
94                    } else {
95                        url.to_string()
96                    };
97                    let updated = parts(url);
98                    let reparsed = Url::parse(&href).ok().map(parts);
99                    // Never retain components that change meaning when the next op reparses href.
100                    Ok(Some(match reparsed {
101                        Some(parsed) if parsed[1..] == updated[1..] => parsed,
102                        _ => parts(original),
103                    }))
104                },
105            ),
106            Op::new("op_url_search_parse", |query: String| {
107                Ok::<_, Infallible>(
108                    form_urlencoded::parse(query.as_bytes())
109                        .into_owned()
110                        .collect::<Vec<_>>(),
111                )
112            }),
113            Op::new("op_url_search_serialize", |pairs: Vec<(String, String)>| {
114                Ok::<_, Infallible>(
115                    form_urlencoded::Serializer::new(String::new())
116                        .extend_pairs(pairs)
117                        .finish(),
118                )
119            }),
120        ],
121        modules: &[
122            (
123                "ext:url/main.js",
124                include_str!(concat!(env!("OUT_DIR"), "/main.js")),
125            ),
126            (
127                "ext:url/url.js",
128                include_str!(concat!(env!("OUT_DIR"), "/url.js")),
129            ),
130            (
131                "ext:url/search_params.js",
132                include_str!(concat!(env!("OUT_DIR"), "/search_params.js")),
133            ),
134            (
135                "ext:url/state.js",
136                include_str!(concat!(env!("OUT_DIR"), "/state.js")),
137            ),
138        ],
139        entry_point: Some("ext:url/main.js"),
140    }
141}