gecos validator
This commit is contained in:
parent
942c05f348
commit
7911287261
42
src/User.zig
42
src/User.zig
@ -46,7 +46,7 @@ pub fn clone(
|
|||||||
|
|
||||||
// fromLine accepts a line of /etc/passwd (with or without the EOL) and makes a
|
// fromLine accepts a line of /etc/passwd (with or without the EOL) and makes a
|
||||||
// User.
|
// User.
|
||||||
fn fromLine(allocator: Allocator, err_ctx: *ErrCtx, line: []const u8) error{ InvalidRecord, OutOfMemory }!User {
|
fn fromLine(allocator: Allocator, err: *ErrCtx, line: []const u8) error{ InvalidRecord, OutOfMemory }!User {
|
||||||
var it = mem.split(u8, line, ":");
|
var it = mem.split(u8, line, ":");
|
||||||
const name = it.next();
|
const name = it.next();
|
||||||
_ = it.next(); // password
|
_ = it.next(); // password
|
||||||
@ -58,7 +58,7 @@ fn fromLine(allocator: Allocator, err_ctx: *ErrCtx, line: []const u8) error{ Inv
|
|||||||
|
|
||||||
// all fields are set
|
// all fields are set
|
||||||
if (shell == null) {
|
if (shell == null) {
|
||||||
err_ctx.print("too few user fields in line: {s}", .{line});
|
err.print("too few user fields in line: {s}", .{line});
|
||||||
return error.InvalidRecord;
|
return error.InvalidRecord;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,19 +67,19 @@ fn fromLine(allocator: Allocator, err_ctx: *ErrCtx, line: []const u8) error{ Inv
|
|||||||
return error.InvalidRecord;
|
return error.InvalidRecord;
|
||||||
|
|
||||||
const uid = fmt.parseInt(u32, uids.?, 10) catch
|
const uid = fmt.parseInt(u32, uids.?, 10) catch
|
||||||
return err_ctx.returnf("bad uid: {s}", .{uids.?}, error.InvalidRecord);
|
return err.returnf("bad uid: {s}", .{uids.?}, error.InvalidRecord);
|
||||||
|
|
||||||
const gid = fmt.parseInt(u32, gids.?, 10) catch
|
const gid = fmt.parseInt(u32, gids.?, 10) catch
|
||||||
return err_ctx.returnf("bad uid: {s}", .{gids.?}, error.InvalidRecord);
|
return err.returnf("bad uid: {s}", .{gids.?}, error.InvalidRecord);
|
||||||
|
|
||||||
validate.utf8(name.?) catch
|
validate.name(name.?, err) catch
|
||||||
return err_ctx.returnf("name is invalid utf8: '{s}'", .{name.?}, error.InvalidRecord);
|
return err.returnf("invalid name '{s}'", .{name.?}, error.InvalidRecord);
|
||||||
validate.utf8(gecos.?) catch
|
validate.gecos(gecos.?, err) catch
|
||||||
return err_ctx.returnf("gecos is invalid utf8: '{s}'", .{gecos.?}, error.InvalidRecord);
|
return err.returnf("invalid gecos '{s}'", .{gecos.?}, error.InvalidRecord);
|
||||||
validate.utf8(home.?) catch
|
validate.utf8(home.?) catch
|
||||||
return err_ctx.returnf("home is invalid utf8: '{s}'", .{home.?}, error.InvalidRecord);
|
return err.returnf("home is invalid utf8: '{s}'", .{home.?}, error.InvalidRecord);
|
||||||
validate.utf8(shell.?) catch
|
validate.utf8(shell.?) catch
|
||||||
return err_ctx.returnf("shell is invalid utf8: '{s}'", .{shell.?}, error.InvalidRecord);
|
return err.returnf("shell is invalid utf8: '{s}'", .{shell.?}, error.InvalidRecord);
|
||||||
|
|
||||||
const user = User{
|
const user = User{
|
||||||
.uid = uid,
|
.uid = uid,
|
||||||
@ -138,7 +138,7 @@ pub fn deinit(self: *User, allocator: Allocator) void {
|
|||||||
self.* = undefined;
|
self.* = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fromReader(allocator: Allocator, err_ctx: *ErrCtx, reader: anytype) ![]User {
|
pub fn fromReader(allocator: Allocator, err: *ErrCtx, reader: anytype) ![]User {
|
||||||
var users = ArrayList(User).init(allocator);
|
var users = ArrayList(User).init(allocator);
|
||||||
errdefer {
|
errdefer {
|
||||||
for (users.items) |*user| user.deinit(allocator);
|
for (users.items) |*user| user.deinit(allocator);
|
||||||
@ -147,7 +147,7 @@ pub fn fromReader(allocator: Allocator, err_ctx: *ErrCtx, reader: anytype) ![]Us
|
|||||||
|
|
||||||
var buf: [max_line_len + 1]u8 = undefined;
|
var buf: [max_line_len + 1]u8 = undefined;
|
||||||
while (try reader.readUntilDelimiterOrEof(buf[0..], '\n')) |line| {
|
while (try reader.readUntilDelimiterOrEof(buf[0..], '\n')) |line| {
|
||||||
var user = try fromLine(allocator, err_ctx, line);
|
var user = try fromLine(allocator, err, line);
|
||||||
try users.append(user);
|
try users.append(user);
|
||||||
}
|
}
|
||||||
return users.toOwnedSlice();
|
return users.toOwnedSlice();
|
||||||
@ -176,7 +176,7 @@ pub const max_user = User{
|
|||||||
|
|
||||||
const from_line_examples = [_]struct {
|
const from_line_examples = [_]struct {
|
||||||
line: []const u8,
|
line: []const u8,
|
||||||
want: error{InvalidUser}!User,
|
want: error{InvalidRecord}!User,
|
||||||
wantErrStr: []const u8 = &[_]u8{},
|
wantErrStr: []const u8 = &[_]u8{},
|
||||||
}{
|
}{
|
||||||
.{
|
.{
|
||||||
@ -191,20 +191,20 @@ const from_line_examples = [_]struct {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
.{
|
.{
|
||||||
.line = "\x00emas:x:0:0:root:/root:/bin/bash",
|
.line = "žemas:x:0:0:root:/root:/bin/bash",
|
||||||
.want = error.InvalidUser,
|
.want = error.InvalidRecord,
|
||||||
.wantErrStr = "foobar",
|
.wantErrStr = "invalid name 'žemas': invalid character at position 0",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
test "User.fromLine" {
|
test "User.fromLine" {
|
||||||
if (true) return error.SkipZigTest;
|
//if (true) return error.SkipZigTest;
|
||||||
const allocator = testing.allocator;
|
const allocator = testing.allocator;
|
||||||
for (from_line_examples) |tt| {
|
for (from_line_examples) |tt| {
|
||||||
var err_ctx = ErrCtx{};
|
var err = ErrCtx{};
|
||||||
|
|
||||||
if (tt.want) |want_user| {
|
if (tt.want) |want_user| {
|
||||||
var got = try fromLine(allocator, &err_ctx, tt.line);
|
var got = try fromLine(allocator, &err, tt.line);
|
||||||
defer got.deinit(allocator);
|
defer got.deinit(allocator);
|
||||||
try testing.expectEqual(want_user.uid, got.uid);
|
try testing.expectEqual(want_user.uid, got.uid);
|
||||||
try testing.expectEqual(want_user.gid, got.gid);
|
try testing.expectEqual(want_user.gid, got.gid);
|
||||||
@ -215,11 +215,11 @@ test "User.fromLine" {
|
|||||||
} else |want_err| {
|
} else |want_err| {
|
||||||
try testing.expectError(
|
try testing.expectError(
|
||||||
want_err,
|
want_err,
|
||||||
fromLine(allocator, &err_ctx, tt.line),
|
fromLine(allocator, &err, tt.line),
|
||||||
);
|
);
|
||||||
try testing.expectEqualStrings(
|
try testing.expectEqualStrings(
|
||||||
tt.wantErrStr,
|
tt.wantErrStr,
|
||||||
err_ctx.unwrap().constSlice(),
|
err.unwrap().constSlice(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,19 @@ pub fn name(s: []const u8, err: *ErrCtx) error{InvalidRecord}!void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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(": is not allowed", .{}, error.InvalidRecord);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const testing = std.testing;
|
const testing = std.testing;
|
||||||
|
|
||||||
test "validate name" {
|
test "validate name" {
|
||||||
@ -43,33 +56,49 @@ test "validate name" {
|
|||||||
name: []const u8,
|
name: []const u8,
|
||||||
wantErr: ?[]const u8 = null,
|
wantErr: ?[]const u8 = null,
|
||||||
}{
|
}{
|
||||||
.{
|
.{ .name = "all-good" },
|
||||||
.name = "all-good",
|
.{ .name = "..." },
|
||||||
},
|
.{ .name = "", .wantErr = "cannot be empty" },
|
||||||
.{
|
.{ .name = "-no-start-dash", .wantErr = "invalid character at position 0" },
|
||||||
.name = "...",
|
.{ .name = "Herbasž", .wantErr = "invalid character at position 7" },
|
||||||
},
|
|
||||||
.{
|
|
||||||
.name = "",
|
|
||||||
.wantErr = "cannot be empty",
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.name = "-no-start-dash",
|
|
||||||
.wantErr = "invalid character at position 0",
|
|
||||||
},
|
|
||||||
.{
|
|
||||||
.name = "Herbasž",
|
|
||||||
.wantErr = "invalid character at position 7",
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
for (examples) |tt| {
|
for (examples) |tt| {
|
||||||
var err = ErrCtx{};
|
var err = ErrCtx{};
|
||||||
|
|
||||||
|
const got = name(tt.name, &err);
|
||||||
if (tt.wantErr) |wantErr| {
|
if (tt.wantErr) |wantErr| {
|
||||||
try testing.expectError(error.InvalidRecord, name(tt.name, &err));
|
try testing.expectError(error.InvalidRecord, got);
|
||||||
try testing.expectEqualStrings(wantErr, err.unwrap().constSlice());
|
try testing.expectEqualStrings(wantErr, err.unwrap().constSlice());
|
||||||
} else {
|
} else {
|
||||||
try name(tt.name, &err);
|
// TODO: how to assert `got` is a non-error in a single line?
|
||||||
|
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 = ": 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 {
|
||||||
|
// TODO: how to assert `got` is a non-error in a single line?
|
||||||
|
if (got) |_| {} else |_| return error.TestUnExpectedError;
|
||||||
try testing.expectEqualStrings("", err.unwrap().constSlice());
|
try testing.expectEqualStrings("", err.unwrap().constSlice());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user