PranitInitial commit439f83c3 months agoHistory
100 lines · 3.5 KB · Zig
RawDownload
const std = @import("std");const io_mod = @import("../shared/io.zig"); const Allocator = std.mem.Allocator;const session_id_random_bytes: usize = 8; pub fn sessionDirPath(alloc: Allocator, sessions_dir: []const u8, session_id: []const u8) ![]u8 {    try validateSessionId(session_id);    return std.fs.path.join(alloc, &.{ sessions_dir, session_id });} pub fn validateSessionId(session_id: []const u8) !void {    if (session_id.len == 0 or session_id.len > 255 or        std.mem.eql(u8, session_id, ".") or std.mem.eql(u8, session_id, ".."))    {        return error.InvalidSessionId;    }    for (session_id) |byte| {        if (!std.ascii.isAlphanumeric(byte) and byte != '.' and byte != '_' and byte != '-') {            return error.InvalidSessionId;        }    }} pub fn generateSessionId(alloc: Allocator) ![]u8 {    var random_bytes: [session_id_random_bytes]u8 = undefined;    io_mod.getIo().random(&random_bytes);    const random_hex = std.fmt.bytesToHex(random_bytes, .lower);    return std.fmt.allocPrint(alloc, "{d}-{d}-{s}", .{ io_mod.milliTimestamp(), io_mod.nanoTimestamp(), random_hex });} fn isLowerHex(text: []const u8) bool {    for (text) |c| {        if (!((c >= '0' and c <= '9') or (c >= 'a' and c <= 'f'))) return false;    }    return true;} test "generated session id includes random hex suffix" {    const id = try generateSessionId(std.testing.allocator);    defer std.testing.allocator.free(id);     var segments = std.mem.splitScalar(u8, id, '-');    const millis = segments.next() orelse return error.TestExpectedEqual;    const nanos = segments.next() orelse return error.TestExpectedEqual;    const suffix = segments.next() orelse return error.TestExpectedEqual;     try std.testing.expect(segments.next() == null);    try std.testing.expect(millis.len > 0);    try std.testing.expect(nanos.len > 0);    _ = try std.fmt.parseInt(i64, millis, 10);    _ = try std.fmt.parseInt(i128, nanos, 10);    try std.testing.expectEqual(@as(usize, 16), suffix.len);    try std.testing.expect(isLowerHex(suffix));} test "session directory path rejects unsafe ids" {    const alloc = std.testing.allocator;    inline for (.{ "", ".", "..", "../outside", "/tmp/outside", "nested/session", "nested\\session" }) |id| {        try std.testing.expectError(error.InvalidSessionId, sessionDirPath(alloc, "/tmp/sessions", id));    }     const unsupported = [_]u8{ 0xff, 'a' };    try std.testing.expectError(        error.InvalidSessionId,        sessionDirPath(alloc, "/tmp/sessions", &unsupported),    );     var too_long: [256]u8 = undefined;    @memset(&too_long, 'a');    try std.testing.expectError(        error.InvalidSessionId,        sessionDirPath(alloc, "/tmp/sessions", &too_long),    );     const dotted = try sessionDirPath(alloc, "/tmp/sessions", "session.v3");    defer alloc.free(dotted);    try std.testing.expectEqualStrings("/tmp/sessions/session.v3", dotted);    inline for (.{        ".hidden-session",        "session..branch",        "last",        "resume",    }) |id| {        const valid = try sessionDirPath(alloc, "/tmp/sessions", id);        alloc.free(valid);    }     const nul_id = [_]u8{ 'a', 0, 'b' };    try std.testing.expectError(        error.InvalidSessionId,        sessionDirPath(alloc, "/tmp/sessions", &nul_id),    );     var max_id: [255]u8 = undefined;    @memset(&max_id, 'a');    const maximum = try sessionDirPath(alloc, "/tmp/sessions", &max_id);    defer alloc.free(maximum);    try std.testing.expect(std.mem.endsWith(u8, maximum, &max_id));}