Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 53 additions & 4 deletions src/cli.zig
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,20 @@ pub const Input = struct {
self.stdin = null;
return null;
};
const trimmed = std.mem.trim(u8, line, " \t\r\n");
// Blank lines separate records, they are not records.
if (trimmed.len == 0) continue;
return .{ .line = trimmed };
// Only the line ending comes off, never the reader's own bytes.
//
// This used to trim spaces and tabs from both ends of every record,
// which is harmless for a code or a JSON object and silent data
// loss for a message: `printf ' hello '` piped into
// `deed encrypt` encrypted `hello`, and nothing said so. A record
// is whatever sat between two newlines, and what to do about the
// whitespace in it is the verb's business, not this one's.
const body = if (std.mem.endsWith(u8, line, "\r")) line[0 .. line.len - 1] else line;
// A truly empty line separates records and is not one. A line of
// spaces IS one, because for `deed encrypt` it is a message
// somebody chose to send.
if (body.len == 0) continue;
return .{ .line = body };
}
}
};
Expand All @@ -114,6 +124,45 @@ test "no positionals and no stdin yields nothing" {
try std.testing.expect((try input.next()) == null);
}

test "a record is handed back as it was written" {
// `deed encrypt` takes a record as the MESSAGE. This used to trim spaces and
// tabs off both ends of every record, so `printf ' hello '` piped in
// encrypted `hello` and nothing said so: the reader's own words, altered on
// the way to being sealed.
var source: std.Io.Reader = .fixed(" hello \nplain\n\ttabbed\t\n");
var window: [64]u8 = undefined;
var limited = source.limited(.unlimited, &window);
var input = Input.init(&.{}, &limited.interface);

try std.testing.expectEqualStrings(" hello ", (try input.next()).?.line);
try std.testing.expectEqualStrings("plain", (try input.next()).?.line);
try std.testing.expectEqualStrings("\ttabbed\t", (try input.next()).?.line);
try std.testing.expect((try input.next()) == null);
}

test "a line ending comes off, and an empty line is still a separator" {
// CRLF is the one thing that is not the reader's bytes: it is how the line
// ended, not part of what they wrote.
var source: std.Io.Reader = .fixed("one\r\n\ntwo\r\n");
var window: [64]u8 = undefined;
var limited = source.limited(.unlimited, &window);
var input = Input.init(&.{}, &limited.interface);

try std.testing.expectEqualStrings("one", (try input.next()).?.line);
try std.testing.expectEqualStrings("two", (try input.next()).?.line);
try std.testing.expect((try input.next()) == null);
}

test "a line of spaces is a record, not a separator" {
// For `deed encrypt` it is a message somebody chose to send. Only a truly
// empty line separates.
var source: std.Io.Reader = .fixed(" \n");
var window: [64]u8 = undefined;
var limited = source.limited(.unlimited, &window);
var input = Input.init(&.{}, &limited.interface);
try std.testing.expectEqualStrings(" ", (try input.next()).?.line);
}

test "a record too long to hold does not take the stream with it" {
// A 32-byte window over input whose second record is far longer than it.
const data = "aa\n" ++ ("x" ** 200) ++ "\nbb\ncc\n";
Expand Down
9 changes: 9 additions & 0 deletions src/cmd_crypt.zig
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,15 @@ pub fn run(
});
return cli.exit_ok;
}
// Everything after `--` is a message, whatever it starts with. Without
// this there was no way to encrypt a line beginning with a hyphen: it
// came back as an unknown option, which is a refusal to carry somebody's
// words on account of their first character.
if (std.mem.eql(u8, a, "--")) {
i += 1;
while (i < args.len) : (i += 1) try positionals.append(gpa, args[i]);
break;
}
if (std.mem.startsWith(u8, a, "-")) {
const is_sec = std.mem.eql(u8, a, "--sec");
const is_peer = std.mem.eql(u8, a, dir.peerFlag());
Expand Down
4 changes: 3 additions & 1 deletion src/cmd_decode.zig
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ pub fn run(
var failures: usize = 0;
while (try input.next()) |record| {
const code = switch (record) {
.line => |l| l,
// Trimmed here rather than in `Input`: surrounding space around
// a code is noise, and `deed encrypt` needs the same bytes it was given.
.line => |l| std.mem.trim(u8, l, " \t"),
.too_long => {
try err.print(
"deed decode: skipped a code longer than {d} bytes\n",
Expand Down
4 changes: 3 additions & 1 deletion src/cmd_event.zig
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,9 @@ pub fn run(
var failures: usize = 0;
while (try input.next()) |record| {
const json = switch (record) {
.line => |l| l,
// Trimmed here rather than in `Input`: surrounding space around
// a draft is noise, and `deed encrypt` needs the same bytes it was given.
.line => |l| std.mem.trim(u8, l, " \t"),
.too_long => {
try err.print(
"deed event: skipped a draft longer than {d} bytes\n",
Expand Down
22 changes: 21 additions & 1 deletion src/cmd_key.zig
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,14 @@ fn public(

var signer = keys.Signer.init();
defer signer.deinit();
const kp = try signer.keyPairFromSecretKey(sk);
// Caught rather than propagated. 64 hex characters can still be a number
// outside the curve's range, and letting that escape printed `deed:
// InvalidSecretKey`, which names the branch the code took rather than
// telling the reader what is wrong with what they pasted.
const kp = signer.keyPairFromSecretKey(sk) catch {
try err.writeAll("deed key public: that is not a usable secret key (the number is outside the curve's range)\n");
return cli.exit_fail;
};

const text = if (as_hex)
try hex.encode(gpa, &kp.public_key)
Expand Down Expand Up @@ -263,6 +270,19 @@ test "help is printed on stdout and succeeds" {
try std.testing.expectEqualStrings("", r.err);
}

test "a key outside the curve's range is explained, not named" {
// 64 hex characters can still be a number the curve cannot use. Letting it
// escape printed `deed: InvalidSecretKey`, which names the branch the code
// took rather than telling the reader what is wrong with what they pasted.
var ob: [1024]u8 = undefined;
var eb: [1024]u8 = undefined;
const r = try runKey(&.{ "public", "f" ** 64 }, &ob, &eb);
try std.testing.expectEqual(cli.exit_fail, r.code);
try std.testing.expectEqualStrings("", r.out);
try std.testing.expect(std.mem.indexOf(u8, r.err, "InvalidSecretKey") == null);
try std.testing.expect(std.mem.indexOf(u8, r.err, "curve") != null);
}

test "the subcommands answer --help too" {
// They used to reject it as an unknown option, because help was only caught
// at the subcommand position and never inside the argument loops.
Expand Down
4 changes: 3 additions & 1 deletion src/cmd_verify.zig
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ pub fn run(
var failures: usize = 0;
while (try input.next()) |record| {
const json = switch (record) {
.line => |l| l,
// Trimmed here rather than in `Input`: surrounding space around
// an event is noise, and `deed encrypt` needs the same bytes it was given.
.line => |l| std.mem.trim(u8, l, " \t"),
.too_long => {
try err.print(
"deed verify: skipped an event longer than {d} bytes\n",
Expand Down
Loading