diff --git a/src/main.zig b/src/main.zig index 8fd6690..c4eb1be 100644 --- a/src/main.zig +++ b/src/main.zig @@ -16,15 +16,13 @@ const Base32Error = @import("base32.zig").Base32Error; pub fn main() !void { mainInternal() catch |err| { switch (err) { - Base32Error.InvalidCharacter => { - try std.io.getStdErr().writer().print("The secret is invalid.\n", .{}); - }, - Base32Error.InvalidPadding => { + Base32Error.InvalidCharacter, Base32Error.InvalidPadding => { try std.io.getStdErr().writer().print("The secret is invalid.\n", .{}); }, ArgumentError.UnknownParameter => { - try std.io.getStdErr().writer().print("Unknown argument\n", .{}); + // do nothing, error message is already written (because the message contains the name of the unknown parameter) }, + ArgumentError.InvalidOtpAuthUrl => {}, else => |leftover_err| { try std.io.getStdErr().writer().print("{?}\n", .{leftover_err}); }, @@ -34,8 +32,9 @@ pub fn main() !void { } fn mainInternal() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + defer arena.deinit(); + const allocator = arena.allocator(); const args = try std.process.argsAlloc(allocator); @@ -171,15 +170,14 @@ fn parseArgs(allocator: Allocator, args: []const []const u8) !Args { } result.show = args[i]; } else { - std.debug.print("unknown parameter: {s}\n", .{arg}); + std.debug.print("unknown argument: {s}\n", .{arg}); return ArgumentError.UnknownParameter; } } return result; } -// todo use a real url parser -fn parseOtpAuthUrl(url: []const u8) !OtpAuthUrl { +fn parseOtpAuthUrl(allocator: Allocator, url: []const u8) !OtpAuthUrl { const uri = try std.Uri.parse(url); if (!eql(u8, uri.scheme, "otpauth")) { @@ -190,9 +188,6 @@ fn parseOtpAuthUrl(url: []const u8) !OtpAuthUrl { return error.InvalidOtpAuthUrl; } - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); - var name = try uri.path.toRawMaybeAlloc(allocator); name = std.mem.trimLeft(u8, name, "/"); @@ -259,11 +254,25 @@ fn read_config(allocator: Allocator, config_location: []const u8) !ArrayList(Otp var authenticators = std.ArrayList(OtpAuthUrl).init(allocator); + var line_no: usize = 1; while (try in_stream.readUntilDelimiterOrEofAlloc(allocator, '\n', 1024 * 1024)) |line| { + //defer allocator.free(line); if (line.len > 0 and std.mem.trim(u8, line, " \r").len > 0) { - const authenticator = try parseOtpAuthUrl(line); - try authenticators.append(authenticator); + const authenticatorResult = parseOtpAuthUrl(allocator, line); + if (authenticatorResult) |authenticator| { + try authenticators.append(authenticator); + } else |err| { + switch (err) { + error.InvalidOtpAuthUrl => { + try std.io.getStdErr().writer().print("invalid otpauth url in line {d}\n", .{line_no}); + }, + else => { + return err; + }, + } + } } + line_no += 1; } return authenticators; } @@ -294,16 +303,18 @@ fn getAuthenticator(allocator: Allocator, name: []const u8, config_location: []c } test "parse command line parameter: 'list'" { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); const arg = try parseArgs(allocator, &[_][]const u8{ "path/of/executable", "list" }); try expect(arg.list); } test "read list of entries" { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const allocator = gpa.allocator(); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); std.fs.cwd().deleteTree("test-tmp") catch unreachable; try std.fs.cwd().makeDir("test-tmp"); @@ -313,7 +324,7 @@ test "read list of entries" { std.fs.cwd().deleteTree("test-tmp") catch unreachable; } - _ = try file.write("otpauth://totp/token1?secret=c2VjcmV0Cg==\notpauth://totp/token2?secret=c2VjcmV0Cg==\n"); + _ = try file.write("otpauth://totp/token1?secret=c2VjcmV0Cg==\notpauth://totp/token2?secret=c2VjcmV0Cg=="); const list: std.ArrayList([]const u8) = try executeGetList(allocator, "test-tmp/zig-totp"); try std.testing.expectEqualStrings("token1", list.items[0]); @@ -321,8 +332,33 @@ test "read list of entries" { try std.testing.expectEqual(2, list.items.len); } +test "read list of entries, ignoring invalid otpauth urls" { + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + std.fs.cwd().deleteTree("test-tmp") catch unreachable; + try std.fs.cwd().makeDir("test-tmp"); + const file = try std.fs.cwd().createFile("test-tmp/zig-totp", .{ .read = true }); + defer { + file.close(); + std.fs.cwd().deleteTree("test-tmp") catch unreachable; + } + + _ = try file.write("otpauth://totp/token1?secret=c2VjcmV0Cg==\notpauth://invalid\n"); + + const list: std.ArrayList([]const u8) = try executeGetList(allocator, "test-tmp/zig-totp"); + + try std.testing.expectEqualStrings("token1", list.items[0]); + try std.testing.expectEqual(1, list.items.len); +} + test "parse oth pauth url" { - const actual: OtpAuthUrl = try parseOtpAuthUrl("otpauth://totp/foo?secret=MFQQ&period=31&digits=7"); + var arena = std.heap.ArenaAllocator.init(std.testing.allocator); + defer arena.deinit(); + const allocator = arena.allocator(); + + const actual: OtpAuthUrl = try parseOtpAuthUrl(allocator, "otpauth://totp/foo?secret=MFQQ&period=31&digits=7"); try std.testing.expectEqualStrings("MFQQ", actual.secretEncoded); try std.testing.expectEqualStrings("foo", actual.name);