Skip to content

refactor: filter pipeline DRY, procmail DSL consolidation, token substitution#137

Open
denzuko wants to merge 2 commits into
developfrom
refactor/filter-pipeline-dry
Open

refactor: filter pipeline DRY, procmail DSL consolidation, token substitution#137
denzuko wants to merge 2 commits into
developfrom
refactor/filter-pipeline-dry

Conversation

@denzuko

@denzuko denzuko commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Six fixes from a codebase-wide milter/plugin architecture audit, following the lesson learned in #126/#127 that filters are complete autonomous actors.

Fix 1 — invoke-filter-chain space-split bug

Filter paths with arguments were silently broken — split on whitespace, arguments dropped. Fixed via a probe-file heuristic: if every whitespace token is an existing file, treat as a legacy multi-filter list (backward compatible); otherwise treat as one command with arguments preserved.

Also found and fixed a related bug in main.lisp: both filter call sites independently pre-wrapped the configured value in (list value), bypassing the new disambiguation logic entirely. Removed the redundant wrapping.

Fix 2 — procmail-gen.lisp hanging TODO

cmd-install-procmail/cmd-install-bugs-procmail now use the existing DSL (list-recipes/bugs-recipes + write-procmail-recipes) instead of ad-hoc format strings. Removed ~50 lines of dead orphaned code left from a prior partial edit.

Fix 3 — header parsing duplication

read-message-headers now delegates to parse-headers (the canonical RFC 5322 parser) instead of a colon-position reimplementation with no folded-header support.

Fix 4 — mta.lisp docstring

Corrected to accurately describe contents (audit log, sendmail, RFC 2369 headers, subscriber lifecycle) rather than implying pure MTA concerns.

Fix 5 — pipe-through-command/invoke-single-filter DRY

Extracted with-temp-io-files macro (accepts &body) for the shared temp-file lifecycle. Both functions now share run-shell-command.

Fix 6 — render-template token substitution

Implemented substitute-tokens/substitute-sexp-tokens. Templates can now use {{list-address}}, {{list-name}}, {{list-id}} instead of hardcoding per-list values.

Verification: 480/480 (402 BATS + 78 FiveAM)

All existing specs (PM-1..25, PMG-1..8, BUG-1..33, FLT-1..6) pass unchanged against the refactored code. New FLT-7/FLT-8 cover the argument-preservation fix.

denzuko added 2 commits June 20, 2026 01:42
…titution

Six fixes from a codebase-wide milter/plugin architecture audit.

## Fix 1 -- invoke-filter-chain space-split bug

Filter paths with arguments (e.g. '/path/neural.sh --model llama3')
were silently broken: the chain split on whitespace and probe-file
checked each token as a separate filter, dropping the arguments.

Fix: invoke-filter-chain now disambiguates via a probe-file heuristic.
A string where every whitespace token is an existing file path is
treated as a legacy space-separated multi-filter list (backward
compatible). Otherwise the string is one shell command with arguments,
passed verbatim to sh -c.

Also found and fixed a related bug in src/main.lisp: both the
pre-filter and post-filter call sites independently wrapped the
:pre-filter/:post-filter value in (list value) BEFORE calling
invoke-filter-chain, which made the value always satisfy listp and
bypassed the new string-disambiguation logic entirely. Removed the
redundant wrapping at both call sites -- invoke-filter-chain now
receives the raw configured value and handles all three cases
(nil/string/list) itself.

## Fix 2 -- procmail-gen.lisp hanging TODO

cmd-install-procmail and cmd-install-bugs-procmail each had ad-hoc
format-string recipe generation duplicating the procmail-gen.lisp DSL.
The file header self-documented this as 'not yet refactored.'

Added list-recipes/bugs-recipes builder functions to procmail-gen.lisp
returning :recipe plists. Both admin commands now build recipes via
these builders and write via the existing write-procmail-recipes.
Removed the old procmail-recipe/procmailrc-has-list-p ad-hoc functions
and ~50 lines of dead orphaned code left over from a prior partial
edit in this session. TODO comment removed from procmail-gen.lisp.

## Fix 3 -- header parsing duplication

requests.lisp's read-message-headers re-implemented colon-position
header parsing (no folded-header support) instead of using parser.lisp's
parse-headers (the canonical RFC 5322 parser already used elsewhere).
Now delegates to parse-headers. filters.lisp's string-to-message was
already rewritten this session to delegate to parse-headers as well.

bounce.lisp's DSN Final-Recipient:/X-Failed-Recipients: parsing and
bugs.lisp's pseudo-header (Severity:/Tags:/Owner:) extraction are
narrower, semantically distinct patterns -- not RFC 5322 header
parsing -- and correctly remain separate.

## Fix 4 -- mta.lisp misleading docstring

File contains audit logging, sendmail delivery, RFC 2369 headers, and
subscriber lifecycle handlers -- none of which are strictly 'MTA'
concerns. Renaming the file is too invasive for this PR; docstring
corrected to accurately describe contents instead.

## Fix 5 -- pipe-through-command / invoke-single-filter DRY

Both functions wrote stdin to a temp file, ran sh -c 'cmd < in > out',
read stdout, cleaned up -- ~40 lines duplicated almost verbatim.
Extracted with-temp-io-files macro (accepts &body) handling the temp
file lifecycle. Both functions now call shared run-shell-command,
which uses the macro. Security-boundary distinction (admin-generated
vs untrusted-email input) preserved in docstrings only.

## Fix 6 -- render-template extra-bindings TODO

(declare (ignore extra-bindings)) ; future: token substitution

troff.lisp's templates/*.sexp files hardcode list addresses/names per
file rather than using a substitution mechanism. Implemented:

  substitute-tokens (str bindings)      -- {{key}} -> value in a string
  substitute-sexp-tokens (form bindings) -- recursive over DSL sexp tree

render-template now builds default bindings from list state
({{list-address}}, {{list-name}}, {{list-id}}), merges with caller
extra-bindings (caller bindings take precedence), and substitutes
before compiling to troff. Existing templates are unaffected (no
{{tokens}} present yet) -- this is purely additive.

## Verification

FiveAM: 78/78 (53+25)
BATS:   402/402 (400 baseline + FLT-7/FLT-8 new specs for the
        argument-preservation fix)
All existing PM-1..25 (install-procmail), PMG-1..8 (procmail-gen
binary), BUG-1..33 (mlisp-bugs incl. BUG-29 procmail recipes),
FLT-1..6 (existing filter pipeline specs) pass unchanged against
the refactored code -- confirms backward compatibility.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant