diff --git a/src/bun.js/node/types.zig b/src/bun.js/node/types.zig index 1c587328db214e..61f3d9654ca881 100644 --- a/src/bun.js/node/types.zig +++ b/src/bun.js/node/types.zig @@ -932,15 +932,15 @@ pub const PathLike = union(enum) { return buf[0..sliced.len :0]; } - pub inline fn sliceZ(this: PathLike, buf: *bun.PathBuffer) [:0]const u8 { + pub fn sliceZ(this: PathLike, buf: *bun.PathBuffer) callconv(bun.callconv_inline) [:0]const u8 { return sliceZWithForceCopy(this, buf, false); } - pub inline fn sliceW(this: PathLike, buf: *bun.WPathBuffer) [:0]const u16 { + pub fn sliceW(this: PathLike, buf: *bun.WPathBuffer) callconv(bun.callconv_inline) [:0]const u16 { return strings.toWPath(buf, this.slice()); } - pub inline fn osPath(this: PathLike, buf: *bun.OSPathBuffer) bun.OSPathSliceZ { + pub fn osPath(this: PathLike, buf: *bun.OSPathBuffer) callconv(bun.callconv_inline) bun.OSPathSliceZ { if (comptime Environment.isWindows) { return sliceW(this, buf); } @@ -948,7 +948,7 @@ pub const PathLike = union(enum) { return sliceZWithForceCopy(this, buf, false); } - pub inline fn osPathKernel32(this: PathLike, buf: *bun.PathBuffer) bun.OSPathSliceZ { + pub fn osPathKernel32(this: PathLike, buf: *bun.PathBuffer) callconv(bun.callconv_inline) bun.OSPathSliceZ { if (comptime Environment.isWindows) { const s = this.slice(); const b = bun.PathBufferPool.get(); @@ -958,7 +958,7 @@ pub const PathLike = union(enum) { const normal = path_handler.normalizeBuf(resolve, b, .windows); return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), normal); } - const normal = path_handler.normalizeBuf(s, b, .windows); + const normal = path_handler.normalizeStringBuf(s, b, true, .windows, false); return strings.toKernel32Path(@alignCast(std.mem.bytesAsSlice(u16, buf)), normal); } diff --git a/test/js/node/fs/fs.test.ts b/test/js/node/fs/fs.test.ts index af094029a2cfb8..11086faf8ba78d 100644 --- a/test/js/node/fs/fs.test.ts +++ b/test/js/node/fs/fs.test.ts @@ -42,7 +42,7 @@ import fs, { writevSync, } from "node:fs"; import * as os from "node:os"; -import { dirname, relative, resolve } from "node:path"; +import path, { dirname, relative, resolve } from "node:path"; import { promisify } from "node:util"; import _promises, { type FileHandle } from "node:fs/promises"; @@ -3603,3 +3603,46 @@ it("fs.Stat.atime reflects date matching Node.js behavior", () => { expect(stats.atime).toEqual(new Date(0)); } }); + +describe('kernel32 long path conversion does not mangle "../../path" into "path"', () => { + const tmp1 = tempDirWithFiles("longpath", { + "a/b/config": "true", + }); + const tmp2 = tempDirWithFiles("longpath", { + "a/b/hello": "true", + "config": "true", + }); + const workingDir1 = path.join(tmp1, "a/b"); + const workingDir2 = path.join(tmp2, "a/b"); + const nonExistTests = [ + ["existsSync", 'assert.strictEqual(fs.existsSync("../../config"), false)'], + ["accessSync", 'assert.throws(() => fs.accessSync("../../config"), { code: "ENOENT" })'], + ]; + const existTests = [ + ["existsSync", 'assert.strictEqual(fs.existsSync("../../config"), true)'], + ["accessSync", 'assert.strictEqual(fs.accessSync("../../config"), null)'], + ]; + + for (const [name, code] of nonExistTests) { + it(`${name} (not existing)`, () => { + const { success } = spawnSync({ + cmd: [bunExe(), "-e", code], + cwd: workingDir1, + stdio: ["ignore", "inherit", "inherit"], + env: bunEnv, + }); + expect(success).toBeTrue(); + }); + } + for (const [name, code] of existTests) { + it(`${name} (existing)`, () => { + const { success } = spawnSync({ + cmd: [bunExe(), "-e", code], + cwd: workingDir2, + stdio: ["ignore", "inherit", "inherit"], + env: bunEnv, + }); + expect(success).toBeTrue(); + }); + } +});