aboutsummaryrefslogtreecommitdiff
path: root/src/resolver/resolve_path.zig
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-16 23:25:12 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-16 23:25:12 -0700
commit154e049638753abc10ed0eca2012685fe3b831be (patch)
treebdeb6b0bf8137ee36df0aab436ac50713ddeb5ef /src/resolver/resolve_path.zig
parente80f865974df7aae5e2f6abb966b36497da693c6 (diff)
downloadbun-154e049638753abc10ed0eca2012685fe3b831be.tar.gz
bun-154e049638753abc10ed0eca2012685fe3b831be.tar.zst
bun-154e049638753abc10ed0eca2012685fe3b831be.zip
lots
Former-commit-id: 9ccb4dd082afbc4f94982bf092360487232d8b60
Diffstat (limited to 'src/resolver/resolve_path.zig')
-rw-r--r--src/resolver/resolve_path.zig521
1 files changed, 468 insertions, 53 deletions
diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig
index f639bff1b..9cb3e635c 100644
--- a/src/resolver/resolve_path.zig
+++ b/src/resolver/resolve_path.zig
@@ -1,83 +1,498 @@
-// https://github.com/MasterQ32/ftz/blob/3183b582211f8e38c1c3363c56753026ca45c11f/src/main.zig#L431-L509
-// Thanks, Felix! We should get this into std perhaps.
+const tester = @import("../test/tester.zig");
const std = @import("std");
-/// Resolves a unix-like path and removes all "." and ".." from it. Will not escape the root and can be used to sanitize inputs.
-pub fn resolvePath(buffer: []u8, src_path: []const u8) ?[]u8 {
- var end: usize = 0;
- buffer[0] = '.';
+threadlocal var parser_join_input_buffer: [1024]u8 = undefined;
+threadlocal var parser_buffer: [1024]u8 = undefined;
- var iter = std.mem.tokenize(src_path, "/");
- while (iter.next()) |segment| {
- if (end >= buffer.len) break;
+// This function is based on Node.js' path.normalize function.
+// https://github.com/nodejs/node/blob/36bb31be5f0b85a0f6cbcb36b64feb3a12c60984/lib/path.js#L66
+pub fn normalizeStringGeneric(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime separator: u8, comptime isPathSeparator: anytype, lastIndexOfSeparator: anytype) []u8 {
+ var i: usize = 0;
+ var last_segment_length: i32 = 0;
+ var last_slash: i32 = -1;
+ var dots: i32 = 0;
+ var code: u8 = 0;
- if (std.mem.eql(u8, segment, ".")) {
- continue;
- } else if (std.mem.eql(u8, segment, "..")) {
- while (true) {
- if (end == 0)
- break;
- if (buffer[end] == '/') {
- break;
+ var written_len: usize = 0;
+ const stop_len = str.len;
+
+ while (i <= stop_len) : (i += 1) {
+ if (i < stop_len) {
+ code = str[i];
+ } else if (@call(std.builtin.CallOptions{ .modifier = .always_inline }, isPathSeparator, .{code})) {
+ break;
+ } else {
+ code = separator;
+ }
+
+ if (@call(std.builtin.CallOptions{ .modifier = .always_inline }, isPathSeparator, .{code})) {
+ if (last_slash == @intCast(i32, i) - 1 or dots == 1) {
+ // NOOP
+ } else if (dots == 2) {
+ if (written_len < 2 or last_segment_length != 2 or buf[written_len - 1] != '.' or buf[written_len - 2] != '.') {
+ if (written_len > 2) {
+ if (lastIndexOfSeparator(buf[0..written_len])) |last_slash_index| {
+ written_len = last_slash_index;
+ last_segment_length = @intCast(i32, written_len - 1 - (lastIndexOfSeparator(buf[0..written_len]) orelse 0));
+ } else {
+ written_len = 0;
+ }
+ last_slash = @intCast(i32, i);
+ dots = 0;
+ continue;
+ } else if (written_len != 0) {
+ written_len = 0;
+ last_segment_length = 0;
+ last_slash = @intCast(i32, i);
+ dots = 0;
+ continue;
+ }
+
+ if (allow_above_root) {
+ if (written_len > 0) {
+ buf[written_len] = separator;
+ written_len += 1;
+ }
+
+ buf[written_len] = '.';
+ written_len += 1;
+ buf[written_len] = '.';
+ written_len += 1;
+
+ last_segment_length = 2;
+ }
+ }
+ } else {
+ if (written_len > 0) {
+ buf[written_len] = separator;
+ written_len += 1;
}
- end -= 1;
+
+ const slice = str[@intCast(usize, @intCast(usize, last_slash + 1))..i];
+ std.mem.copy(u8, buf[written_len .. written_len + slice.len], slice);
+ written_len += slice.len;
+ last_segment_length = @intCast(i32, i) - last_slash - 1;
}
+
+ last_slash = @intCast(i32, i);
+ dots = 0;
+ } else if (code == '.' and dots != -1) {
+ dots += 1;
} else {
- if (end + segment.len + 1 > buffer.len)
+ dots = -1;
+ }
+ }
+
+ return buf[0..written_len];
+}
+
+pub const Platform = enum {
+ auto,
+ loose,
+ windows,
+ posix,
+
+ pub fn isSeparator(comptime _platform: Platform, char: u8) bool {
+ const platform = _platform.resolve();
+ switch (platform) {
+ .auto => unreachable,
+ .loose => {
+ return isSepAny(char);
+ },
+ .windows => {
+ return isSepWin32(char);
+ },
+ .posix => {
+ return isSepPosix(char);
+ },
+ }
+ }
+
+ pub fn leadingSeparatorIndex(comptime _platform: Platform, path: anytype) ?usize {
+ switch (_platform.resolve()) {
+ .windows => {
+ if (path.len < 1)
+ return null;
+
+ if (path[0] == '/')
+ return 0;
+
+ if (path[0] == '\\')
+ return 0;
+
+ if (path.len < 3)
+ return null;
+
+ // C:\
+ // C:/
+ if (path[0] >= 'A' and path[0] <= 'Z' and path[1] == ':') {
+ if (path[2] == '/')
+ return 2;
+ if (path[2] == '\\')
+ return 2;
+ }
+
return null;
+ },
+ .posix => {
+ if (path.len > 0 and path[0] == '/') {
+ return 0;
+ } else {
+ return null;
+ }
+ },
+ else => {
+ return leadingSeparatorIndex(.windows, path) orelse leadingSeparatorIndex(.posix, path);
+ },
+ }
+ }
+
+ pub fn resolve(comptime _platform: Platform) Platform {
+ if (_platform == .auto) {
+ switch (std.Target.current.os.tag) {
+ .windows => {
+ return .windows;
+ },
+
+ .freestanding, .emscripten, .other => {
+ return .loose;
+ },
- const start = end;
- buffer[end] = '/';
- end += segment.len + 1;
- std.mem.copy(u8, buffer[start + 1 .. end], segment);
+ else => {
+ return .posix;
+ },
+ }
}
+
+ return _platform;
+ }
+};
+
+pub fn normalizeString(str: []const u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 {
+ return normalizeStringBuf(str, &parser_buffer, allow_above_root, _platform);
+}
+
+pub fn normalizeStringBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool, comptime _platform: Platform) []u8 {
+ comptime const platform = _platform.resolve();
+
+ switch (platform) {
+ .auto => unreachable,
+
+ .windows => {
+ return normalizeStringWindowsBuf(str, buf, allow_above_root);
+ },
+ .posix => {
+ return normalizeStringPosixBuf(str, buf, allow_above_root);
+ },
+
+ .loose => {
+ return normalizeStringLooseBuf(str, buf, allow_above_root);
+ },
+ }
+}
+
+pub fn normalizeStringAlloc(allocator: *std.mem.Allocator, str: []const u8, comptime allow_above_root: bool, comptime _platform: Platform) ![]const u8 {
+ return try allocator.dupe(u8, normalizeString(str, allow_above_root, _platform));
+}
+
+pub fn normalizeAndJoin2(_cwd: []const u8, comptime _platform: Platform, part: anytype, part2: anytype) []const u8 {
+ const parts = [_][]const u8{ part, part2 };
+ const slice = normalizeAndJoinString(_cwd, &parts, _platform);
+ return slice;
+}
+
+pub fn normalizeAndJoin(_cwd: []const u8, comptime _platform: Platform, part: anytype) []const u8 {
+ const parts = [_][]const u8{
+ part,
+ };
+ const slice = normalizeAndJoinString(_cwd, &parts, _platform);
+ return slice;
+}
+
+// Convert parts of potentially invalid file paths into a single valid filpeath
+// without querying the filesystem
+// This is the equivalent of
+pub fn normalizeAndJoinString(_cwd: []const u8, parts: anytype, comptime _platform: Platform) []const u8 {
+ return normalizeAndJoinStringBuf(_cwd, &parser_join_input_buffer, parts, _platform);
+}
+
+pub fn normalizeAndJoinStringBuf(_cwd: []const u8, buf: []u8, parts: anytype, comptime _platform: Platform) []const u8 {
+ if (parts.len == 0) {
+ return _cwd;
+ }
+
+ if ((_platform == .loose or _platform == .posix) and parts.len == 1 and parts[0].len == 1 and parts[0] == std.fs.path.sep_posix) {
+ return "/";
+ }
+
+ var cwd = _cwd;
+ var out: usize = 0;
+ // When parts[0] is absolute, we treat that as, effectively, the cwd
+ var ignore_cwd = cwd.len == 0;
+
+ // Windows leading separators can be a lot of things...
+ // So we need to do this instead of just checking the first char.
+ var leading_separator: []const u8 = "";
+ if (_platform.leadingSeparatorIndex(parts[0])) |leading_separator_i| {
+ leading_separator = parts[0][0 .. leading_separator_i + 1];
+ ignore_cwd = true;
}
- const result = if (end == 0)
- buffer[0 .. end + 1]
- else
- buffer[0..end];
+ if (!ignore_cwd) {
+ leading_separator = cwd[0 .. 1 + (_platform.leadingSeparatorIndex(_cwd) orelse unreachable)]; // cwd must be absolute
+ cwd = _cwd[leading_separator.len..cwd.len];
+ out = cwd.len;
+ std.debug.assert(out < buf.len);
+ std.mem.copy(u8, buf[0..out], cwd);
+ }
+
+ for (parts) |part, i| {
+ // This never returns leading separators.
+ var normalized_part = normalizeString(part, true, _platform);
+ if (normalized_part.len == 0) {
+ continue;
+ }
+ switch (_platform.resolve()) {
+ .windows => {
+ buf[out] = std.fs.path.sep_windows;
+ },
+ else => {
+ buf[out] = std.fs.path.sep_posix;
+ },
+ }
+
+ out += 1;
- if (std.mem.eql(u8, result, src_path)) {
- return null;
+ const start = out;
+ out += normalized_part.len;
+ std.debug.assert(out < buf.len);
+ std.mem.copy(u8, buf[start..out], normalized_part);
}
- return result;
+ // One last normalization, to remove any ../ added
+ const result = normalizeStringBuf(buf[0..out], parser_buffer[leading_separator.len..parser_buffer.len], false, _platform);
+ std.mem.copy(u8, buf[0..leading_separator.len], leading_separator);
+ std.mem.copy(u8, buf[leading_separator.len .. result.len + leading_separator.len], result);
+
+ return buf[0 .. result.len + leading_separator.len];
+}
+
+pub fn isSepPosix(char: u8) bool {
+ return char == std.fs.path.sep_posix;
+}
+
+pub fn isSepWin32(char: u8) bool {
+ return char == std.fs.path.sep_windows;
+}
+
+pub fn isSepAny(char: u8) bool {
+ return @call(.{ .modifier = .always_inline }, isSepPosix, .{char}) or @call(.{ .modifier = .always_inline }, isSepWin32, .{char});
}
-fn testResolve(expected: []const u8, input: []const u8) !void {
- var buffer: [1024]u8 = undefined;
+pub fn lastIndexOfSeparatorWindows(slice: []const u8) ?usize {
+ return std.mem.lastIndexOfScalar(u8, slice, std.fs.path.sep_windows);
+}
- const actual = try resolvePath(&buffer, input);
- std.testing.expectEqualStrings(expected, actual);
+pub fn lastIndexOfSeparatorPosix(slice: []const u8) ?usize {
+ return std.mem.lastIndexOfScalar(u8, slice, std.fs.path.sep_posix);
}
-test "resolvePath" {
- try testResolve("/", "");
- try testResolve("/", "/");
- try testResolve("/", "////////////");
+pub fn lastIndexOfSeparatorLoose(slice: []const u8) ?usize {
+ return std.mem.lastIndexOfAny(u8, slice, "/\\");
+}
- try testResolve("/a", "a");
- try testResolve("/a", "/a");
- try testResolve("/a", "////////////a");
- try testResolve("/a", "////////////a///");
+pub fn normalizeStringPosix(str: []const u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix);
+}
- try testResolve("/a/b/c/d", "/a/b/c/d");
+pub fn normalizeStringPosixBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepPosix, lastIndexOfSeparatorPosix);
+}
- try testResolve("/a/b/d", "/a/b/c/../d");
+pub fn normalizeStringWindows(str: []const u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows);
+}
+
+pub fn normalizeStringWindowsBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_windows, isSepWin32, lastIndexOfSeparatorWindows);
+}
+
+pub fn normalizeStringLoose(str: []const u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGenericBuf(str, &parser_buffer, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose);
+}
+
+pub fn normalizeStringLooseBuf(str: []const u8, buf: []u8, comptime allow_above_root: bool) []u8 {
+ return normalizeStringGeneric(str, buf, allow_above_root, std.fs.path.sep_posix, isSepAny, lastIndexOfSeparatorLoose);
+}
+
+test "normalizeAndJoinStringPosix" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
+ const string = []const u8;
+ const cwd = "/Users/jarredsumner/Code/app";
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/bar/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "file.js" }, .posix),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "../file.js" }, .posix),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "./bar", "../file.js" }, .posix),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "././././foo", "././././bar././././", "../file.js" }, .posix),
+ @src(),
+ );
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "././././bar././././", "../file.js" }, .posix),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", ".", "././././bar././././", ".", "../file.js" }, .posix),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "..", "././././bar././././", ".", "../file.js" }, .posix),
+ @src(),
+ );
+}
+
+test "normalizeAndJoinStringLoose" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
+ const string = []const u8;
+ const cwd = "/Users/jarredsumner/Code/app";
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/bar/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "../file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "./bar", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "././././foo", "././././bar././././", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "././././bar././././", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", ".", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "..", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/bar/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "bar", "../file.js" }, .loose),
+ @src(),
+ );
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "foo", "./bar", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Users/jarredsumner/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ ".\\.\\.\\.\\foo", "././././bar././././", "..\\file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "././././bar././././", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/foo/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", ".", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+
+ _ = t.expect(
+ "/Code/app/file.js",
+ normalizeAndJoinString(cwd, [_]string{ "/Code/app", "././././foo", "..", "././././bar././././", ".", "../file.js" }, .loose),
+ @src(),
+ );
+}
- try testResolve("/", "..");
- try testResolve("/", "/..");
- try testResolve("/", "/../../../..");
- try testResolve("/a/b/c", "a/b/c/");
+test "normalizeStringPosix" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
- try testResolve("/new/date.txt", "/new/../../new/date.txt");
+ // Don't mess up strings that
+ _ = t.expect("foo/bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar.txt", true, .posix), @src());
+ _ = t.expect("foo/bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar.txt", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar", true, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/././foo/././././././bar/../bar/../bar", true, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/foo/bar//////", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/////foo/bar//////", false, .posix), @src());
+ _ = t.expect("foo/bar", try normalizeStringAlloc(std.heap.c_allocator, "/////foo/bar", false, .posix), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, "/////", false, .posix), @src());
+ _ = t.expect("..", try normalizeStringAlloc(std.heap.c_allocator, "../boom/../", true, .posix), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, "./", true, .posix), @src());
}
-test "resolvePath overflow" {
- var buf: [1]u8 = undefined;
+test "normalizeStringWindows" {
+ var t = tester.Tester.t(std.heap.c_allocator);
+ defer t.report(@src());
- std.testing.expectEqualStrings("/", try resolvePath(&buf, "/"));
- std.testing.expectError(error.BufferTooSmall, resolvePath(&buf, "a")); // will resolve to "/a"
+ // Don't mess up strings that
+ _ = t.expect("foo\\bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar.txt", true, .windows), @src());
+ _ = t.expect("foo\\bar.txt", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar.txt", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar", true, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\.\\.\\foo\\.\\.\\.\\.\\.\\.\\bar\\..\\bar\\..\\bar", true, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\foo\\bar\\\\\\\\\\\\", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\\\\\\\\\foo\\bar\\\\\\\\\\\\", false, .windows), @src());
+ _ = t.expect("foo\\bar", try normalizeStringAlloc(std.heap.c_allocator, "\\\\\\\\\\foo\\bar", false, .windows), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, "\\\\\\\\\\", false, .windows), @src());
+ _ = t.expect("..", try normalizeStringAlloc(std.heap.c_allocator, "..\\boom\\..\\", true, .windows), @src());
+ _ = t.expect("", try normalizeStringAlloc(std.heap.c_allocator, ".\\", true, .windows), @src());
}