aboutsummaryrefslogtreecommitdiff
path: root/src/resolver/resolve_path.zig
blob: 78f00cf96936bfdcc41730eb24fbcb951295f490 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
// 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) ?[]u8 {
    var end: usize = 0;
    buffer[0] = '.';

    var iter = std.mem.tokenize(src_path, "/");
    while (iter.next()) |segment| {
        if (end >= buffer.len) break;

        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);
        }
    }

    const result = if (end == 0)
        buffer[0 .. end + 1]
    else
        buffer[0..end];

    if (std.mem.eql(u8, result, src_path)) {
        return null;
    }

    return result;
}

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"
}