diff options
Diffstat (limited to 'ext/url/lib.rs')
-rw-r--r-- | ext/url/lib.rs | 236 |
1 files changed, 160 insertions, 76 deletions
diff --git a/ext/url/lib.rs b/ext/url/lib.rs index be229cec5..c6e91e155 100644 --- a/ext/url/lib.rs +++ b/ext/url/lib.rs @@ -3,7 +3,6 @@ mod urlpattern; use deno_core::error::type_error; -use deno_core::error::uri_error; use deno_core::error::AnyError; use deno_core::include_js_files; use deno_core::op; @@ -11,6 +10,7 @@ use deno_core::url::form_urlencoded; use deno_core::url::quirks; use deno_core::url::Url; use deno_core::Extension; +use deno_core::OpState; use deno_core::ZeroCopyBuf; use std::path::PathBuf; @@ -25,8 +25,10 @@ pub fn init() -> Extension { "01_urlpattern.js", )) .ops(vec![ - op_url_parse::decl(), op_url_reparse::decl(), + op_url_parse::decl(), + op_url_get_serialization::decl(), + op_url_parse_with_base::decl(), op_url_parse_search_params::decl(), op_url_stringify_search_params::decl(), op_urlpattern_parse::decl(), @@ -35,41 +37,95 @@ pub fn init() -> Extension { .build() } -// UrlParts is a \n joined string of the following parts: -// #[derive(Serialize)] -// pub struct UrlParts { -// href: String, -// hash: String, -// host: String, -// hostname: String, -// origin: String, -// password: String, -// pathname: String, -// port: String, -// protocol: String, -// search: String, -// username: String, -// } -// TODO: implement cleaner & faster serialization -type UrlParts = String; - -/// Parse `UrlParseArgs::href` with an optional `UrlParseArgs::base_href`, or an -/// optional part to "set" after parsing. Return `UrlParts`. +/// Parse `href` with a `base_href`. Fills the out `buf` with URL components. #[op] -pub fn op_url_parse( +pub fn op_url_parse_with_base( + state: &mut OpState, + href: String, + base_href: String, + buf: &mut [u8], +) -> u32 { + let base_url = match Url::parse(&base_href) { + Ok(url) => url, + Err(_) => return ParseStatus::Err as u32, + }; + parse_url(state, href, Some(&base_url), buf) +} + +#[repr(u32)] +pub enum ParseStatus { + Ok = 0, + OkSerialization = 1, + Err, +} + +struct UrlSerialization(String); + +#[op] +pub fn op_url_get_serialization(state: &mut OpState) -> String { + state.take::<UrlSerialization>().0 +} + +/// Parse `href` without a `base_url`. Fills the out `buf` with URL components. +#[op] +pub fn op_url_parse(state: &mut OpState, href: String, buf: &mut [u8]) -> u32 { + parse_url(state, href, None, buf) +} + +/// `op_url_parse` and `op_url_parse_with_base` share the same implementation. +/// +/// This function is used to parse the URL and fill the `buf` with internal +/// offset values of the URL components. +/// +/// If the serialized URL is the same as the input URL, then `UrlSerialization` is +/// not set and returns `ParseStatus::Ok`. +/// +/// If the serialized URL is different from the input URL, then `UrlSerialization` is +/// set and returns `ParseStatus::OkSerialization`. JS side should check status and +/// use `op_url_get_serialization` to get the serialized URL. +/// +/// If the URL is invalid, then `UrlSerialization` is not set and returns `ParseStatus::Err`. +/// +/// ```js +/// const buf = new Uint32Array(8); +/// const status = op_url_parse("http://example.com", buf.buffer); +/// let serializedUrl = ""; +/// if (status === ParseStatus.Ok) { +/// serializedUrl = "http://example.com"; +/// } else if (status === ParseStatus.OkSerialization) { +/// serializedUrl = op_url_get_serialization(); +/// } +/// ``` +#[inline] +fn parse_url( + state: &mut OpState, href: String, - base_href: Option<String>, -) -> Result<UrlParts, AnyError> { - let base_url = base_href - .as_ref() - .map(|b| Url::parse(b).map_err(|_| type_error("Invalid base URL"))) - .transpose()?; - let url = Url::options() - .base_url(base_url.as_ref()) - .parse(&href) - .map_err(|_| type_error("Invalid URL"))?; - - Ok(url_parts(url)) + base_href: Option<&Url>, + buf: &mut [u8], +) -> u32 { + match Url::options().base_url(base_href).parse(&href) { + Ok(url) => { + let inner_url = quirks::internal_components(&url); + + let buf: &mut [u32] = as_u32_slice(buf); + buf[0] = inner_url.scheme_end; + buf[1] = inner_url.username_end; + buf[2] = inner_url.host_start; + buf[3] = inner_url.host_end; + buf[4] = inner_url.port.unwrap_or(0) as u32; + buf[5] = inner_url.path_start; + buf[6] = inner_url.query_start.unwrap_or(0); + buf[7] = inner_url.fragment_start.unwrap_or(0); + let serialization: String = url.into(); + if serialization != href { + state.put(UrlSerialization(serialization)); + ParseStatus::OkSerialization as u32 + } else { + ParseStatus::Ok as u32 + } + } + Err(_) => ParseStatus::Err as u32, + } } #[derive(PartialEq, Debug)] @@ -86,58 +142,86 @@ pub enum UrlSetter { Username = 8, } +const NO_PORT: u32 = 65536; + +fn as_u32_slice(slice: &mut [u8]) -> &mut [u32] { + assert_eq!(slice.len() % std::mem::size_of::<u32>(), 0); + // SAFETY: size is multiple of 4 + unsafe { + std::slice::from_raw_parts_mut( + slice.as_mut_ptr() as *mut u32, + slice.len() / std::mem::size_of::<u32>(), + ) + } +} + #[op] pub fn op_url_reparse( + state: &mut OpState, href: String, - setter_opts: (u8, String), -) -> Result<UrlParts, AnyError> { - let mut url = Url::options() - .parse(&href) - .map_err(|_| type_error("Invalid URL"))?; + setter: u8, + setter_value: String, + buf: &mut [u8], +) -> u32 { + let mut url = match Url::options().parse(&href) { + Ok(url) => url, + Err(_) => return ParseStatus::Err as u32, + }; - let (setter, setter_value) = setter_opts; if setter > 8 { - return Err(type_error("Invalid URL setter")); + return ParseStatus::Err as u32; } // SAFETY: checked to be less than 9. let setter = unsafe { std::mem::transmute::<u8, UrlSetter>(setter) }; let value = setter_value.as_ref(); - match setter { - UrlSetter::Hash => quirks::set_hash(&mut url, value), - UrlSetter::Host => quirks::set_host(&mut url, value) - .map_err(|_| uri_error("Invalid host"))?, - UrlSetter::Hostname => quirks::set_hostname(&mut url, value) - .map_err(|_| uri_error("Invalid hostname"))?, - UrlSetter::Password => quirks::set_password(&mut url, value) - .map_err(|_| uri_error("Invalid password"))?, - UrlSetter::Pathname => quirks::set_pathname(&mut url, value), - UrlSetter::Port => quirks::set_port(&mut url, value) - .map_err(|_| uri_error("Invalid port"))?, - UrlSetter::Protocol => quirks::set_protocol(&mut url, value) - .map_err(|_| uri_error("Invalid protocol"))?, - UrlSetter::Search => quirks::set_search(&mut url, value), - UrlSetter::Username => quirks::set_username(&mut url, value) - .map_err(|_| uri_error("Invalid username"))?, - } + let e = match setter { + UrlSetter::Hash => { + quirks::set_hash(&mut url, value); + Ok(()) + } + UrlSetter::Host => quirks::set_host(&mut url, value), - Ok(url_parts(url)) -} + UrlSetter::Hostname => quirks::set_hostname(&mut url, value), + + UrlSetter::Password => quirks::set_password(&mut url, value), -fn url_parts(url: Url) -> UrlParts { - [ - quirks::href(&url), - quirks::hash(&url), - quirks::host(&url), - quirks::hostname(&url), - &quirks::origin(&url), - quirks::password(&url), - quirks::pathname(&url), - quirks::port(&url), - quirks::protocol(&url), - quirks::search(&url), - quirks::username(&url), - ] - .join("\n") + UrlSetter::Pathname => { + quirks::set_pathname(&mut url, value); + Ok(()) + } + UrlSetter::Port => quirks::set_port(&mut url, value), + + UrlSetter::Protocol => quirks::set_protocol(&mut url, value), + UrlSetter::Search => { + quirks::set_search(&mut url, value); + Ok(()) + } + UrlSetter::Username => quirks::set_username(&mut url, value), + }; + + match e { + Ok(_) => { + let inner_url = quirks::internal_components(&url); + + let buf: &mut [u32] = as_u32_slice(buf); + buf[0] = inner_url.scheme_end; + buf[1] = inner_url.username_end; + buf[2] = inner_url.host_start; + buf[3] = inner_url.host_end; + buf[4] = inner_url.port.map(|p| p as u32).unwrap_or(NO_PORT); + buf[5] = inner_url.path_start; + buf[6] = inner_url.query_start.unwrap_or(0); + buf[7] = inner_url.fragment_start.unwrap_or(0); + let serialization: String = url.into(); + if serialization != href { + state.put(UrlSerialization(serialization)); + ParseStatus::OkSerialization as u32 + } else { + ParseStatus::Ok as u32 + } + } + Err(_) => ParseStatus::Err as u32, + } } #[op] |