const std = @import("std"); const ascii = std.ascii; const BoundedArray = std.BoundedArray; const ErrCtx = @import("ErrCtx.zig"); pub fn downCast(comptime T: type, n: u64) error{InvalidRecord}!T { return std.math.cast(T, n) orelse return error.InvalidRecord; } pub fn utf8(s: []const u8) error{InvalidRecord}!void { if (!std.unicode.utf8ValidateSlice(s)) { return error.InvalidRecord; } } fn debugChar(c: u8) BoundedArray(u8, 4) { var res = BoundedArray(u8, 4).init(0) catch unreachable; if (ascii.isPrint(c)) { res.writer().print("'{c}'", .{c}) catch unreachable; } else res.writer().print("0x{X}", .{c}) catch unreachable; return res; } // # adduser žmogus // adduser: To avoid problems, the username should consist only of letters, // digits, underscores, periods, at signs and dashes, and not start with a dash // (as defined by IEEE Std 1003.1-2001). For compatibility with Samba machine // accounts $ is also supported at the end of the username pub fn name(s: []const u8, err: *ErrCtx) error{InvalidRecord}!void { if (s.len == 0) return err.returnf("cannot be empty", .{}, error.InvalidRecord); const c0 = s[0]; if (!(ascii.isAlphanumeric(c0) or c0 == '_' or c0 == '.' or c0 == '@')) return err.returnf( "invalid character {s} at position 0", .{debugChar(c0).constSlice()}, error.InvalidRecord, ); for (s[1..], 0..) |c, i| { if (!(ascii.isAlphanumeric(c) or c == '_' or c == '.' or c == '@' or c == '-')) return err.returnf( "invalid character {s} at position {d}", .{ debugChar(c).constSlice(), i + 2 }, error.InvalidRecord, ); } } // gecos must be utf8 without ':' pub fn gecos(s: []const u8, errc: *ErrCtx) error{InvalidRecord}!void { var utf8view = std.unicode.Utf8View.init(s) catch |err| switch (err) { error.InvalidUtf8 => return errc.returnf("invalid utf8", .{}, error.InvalidRecord), }; var it = utf8view.iterator(); while (it.nextCodepoint()) |codepoint| { if (codepoint == ':') return errc.returnf("colon is not allowed", .{}, error.InvalidRecord); } } pub fn path(s: []const u8, err: *ErrCtx) error{InvalidRecord}!void { if (s.len == 0) return err.returnf("cannot be empty", .{}, error.InvalidRecord); if (s[0] != '/') return err.returnf("must start with /", .{}, error.InvalidRecord); for (s[1..], 0..) |c, i| { if (!(ascii.isAlphanumeric(c) or c == '/' or c == '_' or c == '.' or c == '@' or c == '-')) return err.returnf( "invalid character 0xD at position {d}", .{i + 2}, error.InvalidRecord, ); } } const testing = std.testing; test "validate name" { const examples = [_]struct { name: []const u8, wantErr: ?[]const u8 = null, }{ .{ .name = "all-good" }, .{ .name = "..." }, .{ .name = "", .wantErr = "cannot be empty" }, .{ .name = "-no-start-dash", .wantErr = "invalid character '-' at position 0" }, .{ .name = "Herbasž", .wantErr = "invalid character 0xC5 at position 7" }, }; for (examples) |tt| { var err = ErrCtx{}; const got = name(tt.name, &err); if (tt.wantErr) |wantErr| { try testing.expectError(error.InvalidRecord, got); try testing.expectEqualStrings(wantErr, err.unwrap().constSlice()); } else { if (got) |_| {} else |_| return error.TestUnExpectedError; try testing.expectEqualStrings("", err.unwrap().constSlice()); } } } test "validate gecos" { const examples = [_]struct { gecos: []const u8, wantErr: ?[]const u8 = null, }{ .{ .gecos = "all-good" }, .{ .gecos = "..." }, .{ .gecos = "" }, .{ .gecos = "Vidmantas Kaminskas Ž" }, .{ .gecos = "Not\xffUnicode", .wantErr = "invalid utf8" }, .{ .gecos = "Has:Colon", .wantErr = "colon is not allowed" }, }; for (examples) |tt| { var err = ErrCtx{}; const got = gecos(tt.gecos, &err); if (tt.wantErr) |wantErr| { try testing.expectError(error.InvalidRecord, got); try testing.expectEqualStrings(wantErr, err.unwrap().constSlice()); } else { if (got) |_| {} else |_| return error.TestUnExpectedError; try testing.expectEqualStrings("", err.unwrap().constSlice()); } } } test "validate path" { const examples = [_]struct { path: []const u8, wantErr: ?[]const u8 = null, }{ .{ .path = "/path/ok" }, .{ .path = "/" }, .{ .path = "foo", .wantErr = "must start with /" }, .{ .path = "", .wantErr = "cannot be empty" }, .{ .path = "/path:motiejus", .wantErr = "invalid character 0xD at position 6" }, .{ .path = "/Herbasž", .wantErr = "invalid character 0xD at position 8" }, }; for (examples) |tt| { var err = ErrCtx{}; const got = path(tt.path, &err); if (tt.wantErr) |wantErr| { try testing.expectError(error.InvalidRecord, got); try testing.expectEqualStrings(wantErr, err.unwrap().constSlice()); } else { if (got) |_| {} else |_| return error.TestUnExpectedError; try testing.expectEqualStrings("", err.unwrap().constSlice()); } } }