Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,23 @@ SIXEL_IMAGE_MAX_BYTES=5242880
SIXEL_PIXELS_PER_COL=9
SIXEL_PIXELS_PER_ROW=16

# ANSI-art message handling on the Telnet/SSH reader.
# Message bodies are stripped of cursor-positioning sequences before the plain
# reader would see them (GHSA-4225-c933-76f3), so absolute-positioned ANSI art
# cannot be shown verbatim.
# canvas = art is rendered onto an off-screen character grid and the coloured
# result is shown inline in the normal reader; press A for a
# full-screen positioned render (default)
# viewer = inline reader shows the reflowed body; press A for the full-screen
# render
# inline = the full-screen render opens automatically for art messages; any
# key returns to the normal reader
# raw = cursor-positioning/erase codes pass through to the normal reader
# for art messages (not word-wrapped); sysop accepts in-screen
# display spoofing
# OSC/DCS/answerback vectors stay blocked in every mode.
# TERM_ANSI_ART_MODE=canvas

# Outbound File Requests (FREQ) feature - lets users request a file from a remote FTN node
# via the web UI, terminal server UI, and /api/freq/requests.
# false = disabled for everyone (default) | true = any logged-in user | sysop = admin accounts only
Expand Down
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "awehttam/binkterm-php",
"version": "1.10.5",
"version": "1.10.6",
"description": "Multi-protocol BBS platform built around native FTN messaging, with a browser-based community interface, real-time event bus, and door game framework, accessible from browsers, Telnet/SSH, Gemini, QWK, AI assistants, and mesh radio nodes",
"type": "project",
"require": {
Expand Down
2 changes: 2 additions & 0 deletions config/i18n/de/terminalserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,8 @@
'ui.terminalserver.message.help_headers' => 'Nachrichtenkopfzeilen anzeigen',
'ui.terminalserver.message.help_download' => 'Anhang herunterladen (ZMODEM)',
'ui.terminalserver.message.help_images' => 'Eingebettetes Bild(er) anzeigen',
'ui.terminalserver.message.help_ansi_art' => 'Als ANSI-Grafik anzeigen',
'ui.terminalserver.message.ansi_art_dismiss' => 'ANSI-Grafikansicht - beliebige Taste zum Zurückkehren...',
'ui.terminalserver.message.help_quit' => 'Beenden / Nachricht schliessen',
'ui.terminalserver.netmail.help_delete' => 'Nachricht löschen',
'ui.terminalserver.netmail.help_bookmark' => 'Nachricht mit Lesezeichen versehen / entfernen',
Expand Down
2 changes: 2 additions & 0 deletions config/i18n/en/terminalserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@
'ui.terminalserver.message.help_headers' => 'View message headers',
'ui.terminalserver.message.help_download' => 'Download attachment (ZMODEM)',
'ui.terminalserver.message.help_images' => 'View inline image(s)',
'ui.terminalserver.message.help_ansi_art' => 'View as ANSI art',
'ui.terminalserver.message.ansi_art_dismiss' => 'ANSI art view - press any key to return...',
'ui.terminalserver.message.help_quit' => 'Quit / close message',
'ui.terminalserver.netmail.help_delete' => 'Delete message',
'ui.terminalserver.netmail.help_bookmark' => 'Bookmark / unsave message',
Expand Down
2 changes: 2 additions & 0 deletions config/i18n/es/terminalserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,8 @@
'ui.terminalserver.message.help_headers' => 'Ver encabezados del mensaje',
'ui.terminalserver.message.help_download' => 'Descargar adjunto (ZMODEM)',
'ui.terminalserver.message.help_images' => 'Ver imagen(es) incrustada(s)',
'ui.terminalserver.message.help_ansi_art' => 'Ver como arte ANSI',
'ui.terminalserver.message.ansi_art_dismiss' => 'Vista de arte ANSI - pulse cualquier tecla para volver...',
'ui.terminalserver.message.help_quit' => 'Salir / cerrar mensaje',
'ui.terminalserver.netmail.help_delete' => 'Eliminar mensaje',
'ui.terminalserver.netmail.help_bookmark' => 'Marcar / desmarcar mensaje',
Expand Down
2 changes: 2 additions & 0 deletions config/i18n/fr/terminalserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,8 @@
'ui.terminalserver.message.help_headers' => 'Afficher les en-têtes du message',
'ui.terminalserver.message.help_download' => 'Télécharger la pièce jointe (ZMODEM)',
'ui.terminalserver.message.help_images' => 'Afficher la ou les images intégrées',
'ui.terminalserver.message.help_ansi_art' => 'Afficher en art ANSI',
'ui.terminalserver.message.ansi_art_dismiss' => 'Vue art ANSI - appuyez sur une touche pour revenir...',
'ui.terminalserver.message.help_quit' => 'Quitter / fermer le message',
'ui.terminalserver.netmail.help_delete' => 'Supprimer le message',
'ui.terminalserver.netmail.help_bookmark' => 'Marquer / démarquer le message',
Expand Down
2 changes: 2 additions & 0 deletions config/i18n/it/terminalserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -563,6 +563,8 @@
'ui.terminalserver.message.help_headers' => 'Visualizza intestazioni messaggio',
'ui.terminalserver.message.help_download' => 'Scarica allegato (ZMODEM)',
'ui.terminalserver.message.help_images' => 'Visualizza immagine/i incorporata/e',
'ui.terminalserver.message.help_ansi_art' => 'Visualizza come arte ANSI',
'ui.terminalserver.message.ansi_art_dismiss' => 'Vista arte ANSI - premi un tasto per tornare...',
'ui.terminalserver.message.help_quit' => 'Esci / chiudi messaggio',
'ui.terminalserver.netmail.help_delete' => 'Elimina messaggio',
'ui.terminalserver.netmail.help_bookmark' => 'Aggiungi / rimuovi segnalibro',
Expand Down
2 changes: 2 additions & 0 deletions config/i18n/ru/terminalserver.php
Original file line number Diff line number Diff line change
Expand Up @@ -564,6 +564,8 @@
'ui.terminalserver.message.help_headers' => 'Просмотреть заголовки сообщения',
'ui.terminalserver.message.help_download' => 'Скачать вложение (ZMODEM)',
'ui.terminalserver.message.help_images' => 'Посмотреть встроенные изображения',
'ui.terminalserver.message.help_ansi_art' => 'Показать как ANSI-графику',
'ui.terminalserver.message.ansi_art_dismiss' => 'Просмотр ANSI-графики - нажмите любую клавишу для возврата...',
'ui.terminalserver.message.help_quit' => 'Выход / закрыть сообщение',
'ui.terminalserver.netmail.help_delete' => 'Удалить сообщение',
'ui.terminalserver.netmail.help_bookmark' => 'Добавить в закладки / убрать из сохранённых',
Expand Down
7 changes: 7 additions & 0 deletions docs/TerminalServer.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@ Pipe-code rendering for plain bulletins and other ANSI/pipe text shared with the
- Press `T` in the echomail viewer to download the current message as a plain-text `.txt` file via ZMODEM. The filename is derived from the message subject. Uses the built-in ZMODEM implementation by default; no additional software required.
- Press `E` in the echomail viewer to forward the current message to the logged-in user's email address. Requires outbound email to be configured on the BBS; an error is shown inline if it is not.
- Press `F` in the echomail viewer to forward the current message. A dialog prompts the user to choose **Echomail** (forward to another subscribed echoarea — opens compose pre-filled with a `Fwd:` subject, attribution header, and quoted body) or **Netmail** (forward as a netmail to an FTN address — uses the standard netmail compose flow). The source echoarea appears in the attribution header in both cases.
- **ANSI-art messages** (echomail or netmail whose body positions the cursor to place its pieces) are rendered onto a virtual character canvas and shown inline in the normal reader — the reader scrolls, repaints and resizes the art like any other message, and no cursor-control code reaches your terminal. Message bodies are stripped of cursor-positioning sequences before the plain reader would otherwise see them (security hardening in 1.10.5), which is why the canvas exists: it resolves the cursor moves against an off-screen grid first. Press `A` (shown in the Ctrl-K help overlay only when the body is art) for a full-screen render with real cursor positioning for maximum fidelity.
- The `TERM_ANSI_ART_MODE` setting selects how art is handled:
- `canvas` (default) — virtual-canvas render inline; `A` for the full-screen view.
- `viewer` — inline reader shows the escape-filtered, reflowed body; `A` for the full-screen view.
- `inline` — the full-screen view opens automatically whenever an art message is opened; any key returns to the reader.
- `raw` — cursor-positioning and erase codes pass straight through to the normal reader for art messages (which are then not word-wrapped), so the art draws itself in place as you scroll. This is the sysop opting in to in-screen display spoofing within the reader.
The window-title/clipboard (OSC) and answerback/device-status protections apply in every mode.
- Press `G` in the echomail viewer to **add an ignore rule** for the current message's sender. A sub-menu offers three options: **By sender name** (hides all future messages from that name), **By FTN address** (hides messages from that name at that address — only shown if the message carries an FTN address), and **Subject keyword** (hides future messages from that sender whose subject contains the given keyword). Options 1 and 2 show a confirmation dialog pre-filled from the message header; option 3 prompts for a keyword string. The rule is saved via `POST /api/messages/echomail/ignore-rules`. `G` is available in the Ctrl-K help overlay only (not the status bar).
- Press `G` from the **echoarea list** to open the **Ignore Rules** management screen. Rules are fetched from `GET /api/user/echomail-ignore-rules` and displayed in a paginated selectable list. Each row shows the sender name and, where set, the FTN address and subject keyword. Select a rule and press Enter or `D` to delete it after confirmation via `DELETE /api/user/echomail-ignore-rules/{id}`. `G` appears in the Ctrl-K help overlay on the echoarea list.
- The **echoarea list** (the screen showing your subscribed areas) uses the same navigable list interface as message lists. Arrow Up/Down moves the highlight cursor; Arrow Left/Right (or `n`/`p`) changes pages; Enter selects the highlighted area; typing a number jumps the cursor to that row. A status bar at the bottom shows available actions. Press `/` to filter the list by tag or description, `C` to clear the filter, and (when Interests is enabled) `I` to open the interests browser. The list redraws immediately on terminal resize.
Expand Down
45 changes: 45 additions & 0 deletions docs/TerminalServerDevGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,51 @@ forward bodies), `TelnetUtils::formatMessageListEntry()` and
radio links are plain text). Any new surface that renders remote message content
must call the sanitizer too.

Because the sanitizer strips absolute cursor positioning, ANSI-art messages
arrive at the wrapper as a few long logical lines rather than many screen-placed
fragments. `TelnetUtils::wrapTextLines()` handles this: it is ANSI- and
UTF-8-aware, treating escape sequences as zero-width atomic units (never split
across a wrap) and breaking only on character boundaries so multi-byte glyphs
stay intact. A line with no escape sequences and no high bytes takes a fast
byte-oriented `wordwrap()` path. Do not reintroduce a raw `wordwrap(..., true)`
on text that may contain colour codes or UTF-8.

### ANSI art

Two pieces cooperate. `TerminalTextSanitizer::sanitize($raw, POLICY_POSITIONING)`
keeps a whitelist of cursor-movement and erase sequences on top of SGR while
still removing OSC, DCS/APC/PM, private-mode sequences,
device-status/answerback queries and C0/C1 bytes — the input-injection and
clipboard/title vectors stay closed regardless of mode.

`AnsiCanvasRenderer::render($positioningSanitizedBody, $width)` (`telnet/src/`,
ported from the browser `AnsiTerminal` in `public_html/js/ansisys.js`) resolves
every cursor move against an off-screen cell grid (`$width` columns, height
capped at 1000 rows) and serialises the used rows back to strings that carry
**SGR codes only** — no positioning. The message viewers feed these straight in
as `$wrappedLines`, so the scroll viewer, resize rebuild and repaint all work
unchanged and nothing but colour reaches the terminal.

`AnsiArtViewer::show()` is the alternative: a full-screen render of the
positioning-sanitized body with real cursor moves, reached with the `A` key
(`a => 'viewart'` in `$extraKeys` plus a help item; `case 'viewart'` in the
switch). Maximum fidelity, at the cost of clearing the screen.

`AnsiArtViewer::isArt($rawBody)` (over
`TerminalTextSanitizer::hasPositionedAnsi()`) must be called on the **raw** body,
before sanitization, to decide whether a message qualifies. `readerRenderMode()`
then returns how `$buildView` should turn the body into lines:

| `TERM_ANSI_ART_MODE` | `readerRenderMode($isArt=true)` | Body policy | `$buildView` lines |
|----------------------|--------------------------------|-------------|--------------------|
| `canvas` (default) | `canvas` | `POLICY_POSITIONING` | `AnsiCanvasRenderer::render()` |
| `viewer` | `strict` | `POLICY_STRIP` | `wrapTextLines()` |
| `inline` | `strict` | `POLICY_STRIP` | `wrapTextLines()` (plus `show()` auto-launches once per open) |
| `raw` | `raw` | `POLICY_POSITIONING` | split on newlines, no wrap |

For non-art bodies `readerRenderMode()` is always `strict`, so every ordinary
message keeps the plain sanitize + wrap path.

### Status Bar Discipline

The bottom status bar has limited width. Keep it to the **most-used primary actions only** — typically scroll, prev/next, reply, and quit. Every other key belongs exclusively in the Ctrl-K help overlay.
Expand Down
70 changes: 70 additions & 0 deletions docs/UPGRADING_1.10.6.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# Upgrading to 1.10.6

Make sure you have a current backup of your database and files before upgrading.

## Table of Contents

- [Summary of Changes](#summary-of-changes)
- [Upgrade Instructions](#upgrade-instructions)
- [From Git](#from-git)
- [Using the Installer](#using-the-installer)

## Summary of Changes

<!--
Group bullet points by major feature area as changes land during this release
cycle. Each bullet should be self-contained: state what changed, why it matters,
and what (if anything) the upgrader must do.
-->

### Terminal Server

- **ANSI message wrapping fix:** the Telnet/SSH message reader now wraps message
bodies with an ANSI- and UTF-8-aware word-wrapper. Previously, colour codes and
multi-byte box-drawing characters were counted as literal bytes toward the line
width, so coloured or ANSI-art messages could be hard-cut in the middle of an
escape sequence (showing stray text such as `[35m`) or a multi-byte character
(showing mojibake), and lines could overflow the terminal width. This was most
visible on ANSI-art posts after the 1.10.5 escape-sequence filtering removed
their absolute cursor positioning. Escape sequences are now treated as
zero-width and are never split; wrapping only breaks on character boundaries.

- **ANSI-art messages render on a virtual canvas:** echomail and netmail whose
body is ANSI art (it positions the cursor to place its pieces) are now drawn
onto an off-screen character grid and the resulting coloured lines are shown
inline in the normal reader. The 1.10.5 security fix strips absolute cursor
positioning from message bodies, which left ANSI art reflowing into unreadable
text; the canvas resolves every cursor move against the grid first, so the art
keeps its layout and the reader still scrolls, repaints and resizes it like
any other message. No cursor-control code reaches the terminal — only colour.
Pressing `A` in the reader opens a full-screen view that renders the art with
real cursor positioning for maximum fidelity; that view (like the inline
canvas) still strips window-title/clipboard writes (OSC),
answerback/device-status queries and other input-injection sequences.

The `TERM_ANSI_ART_MODE` setting selects the behaviour:

| Value | Behaviour |
|-------|-----------|
| `canvas` (default) | Art rendered on the virtual canvas inline; `A` for the full-screen view. |
| `viewer` | Inline reader shows the escape-filtered, reflowed body; `A` for the full-screen view. |
| `inline` | The full-screen view opens automatically when an art message is opened; any key returns to the reader. |
| `raw` | Cursor-positioning and erase codes pass straight through to the normal reader for art messages (not word-wrapped). Reintroduces in-screen display spoofing within the reader — the sysop opts in. |

The OSC/DCS/answerback protections apply in every mode.

---

## Upgrade Instructions

### From Git

```bash
git pull
php scripts/setup.php
scripts/restart_daemons.sh
```

### Using the Installer

Download the latest installer from the [BinktermPHP website](https://lovelybits.org/binktermphp) and run it. The installer handles file replacement, runs setup, and restarts all daemons automatically — no manual steps required.
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ Specifications published by the LovlyNet Standards Council.

Release-specific upgrade notes, listed newest-first. See [UPGRADING_TEMPLATE.md](UPGRADING_TEMPLATE.md) for the document template.

- [Upgrading to 1.10.6](UPGRADING_1.10.6.md)
- [Upgrading to 1.10.5](UPGRADING_1.10.5.md)
- [Upgrading to 1.10.4](UPGRADING_1.10.4.md)
- [Upgrading to 1.10.3](UPGRADING_1.10.3.md)
Expand Down
Loading
Loading