'use strict'; const libmime = require('libmime'); /** * Class Headers to parse and handle message headers. Headers instance allows to * check existing, delete or add new headers */ class Headers { constructor(headers, config) { config = config || {}; if (Array.isArray(headers)) { // already using parsed headers this.changed = true; this.headers = false; this.parsed = true; this.lines = headers; } else { // using original string/buffer headers this.changed = false; this.headers = headers; this.parsed = false; this.lines = false; } this.mbox = false; this.http = false; this.libmime = new libmime.Libmime({ Iconv: config.Iconv }); } get(key) { if (!this.parsed) { this._parseHeaders(); } key = this._normalizeHeader(key); let lines = this.lines.filter(line => line.key === key).map(line => line.line); return lines; } getDecoded(key) { return this.get(key) .map(line => this.libmime.decodeHeader(line)) .filter(line => line && line.value); } getFirst(key) { if (!this.parsed) { this._parseHeaders(); } key = this._normalizeHeader(key); let header = this.lines.find(line => line.key === key); if (!header) { return ''; } return ((this.libmime.decodeHeader(header.line) || {}).value || '').toString().trim(); } getList() { if (!this.parsed) { this._parseHeaders(); } return this.lines; } add(key, value, index) { if (typeof value === 'undefined') { return; } if (typeof value === 'number') { value = value.toString(); } if (typeof value === 'string') { value = Buffer.from(value); } value = value.toString('binary'); this.addFormatted(key, this.libmime.foldLines(key + ': ' + value.replace(/\r?\n/g, ''), 76, false), index); } addFormatted(key, line, index) { if (!this.parsed) { this._parseHeaders(); } index = index || 0; this.changed = true; let header = { key: this._normalizeHeader(key), line }; if (index < 1) { this.lines.unshift(header); } else if (index >= this.lines.length) { this.lines.push(header); } else { this.lines.splice(index, 0, header); } } remove(key) { if (!this.parsed) { this._parseHeaders(); } key = this._normalizeHeader(key); for (let i = this.lines.length - 1; i >= 0; i--) { if (this.lines[i].key === key) { this.changed = true; this.lines.splice(i, 1); } } } update(key, value) { if (!this.parsed) { this._parseHeaders(); } let keyName = key; let index = 0; key = this._normalizeHeader(key); for (let i = this.lines.length - 1; i >= 0; i--) { if (this.lines[i].key === key) { index = i; this.changed = true; this.lines.splice(i, 1); } } this.add(keyName, value, index); } build(lineEnd) { if (!this.changed && !lineEnd) { return typeof this.headers === 'string' ? Buffer.from(this.headers, 'binary') : this.headers; } if (!this.parsed) { this._parseHeaders(); } lineEnd = lineEnd || '\r\n'; let headers = this.lines.map(line => line.line.replace(/\r?\n/g, lineEnd)).join(lineEnd) + `${lineEnd}${lineEnd}`; if (this.mbox) { headers = this.mbox + lineEnd + headers; } if (this.http) { headers = this.http + lineEnd + headers; } return Buffer.from(headers, 'binary'); } _normalizeHeader(key) { return (key || '').toLowerCase().trim(); } _parseHeaders() { if (!this.headers) { this.lines = []; this.parsed = true; return; } let lines = this.headers .toString('binary') .replace(/[\r\n]+$/, '') .split(/\r?\n/); for (let i = lines.length - 1; i >= 0; i--) { let chr = lines[i].charAt(0); if (i && (chr === ' ' || chr === '\t')) { lines[i - 1] += '\r\n' + lines[i]; lines.splice(i, 1); } else { let line = lines[i]; if (!i && /^From /i.test(line)) { // mbox file this.mbox = line; lines.splice(i, 1); continue; } else if (!i && /^POST /i.test(line)) { // HTTP POST request this.http = line; lines.splice(i, 1); continue; } let key = this._normalizeHeader(line.substr(0, line.indexOf(':'))); lines[i] = { key, line }; } } this.lines = lines; this.parsed = true; } } // expose to the world module.exports = Headers;