diff --git a/src/bun.js/WebKit b/src/bun.js/WebKit index c3712c13dc..9c501b9aa7 160000 --- a/src/bun.js/WebKit +++ b/src/bun.js/WebKit @@ -1 +1 @@ -Subproject commit c3712c13dcdc091cfe4c7cb8f2c1fd16472e6f92 +Subproject commit 9c501b9aa712b7959f80dc99491e8758c151c20e diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig index 38493afcdd..65756ea400 100644 --- a/src/resolver/resolve_path.zig +++ b/src/resolver/resolve_path.zig @@ -2115,10 +2115,12 @@ pub const PosixToWinNormalizer = struct { const root = windowsFilesystemRoot(maybe_posix_path); if (root.len == 1) { std.debug.assert(isSepAny(root[0])); - const source_root = windowsFilesystemRoot(source_dir); - @memcpy(buf[0..source_root.len], source_root); - @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); - return buf[0 .. source_root.len + maybe_posix_path.len - 1]; + if (bun.strings.isWindowsAbsolutePathMissingDriveLetter(u8, maybe_posix_path)) { + const source_root = windowsFilesystemRoot(source_dir); + @memcpy(buf[0..source_root.len], source_root); + @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); + return buf[0 .. source_root.len + maybe_posix_path.len - 1]; + } } } return maybe_posix_path; @@ -2134,13 +2136,14 @@ pub const PosixToWinNormalizer = struct { const root = windowsFilesystemRoot(maybe_posix_path); if (root.len == 1) { std.debug.assert(isSepAny(root[0])); - // note: bun.getcwd will return forward slashes, not what we want. - const cwd = try std.os.getcwd(buf); - std.debug.assert(cwd.ptr == buf.ptr); - const source_root = windowsFilesystemRoot(cwd); - std.debug.assert(source_root.ptr == source_root.ptr); - @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); - return buf[0 .. source_root.len + maybe_posix_path.len - 1]; + if (bun.strings.isWindowsAbsolutePathMissingDriveLetter(u8, maybe_posix_path)) { + const cwd = try std.os.getcwd(buf); + std.debug.assert(cwd.ptr == buf.ptr); + const source_root = windowsFilesystemRoot(cwd); + std.debug.assert(source_root.ptr == source_root.ptr); + @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); + return buf[0 .. source_root.len + maybe_posix_path.len - 1]; + } } } @@ -2157,14 +2160,15 @@ pub const PosixToWinNormalizer = struct { const root = windowsFilesystemRoot(maybe_posix_path); if (root.len == 1) { std.debug.assert(isSepAny(root[0])); - // note: bun.getcwd will return forward slashes, not what we want. - const cwd = try std.os.getcwd(buf); - std.debug.assert(cwd.ptr == buf.ptr); - const source_root = windowsFilesystemRoot(cwd); - std.debug.assert(source_root.ptr == source_root.ptr); - @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); - buf[source_root.len + maybe_posix_path.len - 1] = 0; - return buf[0 .. source_root.len + maybe_posix_path.len - 1 :0]; + if (bun.strings.isWindowsAbsolutePathMissingDriveLetter(u8, maybe_posix_path)) { + const cwd = try std.os.getcwd(buf); + std.debug.assert(cwd.ptr == buf.ptr); + const source_root = windowsFilesystemRoot(cwd); + std.debug.assert(source_root.ptr == source_root.ptr); + @memcpy(buf[source_root.len..][0 .. maybe_posix_path.len - 1], maybe_posix_path[1..]); + buf[source_root.len + maybe_posix_path.len - 1] = 0; + return buf[0 .. source_root.len + maybe_posix_path.len - 1 :0]; + } } } diff --git a/src/string_immutable.zig b/src/string_immutable.zig index f59c365f63..3994822af4 100644 --- a/src/string_immutable.zig +++ b/src/string_immutable.zig @@ -1683,22 +1683,41 @@ pub fn utf16Codepoint(comptime Type: type, input: Type) UTF16Replacement { } } -/// '/hello' -> true -/// '\hello' -> true -/// 'C:/hello' -> false -/// '\??\C:\hello' -> false -fn windowsPathIsPosixAbsolute(comptime T: type, chars: []const T) bool { - if (chars.len == 0) return false; - if (!(chars[0] == '/' or chars[0] == '\\')) return false; +/// Checks if a path is missing a windows drive letter. Not a perfect check, +/// but it is good enough for most cases. For windows APIs, this is used for +/// an assertion, and PosixToWinNormalizer can help make an absolute path +/// contain a drive letter. +pub fn isWindowsAbsolutePathMissingDriveLetter(comptime T: type, chars: []const T) bool { + std.debug.assert(bun.path.Platform.windows.isAbsoluteT(T, chars)); + std.debug.assert(chars.len > 0); + + // 'C:\hello' -> false + if (!(chars[0] == '/' or chars[0] == '\\')) { + std.debug.assert(chars.len > 2); + std.debug.assert(chars[1] == ':'); + return false; + } + + // '\\hello' -> false (probably a UNC path) if (chars.len > 1 and (chars[1] == '/' or chars[1] == '\\')) return false; - if (chars.len > 2 and - chars[2] == ':') return false; - if (chars.len > 4 and - chars[1] == '?' and - chars[2] == '?' and - (chars[3] == '/' or chars[3] == '\\')) - return windowsPathIsPosixAbsolute(T, chars[4..]); + + if (chars.len > 4) { + // '\??\hello' -> false (has the NT object prefix) + if (chars[1] == '?' and + chars[2] == '?' and + (chars[3] == '/' or chars[3] == '\\')) + return false; + // '\\?\hello' -> false (has the other NT object prefix) + // '\\.\hello' -> false (has the NT device prefix) + if ((chars[1] == '/' or chars[1] == '\\') and + (chars[2] == '?' or chars[2] == '.') and + (chars[3] == '/' or chars[3] == '\\')) + return false; + } + + // oh no, '/hello/world' + // where is the drive letter! return true; } @@ -1801,7 +1820,9 @@ pub fn toWDirPath(wbuf: []u16, utf8: []const u8) [:0]const u16 { pub fn assertIsValidWindowsPath(comptime T: type, path: []const T) void { if (Environment.allow_assert and Environment.isWindows) { - if (windowsPathIsPosixAbsolute(T, path)) { + if (bun.path.Platform.windows.isAbsoluteT(T, path) and + isWindowsAbsolutePathMissingDriveLetter(T, path)) + { std.debug.panic("Do not pass posix paths to windows APIs, was given '{s}' (missing a root like 'C:\\', see PosixToWinNormalizer for why this is an assertion)", .{ if (T == u8) path else std.unicode.fmtUtf16le(path), }); diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts index 5826b2aa34..2dff2d4ad9 100644 --- a/test/js/node/fs/fs.test.ts +++ b/test/js/node/fs/fs.test.ts @@ -2729,3 +2729,30 @@ it("fs.ReadStream allows functions", () => { // @ts-expect-error expect(() => new fs.ReadStream(".", {})).not.toThrow(); }); + +describe.if(isWindows)("windows path handling", () => { + const file = import.meta.path.slice(3); + const drive = import.meta.path[0]; + const filenames = [ + `${drive}:\\${file}`, + `\\\\127.0.0.1\\${drive}$\\${file}`, + `\\\\LOCALHOST\\${drive}$\\${file}`, + `\\\\.\\${drive}:\\${file}`, + `\\\\?\\${drive}:\\${file}`, + `\\\\.\\UNC\\LOCALHOST\\${drive}$\\${file}`, + `\\\\?\\UNC\\LOCALHOST\\${drive}$\\${file}`, + `\\\\127.0.0.1\\${drive}$\\${file}`, + ]; + + for (const filename of filenames) { + test(`Can read '${filename}' with node:fs`, async () => { + const stats = await fs.promises.stat(filename); + expect(stats.size).toBeGreaterThan(0); + }); + + test(`Can read '${filename}' with Bun.file`, async () => { + const stats = await Bun.file(filename).text(); + expect(stats.length).toBeGreaterThan(0); + }); + } +});