/
githubmirror
/
deno
Обзор
Документация
Войти
/
githubmirror
/
deno
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
ext/web/url.rs
407 строк
10 KB
Nathan Whitaker
perf(ext/http): add Deno-owned HTTP/1.1 serve path (#34446)
05 июн 2026, 22:04
Не верифицирован
05 июн 2026, 22:04
92a7dae
Код
Авторство
О чём код?
// Copyright 2018-2026 the Deno authors. MIT license. 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; /// Parse `href` with a `base_href`. Fills the out `buf` with URL components. #[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 } /// Parse `href` without a `base_url`. Fills the out `buf` with URL components. #[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) } /// `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: &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, } } // Keep in sync with parseSimpleSpecialUrl() in ext/web/00_url.js. 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; } // SAFETY: checked to be less than 9. 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() }