#![warn(unused_must_use)]
use core::cell::RefCell;
use bun_collections::bit_set::{ArrayBitSet, num_masks_for};
use bun_core::{self, fmt as bun_fmt};
use bun_core::{String as BunString, Tag as BunStringTag, immutable as strings};
use bun_paths::resolve_path::{self, platform};
use bun_wyhash::hash as wyhash;
pub mod api {
pub use bun_core::StringPointer;
}
use bun_core::io::Write as _;
pub mod route_param {
#[derive(Clone, Copy)]
pub struct Param<'a> {
pub name: &'a [u8],
pub value: &'a [u8],
}
pub type List<'a> = Vec<Param<'a>>;
}
pub use route_param::List as ParamsList;
pub mod whatwg {
use core::ptr::NonNull;
use super::BunString as String;
pub struct URL {
href: Box<[u8]>,
}
impl URL {
fn parsed(&self) -> super::URL<'_> {
super::URL::parse(&self.href)
}
fn component_string(&self, pick: impl FnOnce(super::URL<'_>) -> &[u8]) -> String {
let bytes = pick(self.parsed());
String::from_bytes(bytes)
}
pub fn from_string(str: &String) -> Option<NonNull<URL>> {
let utf8 = str.to_utf8();
let bytes = utf8.slice();
Self::from_utf8(bytes)
}
pub fn from_utf8(input: &[u8]) -> Option<NonNull<URL>> {
if input.is_empty() {
return None;
}
let parsed = super::URL::parse(input);
if parsed.protocol.is_empty() {
return None;
}
let owned = Box::new(URL {
href: input.to_vec().into_boxed_slice(),
});
Some(unsafe { NonNull::new_unchecked(Box::into_raw(owned)) })
}
pub fn hash(&self) -> String {
self.component_string(|u| u.hash)
}
pub fn fragment_identifier(&self) -> String {
self.component_string(|u| {
if u.hash.starts_with(b"#") {
&u.hash[1..]
} else {
u.hash
}
})
}
pub fn protocol(&self) -> String {
let p = self.parsed().protocol;
if p.is_empty() {
return String::empty();
}
let mut buf = Vec::with_capacity(p.len() + 1);
buf.extend_from_slice(p);
buf.push(b':');
string_from_owned_bytes(buf)
}
pub fn href(&self) -> String {
String::from_bytes(&self.href)
}
pub fn username(&self) -> String {
self.component_string(|u| u.username)
}
pub fn password(&self) -> String {
self.component_string(|u| u.password)
}
pub fn search(&self) -> String {
self.component_string(|u| u.search)
}
pub fn host(&self) -> String {
self.component_string(|u| u.hostname)
}
pub fn hostname(&self) -> String {
self.component_string(|u| u.host)
}
pub fn port(&self) -> u32 {
let p = self.parsed().port;
if p.is_empty() {
return u32::MAX;
}
bun_core::fmt::parse_int::<u16>(p, 10)
.map(|v| v as u32)
.unwrap_or(u32::MAX)
}
pub fn pathname(&self) -> String {
self.component_string(|u| u.pathname)
}
pub fn deinit(&mut self) {
unsafe {
drop(Box::from_raw(self as *mut URL));
}
}
}
fn string_from_owned_bytes(bytes: Vec<u8>) -> String {
if bytes.is_empty() {
return String::empty();
}
let leaked: &'static [u8] = Box::leak(bytes.into_boxed_slice());
String::static_(leaked)
}
pub fn href_from_string(str: &String) -> String {
let utf8 = str.to_utf8();
let bytes = utf8.slice();
if bytes.is_empty() {
return String::dead();
}
let url = super::URL::parse(bytes);
if url.protocol.is_empty() {
return String::dead();
}
drop(utf8);
*str
}
pub fn join(base: &String, relative: &String) -> String {
let base_utf8 = base.to_utf8();
let base_bytes = base_utf8.slice();
if base_bytes.is_empty() {
return String::dead();
}
let base_url = super::URL::parse(base_bytes);
if base_url.protocol.is_empty() {
return String::dead();
}
let rel_utf8 = relative.to_utf8();
let rel_bytes = rel_utf8.slice();
if rel_bytes.is_empty() {
return *base;
}
let rel_url = super::URL::parse(rel_bytes);
if !rel_url.protocol.is_empty() {
return *relative;
}
let origin = base_url.origin;
let mut buf: Vec<u8> = Vec::with_capacity(origin.len() + 1 + rel_bytes.len());
buf.extend_from_slice(origin);
if !rel_bytes.starts_with(b"/") {
let dir = base_url.pathname;
let dir_end = dir.iter().rposition(|&c| c == b'/').map_or(0, |i| i + 1);
buf.extend_from_slice(&dir[..dir_end]);
}
buf.extend_from_slice(rel_bytes);
string_from_owned_bytes(buf)
}
pub fn file_url_from_string(str: &String) -> String {
let utf8 = str.to_utf8();
let path = utf8.slice();
if path.is_empty() {
return String::dead();
}
if path.starts_with(b"file:") {
return *str;
}
let mut buf: Vec<u8> = Vec::with_capacity(path.len() + 8);
buf.extend_from_slice(b"file://");
if !path.starts_with(b"/") {
buf.push(b'/');
}
for &c in path {
match c {
b'%' | b'?' | b'#' | b' ' | 0x00..=0x1f | 0x7f..=0xff => {
const HEX: &[u8; 16] = b"0123456789ABCDEF";
buf.push(b'%');
buf.push(HEX[(c >> 4) as usize]);
buf.push(HEX[(c & 0xf) as usize]);
}
_ => buf.push(c),
}
}
string_from_owned_bytes(buf)
}
pub fn path_from_file_url(str: &String) -> String {
let utf8 = str.to_utf8();
let s = utf8.slice();
let rest = if let Some(r) = s.strip_prefix(b"file://") {
r
} else if let Some(r) = s.strip_prefix(b"file:") {
r
} else {
return String::dead();
};
let path = if rest.starts_with(b"/") {
rest
} else if let Some(idx) = rest.iter().position(|&c| c == b'/') {
&rest[idx..]
} else if rest.is_empty() {
b"/"
} else {
return string_from_owned_bytes({
let mut v = Vec::with_capacity(rest.len() + 1);
v.push(b'/');
v.extend_from_slice(rest);
v
});
};
string_from_owned_bytes(path.to_vec())
}
#[inline]
pub fn origin_from_slice(slice: &[u8]) -> Option<&[u8]> {
let url = super::URL::parse(slice);
if url.protocol.is_empty() || url.origin.is_empty() {
return None;
}
Some(url.origin)
}
}
pub use whatwg::{
file_url_from_string, href_from_string, join, origin_from_slice, path_from_file_url,
};
#[derive(Clone)]
pub struct URL<'a> {
pub hash: &'a [u8],
pub host: &'a [u8],
pub hostname: &'a [u8],
pub href: &'a [u8],
pub origin: &'a [u8],
pub password: &'a [u8],
pub pathname: &'a [u8],
pub path: &'a [u8],
pub port: &'a [u8],
pub protocol: &'a [u8],
pub search: &'a [u8],
pub search_params: Option<QueryStringMap>,
pub username: &'a [u8],
pub port_was_automatically_set: bool,
}
impl<'a> Default for URL<'a> {
fn default() -> Self {
Self {
hash: b"",
host: b"",
hostname: b"",
href: b"",
origin: b"",
password: b"",
pathname: b"/",
path: b"/",
port: b"",
protocol: b"",
search: b"",
search_params: None,
username: b"",
port_was_automatically_set: false,
}
}
}
#[derive(Default, Clone)]
pub struct OwnedURL {
href: Box<[u8]>,
}
impl OwnedURL {
#[inline]
pub fn url(&self) -> URL<'_> {
URL::parse(&self.href)
}
#[inline]
pub fn href(&self) -> &[u8] {
&self.href
}
#[inline]
pub fn into_href(self) -> Box<[u8]> {
self.href
}
#[inline]
pub fn from_href(href: Box<[u8]>) -> Self {
Self { href }
}
}
impl<'a> URL<'a> {
#[inline(always)]
#[allow(unsafe_op_in_unsafe_fn)]
pub unsafe fn erase_lifetime<'b>(self) -> URL<'b> {
#[inline(always)]
unsafe fn d<'b>(s: &[u8]) -> &'b [u8] {
unsafe { &*core::ptr::from_ref::<[u8]>(s) }
}
URL {
hash: d(self.hash),
host: d(self.host),
hostname: d(self.hostname),
href: d(self.href),
origin: d(self.origin),
password: d(self.password),
pathname: d(self.pathname),
path: d(self.path),
port: d(self.port),
protocol: d(self.protocol),
search: d(self.search),
search_params: self.search_params,
username: d(self.username),
port_was_automatically_set: self.port_was_automatically_set,
}
}
pub fn is_file(&self) -> bool {
self.protocol == b"file"
}
pub fn host_with_path(&self) -> &'a [u8] {
if !self.host.is_empty() {
if self.path.len() > 1
&& bun_alloc::is_slice_in_buffer(self.path, self.href)
&& bun_alloc::is_slice_in_buffer(self.host, self.href)
{
let end = self.path.as_ptr() as usize + self.path.len();
let start = self.host.as_ptr() as usize;
let len: usize = end
- start
- (if self.path.ends_with(b"/") {
1usize
} else {
0usize
});
let ptr = start as *const u8;
return unsafe { core::slice::from_raw_parts(ptr, len) };
}
return self.host;
}
b""
}
const BLOB_SPECIFIER_LEN: usize = b"blob:".len() + 36;
pub fn is_blob(&self) -> bool {
self.href.len() == Self::BLOB_SPECIFIER_LEN && self.href.starts_with(b"blob:")
}
pub fn from_string(input: &BunString) -> Result<OwnedURL, bun_core::Error> {
let href = whatwg::href_from_string(input);
if href.tag() == BunStringTag::Dead {
return Err(bun_core::err!("InvalidURL"));
}
let owned = href.to_owned_slice().into_boxed_slice();
href.deref();
Ok(OwnedURL { href: owned })
}
pub fn from_utf8(input: &[u8]) -> Result<OwnedURL, bun_core::Error> {
Self::from_string(&BunString::borrow_utf8(input))
}
pub fn is_localhost(&self) -> bool {
self.hostname.is_empty() || self.hostname == b"localhost" || self.hostname == b"0.0.0.0"
}
#[inline]
pub fn is_unix(&self) -> bool {
self.protocol.starts_with(b"unix")
}
pub fn display_protocol(&self) -> &[u8] {
if !self.protocol.is_empty() {
return self.protocol;
}
if let Some(port) = self.get_port() {
if port == 443 {
return b"https";
}
}
b"http"
}
#[inline]
pub fn is_https(&self) -> bool {
self.protocol == b"https"
}
#[inline]
pub fn is_s3(&self) -> bool {
self.protocol == b"s3"
}
#[inline]
pub fn is_http(&self) -> bool {
self.protocol == b"http"
}
pub fn display_hostname(&self) -> &[u8] {
if !self.hostname.is_empty() {
self.hostname
} else {
b"localhost"
}
}
pub fn s3_path(&self) -> &'a [u8] {
if !self.protocol.is_empty() && self.href.len() > self.protocol.len() + 2 {
&self.href[self.protocol.len() + 2..]
} else {
self.href
}
}
pub fn display_host(&self) -> bun_fmt::HostFormatter<'_> {
bun_fmt::HostFormatter {
host: if !self.host.is_empty() {
self.host
} else {
self.display_hostname()
},
port: if !self.port.is_empty() {
self.get_port()
} else {
None
},
is_https: self.is_https(),
}
}
pub fn href_without_auth(&self) -> Box<[u8]> {
let proto = self.display_protocol();
let path = strings::trim(self.pathname, b"/");
let mut buf: Vec<u8> =
Vec::with_capacity(proto.len() + 3 + self.host.len() + 1 + path.len() + 1);
buf.extend_from_slice(proto);
buf.extend_from_slice(b"://");
let _ = buf.print(format_args!("{}", self.display_host()));
buf.push(b'/');
buf.extend_from_slice(path);
buf.push(b'/');
buf.into_boxed_slice()
}
pub fn has_http_like_protocol(&self) -> bool {
self.protocol == b"http" || self.protocol == b"https"
}
pub fn get_port(&self) -> Option<u16> {
bun_core::fmt::parse_int::<u16>(self.port, 10).ok()
}
pub fn get_port_auto(&self) -> u16 {
self.get_port().unwrap_or_else(|| self.get_default_port())
}
pub fn get_default_port(&self) -> u16 {
if self.is_https() { 443u16 } else { 80u16 }
}
pub fn is_ip_address(&self) -> bool {
strings::is_ip_address(self.hostname)
}
pub fn has_valid_port(&self) -> bool {
self.get_port().unwrap_or(0) > 0
}
pub fn is_empty(&self) -> bool {
self.href.is_empty()
}
pub fn is_absolute(&self) -> bool {
!self.hostname.is_empty() && !self.pathname.is_empty()
}
pub fn join_normalize<'b>(
out: &'b mut [u8],
prefix: &[u8],
dirname: &[u8],
basename: &[u8],
extname: &[u8],
) -> &'b [u8] {
let mut buf = [0u8; 2048];
let mut path_parts: [&[u8]; 10] = [b""; 10];
let mut path_end: usize = 0;
path_parts[0] = b"/";
path_end += 1;
if !prefix.is_empty() {
path_parts[path_end] = prefix;
path_end += 1;
}
if !dirname.is_empty() {
path_parts[path_end] = strings::trim(dirname, b"/\\");
path_end += 1;
}
if !basename.is_empty() {
if !dirname.is_empty() {
path_parts[path_end] = b"/";
path_end += 1;
}
path_parts[path_end] = strings::trim(basename, b"/\\");
path_end += 1;
}
if !extname.is_empty() {
path_parts[path_end] = extname;
path_end += 1;
}
let mut buf_i: usize = 0;
for part in &path_parts[0..path_end] {
buf[buf_i..buf_i + part.len()].copy_from_slice(part);
buf_i += part.len();
}
resolve_path::normalize_string_buf::<false, platform::Loose, false>(&buf[0..buf_i], out)
}
pub fn join_write(
&self,
writer: &mut impl bun_core::io::Write,
prefix: &[u8],
dirname: &[u8],
basename: &[u8],
extname: &[u8],
) -> Result<(), bun_core::Error> {
let mut out = [0u8; 2048];
let normalized_path = Self::join_normalize(&mut out, prefix, dirname, basename, extname);
writer.write_all(self.origin)?;
writer.write_all(b"/")?;
writer.write_all(normalized_path)?;
Ok(())
}
pub fn join_alloc(
&self,
prefix: &[u8],
dirname: &[u8],
basename: &[u8],
extname: &[u8],
absolute_path: &[u8],
) -> Result<Box<[u8]>, bun_core::Error> {
let has_uplevels = strings::index_of(dirname, b"../").is_some();
if has_uplevels {
let mut v = Vec::with_capacity(self.origin.len() + 5 + absolute_path.len());
v.extend_from_slice(self.origin);
v.extend_from_slice(b"/abs:");
v.extend_from_slice(absolute_path);
Ok(v.into_boxed_slice())
} else {
let mut out = [0u8; 2048];
let normalized_path =
Self::join_normalize(&mut out, prefix, dirname, basename, extname);
let mut v = Vec::with_capacity(self.origin.len() + 1 + normalized_path.len());
v.extend_from_slice(self.origin);
v.extend_from_slice(b"/");
v.extend_from_slice(normalized_path);
Ok(v.into_boxed_slice())
}
}
pub fn parse(base: &'a [u8]) -> URL<'a> {
if base.is_empty() {
return URL::default();
}
let mut url = URL {
href: base,
..Default::default()
};
let mut offset: u32 = 0;
match base[0] {
b'@' => {
offset += url.parse_password(&base[offset as usize..]).unwrap_or(0);
offset += url.parse_host(&base[offset as usize..]).unwrap_or(0);
}
b'[' => {
offset += url.parse_host(base).unwrap_or(0);
}
b'/' | b'a'..=b'z' | b'A'..=b'Z' | b'0'..=b'9' | b'-' | b'_' | b':' => {
let is_protocol_relative = base.len() > 1 && base[1] == b'/';
if is_protocol_relative {
offset += 1;
} else {
offset += url.parse_protocol(&base[offset as usize..]).unwrap_or(0);
}
let is_relative_path = !is_protocol_relative && base[0] == b'/';
if !is_relative_path {
if offset > 0 {
let first_at =
strings::index_of_char(&base[offset as usize..], b'@').unwrap_or(0);
let first_colon =
strings::index_of_char(&base[offset as usize..], b':').unwrap_or(0);
if first_at > first_colon
&& first_at
< strings::index_of_char(&base[offset as usize..], b'/')
.unwrap_or(u32::MAX)
{
offset += url.parse_username(&base[offset as usize..]).unwrap_or(0);
offset += url.parse_password(&base[offset as usize..]).unwrap_or(0);
}
}
offset += url.parse_host(&base[offset as usize..]).unwrap_or(0);
}
}
_ => {}
}
url.origin = &base[0..offset as usize];
let mut hash_offset: u32 = u32::MAX;
if offset as usize > base.len() {
return url;
}
let path_offset = offset;
let mut can_update_path = true;
if base.len() > offset as usize + 1
&& base[offset as usize] == b'/'
&& !base[offset as usize..].is_empty()
{
url.path = &base[offset as usize..];
url.pathname = url.path;
}
if let Some(q) = strings::index_of_char(&base[offset as usize..], b'?') {
offset += q;
url.path = &base[path_offset as usize..][0..q as usize];
can_update_path = false;
url.search = &base[offset as usize..];
}
if let Some(hash) = strings::index_of_char(&base[offset as usize..], b'#') {
offset += hash;
hash_offset = offset;
if can_update_path {
url.path = &base[path_offset as usize..][0..hash as usize];
}
url.hash = &base[offset as usize..];
if !url.search.is_empty() {
url.search = &url.search[0..url.search.len() - url.hash.len()];
}
}
if base.len() > path_offset as usize && base[path_offset as usize] == b'/' && offset > 0 {
if !url.search.is_empty() {
url.pathname = &base[path_offset as usize
..((offset as usize + url.search.len()).min(base.len()))
.min(hash_offset as usize)];
} else if hash_offset < u32::MAX {
url.pathname = &base[path_offset as usize..hash_offset as usize];
}
url.origin = &base[0..path_offset as usize];
}
if url.path.len() > 1 {
let trimmed = strings::trim(url.path, b"/");
if trimmed.len() > 1 {
let ptr_diff = (trimmed.as_ptr() as usize) - (url.path.as_ptr() as usize);
let start = (ptr_diff.max(1) - 1).min(hash_offset as usize);
url.path = &url.path[start..];
} else {
url.path = b"/";
}
} else {
url.path = b"/";
}
if url.pathname.is_empty() {
url.pathname = b"/";
}
const SLASH_SLASH: u16 = u16::from_le_bytes(*b"//");
while url.pathname.len() > 1
&& u16::from_le_bytes([url.pathname[0], url.pathname[1]]) == SLASH_SLASH
{
url.pathname = &url.pathname[1..];
}
url.origin = strings::trim(url.origin, b"/ ?#");
url
}
pub fn parse_protocol(&mut self, str: &'a [u8]) -> Option<u32> {
if str.len() < b"://".len() {
return None;
}
for i in 0..str.len() {
match str[i] {
b'/' | b'?' | b'%' => {
return None;
}
b':' => {
if i + 3 <= str.len() && str[i + 1] == b'/' && str[i + 2] == b'/' {
self.protocol = &str[0..i];
return Some(u32::try_from(i + 3).expect("int cast"));
}
}
_ => {}
}
}
None
}
pub fn parse_username(&mut self, str: &'a [u8]) -> Option<u32> {
self.username = b"";
if str.len() < b"@".len() {
return None;
}
for i in 0..str.len() {
match str[i] {
b':' | b'@' => {
self.username = &str[0..i];
return Some(u32::try_from(i + 1).expect("int cast"));
}
b'?' | b'/' => {
return None;
}
_ => {}
}
}
None
}
pub fn parse_password(&mut self, str: &'a [u8]) -> Option<u32> {
self.password = b"";
if str.len() < b"@".len() {
return None;
}
for i in 0..str.len() {
match str[i] {
b'@' => {
self.password = &str[0..i];
if cfg!(debug_assertions) {
debug_assert!(
str[i..].len() < 2
|| u16::from_le_bytes([str[i], str[i + 1]])
!= u16::from_le_bytes(*b"//")
);
}
return Some(u32::try_from(i + 1).expect("int cast"));
}
b'?' | b'/' => {
return None;
}
_ => {}
}
}
None
}
pub fn parse_host(&mut self, str: &'a [u8]) -> Option<u32> {
let mut i: u32 = 0;
self.host = b"";
self.hostname = b"";
self.port = b"";
if !str.is_empty() && str[0] == b'[' {
i = 1;
let mut ipv6_i: Option<u32> = None;
let mut colon_i: Option<u32> = None;
while (i as usize) < str.len() {
ipv6_i = if ipv6_i.is_none() && str[i as usize] == b']' {
Some(i)
} else {
ipv6_i
};
colon_i = if ipv6_i.is_some() && colon_i.is_none() && str[i as usize] == b':' {
Some(i)
} else {
colon_i
};
match str[i as usize] {
b'?' | b'/' => {
break;
}
_ => {}
}
i += 1;
}
self.host = &str[0..i as usize];
if let Some(ipv6) = ipv6_i {
self.hostname = &str[0..ipv6 as usize + 1];
}
if let Some(colon) = colon_i {
self.port = &str[colon as usize + 1..i as usize];
}
} else {
let mut colon_i: Option<u32> = None;
while (i as usize) < str.len() {
colon_i = if colon_i.is_none() && str[i as usize] == b':' {
Some(i)
} else {
colon_i
};
match str[i as usize] {
b'?' | b'/' => {
break;
}
_ => {}
}
i += 1;
}
self.host = &str[0..i as usize];
if let Some(colon) = colon_i {
self.hostname = &str[0..colon as usize];
self.port = &str[colon as usize + 1..i as usize];
} else {
self.hostname = &str[0..i as usize];
}
}
Some(i)
}
}
#[derive(Clone, Copy)]
pub struct Param {
pub name: api::StringPointer,
pub name_hash: u64,
pub value: api::StringPointer,
}
pub(crate) type ParamList = Vec<Param>;
pub struct QueryStringMap {
slice: *const [u8],
pub buffer: Vec<u8>,
pub list: ParamList,
pub name_count: Option<usize>,
}
impl Clone for QueryStringMap {
fn clone(&self) -> Self {
let buffer = self.buffer.clone();
let self_slice = unsafe { &*self.slice };
let slice =
if !self.buffer.is_empty() && bun_alloc::is_slice_in_buffer(self_slice, &self.buffer) {
let len = self_slice.len();
&raw const buffer[..len]
} else {
self.slice
};
Self {
slice,
buffer,
list: self.list.clone(),
name_count: self.name_count,
}
}
}
thread_local! {
static NAME_COUNT_BUF: RefCell<[*const [u8]; 8]> = const { RefCell::new([std::ptr::from_ref::<[u8]>(&[]); 8]) };
}
impl QueryStringMap {
pub fn get_name_count(&mut self) -> usize {
self.list.len()
}
pub fn iter(&self) -> Iterator<'_> {
Iterator::init(self)
}
pub fn str(&self, ptr: api::StringPointer) -> &[u8] {
let slice = unsafe { &*self.slice };
&slice[ptr.offset as usize..ptr.offset as usize + ptr.length as usize]
}
pub fn get_index(&self, input: &[u8]) -> Option<usize> {
let hash = wyhash(input);
self.list.iter().position(|p| p.name_hash == hash)
}
pub fn get(&self, input: &[u8]) -> Option<&[u8]> {
let hash = wyhash(input);
let i = self.list.iter().position(|p| p.name_hash == hash)?;
Some(self.str(self.list[i].value))
}
pub fn has(&self, input: &[u8]) -> bool {
self.get_index(input).is_some()
}
pub fn get_all<'s>(&'s self, input: &[u8], target: &mut [&'s [u8]]) -> usize {
let hash = wyhash(input);
self.get_all_with_hash_from_offset(target, hash, 0)
}
pub fn get_all_with_hash_from_offset<'s>(
&'s self,
target: &mut [&'s [u8]],
hash: u64,
offset: usize,
) -> usize {
let mut remainder = &self.list[offset..];
let mut target_i: usize = 0;
while !remainder.is_empty() && target_i < target.len() {
let Some(i) = remainder.iter().position(|p| p.name_hash == hash) else {
break;
};
target[target_i] = self.str(remainder[i].value);
remainder = &remainder[i + 1..];
target_i += 1;
}
target_i
}
pub fn init_with_scanner(
mut scanner: CombinedScanner<'_>,
) -> Result<Option<QueryStringMap>, bun_alloc::AllocError> {
let mut list = ParamList::default();
let mut estimated_str_len: usize = 0;
let mut count: usize = 0;
let mut nothing_needs_decoding = true;
while let Some(result) = scanner.pathname.next() {
if result.name_needs_decoding || result.value_needs_decoding {
nothing_needs_decoding = false;
}
estimated_str_len += result.name.length as usize + result.value.length as usize;
count += 1;
}
debug_assert!(count > 0);
while count < MAX_QUERY_STRING_PARAMS {
let Some(result) = scanner.query.next() else {
break;
};
if result.name_needs_decoding || result.value_needs_decoding {
nothing_needs_decoding = false;
}
estimated_str_len += result.name.length as usize + result.value.length as usize;
count += 1;
}
if count == 0 {
return Ok(None);
}
list.reserve(count.min(MAX_QUERY_STRING_PARAMS)); scanner.reset();
let mut buf: Vec<u8> = Vec::with_capacity(estimated_str_len);
let mut buf_writer_pos: u32 = 0;
while let Some(result) = scanner.pathname.next() {
if list.len() >= MAX_QUERY_STRING_PARAMS {
break;
}
let mut name = result.name;
let mut value = result.value;
let name_slice = result.raw_name(scanner.pathname.routename);
name.length = u32::try_from(name_slice.len()).unwrap();
name.offset = buf_writer_pos;
buf.extend_from_slice(name_slice);
buf_writer_pos += u32::try_from(name_slice.len()).unwrap();
let name_hash: u64 = wyhash(name_slice);
value.length = match PercentEncoding::decode(
&mut buf,
result.raw_value(scanner.pathname.pathname),
) {
Ok(n) => n,
Err(_) => continue,
};
value.offset = buf_writer_pos;
buf_writer_pos += value.length;
list.push(Param {
name,
value,
name_hash,
});
}
let route_parameter_begin = list.len();
while let Some(result) = scanner.query.next() {
if list.len() >= MAX_QUERY_STRING_PARAMS {
break;
}
let mut name = result.name;
let mut value = result.value;
let name_hash: u64;
if result.name_needs_decoding {
name.length = match PercentEncoding::decode(
&mut buf,
&scanner.query.query_string[name.offset as usize..][..name.length as usize],
) {
Ok(n) => n,
Err(_) => continue,
};
name.offset = buf_writer_pos;
buf_writer_pos += name.length;
name_hash = wyhash(&buf[name.offset as usize..][..name.length as usize]);
} else {
name_hash = wyhash(result.raw_name(scanner.query.query_string));
if let Some(index) = list.iter().position(|p| p.name_hash == name_hash) {
if index < route_parameter_begin {
continue;
}
name = list[index].name;
} else {
name.length = match PercentEncoding::decode(
&mut buf,
&scanner.query.query_string[name.offset as usize..][..name.length as usize],
) {
Ok(n) => n,
Err(_) => continue,
};
name.offset = buf_writer_pos;
buf_writer_pos += name.length;
}
}
value.length = match PercentEncoding::decode(
&mut buf,
&scanner.query.query_string[value.offset as usize..][..value.length as usize],
) {
Ok(n) => n,
Err(_) => continue,
};
value.offset = buf_writer_pos;
buf_writer_pos += value.length;
list.push(Param {
name,
value,
name_hash,
});
}
let _ = nothing_needs_decoding;
let slice_ptr: *const [u8] = &raw const buf[0..buf_writer_pos as usize];
Ok(Some(QueryStringMap {
list,
buffer: buf,
slice: slice_ptr,
name_count: None,
}))
}
pub fn init(query_string: &[u8]) -> Result<Option<QueryStringMap>, bun_alloc::AllocError> {
let mut list = ParamList::default();
let mut scanner = Scanner::init(query_string);
let mut count: usize = 0;
let mut estimated_str_len: usize = 0;
let mut nothing_needs_decoding = true;
while count < MAX_QUERY_STRING_PARAMS {
let Some(result) = scanner.next() else {
break;
};
if result.name_needs_decoding || result.value_needs_decoding {
nothing_needs_decoding = false;
}
estimated_str_len += result.name.length as usize + result.value.length as usize;
count += 1;
}
if count == 0 {
return Ok(None);
}
scanner = Scanner::init(query_string);
list.reserve(count);
if nothing_needs_decoding {
scanner = Scanner::init(query_string);
while let Some(result) = scanner.next() {
if list.len() >= MAX_QUERY_STRING_PARAMS {
break;
}
debug_assert!(!result.name_needs_decoding);
debug_assert!(!result.value_needs_decoding);
let name = result.name;
let value = result.value;
let name_hash: u64 = wyhash(result.raw_name(query_string));
list.push(Param {
name,
value,
name_hash,
});
}
return Ok(Some(QueryStringMap {
list,
buffer: Vec::new(),
slice: std::ptr::from_ref::<[u8]>(query_string),
name_count: None,
}));
}
let mut buf: Vec<u8> = Vec::with_capacity(estimated_str_len);
let mut buf_writer_pos: u32 = 0;
while let Some(result) = scanner.next() {
if list.len() >= MAX_QUERY_STRING_PARAMS {
break;
}
let mut name = result.name;
let mut value = result.value;
let name_hash: u64;
if result.name_needs_decoding {
name.length = match PercentEncoding::decode(
&mut buf,
&query_string[name.offset as usize..][..name.length as usize],
) {
Ok(n) => n,
Err(_) => continue,
};
name.offset = buf_writer_pos;
buf_writer_pos += name.length;
name_hash = wyhash(&buf[name.offset as usize..][..name.length as usize]);
} else {
name_hash = wyhash(result.raw_name(query_string));
if let Some(index) = list.iter().position(|p| p.name_hash == name_hash) {
name = list[index].name;
} else {
name.length = match PercentEncoding::decode(
&mut buf,
&query_string[name.offset as usize..][..name.length as usize],
) {
Ok(n) => n,
Err(_) => continue,
};
name.offset = buf_writer_pos;
buf_writer_pos += name.length;
}
}
value.length = match PercentEncoding::decode(
&mut buf,
&query_string[value.offset as usize..][..value.length as usize],
) {
Ok(n) => n,
Err(_) => continue,
};
value.offset = buf_writer_pos;
buf_writer_pos += value.length;
list.push(Param {
name,
value,
name_hash,
});
}
let slice_ptr: *const [u8] = &raw const buf[0..buf_writer_pos as usize];
Ok(Some(QueryStringMap {
list,
buffer: buf,
slice: slice_ptr,
name_count: None,
}))
}
}
const MAX_QUERY_STRING_PARAMS: usize = 2048;
type VisitedMap = ArrayBitSet<MAX_QUERY_STRING_PARAMS, { num_masks_for(MAX_QUERY_STRING_PARAMS) }>;
pub struct Iterator<'a> {
pub i: usize,
pub map: &'a QueryStringMap,
pub visited: VisitedMap,
}
pub struct IteratorResult<'a, 't> {
pub name: &'a [u8],
pub values: &'t mut [&'a [u8]],
}
impl<'a> Iterator<'a> {
pub fn init(map: &'a QueryStringMap) -> Iterator<'a> {
debug_assert!(map.list.len() <= MAX_QUERY_STRING_PARAMS);
Iterator {
i: 0,
map,
visited: VisitedMap::init_empty(),
}
}
pub fn next<'t>(&mut self, target: &'t mut [&'a [u8]]) -> Option<IteratorResult<'a, 't>>
where
'a: 't,
{
while self.i < self.map.list.len() && self.visited.is_set(self.i) {
self.i += 1;
}
if self.i >= self.map.list.len() {
return None;
}
let list = &self.map.list;
let hash = list[self.i].name_hash;
let name_slice = list[self.i].name;
debug_assert!(name_slice.length > 0);
let name = self.map.str(name_slice);
target[0] = self.map.str(list[self.i].value);
self.visited.set(self.i);
self.i += 1;
let remainder = &list[self.i..];
let mut target_i: usize = 1;
let mut current_i: usize = 0;
while let Some(next_index) = remainder[current_i..]
.iter()
.position(|p| p.name_hash == hash)
{
let real_i = current_i + next_index + self.i;
if cfg!(debug_assertions) {
debug_assert!(!self.visited.is_set(real_i));
}
self.visited.set(real_i);
target[target_i] = self.map.str(remainder[current_i + next_index].value);
target_i += 1;
current_i += next_index + 1;
if target_i >= target.len() {
return Some(IteratorResult {
name,
values: &mut target[0..target_i],
});
}
if real_i + 1 >= self.map.list.len() {
return Some(IteratorResult {
name,
values: &mut target[0..target_i],
});
}
}
Some(IteratorResult {
name,
values: &mut target[0..target_i],
})
}
}
pub struct PercentEncoding;
#[derive(Debug)]
pub enum DecodeError {
DecodingError,
Write(bun_core::Error),
}
impl From<bun_core::Error> for DecodeError {
fn from(e: bun_core::Error) -> Self {
DecodeError::Write(e)
}
}
impl From<DecodeError> for bun_core::Error {
fn from(e: DecodeError) -> Self {
match e {
DecodeError::DecodingError => bun_core::err!("DecodingError"),
DecodeError::Write(inner) => inner,
}
}
}
impl PercentEncoding {
pub fn decode(writer: &mut impl bun_core::io::Write, input: &[u8]) -> Result<u32, DecodeError> {
Self::decode_fault_tolerant::<_, false>(writer, input, None)
}
pub fn decode_alloc(input: &[u8]) -> Result<Box<[u8]>, DecodeError> {
let mut buf: Vec<u8> = Vec::with_capacity(input.len());
let len = Self::decode(&mut buf, input)?;
buf.truncate(len as usize);
Ok(buf.into_boxed_slice())
}
pub fn decode_into(out: &mut [u8], input: &[u8]) -> Result<u32, DecodeError> {
let mut w = bun_core::fmt::SliceCursor::new(out);
Self::decode(&mut w, input)
}
pub fn decode_fault_tolerant<W: bun_core::io::Write, const FAULT_TOLERANT: bool>(
writer: &mut W,
input: &[u8],
needs_redirect: Option<&mut bool>,
) -> Result<u32, DecodeError> {
let mut needs_redirect = needs_redirect;
let mut i: usize = 0;
let mut written: u32 = 0;
while i < input.len() {
match input[i] {
b'%' => {
if FAULT_TOLERANT {
if !(i + 3 <= input.len()
&& input[i + 1].is_ascii_hexdigit()
&& input[i + 2].is_ascii_hexdigit())
{
if i + b"PUBLIC_URL%".len() < input.len()
&& &input[i + 1..][..b"PUBLIC_URL%".len()] == b"PUBLIC_URL%"
{
i += b"PUBLIC_URL%".len() + 1;
*needs_redirect.as_deref_mut().unwrap() = true;
continue;
}
return Err(DecodeError::DecodingError);
}
} else {
if !(i + 3 <= input.len()
&& input[i + 1].is_ascii_hexdigit()
&& input[i + 2].is_ascii_hexdigit())
{
return Err(DecodeError::DecodingError);
}
}
writer.write_byte(
(strings::to_ascii_hex_value(input[i + 1]) << 4)
| strings::to_ascii_hex_value(input[i + 2]),
)?;
i += 3;
written += 1;
continue;
}
_ => {
let start = i;
i += 1;
while i < input.len() && input[i] != b'%' {
i += 1;
}
writer.write_all(&input[start..i])?;
written += u32::try_from(i - start).unwrap();
}
}
}
Ok(written)
}
}
#[derive(Clone, Copy)]
pub struct ScannerResult {
pub name_needs_decoding: bool,
pub value_needs_decoding: bool,
pub name: api::StringPointer,
pub value: api::StringPointer,
}
impl ScannerResult {
#[inline]
pub(crate) fn raw_name<'a>(&self, query_string: &'a [u8]) -> &'a [u8] {
if self.name.length > 0 {
&query_string[self.name.offset as usize..][..self.name.length as usize]
} else {
b""
}
}
#[inline]
pub(crate) fn raw_value<'a>(&self, query_string: &'a [u8]) -> &'a [u8] {
if self.value.length > 0 {
&query_string[self.value.offset as usize..][..self.value.length as usize]
} else {
b""
}
}
}
pub struct CombinedScanner<'a> {
pub query: Scanner<'a>,
pub pathname: PathnameScanner<'a>,
}
impl<'a> CombinedScanner<'a> {
pub fn init(
query_string: &'a [u8],
pathname: &'a [u8],
routename: &'a [u8],
url_params: &'a ParamsList<'a>,
) -> CombinedScanner<'a> {
CombinedScanner {
query: Scanner::init(query_string),
pathname: PathnameScanner::init(pathname, routename, url_params),
}
}
pub fn reset(&mut self) {
self.query.reset();
self.pathname.reset();
}
pub fn next(&mut self) -> Option<ScannerResult> {
self.pathname.next().or_else(|| self.query.next())
}
}
fn string_pointer_from_strings(parent: &[u8], in_: &[u8]) -> api::StringPointer {
if in_.is_empty() || parent.is_empty() {
return api::StringPointer::default();
}
if let Some([offset, length]) = bun_core::range_of_slice_in_buffer(in_, parent) {
return api::StringPointer { offset, length };
} else {
if let Some(i) = strings::index_of(parent, in_) {
debug_assert!(strings::eql_long(&parent[i..][..in_.len()], in_, false));
return api::StringPointer {
offset: u32::try_from(i).unwrap(),
length: u32::try_from(in_.len()).unwrap(),
};
}
}
api::StringPointer::default()
}
pub struct PathnameScanner<'a> {
pub params: &'a ParamsList<'a>,
pub pathname: &'a [u8],
pub routename: &'a [u8],
pub i: usize,
}
impl<'a> PathnameScanner<'a> {
#[inline]
pub fn is_done(&self) -> bool {
self.params.len() <= self.i
}
pub fn reset(&mut self) {
self.i = 0;
}
pub fn init(
pathname: &'a [u8],
routename: &'a [u8],
params: &'a ParamsList<'a>,
) -> PathnameScanner<'a> {
PathnameScanner {
pathname,
routename,
params,
i: 0,
}
}
pub fn next(&mut self) -> Option<ScannerResult> {
if self.is_done() {
return None;
}
let param = self.params[self.i];
self.i += 1;
Some(ScannerResult {
name: string_pointer_from_strings(self.routename, param.name),
name_needs_decoding: false,
value: string_pointer_from_strings(self.pathname, param.value),
value_needs_decoding: strings::index_of_char(param.value, b'%').is_some(),
})
}
}
pub struct Scanner<'a> {
pub query_string: &'a [u8],
pub i: usize,
pub start: usize,
}
impl<'a> Scanner<'a> {
pub fn init(query_string: &'a [u8]) -> Scanner<'a> {
if !query_string.is_empty() && query_string[0] == b'?' {
return Scanner {
query_string,
i: 1,
start: 1,
};
}
Scanner {
query_string,
i: 0,
start: 0,
}
}
#[inline]
pub fn reset(&mut self) {
self.i = self.start;
}
pub fn next(&mut self) -> Option<ScannerResult> {
let mut relative_i: usize = 0;
'outer: loop {
if self.i >= self.query_string.len() {
self.i += relative_i;
return None;
}
let slice = &self.query_string[self.i..];
relative_i = 0;
let mut name = api::StringPointer {
offset: u32::try_from(self.i).unwrap(),
length: 0,
};
let mut value = api::StringPointer {
offset: 0,
length: 0,
};
let mut name_needs_decoding = false;
while relative_i < slice.len() {
let char = slice[relative_i];
match char {
b'=' => {
name.length = u32::try_from(relative_i).unwrap();
relative_i += 1;
value.offset = u32::try_from(relative_i + self.i).unwrap();
let offset = relative_i;
let mut value_needs_decoding = false;
while relative_i < slice.len() && slice[relative_i] != b'&' {
value_needs_decoding =
value_needs_decoding || matches!(slice[relative_i], b'%' | b'+');
relative_i += 1;
}
value.length = u32::try_from(relative_i - offset).unwrap();
if name.length == 0 {
self.i += relative_i;
return None;
}
self.i += relative_i;
return Some(ScannerResult {
name,
value,
name_needs_decoding,
value_needs_decoding,
});
}
b'%' | b'+' => {
name_needs_decoding = true;
}
b'&' => {
if relative_i > 0 {
name.length = u32::try_from(relative_i).unwrap();
self.i += relative_i;
return Some(ScannerResult {
name,
value,
name_needs_decoding,
value_needs_decoding: false,
});
}
while relative_i < slice.len() && slice[relative_i] == b'&' {
relative_i += 1;
}
self.i += relative_i;
continue 'outer;
}
_ => {}
}
relative_i += 1;
}
if relative_i == 0 {
self.i += relative_i;
return None;
}
name.length = u32::try_from(relative_i).unwrap();
self.i += relative_i;
return Some(ScannerResult {
name,
value,
name_needs_decoding,
value_needs_decoding: false,
});
}
}
}
#[cfg(test)]
mod bare_bracketed_ipv6_tests {
use super::URL;
#[test]
fn host_with_port_and_trailing_slash() {
let url = URL::parse(b"[::1]:4873/");
assert_eq!(url.host, b"[::1]:4873");
assert_eq!(url.hostname, b"[::1]");
assert_eq!(url.port, b"4873");
assert_eq!(url.pathname, b"/");
assert_eq!(url.protocol, b"");
}
#[test]
fn host_without_port() {
let url = URL::parse(b"[::1]/");
assert_eq!(url.host, b"[::1]");
assert_eq!(url.hostname, b"[::1]");
assert_eq!(url.port, b"");
assert_eq!(url.pathname, b"/");
}
#[test]
fn host_with_path() {
let url = URL::parse(b"[2001:db8::1]:4873/a/b/");
assert_eq!(url.host, b"[2001:db8::1]:4873");
assert_eq!(url.hostname, b"[2001:db8::1]");
assert_eq!(url.port, b"4873");
assert_eq!(url.pathname, b"/a/b/");
}
#[test]
fn host_without_trailing_slash() {
let url = URL::parse(b"[::1]:4873");
assert_eq!(url.host, b"[::1]:4873");
assert_eq!(url.hostname, b"[::1]");
assert_eq!(url.port, b"4873");
}
#[test]
fn schemed_bracketed_host_unchanged() {
let url = URL::parse(b"http://[::1]:4873/");
assert_eq!(url.host, b"[::1]:4873");
assert_eq!(url.hostname, b"[::1]");
assert_eq!(url.port, b"4873");
assert!(!url.protocol.is_empty());
}
}