ITADN

POP3: `TOP N 0` returns full message body instead of zero body lines

#3165Closedxiego 创建于 2026-05-06
X
xiegocommented
## Description The POP3 `TOP <msg> <lines>` command does not respect the `lines` argument when `lines` is `0`. Per RFC 1939, `TOP N 0` should return only the message headers, the blank separator line, and the `.` terminator. Stalwart returns the **entire message body** instead. This breaks header-peek behavior in clients (e.g., Apple Mail) that issue `TOP N 0` to populate the message list without downloading full bodies. For large messages, the client receives hundreds of KB where it expected a few KB, which can leave messages in a degraded state in the local index (observed: messages displayed with empty From/Subject in the inbox view despite valid envelope data on the server). ## Repro Against any POP3 mailbox containing a multi-KB message: ``` TOP 1 0 ``` Observed: server streams full headers + blank line + full body + `.\r\n`. Expected: server streams headers + blank line + `.\r\n` (zero body lines). Verified against Stalwart 0.16.3. ## Root cause In `crates/pop3/src/protocol/response.rs`, the `Response::Message::serialize()` body-truncation loop is gated on `*lines > 0`: ```rust if *lines > 0 && byte == b'\n' { line_count += 1; if line_count == *lines { break; } } ``` When `lines == 0`, the truncation block is skipped entirely and the loop runs to end-of-message. The deeper issue is in the type itself: `Response::Message { lines: u32, .. }` cannot distinguish "no limit" (RETR) from "zero body lines" (TOP N 0). At the call site in `crates/pop3/src/op/fetch.rs`, `lines.unwrap_or(0)` collapses both into `0`, so RETR and `TOP N 0` end up on the same code path. ## Suggested fix Change the field to `Option<u32>`: - `RETR` → `lines: None` (no truncation) - `TOP N k` → `lines: Some(k)` (truncate to k body lines, including `k == 0`) Truncation logic becomes: ```rust if let Some(limit) = *lines { if byte == b'\n' { line_count += 1; if line_count > limit { break; } } } ``` (Using `>` rather than `==` so that `Some(0)` breaks at the blank line separating headers from body, before any body bytes are emitted.) Happy to send a PR if helpful.
关闭于 2026-05-06 1 条评论