• Display message header templates and fields without re-encoding the te

    From Rob Swindell@VERT to GitLab issue in main/sbbs on Sat Oct 31 23:23:58 2026
    open https://gitlab.synchro.net/main/sbbs/-/work_items/1318

    ## Background

    `show_msghdr()` and `list_msg()` (src/sbbs3/getmsg.cpp) display a message header line by formatting a text.dat template (`MsgSubj`, `MsgTo`, `MsgFrom`, `SubMsgLstFmt`, `MailWaitingLstFmt`, `MailOnSystemLstFmt`) together with one or more message header fields through `bprintf()`, which merges them into a single string and emits it under a single `bputs()` mode.

    text.dat strings are always CP437. Header fields are CP437 or UTF-8 depending on the message (`MSG_HFIELDS_UTF8`). Because the two are merged before output, they must be in the same charset first, so since 99b0b63767 (2019-08-03) `msghdr_text()` converts the template to UTF-8 into an `sbbs_t` buffer whenever the fields are UTF-8, and `msghdr_field()` converts fields to CP437 for non-UTF-8 terminals.

    That conversion is what broke in the case fixed by 620c500c87 (2026-10-31): the stock `MsgSubj` template is exactly 128 bytes as UTF-8, the buffer was 128 bytes, and after bb5ff19c18 (2026-07-29) the conversion failed, so the CP437 template was sent in `P_UTF8` mode and rendered as mojibake on UTF-8 terminals. The fix enlarged the buffer and made a failed conversion fall back to an all-CP437 line, but the design still requires a template to be re-encoded (and a fallback for when it can't be) purely so it can share a byte string with the field.

    ## Proposal

    Stop merging template and field into one string. Add a header-line formatter that walks the text.dat format string itself:

    - literal segments are emitted through `outchar()`, which already translates CP437 to the terminal's charset (UTF-8, PETSCII, ASCII);
    - each `%s` argument is emitted with its own print mode (`P_UTF8` when the message's header fields are UTF-8, otherwise none), honoring any `%.Ns` precision as a column count rather than a byte count.

    Consequences:

    - `msghdr_text()`, `sbbs_t::msghdr_utf8_text` and the CP437 fallback path go away; no text.dat string is ever re-encoded.
    - `msghdr_field()`'s UTF-8-to-CP437 conversion remains only for non-UTF-8 terminals, where it is a genuine lossy translation and not an artifact of the merge.
    - `%.70s`-style truncation stops cutting multi-byte UTF-8 fields at a byte boundary and stops mis-sizing the column when the field is UTF-8.

    The formatter only needs to understand the handful of conversions the header templates use (`%s`, `%.Ns`, `%u`, `%d`, `%c`, `%%`); anything else can be rejected at load time or passed through untouched.

    Affected callers: `show_msghdr()`, `list_msg()`, and `msgtoqwk.cpp`'s `QWKTagLineFmt` conversion, which reuses the same buffer for the same reason.

    — *Authored by Claude (Claude Code), on behalf of @rswindell*

    ---
    ■ Synchronet ■ Vertrauen ■ Home of Synchronet ■ [vert/cvs/bbs].synchro.net
  • From Mooserini@VERT/RESONET to Rob Swindell on Sun Nov 1 12:18:18 2026
    You are the king.


    Beware the Shadows...

    ---
    ■ Synchronet ■ The Resonant BBS