Merge pull request #1420 from lightpanda-io/resolve_fix

Handle URL.resolve with path traversal as part of the filename
This commit is contained in:
Karl Seguin
2026-01-28 06:46:35 +08:00
committed by GitHub

View File

@@ -77,8 +77,9 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
} }
// trailing space so that we always have space to append the null terminator // trailing space so that we always have space to append the null terminator
// and so that we can compare the next two characters without needing to length check
var out = try std.mem.join(allocator, "", &.{ normalized_base, "/", path, " " }); var out = try std.mem.join(allocator, "", &.{ normalized_base, "/", path, " " });
const end = out.len - 1; const end = out.len - 2;
const path_marker = path_start + 1; const path_marker = path_start + 1;
@@ -88,14 +89,14 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
var in_i: usize = 0; var in_i: usize = 0;
var out_i: usize = 0; var out_i: usize = 0;
while (in_i < end) { while (in_i < end) {
if (std.mem.startsWith(u8, out[in_i..], "./")) { if (out[in_i] == '.' and (out_i == 0 or out[out_i - 1] == '/')) {
if (out[in_i + 1] == '/') { // always safe, because we added a whitespace
// /./
in_i += 2; in_i += 2;
continue; continue;
} }
if (out[in_i + 1] == '.' and out[in_i + 2] == '/') { // always safe, because we added two whitespaces
if (std.mem.startsWith(u8, out[in_i..], "../")) { // /../
lp.assert(out[out_i - 1] == '/', "URL.resolve", .{ .out = out });
if (out_i > path_marker) { if (out_i > path_marker) {
// go back before the / // go back before the /
out_i -= 2; out_i -= 2;
@@ -113,8 +114,14 @@ pub fn resolve(allocator: Allocator, base: [:0]const u8, path: anytype, comptime
in_i += 3; in_i += 3;
continue; continue;
} }
if (in_i == end - 1) {
// ignore trailing dot
break;
}
}
out[out_i] = out[in_i]; const c = out[in_i];
out[out_i] = c;
in_i += 1; in_i += 1;
out_i += 1; out_i += 1;
} }
@@ -542,6 +549,21 @@ test "URL: resolve" {
}; };
const cases = [_]Case{ const cases = [_]Case{
.{
.base = "https://example/dir",
.path = "abc../test",
.expected = "https://example/abc../test",
},
.{
.base = "https://example/dir",
.path = "abc.",
.expected = "https://example/abc.",
},
.{
.base = "https://example/dir",
.path = "abc/.",
.expected = "https://example/abc/",
},
.{ .{
.base = "https://example/xyz/abc/123", .base = "https://example/xyz/abc/123",
.path = "something.js", .path = "something.js",