digest(<name>) and hmac(...,<name>) resolved algorithm names with the legacy
EVP_get_digestbyname(). On OpenSSL 3.0 that does not resolve hyphenated aliases
(e.g. "sha-1") until the default provider has been lazily loaded by an earlier
successful digest. In threaded netmux that warm-up straddles TC005's two cand()
branches, so smoke went red 4/4 on OpenSSL 3.0.13 (Debian 12 / Ubuntu 22.04 /
RHEL 9), while newer OpenSSL (3.6.2) resolves the alias cold and passes.
Use the provider-native EVP_MD_fetch(NULL, name, NULL) on OpenSSL 3.0+
(non-LibreSSL), which resolves aliases deterministically from a cold process,
and free the fetched EVP_MD. Keep EVP_get_digestbyname() on pre-3.0 / LibreSSL,
where it returns a static const and the lazy-provider behavior does not occur.
Gated on OPENSSL_VERSION_NUMBER rather than AC_CHECK_FUNCS to avoid regenerating
configure with autoconf 2.71 vs the tree's required 2.73 (#1477); EVP_MD_fetch
is inherently a 3.0 API so the version guard is semantically exact.
Also fix an EVP_MD_CTX leak in fun_digest on the unsupported-name path (it
returned after EVP_MD_CTX_new() without freeing the context).
Verified on OpenSSL 3.0.13 (Kagura): reverting just these two files gives
TC005 FAIL 3/3; with the fix TC005 PASS 4/4 and full smoke ALL 1588 PASSED.
digest(sha-1)/hmac(...,sha-1) resolve from a cold process; sha_1/bogus still
rejected. Needs 3.6.2 no-regression confirmation on the box that already passes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mechanical hygiene under the opt-in M_() design: replace T("#-1…") and
T("#-2…") with S_() so softcode ABI tokens are obvious in source and
cannot enter a player catalog. ~400 call sites across engine, exp3,
mail, and driver. Assembled/library-spliced diagnostics (plan §4.2)
are unchanged where they are not a single literal.
fun_json's array and object branches walked their space-separated
input with next_token(), which only advances a pointer and never
NUL-terminates the token it just passed. Each element handed to
safe_str() therefore dragged the entire remaining tail with it:
json(array,a b c) produced [a b c,b c,c], and json(object,...) broke
even at a single key-value pair. Both branches now use split_token(),
the in-place-terminating helper every other list-processing function
already uses.
Also fixes a second defect on the object branch's error path: the
ODD NUMBER OF ELEMENTS reset was *bufc = buff, rewinding to the start
of the entire evaluation buffer and silently discarding any output
evaluated before the call. The reset now rewinds to the function's own
entry position.
Extends json_fn.mux TC001 with the array/object construction coverage
whose absence let this go unnoticed: multi-element arrays (bare and
quoted), one- and two-pair objects, an isjson() validity check, and a
prefix-preservation assertion that pins the buffer-reset fix.
Closes#931.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
url_escape() percent-encodes all bytes except unreserved characters
(A-Z, a-z, 0-9, -, _, ., ~). url_unescape() decodes %XX sequences
and treats + as space for form-urlencoded compatibility.
- 3 smoke test cases (435/435 pass)
- Help text for both functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Tier 1 web parity features identified in server surveys (Penn, Rhost):
- encode64()/decode64(): standard base64 encoding/decoding
- hmac(): HMAC via OpenSSL (default sha256, any digest() algo)
- isjson(): native RFC 8259 JSON validator with optional type check
- json(): JSON construction (string/number/boolean/null/array/object)
- json_query(): JSON queries via SQLite JSON1 (type/size/get/keys/values/exists)
- json_mod(): JSON modification via SQLite JSON1 (set/insert/replace/remove/patch)
New file funcweb.cpp in engine.so. 16 smoke tests (427/427 passing).
Help entries for all 7 functions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>