diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 21cc545f12..ce228176f4 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -901,11 +901,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { if (dir_buf.items.len > 0) try dir_buf.append(self.allocator, fs.path.sep); try dir_buf.appendSlice(self.allocator, app_dir); } - if (dir_buf.items.len > 0) { - // Need to normalize the path, openDirW can't handle things like double backslashes - const normalized_len = windows.normalizePath(u16, dir_buf.items) catch return error.BadPathName; - dir_buf.shrinkRetainingCapacity(normalized_len); - } windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo) catch |no_path_err| { const original_err = switch (no_path_err) { @@ -930,10 +925,6 @@ fn spawnWindows(self: *ChildProcess) SpawnError!void { while (it.next()) |search_path| { dir_buf.clearRetainingCapacity(); try dir_buf.appendSlice(self.allocator, search_path); - // Need to normalize the path, some PATH values can contain things like double - // backslashes which openDirW can't handle - const normalized_len = windows.normalizePath(u16, dir_buf.items) catch continue; - dir_buf.shrinkRetainingCapacity(normalized_len); if (windowsCreateProcessPathExt(self.allocator, &dir_buf, &app_buf, PATHEXT, &cmd_line_cache, envp_ptr, cwd_w_ptr, flags, &siStartInfo, &piProcInfo)) { break :run; diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 3b0d0efe75..4cacf14c7c 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -1,4 +1,5 @@ const std = @import("std"); + const windows = std.os.windows; const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; @@ -39,6 +40,9 @@ pub fn main() anyerror!void { // No PATH, so it should fail to find anything not in the cwd try testExecError(error.FileNotFound, allocator, "something_missing"); + // make sure we don't get error.BadPath traversing out of cwd with a relative path + try testExecError(error.FileNotFound, allocator, "..\\.\\.\\.\\\\..\\more_missing"); + std.debug.assert(windows.kernel32.SetEnvironmentVariableW( utf16Literal("PATH"), tmp_absolute_path_w, @@ -149,6 +153,48 @@ pub fn main() anyerror!void { // If we try to exec but provide a cwd that is an absolute path, the PATH // should still be searched and the goodbye.exe in something should be found. try testExecWithCwd(allocator, "goodbye", tmp_absolute_path, "hello from exe\n"); + + // introduce some extra path separators into the path which is dealt with inside the spawn call. + const denormed_something_subdir_size = std.mem.replacementSize(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\")); + + const denormed_something_subdir_abs_path = try allocator.allocSentinel(u16, denormed_something_subdir_size, 0); + defer allocator.free(denormed_something_subdir_abs_path); + + _ = std.mem.replace(u16, something_subdir_abs_path, utf16Literal("\\"), utf16Literal("\\\\\\\\"), denormed_something_subdir_abs_path); + + const denormed_something_subdir_wtf8 = try std.unicode.wtf16LeToWtf8Alloc(allocator, denormed_something_subdir_abs_path); + defer allocator.free(denormed_something_subdir_wtf8); + + // clear the path to ensure that the match comes from the cwd + std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + utf16Literal("PATH"), + null, + ) == windows.TRUE); + + try testExecWithCwd(allocator, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); + + // normalization should also work if the non-normalized path is found in the PATH var. + std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + utf16Literal("PATH"), + denormed_something_subdir_abs_path, + ) == windows.TRUE); + try testExec(allocator, "goodbye", "hello from exe\n"); + + // now make sure we can launch executables "outside" of the cwd + var subdir_cwd = try tmp.dir.openDir(denormed_something_subdir_wtf8, .{}); + defer subdir_cwd.close(); + + try tmp.dir.rename("something/goodbye.exe", "hello.exe"); + try subdir_cwd.setAsCwd(); + + // clear the PATH again + std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + utf16Literal("PATH"), + null, + ) == windows.TRUE); + + // while we're at it make sure non-windows separators work fine + try testExec(allocator, "../hello", "hello from exe\n"); } fn testExecError(err: anyerror, allocator: std.mem.Allocator, command: []const u8) !void {