Why rebuild a small protocol client.
The repository started as a visual Flutter prototype, but the useful problem was lower-level: parse a Gopher menu faithfully, show what each field means and let a terminal user open a real resource without unbounded network behaviour.
Browsers cannot create the raw TCP connection Gopher needs. Rather than disguise that limitation, the project uses one parser in two honest contexts: live TCP in the CLI and a deterministic fixture in the public explorer.
The protocol boundaries.
A small client still needs explicit network and rendering rules:
- Requests have an absolute deadline, idle timeout, 8 KiB request cap and bounded response size.
- Binary bytes remain binary and are never printed directly to an interactive terminal.
- Terminal control sequences are neutralised before untrusted text reaches the screen.
- Malformed menu lines stay visible so the explorer does not turn parsing errors into plausible data.
One parser, two transports.
The parser and URL rules are useful independently of the network connection. Keeping them in the static site makes the protocol explainable and testable in a browser without introducing a proxy that would change the security model.
The CLI adds the missing transport boundary: bounded TCP, direct unencrypted connections and explicit handling for menu, text, search and binary item types.
The request path.
A gopher:// URL becomes a host, port and selector. The CLI sends the selector over bounded TCP and hands returned bytes to the shared menu/text interpretation. The browser begins at the same parsing boundary with a checked-in fixture.
Decisions that keep the client legible.
The project favours visible protocol behaviour over convenience magic.
Preserve selectors
RFC 4266 search URLs are parsed without collapsing selector dot-segments that belong to the remote protocol path.
Gopher selectors do not behave like familiar HTTP paths, so the distinction must stay explicit.
Fail safely in a terminal
Text is cleaned of terminal control sequences, while binary resources must be redirected to a file.
The client refuses some convenient output paths because terminal integrity matters more.
Keep the web demo deterministic
GitHub Pages uses an included fixture and never claims to connect to a live Gopher server.
The explorer is a teaching surface, not a browser-based network client.
Release integrity.
Release candidates are built twice and the npm archives and normalised SBOM evidence must match byte for byte. A clean-prefix smoke test runs the published command before release.
Tagged publication validates the version, reviewed-main ancestry, checksums, attestations and immutable release inventory. None of that expands the protocol scope: Gopher+, TLS, Telnet, authentication and automatic downloads remain outside it.
What users can inspect.
A terminal user can open real Gopher menus, text and searches through a bounded transport. A browser user can navigate a keyboard-friendly fixture and inspect item type, selector, host and port at each step.
The project is intentionally narrow. That narrowness makes the network boundary, parser behaviour and unsupported features easy to find.
Evidence ledger.
The protocol and transport claims are backed by explicit project limits:
- Verification
- 68 Node.js test declarations and two browser E2E declarations in the audited release.
- Request bounds
- 8 KiB request cap, 5-second total deadline, 2.5-second idle timeout and 10,000 menu-entry limit by default.
- Response bounds
- 1 MiB default response ceiling and 10 MiB absolute configurable ceiling.
- Boundary
- Direct TCP is unencrypted; TLS, authentication, Gopher+, Telnet and automatic downloads are unsupported.