Something went wrong. Try again.
jetstream v2 in zig stream.waow.tech
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100//! Operator CSV path confinement for timestamp import.//!//! Both the configured root and submitted target are canonicalized through//! symlinks before the ancestry check. Only regular files are admitted.
const std = @import("std");
const Allocator = std.mem.Allocator;const Io = std.Io;
pub const Root = struct { allocator: Allocator, io: Io, canonical: [:0]u8,
pub fn open(allocator: Allocator, io: Io, import_dir: []const u8) !Root { if (std.mem.trim(u8, import_dir, " \t\r\n").len == 0) return error.ImportDirectoryRequired; const canonical = try Io.Dir.realPathFileAlloc(.cwd(), io, import_dir, allocator); errdefer allocator.free(canonical); const stat = try Io.Dir.cwd().statFile(io, canonical, .{}); if (stat.kind != .directory) return error.ImportRootNotDirectory; return .{ .allocator = allocator, .io = io, .canonical = canonical }; }
pub fn deinit(self: *Root) void { self.allocator.free(self.canonical); self.* = undefined; }
/// Returns an owned canonical absolute path. pub fn resolve(self: *const Root, requested: []const u8) ![:0]u8 { const trimmed = std.mem.trim(u8, requested, " \t\r\n"); if (trimmed.len == 0) return error.ImportPathRequired; const candidate = if (std.fs.path.isAbsolute(trimmed)) try self.allocator.dupe(u8, trimmed) else try std.fs.path.join(self.allocator, &.{ self.canonical, trimmed }); defer self.allocator.free(candidate);
const canonical = Io.Dir.realPathFileAlloc(.cwd(), self.io, candidate, self.allocator) catch |err| switch (err) { error.FileNotFound, error.NotDir => return error.ImportPathNotFound, else => return err, }; errdefer self.allocator.free(canonical); if (!within(self.canonical, canonical)) return error.ImportPathEscape; const stat = try Io.Dir.cwd().statFile(self.io, canonical, .{}); if (stat.kind != .file) return error.ImportPathNotRegularFile; return canonical; }};
fn within(root: []const u8, target: []const u8) bool { if (!std.mem.startsWith(u8, target, root)) return false; if (target.len == root.len) return true; if (root.len > 0 and std.fs.path.isSep(root[root.len - 1])) return true; return std.fs.path.isSep(target[root.len]);}
test "canonical confinement accepts aliases inside and rejects every escape shape" { const testing = std.testing; var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); try tmp.dir.createDirPath(testing.io, "imports/nested"); try tmp.dir.createDirPath(testing.io, "outside"); try tmp.dir.writeFile(testing.io, .{ .sub_path = "imports/a.csv", .data = "uri,timestamp\n" }); try tmp.dir.writeFile(testing.io, .{ .sub_path = "outside/secret.csv", .data = "secret" }); try tmp.dir.symLink(testing.io, "a.csv", "imports/inside-link.csv", .{}); try tmp.dir.symLink(testing.io, "../outside/secret.csv", "imports/outside-link.csv", .{}); var root_buffer: [std.fs.max_path_bytes]u8 = undefined; const root_len = try tmp.dir.realPath(testing.io, &root_buffer); const imports = try std.fs.path.join(testing.allocator, &.{ root_buffer[0..root_len], "imports" }); defer testing.allocator.free(imports); const absolute = try std.fs.path.join(testing.allocator, &.{ imports, "a.csv" }); defer testing.allocator.free(absolute);
var root = try Root.open(testing.allocator, testing.io, imports); defer root.deinit(); const relative = try root.resolve(" a.csv \n"); defer testing.allocator.free(relative); try testing.expectEqualStrings(absolute, relative); const absolute_result = try root.resolve(absolute); defer testing.allocator.free(absolute_result); try testing.expectEqualStrings(absolute, absolute_result); const alias = try root.resolve("inside-link.csv"); defer testing.allocator.free(alias); try testing.expectEqualStrings(absolute, alias);
try testing.expectError(error.ImportPathRequired, root.resolve(" \t")); try testing.expectError(error.ImportPathNotFound, root.resolve("missing.csv")); try testing.expectError(error.ImportPathEscape, root.resolve("../outside/secret.csv")); try testing.expectError(error.ImportPathEscape, root.resolve("outside-link.csv")); try testing.expectError(error.ImportPathNotRegularFile, root.resolve("nested"));}
test "ancestry check does not confuse sibling prefixes" { try std.testing.expect(within("/srv/import", "/srv/import/a.csv")); try std.testing.expect(!within("/srv/import", "/srv/import-evil/a.csv"));}