aboutsummaryrefslogtreecommitdiff
path: root/src/resolver/resolve_path.zig
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-09 18:57:48 -0700
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2021-05-09 18:57:48 -0700
commit166c353ddbbd943d1bb49ad7e017a058b8f309ea (patch)
treeab0c159c5f8b10291cbcb7a265c127045f1f5646 /src/resolver/resolve_path.zig
parentae90b3deea27f16c1e9d3d791f1cf21e342e45b5 (diff)
downloadbun-166c353ddbbd943d1bb49ad7e017a058b8f309ea.tar.gz
bun-166c353ddbbd943d1bb49ad7e017a058b8f309ea.tar.zst
bun-166c353ddbbd943d1bb49ad7e017a058b8f309ea.zip
1day
Former-commit-id: b7d8fe2f3501caa236aba343aefa1741613ca005
Diffstat (limited to 'src/resolver/resolve_path.zig')
-rw-r--r--src/resolver/resolve_path.zig82
1 files changed, 82 insertions, 0 deletions
diff --git a/src/resolver/resolve_path.zig b/src/resolver/resolve_path.zig
new file mode 100644
index 000000000..81921e510
--- /dev/null
+++ b/src/resolver/resolve_path.zig
@@ -0,0 +1,82 @@
+// https://github.com/MasterQ32/ftz/blob/3183b582211f8e38c1c3363c56753026ca45c11f/src/main.zig#L431-L509
+// Thanks, Felix! We should get this into std perhaps.
+
+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) error{BufferTooSmall}![]u8 {
+ if (buffer.len == 0)
+ return error.BufferTooSmall;
+ if (src_path.len == 0) {
+ buffer[0] = '/';
+ return buffer[0..1];
+ }
+
+ var end: usize = 0;
+ buffer[0] = '/';
+
+ var iter = std.mem.tokenize(src_path, "/");
+ while (iter.next()) |segment| {
+ if (std.mem.eql(u8, segment, ".")) {
+ continue;
+ } else if (std.mem.eql(u8, segment, "..")) {
+ while (true) {
+ if (end == 0)
+ break;
+ if (buffer[end] == '/') {
+ break;
+ }
+ end -= 1;
+ }
+ } else {
+ if (end + segment.len + 1 > buffer.len)
+ return error.BufferTooSmall;
+
+ const start = end;
+ buffer[end] = '/';
+ end += segment.len + 1;
+ std.mem.copy(u8, buffer[start + 1 .. end], segment);
+ }
+ }
+
+ return if (end == 0)
+ buffer[0 .. end + 1]
+ else
+ buffer[0..end];
+}
+
+fn testResolve(expected: []const u8, input: []const u8) !void {
+ var buffer: [1024]u8 = undefined;
+
+ const actual = try resolvePath(&buffer, input);
+ std.testing.expectEqualStrings(expected, actual);
+}
+
+test "resolvePath" {
+ try testResolve("/", "");
+ try testResolve("/", "/");
+ try testResolve("/", "////////////");
+
+ try testResolve("/a", "a");
+ try testResolve("/a", "/a");
+ try testResolve("/a", "////////////a");
+ try testResolve("/a", "////////////a///");
+
+ try testResolve("/a/b/c/d", "/a/b/c/d");
+
+ try testResolve("/a/b/d", "/a/b/c/../d");
+
+ try testResolve("/", "..");
+ try testResolve("/", "/..");
+ try testResolve("/", "/../../../..");
+ try testResolve("/a/b/c", "a/b/c/");
+
+ try testResolve("/new/date.txt", "/new/../../new/date.txt");
+}
+
+test "resolvePath overflow" {
+ var buf: [1]u8 = undefined;
+
+ std.testing.expectEqualStrings("/", try resolvePath(&buf, "/"));
+ std.testing.expectError(error.BufferTooSmall, resolvePath(&buf, "a")); // will resolve to "/a"
+}