diff --git a/src/cli/create_command.zig b/src/cli/create_command.zig index 0e5fffff14..5af5a8c0ea 100644 --- a/src/cli/create_command.zig +++ b/src/cli/create_command.zig @@ -1611,6 +1611,7 @@ pub const CreateCommand = struct { const outdir_path = filesystem.absBuf(&parts, &home_dir_buf); home_dir_buf[outdir_path.len] = 0; const outdir_path_ = home_dir_buf[0..outdir_path.len :0]; + if (bun.path.hasAnyIllegalChars(outdir_path_)) break :outer; std.fs.accessAbsoluteZ(outdir_path_, .{}) catch break :outer; example_tag = Example.Tag.local_folder; break :brk outdir_path; @@ -1622,6 +1623,7 @@ pub const CreateCommand = struct { const outdir_path = filesystem.absBuf(&parts, &home_dir_buf); home_dir_buf[outdir_path.len] = 0; const outdir_path_ = home_dir_buf[0..outdir_path.len :0]; + if (bun.path.hasAnyIllegalChars(outdir_path_)) break :outer; std.fs.accessAbsoluteZ(outdir_path_, .{}) catch break :outer; example_tag = Example.Tag.local_folder; break :brk outdir_path; @@ -1633,6 +1635,7 @@ pub const CreateCommand = struct { const outdir_path = filesystem.absBuf(&parts, &home_dir_buf); home_dir_buf[outdir_path.len] = 0; const outdir_path_ = home_dir_buf[0..outdir_path.len :0]; + if (bun.path.hasAnyIllegalChars(outdir_path_)) break :outer; std.fs.accessAbsoluteZ(outdir_path_, .{}) catch break :outer; example_tag = Example.Tag.local_folder; break :brk outdir_path; diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 19bd7e1478..3014369568 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -596,6 +596,24 @@ pub fn isDriveLetterT(comptime T: type, c: T) bool { return 'a' <= c and c <= 'z' or 'A' <= c and c <= 'Z'; } +pub fn hasAnyIllegalChars(maybe_path: []const u8) bool { + if (!bun.Environment.isWindows) return false; + var maybe_path_ = maybe_path; + // check for disk discrimnator; remove it since it has a ':' + if (startsWithDiskDiscriminator(maybe_path_)) maybe_path_ = maybe_path_[2..]; + // guard against OBJECT_NAME_INVALID => unreachable + return bun.strings.indexAnyComptime(maybe_path_, "<>:\"|?*") != null; +} + +pub fn startsWithDiskDiscriminator(maybe_path: []const u8) bool { + if (!bun.Environment.isWindows) return false; + if (maybe_path.len < 3) return false; + if (!isDriveLetter(maybe_path[0])) return false; + if (maybe_path[1] != ':') return false; + if (maybe_path[2] != '\\') return false; + return true; +} + // path.relative lets you do relative across different share drives pub fn windowsFilesystemRootT(comptime T: type, path: []const T) []const T { // minimum: `C:` diff --git a/test/cli/install/bun-create.test.ts b/test/cli/install/bun-create.test.ts index a8515c1d5d..a81d082f05 100644 --- a/test/cli/install/bun-create.test.ts +++ b/test/cli/install/bun-create.test.ts @@ -1,14 +1,13 @@ import { spawn, spawnSync } from "bun"; -import { afterEach, beforeEach, expect, it, describe } from "bun:test"; -import { bunExe, bunEnv as env } from "harness"; -import { mkdtemp, realpath, rm, mkdir, stat, exists } from "fs/promises"; -import { tmpdir } from "os"; +import { beforeEach, expect, it, describe } from "bun:test"; +import { bunExe, bunEnv as env, tmpdirSync } from "harness"; +import { mkdir, stat, exists } from "fs/promises"; import { join } from "path"; let x_dir: string; beforeEach(async () => { - x_dir = await realpath(await mkdtemp(join(tmpdir(), "bun-x.test"))); + x_dir = tmpdirSync("bun-create.test"); }); describe("should not crash", async () => {