feat: follow upstream twtxt post rules: reject control characters, allow U+2028, make the length cap configurable

This commit is contained in:
randogoth 2026-10-15 13:59:39 +03:00
parent dd6865782a
commit 801c66d3b0
12 changed files with 63 additions and 58 deletions

View file

@ -94,6 +94,7 @@ pub struct PendingRecord {
/// Failures that draw an error reply (PLAN.md, Management mail).
#[derive(Debug)]
pub enum ApplyError {
TooLong,
OverQuota(bool),
Collision(String),
FeedFull,
@ -102,7 +103,7 @@ pub enum ApplyError {
ReplyToChanged,
}
/// Applies a published record to the feed: quota, uniqueness, retention
/// Applies a published record to the feed: length, quota, uniqueness, retention
/// (pruning in Fifo mode, rejecting in Off mode), then the append.
/// Returns the rendered size after appending, for the 90% warning.
pub fn apply_record(
@ -112,6 +113,9 @@ pub fn apply_record(
record: PendingRecord,
now_unix: i64,
) -> Result<(), ApplyError> {
if record.text.chars().count() > cfg.max_post_chars {
return Err(ApplyError::TooLong);
}
State::roll_counters(fs, now_unix);
let (counter, limit) = if record.repost {
(&mut fs.reposts, cfg.reposts_per_day)
@ -929,6 +933,7 @@ impl Daemon {
}
Err(e) => {
let reason = match e {
ApplyError::TooLong => Reason::TooLong { max: self.cfg.max_post_chars },
ApplyError::OverQuota(repost) => Reason::OverQuota { repost },
ApplyError::Collision(timestamp) => Reason::Collision { timestamp },
ApplyError::FeedFull => Reason::FeedFull,
@ -1068,6 +1073,18 @@ mod tests {
assert_eq!(feed.entries().len(), 1);
}
#[test]
fn length_cap_is_configurable() {
let mut feed = smolboid_core::Feed::new();
let mut fs = feed_state();
let cfg = Config { max_post_chars: 5, ..config(1 << 20) };
let five = record("2026-10-01T12:47:00Z", "🙂🙂🙂🙂🙂");
apply_record(&mut feed, &mut fs, &cfg, five, 0).unwrap();
let six = record("2026-10-01T12:47:01Z", "abcdef");
assert!(matches!(apply_record(&mut feed, &mut fs, &cfg, six, 0), Err(ApplyError::TooLong)));
assert_eq!(fs.posts, 1, "a refused post spends no quota");
}
#[test]
fn reposts_publish_as_repost_records() {
let mut feed = smolboid_core::Feed::new();
@ -1136,7 +1153,7 @@ mod tests {
fn warning_flag_flips_at_ninety_percent() {
let mut feed = smolboid_core::Feed::new();
let mut fs = feed_state();
let cfg = config(1300);
let cfg = Config { max_post_chars: 700, ..config(1300) };
apply_record(&mut feed, &mut fs, &cfg, record("2026-10-01T12:47:00Z", "x"), 0).unwrap();
assert!(!fs.warned);
apply_record(&mut feed, &mut fs, &cfg, record("2026-10-01T12:47:01Z", &"y".repeat(700)), 0).unwrap();