1
0
mirror of https://github.com/jhillyerd/inbucket.git synced 2026-10-29 13:42:30 +00:00

feat(luahost): Lua-controlled settings and decoded header access (#630)

* feat(luahost): capture config settings set by Lua scripts

Add inbucket.config, a Lua-settable settings namespace, with its first
field decode_headers registering additional message headers for Inbucket
to decode.  Values are captured once at startup from the first pooled
LState and exposed to the assembly via Host.ScriptConfig().

Settings that only have value to the Lua script belong here rather than
in env config.  No consumers of decode_headers yet; that follows.

* feat(message): expose decoded headers to extensions

StoreManager gains ExtraDecodeHeaders, passed through to
enmime.DecodeHeaders, and the decoded header set (defaults plus any
registered extras) is attached to the InboundMessage event as
Headers (net/textproto.MIMEHeader).

Lua scripts access it via the read-only inbound_message.header field,
added with the new inbound_header binding.  Lookups are exact-then-
case-insensitive, since enmime canonicalizes keys with email-specific
rules that differ from net/textproto.

FullAssembly wires ScriptConfig().DecodeHeaders into StoreManager.

Part of #627.

* docs(extension): document shared and untrusted header semantics

Reviewer note: BeforeMessageStored listeners receive the same decoded
header map, so a Go listener's mutations are visible to later listeners.
Also record that sender-supplied header values (Authentication-Results,
X-Spam-Status) are untrusted unless the deployment controls provenance.

Part of #627.

---------

Co-authored-by: hillyerd-bot <hillyerd-bot@users.noreply.github.com>
This commit is contained in:
James Hillyerd
2026-10-23 15:38:37 -07:00
committed by GitHub
parent 59ecd449b6
commit bd4cc4b842
15 changed files with 498 additions and 5 deletions
+27 -3
View File
@@ -21,9 +21,10 @@ var ErrNoScript error = errors.New("no script file present")
// Host of Lua extensions.
type Host struct {
extHost *extension.Host
pool *statePool
logContext zerolog.Context
extHost *extension.Host
pool *statePool
logContext zerolog.Context
scriptConfig InbucketConfig
}
// New constructs a new Lua Host, pre-compiling the source.
@@ -74,6 +75,7 @@ func NewFromReader(logger zerolog.Logger, extHost *extension.Host, r io.Reader,
h := &Host{extHost: extHost, pool: pool, logContext: logger.With()}
if ls, err := pool.getState(); err == nil {
h.wireFunctions(startLogger, ls)
h.readScriptConfig(startLogger, ls)
// State creation works, put it back.
pool.putState(ls)
@@ -90,6 +92,28 @@ func (h *Host) CreateChannel(name string) chan lua.LValue {
return h.pool.createChannel(name)
}
// ScriptConfig returns the Inbucket settings set by the Lua script at startup.
func (h *Host) ScriptConfig() InbucketConfig {
return h.scriptConfig
}
// readScriptConfig captures the settings the script set on `inbucket.config`.
// The script is re-run for each additional pooled LState, but config is only
// captured here, from the first; scripts must set it deterministically.
func (h *Host) readScriptConfig(logger zerolog.Logger, ls *lua.LState) {
ib, err := getInbucket(ls)
if err != nil {
logger.Fatal().Err(err).Msg("Failed to get inbucket global")
return
}
h.scriptConfig = ib.Config
if len(h.scriptConfig.DecodeHeaders) > 0 {
logger.Info().Strs("headers", h.scriptConfig.DecodeHeaders).
Msg("Captured Lua script config")
}
}
// Detects global lua event listener functions and wires them up.
func (h *Host) wireFunctions(logger zerolog.Logger, ls *lua.LState) {
ib, err := getInbucket(ls)