Serializes argv to a Windows command-line string suitable for passing to a child process and
parsing by the CommandLineToArgvW algorithm. The caller owns the returned slice.
To avoid arbitrary command execution, this function should not be used when spawning .bat/.cmd scripts.
https://flatt.tech/research/posts/batbadbut-you-cant-securely-execute-commands-on-windows/
When executing .bat/.cmd scripts, use argvToScriptCommandLineWindows instead.
fn argvToCommandLineWindows(
allocator: Allocator,
argv: []const []const u8,
) ArgvToCommandLineError![:0]u16
fn argvToCommandLineWindows(
allocator: Allocator,
argv: []const []const u8,
) ArgvToCommandLineError![:0]u16 {
var buf = std.array_list.Managed(u8).init(allocator);
defer buf.deinit();
if (argv.len != 0) {
const arg0 = argv[0];
// The first argument must be quoted if it contains spaces or ASCII control characters
// (excluding DEL). It also follows special quoting rules where backslashes have no special
// interpretation, which makes it impossible to pass certain first arguments containing
// double quotes to a child process without characters from the first argument leaking into
// subsequent ones (which could have security implications).
//
// Empty arguments technically don't need quotes, but we quote them anyway for maximum
// compatibility with different implementations of the 'CommandLineToArgvW' algorithm.
//
// Double quotes are illegal in paths on Windows, so for the sake of simplicity we reject
// all first arguments containing double quotes, even ones that we could theoretically
// serialize in unquoted form.
var needs_quotes = arg0.len == 0;
for (arg0) |c| {
if (c <= ' ') {
needs_quotes = true;
} else if (c == '"') {
return error.InvalidArg0;
}
}
if (needs_quotes) {
try buf.append('"');
try buf.appendSlice(arg0);
try buf.append('"');
} else {
try buf.appendSlice(arg0);
}
for (argv[1..]) |arg| {
try buf.append(' ');
// Subsequent arguments must be quoted if they contain spaces, tabs or double quotes,
// or if they are empty. For simplicity and for maximum compatibility with different
// implementations of the 'CommandLineToArgvW' algorithm, we also quote all ASCII
// control characters (again, excluding DEL).
needs_quotes = for (arg) |c| {
if (c <= ' ' or c == '"') {
break true;
}
} else arg.len == 0;
if (!needs_quotes) {
try buf.appendSlice(arg);
continue;
}
try buf.append('"');
var backslash_count: usize = 0;
for (arg) |byte| {
switch (byte) {
'\\' => {
backslash_count += 1;
},
'"' => {
try buf.appendNTimes('\\', backslash_count * 2 + 1);
try buf.append('"');
backslash_count = 0;
},
else => {
try buf.appendNTimes('\\', backslash_count);
try buf.append(byte);
backslash_count = 0;
},
}
}
try buf.appendNTimes('\\', backslash_count * 2);
try buf.append('"');
}
}
return try std.unicode.wtf8ToWtf16LeAllocZ(allocator, buf.items);
}