use deno_core::JsBuffer;
use deno_core::OpState;
use deno_core::op2;
use deno_core::url::Url;
use deno_core::url::form_urlencoded;
use deno_core::url::quirks;
use deno_error::JsErrorBox;
#[op2(fast)]
#[smi]
pub fn op_url_parse_with_base(
state: &mut OpState,
#[string] href: &str,
#[string] base_href: &str,
#[buffer] buf: &mut [u32],
) -> 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);
#[op2]
#[string]
pub fn op_url_get_serialization(state: &mut OpState) -> String {
state.take::<UrlSerialization>().0
}
#[op2(fast)]
#[smi]
pub fn op_url_parse(
state: &mut OpState,
#[string] href: &str,
#[buffer] buf: &mut [u32],
) -> u32 {
if parse_simple_special_url(href, buf) {
return ParseStatus::Ok as u32;
}
parse_url(state, href, None, buf)
}
#[inline]
fn parse_url(
state: &mut OpState,
href: &str,
base_href: Option<&Url>,
buf: &mut [u32],
) -> u32 {
match Url::options().base_url(base_href).parse(href) {
Ok(url) => {
let inner_url = quirks::internal_components(&url);
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,
}
}
fn parse_simple_special_url(href: &str, buf: &mut [u32]) -> bool {
let bytes = href.as_bytes();
let (scheme_end, default_port) = if bytes.starts_with(b"http://") {
(4, 80u32)
} else if bytes.starts_with(b"https://") {
(5, 443u32)
} else {
return false;
};
let host_start = scheme_end + 3;
let mut path_start = host_start;
while path_start < bytes.len() && bytes[path_start] != b'/' {
match bytes[path_start] {
b'a'..=b'z' | b'0'..=b'9' | b'.' | b'-' | b':' => {
path_start += 1;
}
_ => return false,
}
}
if path_start == host_start || path_start == bytes.len() {
return false;
}
let mut host_end = path_start;
let mut port = 65536u32;
for i in host_start..path_start {
if bytes[i] == b':' {
if i == host_start || i + 1 == path_start {
return false;
}
host_end = i;
port = 0;
if i + 2 < path_start && bytes[i + 1] == b'0' {
return false;
}
for &byte in &bytes[i + 1..path_start] {
if !byte.is_ascii_digit() {
return false;
}
let Some(next_port) = port
.checked_mul(10)
.and_then(|port| port.checked_add((byte - b'0') as u32))
else {
return false;
};
if next_port > 65535 {
return false;
}
port = next_port;
}
if port == default_port {
return false;
}
break;
}
}
if !simple_special_host_is_canonical(&bytes[host_start..host_end]) {
return false;
}
let mut query_start = 0u32;
for i in path_start..bytes.len() {
if bytes[i] == b'.'
&& i > path_start
&& bytes[i - 1] == b'/'
&& (i + 1 == bytes.len() || matches!(bytes[i + 1], b'/' | b'?' | b'.'))
{
return false;
}
if query_start != 0 && bytes[i] == b'\'' {
return false;
}
match bytes[i] {
b'a'..=b'z'
| b'A'..=b'Z'
| b'0'..=b'9'
| b'/'
| b'.'
| b'_'
| b'~'
| b'-'
| b'!'
| b'$'
| b'&'
| b'\''
| b'('
| b')'
| b'*'
| b'+'
| b','
| b';'
| b'='
| b':'
| b'@' => {}
b'?' if query_start == 0 => query_start = i as u32,
_ => return false,
}
}
buf[0] = scheme_end as u32;
buf[1] = host_start as u32;
buf[2] = host_start as u32;
buf[3] = host_end as u32;
buf[4] = port;
buf[5] = path_start as u32;
buf[6] = query_start;
buf[7] = 0;
true
}
fn simple_special_host_is_canonical(host: &[u8]) -> bool {
if host.is_empty() || host[0] == b'.' || host[host.len() - 1] == b'.' {
return false;
}
if host
.iter()
.all(|byte| byte.is_ascii_digit() || *byte == b'.')
{
let mut dots = 0;
let mut part = 0u32;
let mut part_len = 0;
for (i, &byte) in host.iter().enumerate() {
if byte == b'.' {
if part_len == 0
|| part > 255
|| (part_len > 1 && host[i - part_len] == b'0')
{
return false;
}
dots += 1;
part = 0;
part_len = 0;
continue;
}
part = part * 10 + (byte - b'0') as u32;
part_len += 1;
if part_len > 3 {
return false;
}
}
return dots == 3
&& part_len != 0
&& part <= 255
&& (part_len == 1 || host[host.len() - part_len] != b'0');
}
if host[0].is_ascii_digit() {
return false;
}
let mut label_len = 0;
let mut label_start = 0;
let mut final_label_all_digits = true;
for (i, &byte) in host.iter().enumerate() {
match byte {
b'a'..=b'z' | b'0'..=b'9' | b'-' => {
if !byte.is_ascii_digit() {
final_label_all_digits = false;
}
label_len += 1;
}
b'.' => {
if label_len == 0 {
return false;
}
label_len = 0;
label_start = i + 1;
final_label_all_digits = true;
}
_ => return false,
}
if i == label_start + 3 && &host[label_start..=i] == b"xn--" {
return false;
}
}
label_len != 0
&& !final_label_all_digits
&& !(label_len >= 2 && &host[label_start..label_start + 2] == b"0x")
}
#[allow(dead_code, reason = "used in JS")]
#[derive(Eq, PartialEq, Debug)]
#[repr(u8)]
pub enum UrlSetter {
Hash = 0,
Host = 1,
Hostname = 2,
Password = 3,
Pathname = 4,
Port = 5,
Protocol = 6,
Search = 7,
Username = 8,
}
const NO_PORT: u32 = 65536;
#[op2(fast)]
#[smi]
pub fn op_url_reparse(
state: &mut OpState,
#[string] href: String,
#[smi] setter: u8,
#[string] setter_value: String,
#[buffer] buf: &mut [u32],
) -> u32 {
let mut url = match Url::options().parse(&href) {
Ok(url) => url,
Err(_) => return ParseStatus::Err as u32,
};
if setter > 8 {
return ParseStatus::Err as u32;
}
let setter = unsafe { std::mem::transmute::<u8, UrlSetter>(setter) };
let value = setter_value.as_ref();
let e = match setter {
UrlSetter::Hash => {
quirks::set_hash(&mut url, value);
Ok(())
}
UrlSetter::Host => quirks::set_host(&mut url, value),
UrlSetter::Hostname => quirks::set_hostname(&mut url, value),
UrlSetter::Password => quirks::set_password(&mut url, value),
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);
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,
}
}
#[op2]
pub fn op_url_parse_search_params(
#[string] args: Option<String>,
#[buffer] zero_copy: Option<JsBuffer>,
) -> Result<Vec<(String, String)>, JsErrorBox> {
let params = match (args, zero_copy) {
(None, Some(zero_copy)) => form_urlencoded::parse(&zero_copy)
.into_iter()
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
.collect(),
(Some(args), None) => form_urlencoded::parse(args.as_bytes())
.into_iter()
.map(|(k, v)| (k.as_ref().to_owned(), v.as_ref().to_owned()))
.collect(),
_ => return Err(JsErrorBox::type_error("invalid parameters")),
};
Ok(params)
}
#[op2]
#[string]
pub fn op_url_stringify_search_params(
#[scoped] args: Vec<(String, String)>,
) -> String {
form_urlencoded::Serializer::new(String::new())
.extend_pairs(args)
.finish()
}