haraka-email-message
v1.3.2
Published
Haraka email message
Readme
haraka-email-message
RFC 2822 header parser and MIME body parser used throughout Haraka. Can be used standalone.
Installation
npm install haraka-email-messageQuick start
const { Header, Body } = require('haraka-email-message')
const header = new Header()
header.parse([
'From: Alice <[email protected]>\r\n',
'To: Bob <[email protected]>\r\n',
'Subject: Hello\r\n',
])
const body = new Body(header)
body.parse_more('This is the message body.\n')
body.parse_end()
console.log(header.get('from')) // 'Alice <[email protected]>'
console.log(body.bodytext) // 'This is the message body.\n'Header
An RFC 2822 header block parser. Each MIME part in a Body also carries its own Header instance.
Construction
const header = new Header()header.parse(lines)
Parses an array of raw header lines. Handles folded (multi-line) headers and decodes RFC 2047 encoded-words and RFC 2231 parameter continuations.
header.parse([
'Subject: =?UTF-8?Q?Hello_World?=\r\n',
'Content-Type: multipart/alternative;\r\n',
'\tboundary="----=_Part_1"\r\n',
])header.get(key) → string
Returns the raw value(s) for key (case-insensitive). Multiple values are
joined with \n. Returns '' if absent.
header.get('content-type')
// 'multipart/alternative;\r\n\tboundary="----=_Part_1"'header.get_all(key) → string[] (frozen)
Returns all values for key as a frozen array. Useful for headers that
legitimately repeat (e.g. Received).
header.get_all('received') // ['from mx1 ...', 'from mx2 ...']header.get_decoded(key) → string
Like get(), but with RFC 2047 encoded-words decoded and RFC 2231 parameter
continuations resolved. Use this for display or further parsing.
header.get_decoded('subject') // 'Hello World'header.add(key, value)
Prepends a header. Non-ASCII values are automatically Q-encoded.
header.add('X-Spam-Score', '3.2')
header.add('X-Résumé', 'présent') // encoded automaticallyNote (Haraka users): use
transaction.add_header()instead — that also updates the message stream so the change is reflected in the DATA.
header.add_end(key, value)
Same as add() but appends instead of prepends.
header.remove(key)
Removes all headers with the given name.
Note (Haraka users): use
transaction.remove_header()instead.
header.lines() → string[] (frozen)
Returns the raw header block as an array of lines (one per logical header, continuations merged).
header.toString() → string
Returns the entire header block as a single string.
header.header_list
The internal array of raw header lines. Readable but treat as read-only; mutations bypass the decoded caches.
Body
A streaming MIME body parser. Feed it lines one at a time with parse_more(),
then call parse_end() to finalise.
Construction
const body = new Body(header, options)header is a Header instance (or omit for a headerless body that defaults
to text/plain). options is currently reserved for future use.
body.parse_more(line) → Buffer | ''
Feeds one line into the parser. line may be a Buffer or string.
Returns the (possibly transformed) line, or '' when a filter has consumed
the output.
body.parse_end([line]) → Buffer
Signals end of the part. Decodes the accumulated body, runs any filters, and
sets body.bodytext. Call once after all lines have been fed.
for (const line of rawLines) body.parse_more(line)
const lastLine = body.parse_end()body.bodytext → string
The decoded, UTF-8 body text of this MIME part. HTML parts retain their tags.
body.ct → string | null
The Content-Type of this part, set during parse_start (first non-header
line). null before parsing begins.
body.children → Body[]
Child MIME parts. For a multipart/* message:
body ← multipart/alternative
body.children[0] ← text/plain
body.children[1] ← text/htmlbody.header → Header
The Header of this MIME part.
Event: attachment_start
body.on('attachment_start', (contentType, filename, part, stream) => {
stream.on('data', (chunk) => {
/* Buffer */
})
stream.on('end', () => {
/* done */
})
})Emitted when a non-inline part (attachment or non-text content type) is
encountered. stream is an AttachmentStream. The event propagates to child
parts automatically.
Event: mime_boundary
body.on('mime_boundary', (line) => {
/* raw boundary line */
})Emitted at each MIME boundary line.
body.add_filter(fn)
Registers a filter function invoked at parse_end() with the decoded body
buffer. Receives (contentType, encoding, buf, contentDisposition) and should
return a Buffer (or undefined to leave unchanged).
body.add_filter((ct, enc, buf) => {
if (/text\/plain/i.test(ct)) {
return Buffer.from(buf.toString().toUpperCase())
}
})body.set_banner([textBanner, htmlBanner])
Convenience method that adds a filter inserting banners at the end of
text/plain and text/html parts. HTML banners are wrapped in <P>...</P>
and inserted just before </body> or </html>.
body.set_banner([
'This message was scanned by AcmeSecurity.',
'<em>This message was scanned by AcmeSecurity.</em>',
])body.force_end()
Forces attachment streams to emit end even if the parser is still in
attachment state. Used during connection teardown.
AttachmentStream
A Stream subclass that buffers attachment data and supports backpressure via
pause() / resume(). Received from the attachment_start event.
body.on('attachment_start', (ct, filename, part, stream) => {
const chunks = []
stream.on('data', (chunk) => chunks.push(chunk))
stream.on('end', () => {
const data = Buffer.concat(chunks)
console.log(`${filename}: ${data.length} bytes`)
})
stream.pipe(fs.createWriteStream(`/tmp/${filename}`))
})stream.pause() / stream.resume()
Pauses and resumes data emission. Buffered chunks are held in memory until resumed.
stream.setEncoding('binary')
Makes data events emit strings rather than Buffers. Only 'binary' is
supported.
stream.connection
Optional back-reference to the network socket. When set, pause() /
resume() also pause / resume the connection to provide true backpressure.
stream export
Re-exports haraka-message-stream,
a dual-mode write-then-read buffer for the raw RFC 2822 wire bytes.
const { stream: MessageStream } = require('haraka-email-message')
const ms = new MessageStream(cfg, uuid)
ms.add_line('From: [email protected]\r\n')
ms.add_line('\r\n')
ms.add_line('Body text\r\n')
ms.add_line_end()
ms.pipe(socket, { dot_stuffed: true, ending_dot: true })createAttachmentStream(header) → AttachmentStream
Factory function that returns a new AttachmentStream. In Haraka, plugins
can replace this export to substitute a custom implementation.
const msg = require('haraka-email-message')
const orig = msg.createAttachmentStream
msg.createAttachmentStream = (header) => {
const s = orig(header)
s.on('end', () => console.log('attachment done'))
return s
}Encoding support
iconv-lite handles most encodings. For rare legacy encodings not covered by
iconv-lite (e.g. x-mac-cyrillic, koi8-r), install the optional native
iconv binding:
npm install iconvWhen the native binding is absent a lognotice-level message is emitted at
startup. Unrecognised encodings fall back to Buffer.toString() and
body.body_encoding is set to broken//<enc>.
