UNPKG

@struktoai/mirage-core

Version:

A Unified Virtual Filesystem For AI Agents

512 lines • 20.3 kB
// ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ========= Copyright 2026 @ Strukto.AI All Rights Reserved. ========= // Ports parts of CPython 3.12's email package (PSF-2.0; see // licenses/third_party/cpython.txt). import { encodeBase64 } from "./base64.js"; // A byte-exact port of the corners of python's email package that the // himalaya builder rides: RFC 2047 encoded words with the q/b length // chooser, the unstructured-header refolder, mailbox display-name // encoding, RFC 2231 mime-parameter folding, and the content manager's // 7bit/8bit/quoted-printable/base64 body selection. Python is the // reference implementation (EmailMessage + policy.SMTP); every rule // here is pinned against it in integ/fixtures/himalaya/mime_parity.json // rather than against the RFCs, because the two builders must agree to // the byte. const MAXLEN = 78; const EW_CHROME = 'utf-8'.length + 7; const ENC = new TextEncoder(); /** A latin-1 view of raw bytes, for the quoted-printable scanner. */ function latin1(bytes) { let out = ''; const chunk = 0x8000; for (let i = 0; i < bytes.length; i += chunk) { out += String.fromCharCode(...bytes.subarray(i, i + chunk)); } return out; } /** True when every character is 7-bit ASCII. */ function isAscii(value) { // eslint-disable-next-line no-control-regex return !/[^\x00-\x7f]/.test(value); } const Q_SAFE = new Set(); for (const c of '-!*+/abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789') { Q_SAFE.add(c.charCodeAt(0)); } function hex2(byte) { return byte.toString(16).toUpperCase().padStart(2, '0'); } function encodeQ(bytes) { let out = ''; for (const byte of bytes) { if (byte === 0x20) out += '_'; else if (Q_SAFE.has(byte)) out += String.fromCharCode(byte); else out += `=${hex2(byte)}`; } return out; } function lenQ(bytes) { let total = 0; for (const byte of bytes) total += byte === 0x20 || Q_SAFE.has(byte) ? 1 : 3; return total; } function lenB(bytes) { const groups = Math.floor(bytes.length / 3); return groups * 4 + (bytes.length % 3 === 0 ? 0 : 4); } /** * One RFC 2047 encoded word, choosing the shorter transfer encoding * with python's bias: q wins unless b is at least five characters * shorter (email._encoded_words.encode). */ function encodeWord(text) { const bytes = ENC.encode(text); const encoding = lenQ(bytes) - lenB(bytes) < 5 ? 'q' : 'b'; const encoded = encoding === 'q' ? encodeQ(bytes) : encodeBase64(bytes); return `=?utf-8?${encoding}?${encoded}?=`; } /** * Split into runs of linear whitespace and everything else. A phrase * (an address display name) also splits at dots, which the address * grammar lexes as their own terminals, so a dot stays plain next to * an encoded word ('J. =?utf-8?q?B=C3=B6b?='). */ function tokenize(value, phrase) { const tokens = []; const splitter = phrase ? /([ \t]+|\.)/ : /([ \t]+)/; for (const piece of value.split(splitter)) { if (piece === '') continue; tokens.push({ text: piece, fws: piece.startsWith(' ') || piece.startsWith('\t') }); } return tokens; } function stealTrailingWsp(lines) { const last = lines[lines.length - 1] ?? ''; const tail = last.slice(-1); if (tail !== ' ' && tail !== '\t') return ''; lines[lines.length - 1] = last.slice(0, -1); return tail; } /** Append to the last line in place; folding always has one. */ function appendLast(lines, text) { lines[lines.length - 1] = (lines[lines.length - 1] ?? '') + text; } /** * email._header_value_parser._fold_as_ew: write to_encode as encoded * words, merging with the encoded span already on the line and * splitting into per-line chunks that fit. */ function foldAsEw(toEncode, lines, maxlen, state) { if (state.lastEw !== null) { toEncode = state.spanPlain + toEncode; lines[lines.length - 1] = (lines[lines.length - 1] ?? '').slice(0, state.lastEw); } else if (toEncode.startsWith(' ') || toEncode.startsWith('\t')) { const leadingWsp = toEncode[0] ?? ''; toEncode = toEncode.slice(1); if ((lines[lines.length - 1] ?? '').length === maxlen) { lines.push(stealTrailingWsp(lines)); } appendLast(lines, leadingWsp); } let trailingWsp = ''; const tail = toEncode.slice(-1); if (tail === ' ' || tail === '\t') { trailingWsp = tail; toEncode = toEncode.slice(0, -1); } let newLastEw = state.lastEw ?? (lines[lines.length - 1] ?? '').length; let leadingWs = state.leadingWs; // The decoded text of encoded words already written to the line the // final chunk lands on: a folded whitespace run gets written as its // own encoded word (RFC 2047 drops the plain whitespace between two // encoded words), and a later merge has to see it. let spanPrefix = ''; let lastChunk = ''; // Code points, not UTF-16 units: CPython slices str by code point, // so the chunk budgets must count the same way. let rest = Array.from(toEncode); while (rest.length > 0) { const remainingSpace = maxlen - (lines[lines.length - 1] ?? '').length; const textSpace = remainingSpace - EW_CHROME - leadingWs.length; if (textSpace <= 0) { lines.push(' '); continue; } if (lines.length > 1 && (lines[lines.length - 1] ?? '').length === 1 && leadingWs !== '') { appendLast(lines, encodeWord(leadingWs)); spanPrefix = leadingWs; leadingWs = ''; } let word = rest.slice(0, textSpace); let encodedWord = encodeWord(word.join('')); while (encodedWord.length - remainingSpace > 0) { word = word.slice(0, -1); encodedWord = encodeWord(word.join('')); } appendLast(lines, encodedWord); rest = rest.slice(word.length); leadingWs = ''; lastChunk = word.join(''); if (rest.length > 0) { lines.push(' '); newLastEw = 1; spanPrefix = ''; } } appendLast(lines, trailingWsp); state.lastEw = newLastEw; state.spanPlain = spanPrefix + lastChunk + trailingWsp; state.leadingWs = ''; } /** * email._header_value_parser._refold_parse_tree over the terminals of * an unstructured value: plain runs pack greedily, folds happen at * whitespace, and any run that carries non-ASCII (or a newline, or is * too long to fold plainly) becomes an encoded word. */ function foldTokens(lines, value, maxlen, phrase = false) { const state = { lastEw: null, spanPlain: '', leadingWs: '' }; const queue = tokenize(value, phrase); while (queue.length > 0) { const token = queue.shift(); if (token === undefined) break; const tstr = token.text; const wantEncoding = !isAscii(tstr) || /[\r\n]/.test(tstr); if (wantEncoding) { foldAsEw(tstr, lines, maxlen, state); continue; } if ((lines[lines.length - 1] ?? '').length + tstr.length <= maxlen) { appendLast(lines, tstr); if (state.lastEw !== null) state.spanPlain += tstr; continue; } state.leadingWs = ''; if (tstr.length + 1 <= maxlen) { const newline = stealTrailingWsp(lines); if (newline !== '' || token.fws) { lines.push(newline + tstr); const match = /^[ \t]*/.exec(lines[lines.length - 1] ?? ''); state.leadingWs = match === null ? '' : match[0]; state.lastEw = null; state.spanPlain = ''; continue; } } // Too long to fold as plain text: encode it just to wrap it, which // is what python does with an over-long word. foldAsEw(tstr, lines, maxlen, state); } } // The line boundaries python's str.splitlines recognizes, which is the // set EmailMessage's header guard checks against. // eslint-disable-next-line no-control-regex const HEADER_LINE_BREAKS = /\r\n|[\n\r\v\f\x1c\x1d\x1e\u0085\u2028\u2029]/; /** * Refuse a header value EmailMessage would refuse (header injection). * * Python's check is str.splitlines-based: a value is refused when it * spans more than one line, so a single trailing terminator passes (and * serializes inside an encoded word). */ export function assertHeaderValue(value) { const lines = value.split(HEADER_LINE_BREAKS); if (lines[lines.length - 1] === '') lines.pop(); if (lines.length > 1) { throw new Error('Header values may not contain linefeed or carriage return characters'); } } /** * Fold one unstructured header (Subject, References, In-Reply-To) the * way EmailMessage.as_bytes(policy=SMTP) does, continuation lines and * encoded words included. Returns the header with embedded CRLFs and * no trailing linesep. An empty value renders as `Name:` with no * trailing blank, matching python's empty parse tree. */ export function foldUnstructured(name, value) { const lines = [`${name}:`]; if (value === '') return lines.join('\r\n'); appendLast(lines, ' '); if (isAscii(value) && !/[\r\n]/.test(value)) { if ((lines[lines.length - 1] ?? '').length + value.length <= MAXLEN) { appendLast(lines, value); return lines.join('\r\n'); } if (value.length + 1 <= MAXLEN) { const newline = stealTrailingWsp(lines); if (newline !== '' || value.startsWith(' ') || value.startsWith('\t')) { lines.push(newline + value); return lines.join('\r\n'); } } } foldTokens(lines, value, MAXLEN); return lines.join('\r\n'); } /** * One mailbox as python's address folder renders it when it fits on a * line: ASCII passes through untouched, and a non-ASCII display name * becomes encoded words while the angle-addr and the whitespace run * before it stay plain. * * The input domain is what the CLI can produce: comma-free (the flag * splitter cuts on commas first) and trimmed. A display name that * python's address grammar would tear apart - an unquoted comma from a * parsed source header, say - is malformed on both sides and is not * chased to the byte. */ function renderMailbox(raw) { const value = raw.trim(); if (isAscii(value)) return value; const lt = value.lastIndexOf('<'); if (lt <= 0) return value; const rest = value.slice(lt); if (!isAscii(rest)) return value; const beforeAngle = value.slice(0, lt); const gap = /[ \t]*$/.exec(beforeAngle)?.[0] ?? ''; let name = beforeAngle.slice(0, beforeAngle.length - gap.length); if (name.length >= 2 && name.startsWith('"') && name.endsWith('"')) { name = name.slice(1, -1).replace(/\\([\s\S])/g, '$1'); } const lines = ['']; foldTokens(lines, name, Number.POSITIVE_INFINITY, true); return `${lines.join('')}${gap}${rest}`; } /** * Fold an address header the way python's AddressList folder does: * the whole rendered list stays on the label's line when it fits, * moves to one continuation line when it fits a line by itself, and * otherwise breaks after commas - the comma stays on the prior line * and the next mailbox starts a continuation line behind one space. */ export function foldAddressList(name, mailboxes) { const rendered = mailboxes.map((mailbox) => renderMailbox(mailbox)); const whole = rendered.join(', '); if (`${name}: `.length + whole.length <= MAXLEN) return `${name}: ${whole}`; if (whole.length <= MAXLEN) return `${name}:\r\n ${whole}`; const lines = [`${name}:`]; for (let i = 0; i < rendered.length; i += 1) { const piece = rendered[i] ?? ''; if ((lines[lines.length - 1] ?? '').length + 1 + piece.length <= MAXLEN) { appendLast(lines, ` ${piece}`); } else if (1 + piece.length <= MAXLEN) { lines.push(` ${piece}`); } else { // A single mailbox longer than a line overflows in place, which // is python's give-up branch too. appendLast(lines, ` ${piece}`); } if (i < rendered.length - 1) appendLast(lines, ','); } return lines.join('\r\n'); } function quoteString(value) { return `"${value.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`; } const PCT_SAFE = new Set(); for (const c of 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_.-~') { PCT_SAFE.add(c.charCodeAt(0)); } function pctQuote(value) { let out = ''; for (const byte of ENC.encode(value)) { out += PCT_SAFE.has(byte) ? String.fromCharCode(byte) : `%${hex2(byte)}`; } return out; } // The line-break characters python's str.splitlines recognizes within // ASCII; the wider set (NEL, LS, PS) is non-ASCII and so can only reach // the RFC 2231 path, which percent-encodes it harmlessly. // eslint-disable-next-line no-control-regex const ASCII_LINE_BREAKS = /[\n\r\v\f\x1c\x1d\x1e]/; /** * The attachment Content-Disposition header with its filename folded * as python's _fold_mime_parameters does: quoted when ASCII and short, * RFC 2231 `filename*=utf-8''…` when non-ASCII, and split into * numbered `filename*N*=` sections when too long for one line. * * An ASCII filename holding any line break is refused outright the way * EmailMessage refuses it (header injection through the quoted-string * form) - trailing terminators included, unlike the header-value guard. */ export function foldContentDisposition(filename) { const ascii = isAscii(filename); if (ascii && ASCII_LINE_BREAKS.test(filename)) { throw new Error('Header values may not contain linefeed or carriage return characters'); } const lines = ['Content-Disposition: attachment']; if (!(lines[lines.length - 1] ?? '').trimEnd().endsWith(';')) appendLast(lines, ';'); const tstr = ascii ? `filename=${quoteString(filename)}` : `filename*=utf-8''${pctQuote(filename)}`; if ((lines[lines.length - 1] ?? '').length + tstr.length + 1 < MAXLEN) { appendLast(lines, ` ${tstr}`); return lines.join('\r\n'); } if (tstr.length + 2 <= MAXLEN) { lines.push(` ${tstr}`); return lines.join('\r\n'); } let section = 0; let extraChrome = ascii ? "us-ascii''" : "utf-8''"; // Code points, matching CPython's str slicing. let rest = Array.from(filename); while (rest.length > 0) { const chromeLen = 'filename'.length + String(section).length + 3 + extraChrome.length; const maxlen = MAXLEN <= chromeLen + 3 ? 78 : MAXLEN; const maxchars = maxlen - chromeLen - 2; let splitpoint = maxchars; let encoded = pctQuote(rest.slice(0, splitpoint).join('')); while (encoded.length > maxchars) { splitpoint -= 1; encoded = pctQuote(rest.slice(0, splitpoint).join('')); } lines.push(` filename*${String(section)}*=${extraChrome}${encoded}`); extraChrome = ''; section += 1; rest = rest.slice(splitpoint); if (rest.length > 0) appendLast(lines, ';'); } return lines.join('\r\n'); } const QP_BODY_SAFE = new Set(); for (const c of ' !"#$%&\'()*+,-./0123456789:;<>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\t') { QP_BODY_SAFE.add(c.charCodeAt(0)); } /** * email.quoprimime.body_encode over a latin-1 view of the bytes, with * python 3.12's soft-break placement and trailing-whitespace quoting. */ function qpBodyEncode(body, maxlinelen = MAXLEN, eol = '\n') { if (body === '') return body; let translated = ''; for (let i = 0; i < body.length; i += 1) { const code = body.charCodeAt(i); if (code === 0x0d || code === 0x0a || QP_BODY_SAFE.has(code)) translated += body[i] ?? ''; else translated += `=${hex2(code)}`; } const softBreak = `=${eol}`; const maxlinelen1 = maxlinelen - 1; const encoded = []; // str.splitlines drops the final empty piece a trailing terminator // leaves behind in a plain split. const bodyLines = translated.split(/\r\n|\r|\n/); if (bodyLines[bodyLines.length - 1] === '') bodyLines.pop(); for (const line of bodyLines) { let start = 0; const laststart = line.length - 1 - maxlinelen; while (start <= laststart) { const stop = start + maxlinelen1; if (line[stop - 2] === '=') { encoded.push(line.slice(start, stop - 1)); start = stop - 2; } else if (line[stop - 1] === '=') { encoded.push(line.slice(start, stop)); start = stop - 1; } else { encoded.push(`${line.slice(start, stop)}=`); start = stop; } } const tail = line.slice(-1); if (line !== '' && (tail === ' ' || tail === '\t')) { const room = start - laststart; let quoted = ''; if (room >= 3) quoted = `=${hex2(line.charCodeAt(line.length - 1))}`; else if (room === 2) quoted = tail + softBreak; else quoted = softBreak + `=${hex2(line.charCodeAt(line.length - 1))}`; encoded.push(line.slice(start, -1) + quoted); } else { encoded.push(line.slice(start)); } } const last = translated.slice(-1); if (last === '\r' || last === '\n') encoded.push(''); return encoded.join(eol); } /** contentmanager._encode_base64: 57 input bytes per 76-char line. */ export function encodeBase64Lines(data) { const perLine = Math.floor(MAXLEN / 4) * 3; let out = ''; for (let i = 0; i < data.length; i += perLine) { out += `${encodeBase64(data.subarray(i, i + perLine))}\n`; } return out; } /** * email.contentmanager._encode_text with cte unset under the default * policy (max_line_length 78, cte_type 8bit, linesep LF): short lines * stay 7bit/8bit, anything longer sniffs the first ten lines to pick * quoted-printable or base64. */ export function encodeText(body) { // bytes.splitlines: \r, \n and \r\n only (unlike str.splitlines, no // \v/\f/\x1c-\x1e), and no phantom empty line after a trailing // terminator. const lines = body.split(/\r\n|[\n\r]/); if (lines[lines.length - 1] === '') lines.pop(); const maxLine = lines.reduce((max, line) => Math.max(max, ENC.encode(line).length), 0); const normal = lines.length === 0 ? '\n' : `${lines.join('\n')}\n`; if (maxLine <= MAXLEN) { if (isAscii(normal)) return { cte: '7bit', payload: normal }; return { cte: '8bit', payload: normal }; } const sniffLines = lines.slice(0, 10); const sniff = `${sniffLines.join('\n')}\n`; const sniffBytes = ENC.encode(sniff); const sniffQp = qpBodyEncode(latin1(sniffBytes), MAXLEN); const sniffBase64Len = lenB(sniffBytes) + 1; if (sniffQp.length > sniffBase64Len) { return { cte: 'base64', payload: encodeBase64Lines(ENC.encode(normal)) }; } if (lines.length <= 10) return { cte: 'quoted-printable', payload: sniffQp }; const normalBytes = ENC.encode(normal); return { cte: 'quoted-printable', payload: qpBodyEncode(latin1(normalBytes), MAXLEN) }; } //# sourceMappingURL=mime.js.map