Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions src/paths/resolve_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1302,6 +1302,45 @@ pub fn normalize_buf_z<'a, P: PlatformT>(str: &[u8], buf: &'a mut [u8]) -> &'a m
unsafe { ZStr::from_raw_mut(buf.as_mut_ptr(), len) }
}

/// [`normalize_buf`] into `buf` when the result is known to fit, otherwise into
/// `spill` (grown as needed). `spill` is untouched in the common case.
Comment thread
robobun marked this conversation as resolved.
Outdated
pub fn normalize_buf_spill<'a, P: PlatformT>(
buf: &'a mut [u8],
spill: &'a mut Vec<u8>,
str: &[u8],
) -> &'a [u8] {
normalize_buf::<P>(str, normalize_buf_or_spill(buf, spill, str))
}

/// [`normalize_buf_z`] into `buf` when the result is known to fit, otherwise
/// into `spill` (grown as needed). `spill` is untouched in the common case.
Comment thread
robobun marked this conversation as resolved.
Outdated
pub fn normalize_buf_z_spill<'a, P: PlatformT>(
buf: &'a mut [u8],
spill: &'a mut Vec<u8>,
str: &[u8],
) -> &'a ZStr {
normalize_buf_z::<P>(str, normalize_buf_or_spill(buf, spill, str))
}

fn normalize_buf_or_spill<'a>(
buf: &'a mut [u8],
spill: &'a mut Vec<u8>,
str: &[u8],
) -> &'a mut [u8] {
// Normalizing only removes bytes, except that `""` becomes `"."` and on
// Windows a drive-relative `C:` becomes `C:.` and a bare UNC volume gains
// its trailing separator (one byte each); `normalize_buf_z` then appends
// the NUL.
Comment thread
robobun marked this conversation as resolved.
Outdated
let needed = str.len() + 2;
if needed <= buf.len() {
return buf;
}
if spill.len() < needed {
spill.resize(needed, 0);
}
&mut spill[..]
}

pub fn normalize_buf_t<'a, T: PathChar, P: PlatformT>(str: &[T], buf: &'a mut [T]) -> &'a mut [T] {
if str.is_empty() {
buf[0] = T::from_u8(b'.');
Expand Down Expand Up @@ -2497,6 +2536,46 @@ mod tests {
.unwrap_or(text_len)
}

/// Returns `haystack_len` when `needle` does not occur, like the kernel.
#[unsafe(no_mangle)]
unsafe extern "C" fn highway_last_index_of_char(
haystack: *const u8,
haystack_len: usize,
needle: u8,
) -> usize {
// SAFETY: test stub; callers pass a valid (ptr, len) pair.
let haystack = unsafe { core::slice::from_raw_parts(haystack, haystack_len) };
haystack
.iter()
.rposition(|&b| b == needle)
.unwrap_or(haystack_len)
}

/// Returns `usize::MAX` when `needle` does not occur, like the kernel. Only
/// reached for u16 input; the u8 tests below merely need it to link.
Comment thread
robobun marked this conversation as resolved.
Outdated
#[unsafe(no_mangle)]
unsafe extern "C" fn highway_memrmem16(
haystack: *const u16,
haystack_len: usize,
needle: *const u16,
needle_len: usize,
) -> usize {
// SAFETY: test stub; callers pass valid (ptr, len) pairs.
let (haystack, needle) = unsafe {
(
core::slice::from_raw_parts(haystack, haystack_len),
core::slice::from_raw_parts(needle, needle_len),
)
};
if needle_len > haystack_len {
return usize::MAX;
}
(0..=haystack_len - needle_len)
.rev()
.find(|&i| haystack[i..i + needle_len] == *needle)
.unwrap_or(usize::MAX)
}

#[test]
fn normalize_string_spill_leaves_spill_untouched_when_the_input_fits() {
let mut spill = Vec::new();
Expand Down Expand Up @@ -2561,4 +2640,79 @@ mod tests {
b"C:."
);
}

#[test]
fn normalize_buf_spill_leaves_spill_untouched_when_the_input_fits() {
let mut buf = [0u8; 32];
let mut spill = Vec::new();
assert_eq!(
normalize_buf_spill::<platform::Posix>(&mut buf, &mut spill, b"./bins/../cli/./x.js"),
b"cli/x.js"
);
assert_eq!(
normalize_buf_z_spill::<platform::Posix>(&mut buf, &mut spill, b"./bins/").as_bytes(),
b"bins/"
);
assert!(spill.is_empty());
}

#[test]
fn normalize_buf_spill_spills_input_longer_than_buf() {
let mut buf = [0u8; 32];
let name = vec![b'b'; buf.len() * 3];
let mut input = b"./".to_vec();
input.extend_from_slice(&name);
input.extend_from_slice(b"/./x.js");
let mut expected = name;
expected.extend_from_slice(b"/x.js");

let mut spill = Vec::new();
assert_eq!(
normalize_buf_spill::<platform::Posix>(&mut buf, &mut spill, &input),
&expected[..]
);
expected.push(0);
assert_eq!(
normalize_buf_z_spill::<platform::Posix>(&mut buf, &mut spill, &input)
.as_bytes_with_nul(),
&expected[..]
);
assert!(!spill.is_empty());
}

#[test]
fn normalize_buf_z_spill_spills_input_exactly_as_long_as_buf() {
// The input normalizes to `buf.len()` bytes, leaving no room for the NUL.
let mut buf = [0u8; 32];
let input = vec![b'a'; buf.len()];
let mut expected = input.clone();
expected.push(0);

let mut spill = Vec::new();
assert_eq!(
normalize_buf_z_spill::<platform::Posix>(&mut buf, &mut spill, &input)
.as_bytes_with_nul(),
&expected[..]
);
assert!(!spill.is_empty());
}

#[test]
fn normalize_buf_spill_sizes_the_spill_for_the_empty_input_becoming_a_dot() {
let mut buf = [0u8; 1];

let mut spill = Vec::new();
assert_eq!(
normalize_buf_spill::<platform::Posix>(&mut buf, &mut spill, b""),
b"."
);
assert_eq!(spill.len(), 2);

let mut spill = Vec::new();
assert_eq!(
normalize_buf_z_spill::<platform::Posix>(&mut buf, &mut spill, b"").as_bytes_with_nul(),
b".\0"
);
assert_eq!(spill.len(), 2);
}
}
27 changes: 16 additions & 11 deletions src/runtime/cli/pack_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ type CowString = CowSlice<u8>;
use crate::cli::run_command::RunCommand;
use bun_core::ZBox;
use bun_core::{ZStr, strings};
use bun_paths::resolve_path;
use bun_paths::resolve_path::{self, normalize_buf_spill};
use bun_semver as Semver;
use bun_sha_hmac::sha;
use bun_sys::{
Expand Down Expand Up @@ -1460,12 +1460,14 @@ fn get_package_bins(json: &Expr) -> Result<Vec<BinInfo>, AllocError> {
let mut bins: Vec<BinInfo> = Vec::new();

let mut path_buf = PathBuffer::uninit();
let mut path_spill: Vec<u8> = Vec::new();

if let Some(bin) = json.as_property(b"bin") {
if let Some(bin_str) = bin.expr.as_string(pack_bump()) {
let normalized = resolve_path::normalize_buf::<resolve_path::platform::Posix>(
bin_str,
let normalized = normalize_buf_spill::<path::platform::Posix>(
&mut path_buf,
&mut path_spill,
bin_str,
);
if !bin_path_escapes_root(normalized) {
bins.push(BinInfo {
Expand All @@ -1484,9 +1486,10 @@ fn get_package_bins(json: &Expr) -> Result<Vec<BinInfo>, AllocError> {
for bin_prop in bin_obj.properties.slice() {
if let Some(bin_prop_value) = &bin_prop.value {
if let Some(bin_str) = bin_prop_value.as_string(pack_bump()) {
let normalized = resolve_path::normalize_buf::<resolve_path::platform::Posix>(
bin_str,
let normalized = normalize_buf_spill::<path::platform::Posix>(
&mut path_buf,
&mut path_spill,
bin_str,
);
if !bin_path_escapes_root(normalized) {
bins.push(BinInfo {
Expand All @@ -1506,9 +1509,10 @@ fn get_package_bins(json: &Expr) -> Result<Vec<BinInfo>, AllocError> {
if let ExprData::EObject(directories_obj) = &directories.expr.data {
if let Some(bin) = directories_obj.as_property(b"bin") {
if let Some(bin_str) = bin.expr.as_string(pack_bump()) {
let normalized = resolve_path::normalize_buf::<resolve_path::platform::Posix>(
bin_str,
let normalized = normalize_buf_spill::<path::platform::Posix>(
&mut path_buf,
&mut path_spill,
bin_str,
);
if !bin_path_escapes_root(normalized) {
bins.push(BinInfo {
Expand Down Expand Up @@ -2309,12 +2313,13 @@ pub(crate) fn pack<const FOR_PUBLISH: bool>(
let mut excludes: Vec<Pattern> = Vec::new();

let mut path_buf = PathBuffer::uninit();
let mut path_spill: Vec<u8> = Vec::new();
while let Some(files_entry) = files_array.next() {
if let Some(file_entry_str) = files_entry.as_string(bump) {
let normalized = resolve_path::normalize_buf::<
resolve_path::platform::Posix,
>(
file_entry_str, &mut path_buf
let normalized = normalize_buf_spill::<path::platform::Posix>(
&mut path_buf,
&mut path_spill,
file_entry_str,
);
let Some(parsed) = Pattern::from_utf8(normalized)? else {
continue;
Expand Down
26 changes: 17 additions & 9 deletions src/runtime/cli/publish_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use bun_install::lockfile::{LoadResult, LoadStep};
use bun_install::{self as install, Lockfile, Npm, PackageManager, Subcommand};
use bun_libarchive::lib::{Archive, ArchiveIterator, IteratorResult as ArchiveIterResult};
use bun_parsers::json as json_mod;
use bun_paths::resolve_path::{join_abs_string_buf_z, normalize_buf, normalize_buf_z};
use bun_paths::resolve_path::{join_abs_string_buf_z, normalize_buf_spill, normalize_buf_z_spill};
use bun_paths::{self as path, PathBuffer};
use bun_resolver::fs::FileSystem;
use bun_sha_hmac as sha;
Expand Down Expand Up @@ -1625,14 +1625,16 @@ impl PublishCommand {
};
}
let mut path_buf = PathBuffer::uninit();
let mut path_spill: Vec<u8> = Vec::new();
if let Some(bin_query) = json.as_property(b"bin") {
match &bin_query.expr.data {
ExprData::EString(bin_str) => {
let mut bin_props: Vec<G::Property> = Vec::new();
let normalized = strings::without_prefix_comptime_z(
normalize_buf_z::<path::platform::Posix>(
bin_str.string(bump)?,
normalize_buf_z_spill::<path::platform::Posix>(
&mut *path_buf,
&mut path_spill,
bin_str.string(bump)?,
),
b"./",
);
Expand Down Expand Up @@ -1677,9 +1679,10 @@ impl PublishCommand {
if ks.len() != 0 {
break 'key Some(Box::<[u8]>::from(
strings::without_prefix(
normalize_buf::<path::platform::Posix>(
ks.string(bump)?,
normalize_buf_spill::<path::platform::Posix>(
&mut *path_buf,
&mut path_spill,
ks.string(bump)?,
),
b"./",
),
Expand All @@ -1702,9 +1705,10 @@ impl PublishCommand {
break 'value Some(bun_core::ZBox::from_bytes(
strings::without_prefix_comptime_z(
// replace separators
normalize_buf_z::<path::platform::Posix>(
vs.string(bump)?,
normalize_buf_z_spill::<path::platform::Posix>(
&mut *path_buf,
&mut path_spill,
vs.string(bump)?,
),
b"./",
)
Expand Down Expand Up @@ -1763,7 +1767,11 @@ impl PublishCommand {
let mut bin_props: Vec<G::Property> = Vec::new();
let normalized_bin_dir = bun_core::ZBox::from_bytes(
strings::without_trailing_slash(strings::without_prefix(
normalize_buf::<path::platform::Posix>(bin_dir_str, &mut *path_buf),
normalize_buf_spill::<path::platform::Posix>(
&mut *path_buf,
&mut path_spill,
bin_dir_str,
),
b"./",
)),
);
Expand All @@ -1780,7 +1788,7 @@ impl PublishCommand {
) {
Ok(fd) => fd,
Err(e) => {
if e.get_errno() == bun_sys::E::ENOENT {
if matches!(e.get_errno(), bun_sys::E::ENOENT | bun_sys::E::ENAMETOOLONG) {
bun_core::warn!(
"bin directory '{}' does not exist",
bstr::BStr::new(normalized_bin_dir.as_bytes()),
Expand Down
Loading