Safe Buffer
Buffer-safety primitives that centralize the input-normalize, capped-collection, and secure-zero patterns previously scattered across parsers, atomic-file, object-store, and log-stream modules.
The safety guarantees the family enforces:
1. Type-discriminated input — every helper accepts the exact set of byte-shaped inputs it documents (Buffer / Uint8Array / string) and throws on anything else, instead of silently coercing undefined to "undefined" or letting an Object slip through Buffer.from.
2. Caller-supplied byte cap enforced BEFORE allocation. Numeric maxBytes opts are validated as positive finite integers — Infinity, NaN, and negative values throw at config time rather than disabling the cap. The bounded-chunk collector checks the running total on every push so a hostile 10-GB upstream rejects on the chunk that overflows, not after accumulating the full payload in memory.
3. UTF-8 BOM (U+FEFF) stripped by default in normalizeText so Windows-authored config files don't break downstream parsers that don't expect a leading BOM.
4. Best-effort secret hygiene via secureZero — buf.fill(0) clears the visible Buffer so a heap-dump won't show the secret in that allocation. JavaScript can't guarantee zeroing across V8 copies, but the in-buffer reference is gone.
5. Format-aware error classes. Each call site (xml-safe, json-safe, atomic-file, …) passes its own errorClass so the byte-handling lives here but the thrown error matches the caller's contract (e.code === "xml/too-large" etc.). A default SafeBufferError is used when no class is supplied.
The byte-shape predicates (isHex, isBase64, isBase64Url, isIpv6Hextet, isTraceIdHex, isSpanIdHex, isHttpToken, hasCrlf) and the strippers beside them (stripCrlf, foldHeaderText, stripTrailingHspace, quoteString) live alongside the buffer helpers because every caller that bounds bytes also tends to validate the textual shape of those bytes (header tokens, hex digests, JOSE compact serializations, DKIM canonicalization).
Each is a character walk rather than a pattern, and each is a FUNCTION rather than an exported pattern object, so no caller can run the shared alphabet with .replace against an unbounded value.
b.safeBuffer.normalizeText(input, opts?) #
{
maxBytes: number, // optional positive finite int; UTF-8 byte cap
stripBom: boolean, // default true; remove leading U+FEFF
errorClass: Function, // caller-supplied Error subclass for thrown errors
typeCode: string, // default "buffer/wrong-input-type"
sizeCode: string, // default "buffer/too-large"
typeMessage: string, // override the wrong-input-type message
sizeMessage: string, // override the too-large message
}
Normalize a byte-shaped input (string / Buffer / Uint8Array) to a UTF-8 string with the byte cap enforced BEFORE the result is handed back. Anything outside the documented input set throws — null, undefined, plain objects, numbers all reject instead of being coerced via Buffer.from. The leading UTF-8 BOM (U+FEFF) is stripped by default so Windows-authored config files don't break downstream parsers.
Numeric maxBytes is validated as a positive finite integer at call-time — Infinity / NaN / negative throw rather than silently disabling the cap.
var b = require("blamejs");
var s = b.safeBuffer.normalizeText(Buffer.from("hello"));
// → "hello"
// BOM stripped by default.
var bom = Buffer.from([0xEF, 0xBB, 0xBF, 0x68, 0x69]);
b.safeBuffer.normalizeText(bom);
// → "hi"
// Non-byte input throws instead of coercing to "undefined".
try { b.safeBuffer.normalizeText(undefined); }
catch (e) { e.code; }
// → "buffer/wrong-input-type"
// maxBytes enforced; Infinity rejected at config time.
try { b.safeBuffer.normalizeText("xxx", { maxBytes: Infinity }); }
catch (e) { e.code; }
// → "buffer/bad-arg"
b.safeBuffer.toBuffer(data, opts?) #
{
maxBytes: number, // optional positive finite int; byte cap
encoding: string, // string→Buffer encoding. default "utf8" (e.g. "hex", "base64")
allowString: boolean, // accept a string input (coerced via `encoding`). default true;
// false = byte inputs only (Buffer/Uint8Array) — a string throws
// (COSE_Key / mdoc CBOR / DNSSEC bytes are byte-only by spec)
errorClass: Function, // caller-supplied Error subclass; thrown as new Class(message, code)
errorFactory: Function, // (code, message) -> Error; for caller error classes whose
// constructor is (code, message) — sidesteps the errorClass order
typeCode: string, // default "buffer/wrong-input-type"
sizeCode: string, // default "buffer/too-large"
typeMessage: string, // override the wrong-input-type message
sizeMessage: string, // override the too-large message
}
Coerce a byte-shaped input (Buffer / Uint8Array / string) to a Buffer with the byte cap enforced before return. Unlike raw Buffer.from, an Object / number / undefined does NOT slip through — every non-byte input throws with a documented code. Buffer.isBuffer(data) returns the input unchanged (zero copy); Uint8Array is wrapped, string is encoded as UTF-8.
var b = require("blamejs");
var buf = b.safeBuffer.toBuffer("hello");
buf.length;
// → 5
// Buffer passes through unchanged (zero copy).
var input = Buffer.from([1, 2, 3]);
b.safeBuffer.toBuffer(input) === input;
// → true
// Object input throws instead of coercing.
try { b.safeBuffer.toBuffer({ not: "bytes" }); }
catch (e) { e.code; }
// → "buffer/wrong-input-type"
// maxBytes cap.
try { b.safeBuffer.toBuffer("abcdef", { maxBytes: 3 }); }
catch (e) { e.code; }
// → "buffer/too-large"
b.safeBuffer.makeByteCoercer(opts) #
{
errorClass: Function, // required — (code, message) error constructor
typeCode: string, // required — error code on a type mismatch
messagePrefix: string, // text before `what`. default: ""
messageSuffix: string, // text after `what`. default: ""
allowString: boolean, // forwarded to toBuffer. default: true
encoding: string, // forwarded to toBuffer. default: "utf8"
}
Bind toBuffer to one module's error contract, returning a coerce(value, what) that validates value is a byte input (with the module's allowString / encoding policy) and, on a type mismatch, throws the module's own error class with a per-field message messagePrefix + what + messageSuffix. The mirror of b.audit.namespaced / b.observability.namespaced for the byte-input boundary: each module bound toBuffer to its error class + code + message template in a hand-rolled function _bytes(x, what) { return toBuffer(x, { errorFactory: (c, m) => new XError(c, m), … }); } wrapper — this owns that binding once.
what names the field being coerced ("issuerAuth", "x coordinate") and is interpolated into the message so one coercer serves every call site in a module. The byte-mode is whatever toBuffer accepts: allowString: false for strict byte-only inputs (COSE / mdoc / DNSSEC wire data), or encoding ("hex" / "base64") for modules that accept an encoded string alongside raw bytes.
var b = require("blamejs");
function DnssecError(code, msg) { this.code = code; this.message = msg; }
var toBytes = b.safeBuffer.makeByteCoercer({
errorClass: DnssecError,
typeCode: "dnssec/bad-bytes",
messagePrefix: "dnssec: ",
messageSuffix: " must be a Buffer",
allowString: false,
});
toBytes(Buffer.from([1, 2]), "RRSIG"); // →
// toBytes("nope", "RRSIG") throws DnssecError("dnssec/bad-bytes",
// "dnssec: RRSIG must be a Buffer")
b.safeBuffer.byteLengthOf(value, encoding?) #
The byte length of a string OR a byte container, measured correctly for either. A String's .length counts UTF-16 code units, NOT bytes — comparing it to a cap named in bytes under-enforces the cap on multibyte input (a 2-4 byte character counts as 1, so the real ceiling is up to ~4x the configured limit). This primitive returns Buffer.byteLength(value, encoding) for a string and value.length for a Buffer / Uint8Array (whose .length already IS the byte count), so a byte cap is enforced the same way regardless of whether the value arrived decoded or raw. Route every byte-cap comparison through it instead of value.length > someBytesCap.
Throws TypeError on any other type (a defensive net — callers type-check their input before measuring).
var b = require("blamejs");
b.safeBuffer.byteLengthOf("a"); // → 1
b.safeBuffer.byteLengthOf("中"); // → 3 (one CJK char, 3 UTF-8 bytes)
"中".length; // → 1 (UTF-16 code units — the trap)
b.safeBuffer.byteLengthOf(Buffer.from([1, 2, 3])); // → 3
b.safeBuffer.byteLengthOfIfMeasurable(value) #
Like byteLengthOf, but returns null for a value that is not a measurable byte-carrier (a plain Array, an array-like object with a numeric .length, a number, ...) instead of throwing.
For capping the size of an UNTRUSTED metadata bag whose byte field may be any shape: a content guard measures its cap only when the value is measurable and treats an unmeasurable value as uncapped-here — its magic/shape inspection reads only the leading bytes, so it is O(1)-bounded regardless of a claimed .length — rather than throwing out of its documented never-throw-on-hostile-metadata inspection contract. Route a hostile-metadata byte cap through this instead of gating byteLengthOf on a hand-rolled typeof x.length === "number" check (which admits array-likes and crashes byteLengthOf).
var b = require("blamejs");
b.safeBuffer.byteLengthOfIfMeasurable("abc"); // → 3
b.safeBuffer.byteLengthOfIfMeasurable([1, 2, 3]); // → null (a plain Array)
b.safeBuffer.byteLengthOfIfMeasurable({ length: 1e9 }); // → null (array-like)
b.safeBuffer.boundedChunkCollector(opts) #
{
maxBytes: number, // REQUIRED positive finite int; total byte cap
errorClass: Function, // caller-supplied Error subclass
sizeCode: string, // default "buffer/too-large"
sizeMessage: string, // override the too-large message
}
Streaming-body collector that enforces maxBytes at every push() — never after. A hostile upstream sending a 10-GB response rejects on the chunk that overflows the cap, instead of accumulating the full 10 GB in memory before the framework discovers the problem.
maxBytes is REQUIRED (positive finite integer). Infinity is rejected at construction because it defeats the entire purpose of the bounded collector. Each push() accepts Buffer / Uint8Array / string; non-byte chunks throw.
Returns { push, result, bytesCollected }. Call result() when the stream ends to get the concatenated Buffer.
var b = require("blamejs");
var c = b.safeBuffer.boundedChunkCollector({ maxBytes: 1024 });
c.push(Buffer.from("hello "));
c.push(Buffer.from("world"));
c.bytesCollected();
// → 11
c.result().toString("utf8");
// → "hello world"
// Cap enforced at push, not at result().
var c2 = b.safeBuffer.boundedChunkCollector({ maxBytes: 4 });
c2.push(Buffer.from("abc"));
try { c2.push(Buffer.from("defgh")); }
catch (e) { e.code; }
// → "buffer/too-large"
// Infinity rejected at construction.
try { b.safeBuffer.boundedChunkCollector({ maxBytes: Infinity }); }
catch (e) { e.code; }
// → "buffer/bad-arg"
b.safeBuffer.collectStream(stream, opts) #
{
maxBytes: number, // REQUIRED positive finite int; total byte cap
errorClass: Function, // caller Error subclass for the too-large reject
sizeCode: string, // default "buffer/too-large"
sizeMessage: string, // override the too-large message
}
Read a Node Readable (an http.IncomingMessage request body, a file stream, an upstream response) fully into one Buffer with the byte cap enforced at every chunk — the streaming sibling of boundedChunkCollector. boundedChunkCollector is a push-based collector object; collectStream is the pump around it, so callers compose the stream case instead of reaching for a (stream, opts) overload that does not exist.
Resolves with the concatenated Buffer when the stream ends. Rejects (and destroys the stream) the moment a chunk would overflow maxBytes, so a hostile sender cannot force unbounded buffering. A bad maxBytes (missing / non-finite / Infinity) rejects rather than throwing synchronously.
var body = await b.safeBuffer.collectStream(req, { maxBytes: 65536 });
var json = b.safeJson.parse(body.toString("utf8"));
// → the parsed request body, never more than 64 KiB buffered
b.safeBuffer.secureZero(buf) #
Best-effort secret hygiene. buf.fill(0) clears the visible Buffer / Uint8Array so a heap-dump won't show the secret in that allocation. JavaScript can't guarantee zeroing across V8 internal copies (string interning, JIT-spilled registers), but the in-buffer reference is gone and that's the only handle the framework can reliably wipe.
Silently no-ops on non-byte inputs and on locked / shared buffers that throw on .fill — the caller's contract is "I'm done with this", not "guarantee zeroing succeeded." Pair with Buffer allocations whose lifetime is short and well-scoped.
var b = require("blamejs");
var key = Buffer.from("super-secret-key");
// ... use key ...
b.safeBuffer.secureZero(key);
key[0];
// → 0
// No-op on non-byte input.
b.safeBuffer.secureZero("a string");
// → undefined
b.safeBuffer.stripTrailingHspace(s) #
Strip trailing horizontal whitespace (spaces and tabs only) from a string — the "rstrip" semantic used by DKIM canonicalization (RFC 6376 §3.4.4 relaxed body), .env parsers, and YAML scalar readers. Does NOT touch CR / LF — pair with stripCrlf when you need full whitespace stripping. Non-string input passes through unchanged so the helper is safe in mixed pipelines.
var b = require("blamejs");
b.safeBuffer.stripTrailingHspace("hello ");
// → "hello"
// Tabs stripped too; internal whitespace preserved.
b.safeBuffer.stripTrailingHspace("a b\t\t");
// → "a b"
// CR / LF intentionally preserved.
b.safeBuffer.stripTrailingHspace("hello \n");
// → "hello \n"
// Non-string passthrough.
b.safeBuffer.stripTrailingHspace(42);
// → 42
b.safeBuffer.stripLeadingHspace(s) #
Strip leading spaces and tabs — the "lstrip" half of the pair, and the step every RFC 5322 header continuation, .env line and YAML scalar takes before it reads a value. CR and LF are left alone, so a folded header keeps the break that folded it; pair with stripCrlf when that has to go too. Non-string input passes through unchanged.
b.safeBuffer.stripLeadingHspace(" value"); // → "value"
b.safeBuffer.stripLeadingHspace("\n value"); // → "\n value"
b.safeBuffer.indexAfterOpenTag(html, tagName) #
Find the offset in html just past the first opening tag (case-insensitive), or -1 when the tag is absent or unterminated. The insertion point a response rewriter uses to splice content right after / without a regex.
This replaces the html.match(/]*>/i) shape, which is O(n^2) in V8: a body carrying many starts with no closing > (e.g. rendered user content) makes the engine retry the greedy [^>]* from every offset — a -repeated 200K-char body benchmarks in seconds. This is a single forward indexOf walk: linear in the input, and stricter than the regex — it requires a real tag boundary after the name (whitespace, >, or /), so is not mistaken for . Non-string input returns -1.
var b = require("blamejs");
b.safeBuffer.indexAfterOpenTag("hi", "body");
// → 20 (just past the '>' of )
b.safeBuffer.indexAfterOpenTag("no body here
", "body");
// → -1
b.safeBuffer.isHex(s, expectedLength?) #
Predicate for non-empty all-hex strings (case-insensitive). Pass expectedLength to bound the protocol-fixed digests — SHA3-512 is 128 hex chars, SHA-256 is 64, etc. Without expectedLength the predicate is length-agnostic and the caller is responsible for bounding length per protocol (X.509 serial, DKIM hash, audit-chain digest).
Non-string input returns false so the helper is safe in defensive request-shape readers.
var b = require("blamejs");
b.safeBuffer.isHex("deadbeef");
// → true
// Length-bounded check (SHA-256 = 64 hex chars).
b.safeBuffer.isHex("deadbeef", 64);
// → false
// Mixed case accepted.
b.safeBuffer.isHex("DeadBeef");
// → true
// Non-string returns false.
b.safeBuffer.isHex(null);
// → false
b.safeBuffer.isBase64Url(s) #
Is s a non-empty unpadded base64url string (RFC 4648 §5)? The alphabet every JOSE compact serialization, DPoP jti, and WebAuthn credential ID is spelled in. Length-agnostic — the caller caps length per its protocol. Non-string input returns false.
b.safeBuffer.isBase64Url("eyJhbGciOiJFZERTQSJ9"); // → true
b.safeBuffer.isBase64Url("a+b/c="); // → false
b.safeBuffer.isBase64(s) #
Is s a standard base64 string (RFC 4648 §4) — the + / / alphabet with up to two trailing = of padding? The empty string qualifies, matching the fields this screens (a backup-manifest digest, a CloudEvents data_base64, an RFC 8941 sf-binary). It checks the ALPHABET and the padding position, not that the length is a multiple of four — a caller that requires canonical encoding checks that itself. Non-string input returns false.
b.safeBuffer.isBase64("aGVsbG8="); // → true
b.safeBuffer.isBase64("a=b"); // → false
b.safeBuffer.isCanonicalBase64(s) #
Is s CANONICAL standard base64 — whole four-character groups, with a final group of two or three characters padded out to four? Stricter than isBase64, which checks the alphabet and where the padding sits but not the grouping.
Canonical also means the bits that encode nothing are zero (RFC 4648 §3.5). A padded group carries fewer bits than its characters can express — two characters carry one byte, so four bits are spare, and three carry two, so two are — and a decoder DISCARDS them. AB== and AA== therefore decode to the same single byte, which is a second spelling of the same value: exactly what a canonical encoding is supposed to rule out.
Use this wherever the value came off a wire that specifies the encoding — an RFC 8941 sf-binary, a CloudEvents data_base64. Node's decoder is permissive: it DROPS characters it cannot use rather than failing, so :A: decodes to nothing at all and the caller sees a verification failure where the truth is a malformed field. Deciding that here means the caller can say which of the two happened.
b.safeBuffer.isCanonicalBase64("aGVsbG8="); // → true
b.safeBuffer.isCanonicalBase64("A"); // → false
b.safeBuffer.isCanonicalBase64("AB=="); // → false
b.safeBuffer.isIpv6Hextet(s) #
Is s one IPv6 hextet — 1 to 4 hexadecimal characters in either case (RFC 4291 §2.2)? The per-group check every parser that splits an address on : applies to each piece. Non-string input returns false.
b.safeBuffer.isIpv6Hextet("db8"); // → true
b.safeBuffer.isIpv6Hextet("db8f0"); // → false
b.safeBuffer.isLowerHex(s, expectedLength?) #
Is s non-empty LOWER-case hexadecimal, and — when expectedLength is given — exactly that many characters? Case matters wherever a wire format fixes the encoding: a W3C trace-context id, a git object name, a lower-case digest a peer will compare byte for byte. isHex is the case-insensitive sibling for the many places that accept either.
b.safeBuffer.isLowerHex("00f067aa0ba902b7", 16); // → true
b.safeBuffer.isLowerHex("00F067AA0BA902B7", 16); // → false
b.safeBuffer.isTraceIdHex(s) #
Is s a W3C trace-context trace-id — exactly 32 lower-case hexadecimal characters (trace-context §3.2.2.3)? The all-zero id is a valid SHAPE and an invalid VALUE; callers that propagate a trace reject it separately.
b.safeBuffer.isTraceIdHex("4bf92f3577b34da6a3ce929d0e0e4736"); // → true
b.safeBuffer.isSpanIdHex(s) #
Is s a W3C trace-context span / parent id — exactly 16 lower-case hexadecimal characters (trace-context §3.2.2.4)?
b.safeBuffer.isSpanIdHex("00f067aa0ba902b7"); // → true
b.safeBuffer.isHttpToken(s) #
Is s a non-empty RFC 9110 §5.6.2 token — a run of tchar? The grammar an HTTP header field name, a MIME parameter name, a W3C Baggage key, and a cache-directive name all share. Length-agnostic — the caller caps length per its protocol. Non-string input returns false.
b.safeBuffer.isHttpToken("x-request-id"); // → true
b.safeBuffer.isHttpToken("bad header"); // → false
b.safeBuffer.hasCrlf(s) #
Detect CR or LF in a string — the canonical injection vector for HTTP-header / SMTP-envelope smuggling. Header values containing CR or LF must be rejected before serialization or stripped via stripCrlf. Non-string input returns false so callers can chain the predicate without pre-typechecking.
var b = require("blamejs");
b.safeBuffer.hasCrlf("X-Custom-Header: ok");
// → false
// Injection attempt.
b.safeBuffer.hasCrlf("ok\r\nX-Injected: bad");
// → true
// Bare LF also detected.
b.safeBuffer.hasCrlf("ok\nbad");
// → true
// Non-string returns false.
b.safeBuffer.hasCrlf(undefined);
// → false
b.safeBuffer.hasCrlfOrNul(s) #
Detect CR, LF or NUL in a string. A header field carries all three as one class of problem: CR and LF end the field, and NUL truncates it in any consumer that hands the value on to C. A multipart part header parsed out of a body, and one about to be serialized into one, both refuse on this predicate.
Non-string input returns false, so the predicate chains without a pre-typecheck, exactly as hasCrlf does — a caller that will COERCE the value into a header must refuse a non-string itself first.
var b = require("blamejs");
b.safeBuffer.hasCrlfOrNul("filename.txt");
// → false
b.safeBuffer.hasCrlfOrNul("ok\r\nX-Injected: bad");
// → true
// A NUL truncates the value wherever it reaches C.
b.safeBuffer.hasCrlfOrNul("ok" + String.fromCharCode(0) + "hidden");
// → true
b.safeBuffer.stripCrlf(s, replacement?) #
Remove every CR and LF from a string, replacing each with the replacement argument (default ""). Use this when the framework must serialize an operator-supplied string into a CRLF-delimited protocol (HTTP header value, SMTP envelope field) and prefers silent stripping over rejecting the request — most security- critical sites should use hasCrlf + reject instead.
Non-string input passes through unchanged.
var b = require("blamejs");
b.safeBuffer.stripCrlf("ok\r\nbad");
// → "okbad"
// Custom replacement (e.g. space).
b.safeBuffer.stripCrlf("a\nb\nc", " ");
// → "a b c"
// Non-string passthrough.
b.safeBuffer.stripCrlf(42);
// → 42
b.safeBuffer.foldHeaderText(value, replacement?) #
Neutralize free-text bound for a CRLF-delimited protocol line: replace every CR and LF with replacement (default a single space) so the text folds onto one line, AND remove every NUL byte. Use this for a value that may LEGITIMATELY wrap — a multi-line SMTP 5xx reply folded into one diagnostic line — where assertHeaderSafe (reject) would be too strict. Unlike bare stripCrlf, this also strips NUL, which is never valid in an RFC 5322 header value and which downstream SMTP / mail parsers treat specially. Non-string input passes through unchanged.
b.safeBuffer.foldHeaderText("550 mailbox full\nX-Injected: evil");
// → "550 mailbox full X-Injected: evil"
b.safeBuffer.foldHeaderText("550 mailbox full\r\nX-Injected: evil");
// → "550 mailbox full X-Injected: evil"
b.safeBuffer.assertHeaderSafe(value, label, ErrorClass, code) #
Throw when a string bound for a CRLF-delimited protocol line — an SMTP / RFC 5322 header value, an HTTP header — contains CR, LF, or a NUL byte, the canonical header-injection / smuggling vector. Route every Name: value\r\n builder's STRUCTURED fields (addresses, domains, identifiers, MTA names) through this; they can never legitimately carry those bytes. For free-text that may legitimately wrap (a multi-line SMTP reply folded into one diagnostic line), fold it with stripCrlf instead of rejecting. Throws new ErrorClass(code, ...) so each caller reports in its own error domain (the validateOpts convention). A non-string value passes through untouched — callers type-check separately.
b.safeBuffer.assertHeaderSafe("rcpt@example.com", "to", MailError, "mail/bad-header");
// → "rcpt@example.com"
b.safeBuffer.assertHeaderSafe("rcpt\r\nBcc: evil@x", "to", MailError, "mail/bad-header");
// → throws MailError("mail/bad-header")
b.safeBuffer.quoteString(s) #
Serialize a value as an RFC quoted-string: coerce to string, escape every backslash and DQUOTE with a leading backslash, and wrap the result in DQUOTEs. One serializer for the quoted-string grammars the framework emits — RFC 8941 §3.3.3 Structured Fields sf-string (Cache-Status, Signature-Input, Server-Timing desc), RFC 8288 Link header parameters, RFC 8601 §2.2 Authentication-Results reason, RFC 3501 §4.3 IMAP quoted strings, and RFC 5804 §1.2 ManageSieve strings — so an unescaped quote can never terminate the string early and smuggle extra parameters into the protocol line.
Escaping only — it does not validate a grammar's character range. A grammar that forbids bytes a quoted-string cannot carry (sf-string is printable-ASCII only; IMAP quoted strings cannot carry CR / LF) enforces its range check before calling this.
var b = require("blamejs");
b.safeBuffer.quoteString("cache miss");
// → "\"cache miss\""
// A quote or backslash in the value cannot break out of the string.
b.safeBuffer.quoteString('say "hi"');
// → "\"say \\\"hi\\\"\""
b.safeBuffer.previewText(value, opts?) #
{
maxChars: number, // default: 64
}
Render a piece of untrusted input for a refusal message: printable US-ASCII passes through, every other code point becomes \u{...}, and the result is capped (64 characters by default) with ... when it was cut.
A parser that quotes the document it refused is quoting bytes a stranger chose. ESC [ 2 K is erase-in-line, so a consumer writing the message to a terminal loses the line it printed beside, and U+202E reverses the remainder of the sentence an operator is reading. A consumer cannot screen the message afterwards, because by then the parser has decided what the sentence says.
var b = require("blamejs");
var esc = String.fromCharCode(0x1b);
b.safeBuffer.previewText("X-Probe" + esc + "[2K");
// → "X-Probe\\u{1b}[2K"
b.safeBuffer.readQuotedString(input, at?) #
Read one operand of the quoted-string grammar b.safeBuffer.quoteString writes: a DQUOTE-wrapped run whose backslash escapes are undone, or a bare run up to the next space. Leading spaces are skipped. Returns { value, next }, where next is the offset just past the operand, so a caller reads a line of operands by feeding next back in.
Returns null when there is no operand to read, and when a quoted run is unterminated or ends on a dangling backslash. An unterminated quote means the sender's framing and the reader's disagree, and guessing where the string ends is how one side reads an argument the other did not send.
The reader lives beside the writer so the two cannot drift apart: a consumer that unquotes differently from the code that quoted disagrees with it about what an argument says.
var b = require("blamejs");
b.safeBuffer.readQuotedString('"two words" after');
// → { value: "two words", next: 11 }
b.safeBuffer.readQuotedString("INBOX rest");
// → { value: "INBOX", next: 5 }
b.safeBuffer.unquotedIndexOf(input, character, from?) #
Report the offset of the first character that is not inside a quoted string, starting at from (default 0), or -1 when there is none. A backslash escapes the character after it, inside a quoted run and outside one alike.
A delimiter that also appears inside quotes is the shape behind the header and command readers that cut an address short: <"a>b"@example.com> ends at the second >, not the first. Searching for the raw character finds the one the sender wrote as data.
Returns -1 for a character that is empty, a DQUOTE or a backslash, since those are the grammar itself rather than a delimiter within it.
var b = require("blamejs");
b.safeBuffer.unquotedIndexOf('"a>b"@example.com>', ">");
// → 17