From d9798a18f1b1586c9e791ce7ae2ef1817d7b6c72 Mon Sep 17 00:00:00 2001 From: randogoth Date: Wed, 14 Oct 2026 22:08:55 +0300 Subject: [PATCH 1/2] fix: trim the body before signing it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The compose buffer was passed through verbatim, so whatever the author last pressed ended up in the payload — usually a trailing newline. That is not content, but it becomes content the moment it is signed, and nothing downstream can remove it without invalidating the signature. smolboid refuses any post whose text carries a line break, because a published record is one line of a feed, so every post sent from carrier was rejected with "contains TAB, LF, CR or U+2028". Trimming has to happen here, before the seal, rather than at the receiver: stripping is not invertible, so a receiver that trimmed could no longer rebuild the bytes the author signed. --- src/ops.rs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ops.rs b/src/ops.rs index 8d3444f..103bce0 100644 --- a/src/ops.rs +++ b/src/ops.rs @@ -716,7 +716,14 @@ pub fn send( .collect(); let draft = client::SendDraft { address: &addr, - text: body.to_string(), + // Trimmed at the send site, before the payload is signed, so the + // signature covers exactly the text the author meant. A compose buffer + // ends with whatever the author last pressed, and a trailing newline + // is not content — but it is content once it has been signed, and + // nothing downstream can remove it without invalidating the + // signature. smolboid, for one, refuses a post whose text carries a + // line break, because a published record is one line of a feed. + text: body.trim().to_string(), subject, reply_to, headers: &owned, From c5ad8d44e9ea77bbcebd18f95b13b34b0f1f778b Mon Sep 17 00:00:00 2001 From: randogoth Date: Thu, 15 Oct 2026 09:22:50 +0300 Subject: [PATCH 2/2] chore: release 0.3.1 Trims the body before signing it, so a trailing newline from the compose buffer no longer ends up in the payload. A published record can't contain a line break, so every post sent from here to a twtxt feed was refused. Builds against fumi-core 0.1.1, which hands an accept token to a contact once instead of attaching it to every message. --- Cargo.lock | 4 ++-- Cargo.toml | 2 +- data/com.randogoth.carrier.metainfo.xml | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c066ee3..afbbcc1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -116,7 +116,7 @@ dependencies = [ [[package]] name = "carrier" -version = "0.3.0" +version = "0.3.1" dependencies = [ "fluent-bundle", "fumi-core", @@ -430,7 +430,7 @@ dependencies = [ [[package]] name = "fumi-core" -version = "0.1.0" +version = "0.1.1" dependencies = [ "chacha20poly1305", "data-encoding", diff --git a/Cargo.toml b/Cargo.toml index ba38e7e..b124695 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "carrier" -version = "0.3.0" +version = "0.3.1" edition = "2021" description = "A minimalist smolmail client built on fumi-core, with a Relm4 UI" license = "Apache-2.0" diff --git a/data/com.randogoth.carrier.metainfo.xml b/data/com.randogoth.carrier.metainfo.xml index 80ce224..e8a4fa9 100644 --- a/data/com.randogoth.carrier.metainfo.xml +++ b/data/com.randogoth.carrier.metainfo.xml @@ -14,6 +14,7 @@ com.randogoth.carrier.desktop +