open
https://gitlab.synchro.net/main/sbbs/-/work_items/1285
## Problem
A SAUCE record appended to an ANSI art file ends up in the body of a message posted from that file, and the built-in message reader displays it as garbage at the end of the message.
Example: DOVE-Net Ads (`DOVE-ADS`) "[ANSI] FuNToPiA BBS" from Mindsurfer, 2026-10-16, imported via QWK from FUNTOPIA (3.22a-Win32). The end of the body:
```
...ESC[0m CRLF CRLF
-- 20261016_113201
1A "SAUCE00" + spaces (blank Title/Author/Group) + "20260609" + 2A 61
CRLF ^An CRLF
--- CRLF * Synchronet * FuNToPiA BBS -
telnet://funtopia...
```
The reader shows a CP437 right-arrow glyph (the Ctrl-Z), `SAUCE00`, a run of spaces, the SAUCE date and `*a`, then the tagline.
### Why
1. Nothing removes the SAUCE trailer (Ctrl-Z EOF + optional `COMNT` block +
128-byte `SAUCE00` record) when a file becomes a message body.
2. The record gets truncated at its first NUL byte. SAUCE FileSize is a
little-endian 32-bit value (here `2A 61 00 00` = 24,874), and message
bodies are C strings: `smb_addmsg()` takes the body length with `strlen()`
(`smbadd.c`), and `postmsg.cpp`, `postmsg.js`/`save_msg()` and smbutil
import all end up there. So DataType, FileType, TInfo, TFlags etc. are lost,
while the printable leading fields survive.
3. The message reader does not stop at Ctrl-Z. Display *files* do:
`prntfile.cpp` detects SAUCE with `sauce_fread_charinfo()` and sets
`P_CPM_EOF`, which `putmsg()` honors. Message bodies never take that path,
and setting `P_CPM_EOF` for them would cut off the tagline/origin that
follows the Ctrl-Z.
How the originating message was posted (`/UPLOAD` into the internal editor, ZMODEM upload in fseditor.js, `postmsg.js`, `smbutil i`, ...) can't be determined from our copy: every path truncates at the NUL the same way, and FUNTOPIA's REP packets have never included HEADERS.DAT, so its `editor`, `columns`, Message-ID, time zone etc. never arrived. (chksetup.js now warns about that hub misconfiguration.)
## Why not just truncate at the first Ctrl-Z?
Because Synchronet itself produces legitimate 0x1A bytes in message text.
CP437 0x1A is the glyph for U+2192 RIGHTWARDS ARROW (`xpdev/unicode.c`), and `unicode_to_cp437()` reverse-maps it, so `utf8_to_cp437_inplace()` turns every right arrow into 0x1A. Verified: `utf8_decode()` of a UTF-8 "a, arrow, b" string yields `"a\u001ab"`. That conversion is applied to message text in:
- `msgtoqwk.cpp`: UTF-8 messages exported to non-UTF-8 QWK hubs/users
- `sbbsecho.c`: FidoNet export to non-UTF-8 links
- `writemsg.cpp`: quoting/editing a UTF-8 message in a CP437 editor
- `mailsrvr.cpp`
Right arrows are common in modern text (Markdown, LLM-written posts), so a receiving system that truncated at Ctrl-Z would lose everything after the
first arrow, including the tagline/origin. (UTF-8 itself is safe either way: 0x1A is never part of a multi-byte sequence.)
## Proposed solution
Strip SAUCE specifically: a Ctrl-Z followed by `COMNT` or `SAUCE00`, through the end of the record. Leave other 0x1A bytes alone. One shared helper
(e.g. `strip_sauce()`), handling both the intact and the truncated form, used at three points:
1. **Where an uploaded/edited file is read in, before anything is appended.**
writemsg after the editor or `/UPLOAD`, `postmsg.js`/`MsgBase.save_msg()`,
smbutil import. The record is still intact here (the NUL truncation only
happens later at `smb_addmsg()`). This must happen *before* writemsg
appends the user's signature and tag file, or they'd be lost too. Here the
SAUCE can also be put to use rather than discarded: `sauce_fread_charinfo()`
already exists, and its width could populate the message's `SMB_COLUMNS`
header field (e.g. for 132-column art).
2. **QWK and FidoNet import** (`qwktomsg`, SBBSecho). Messages from systems
without the fix arrive with the already-truncated form: Ctrl-Z + `SAUCE00`
+ whatever preceded the first NUL, followed by the exporter's newline and
tagline. Stripping here must stop at the 128-byte record length or the
line end, whichever comes first, so the tagline survives. (A FileSize
byte of 0x0D/0x0A would end the line early; a small risk.)
3. **The message reader**, as a display-time fallback, to clean up messages
already stored in message bases, including the example above.
-- *Authored by Claude (Claude Code), on behalf of @rswindell*
---
þ Synchronet þ Vertrauen þ Home of Synchronet þ [vert/cvs/bbs].synchro.net