feat: adopt smolmail protocol 1.1, adaptive nav shell, and server mail retention
This commit is contained in:
parent
eaaa3f2ede
commit
c693e6fcb9
27 changed files with 1190 additions and 592 deletions
|
|
@ -163,24 +163,28 @@ void main() {
|
|||
});
|
||||
|
||||
test("frontmatter parses and fails closed (§5.5)", () {
|
||||
const spec =
|
||||
"---\nSubject: Re: the thing\nIn-Reply-To: 4f2a1c9e8b7d6a5f3e2d1c0b9a8f7e6d\nX-Mood: cautiously optimistic\n---\nBody text starts here.";
|
||||
const inReplyTo =
|
||||
"4f2a1c9e8b7d6a5f3e2d1c0b9a8f7e6d5c4b3a291807f6e5d4c3b2a1908f7e6d5";
|
||||
final spec =
|
||||
"---\nSubject: Re: the thing\nIn-Reply-To: $inReplyTo\nX-Mood: cautiously optimistic\n---\nBody text starts here.";
|
||||
final parsed = parseFrontmatter(spec);
|
||||
expect(parsed.fields["Subject"], "Re: the thing");
|
||||
expect(parsed.fields["In-Reply-To"], "4f2a1c9e8b7d6a5f3e2d1c0b9a8f7e6d");
|
||||
expect(parsed.fields["subject"], "Re: the thing");
|
||||
expect(parsed.fields["in-reply-to"], inReplyTo);
|
||||
expect(parsed.body, "Body text starts here.");
|
||||
// a malformed line invalidates the whole block, which fails closed toward display
|
||||
expect(parseFrontmatter("---\nno colon here\n---\nrest").body,
|
||||
"---\nno colon here\n---\nrest");
|
||||
expect(parseFrontmatter("---\nSubject: x\nno end").body,
|
||||
"---\nSubject: x\nno end");
|
||||
expect(parseFrontmatter("---\nA: 1\nA: 2\n---\ntext").fields["A"], "1");
|
||||
expect(parseFrontmatter("---\nA: 1\nA: 2\n---\ntext").fields["a"], "1");
|
||||
// keys compare case-insensitively; the first occurrence wins (§5.5)
|
||||
expect(parseFrontmatter("---\nSubject: x\nsubject: y\n---\ntext").fields["subject"], "x");
|
||||
expect(buildFrontmatter(const {}, "---\nactual body"),
|
||||
"---\n---\n---\nactual body");
|
||||
expect(buildFrontmatter({"Subject": "hi"}, "there"), "---\nSubject: hi\n---\nthere");
|
||||
expect(buildFrontmatter(const {}, "plain"), "plain");
|
||||
expect(
|
||||
parseFrontmatter("---\n${"X: y\n" * 65}---\nbody").fields["Subject"],
|
||||
parseFrontmatter("---\n${"X: y\n" * 65}---\nbody").fields["subject"],
|
||||
isNull);
|
||||
});
|
||||
|
||||
|
|
@ -195,6 +199,9 @@ void main() {
|
|||
expect(parsed.identity, key);
|
||||
expect(parsed.user, "bob");
|
||||
expect(() => parseAddress("-bob@h"), throwsA(isA<SmolError>()));
|
||||
// §3: never two separators in a row
|
||||
expect(() => parseAddress("a..b@h"), throwsA(isA<SmolError>()));
|
||||
expect(parseAddress("a.b_c@h").user, "a.b_c");
|
||||
// §3: fingerprints are the first 20 base32 characters in groups of four
|
||||
final b32 = b32encode(key);
|
||||
expect(
|
||||
|
|
@ -210,27 +217,31 @@ void main() {
|
|||
});
|
||||
|
||||
test("rotation chains validate, break and oversize per §7", () {
|
||||
const username = "alice";
|
||||
final old = identityFromSeed(randomBytes(32));
|
||||
final mid = randomBytes(32);
|
||||
final fresh = randomBytes(32);
|
||||
final when = nowSeconds();
|
||||
final chain = [
|
||||
makeCert(old, mid, when),
|
||||
makeCert(identityFromSeed(mid), fresh, when),
|
||||
makeCert(username, old, mid, when),
|
||||
makeCert(username, identityFromSeed(mid), fresh, when),
|
||||
];
|
||||
expect(walkChain(old.publicKey, ed25519PublicKey(fresh), chain), isTrue);
|
||||
expect(walkChain(old.publicKey, old.publicKey, []), isTrue);
|
||||
expect(walkChain(username, old.publicKey, ed25519PublicKey(fresh), chain), isTrue);
|
||||
expect(walkChain(username, old.publicKey, old.publicKey, []), isTrue);
|
||||
expect(
|
||||
walkChain(old.publicKey, ed25519PublicKey(fresh), chain.sublist(1)),
|
||||
walkChain(username, old.publicKey, ed25519PublicKey(fresh), chain.sublist(1)),
|
||||
isFalse);
|
||||
final forged = List<Uint8List>.from(chain);
|
||||
forged[1] = makeCert(identityFromSeed(mid), randomBytes(32), when);
|
||||
forged[1] = makeCert(username, identityFromSeed(mid), randomBytes(32), when);
|
||||
expect(
|
||||
walkChain(old.publicKey, ed25519PublicKey(fresh), forged), isFalse);
|
||||
walkChain(username, old.publicKey, ed25519PublicKey(fresh), forged), isFalse);
|
||||
expect(
|
||||
walkChain(old.publicKey, ed25519PublicKey(fresh),
|
||||
walkChain(username, old.publicKey, ed25519PublicKey(fresh),
|
||||
List.filled(17, chain[0])),
|
||||
isFalse);
|
||||
// a chain signed for a different username must not validate (§7)
|
||||
expect(
|
||||
walkChain("bob", old.publicKey, ed25519PublicKey(fresh), chain), isFalse);
|
||||
expect(chain[0].length, certLen);
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue