@timshel_npm/maildev
Version:
SMTP Server with async API and Web Interface for viewing and testing emails during development
254 lines (253 loc) • 10.3 kB
JavaScript
;
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = parse;
const mime_1 = require("mime");
const simpleParser = require("mailparser").simpleParser;
const strtotime = require("./helpers/strtotime");
const logger = require("./logger");
function parse(input) {
return __awaiter(this, void 0, void 0, function* () {
return simpleParser(input, {}).then((parsed) => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
const headers = {
date: getDate(parsed.headers, "date"),
contentType: single(getSructured(parsed.headers, "content-type")),
contentDisposition: single(getSructured(parsed.headers, "content-disposition")),
dkimSignature: getSructured(parsed.headers, "dkim-signature"),
from: getAddress(parsed.headers, "from"),
to: getAddress(parsed.headers, "to"),
cc: getAddress(parsed.headers, "cc"),
bcc: getAddress(parsed.headers, "bcc"),
sender: getAddress(parsed.headers, "sender"),
replyTo: getAddress(parsed.headers, "reply-to"),
deliveredTo: getAddress(parsed.headers, "delivered-to"),
dispositionNotificationTo: getAddress(parsed.headers, "disposition-notification-to"),
priority: getSring(parsed.headers, "priority"),
received: getSringArray(parsed.headers, "received"),
headers: new Map(Array.from(parsed.headers, ([key, value]) => {
const cast = value;
return [key, typeof cast === "string" ? [cast] : cast];
})),
};
const references = (typeof parsed.references === "string" ? [parsed.references] : ((_a = parsed.references) !== null && _a !== void 0 ? _a : [])).map((ref) => {
return ref.replace(/^<(.*)>$/, "$1");
});
const inReplyTo = (typeof parsed.inReplyTo === "string" ? ((_b = parsed.inReplyTo.match(/<([^<>]*)>/g)) !== null && _b !== void 0 ? _b : []) : []).map((ref) => {
return ref.replace(/^<(.*)>$/, "$1");
});
const fileNames = [];
const attachments = ((_c = parsed.attachments) !== null && _c !== void 0 ? _c : []).map((attachment) => {
const generatedFileName = generateFileNames(fileNames, attachment.filename, attachment.contentType);
return Object.assign(Object.assign({}, attachment), { generatedFileName });
});
return Object.assign(Object.assign({}, parsed), { headers, replyTo: (_e = (_d = headers === null || headers === void 0 ? void 0 : headers.replyTo) === null || _d === void 0 ? void 0 : _d.value) !== null && _e !== void 0 ? _e : [], from: (_g = (_f = headers === null || headers === void 0 ? void 0 : headers.from) === null || _f === void 0 ? void 0 : _f.value) !== null && _g !== void 0 ? _g : [], to: (_j = (_h = headers === null || headers === void 0 ? void 0 : headers.to) === null || _h === void 0 ? void 0 : _h.value) !== null && _j !== void 0 ? _j : [], cc: (_l = (_k = headers === null || headers === void 0 ? void 0 : headers.cc) === null || _k === void 0 ? void 0 : _k.value) !== null && _l !== void 0 ? _l : [], bcc: (_o = (_m = headers === null || headers === void 0 ? void 0 : headers.bcc) === null || _m === void 0 ? void 0 : _m.value) !== null && _o !== void 0 ? _o : [], date: (_p = parsed.date) !== null && _p !== void 0 ? _p : new Date(), priority: (_q = headers.priority) !== null && _q !== void 0 ? _q : "normal", receivedDate: parseReceived(parsed.date, headers.received, parsed.headers.get("x-received")), references,
inReplyTo,
attachments });
});
});
}
function single(array) {
return array.length > 0 ? array[0] : undefined;
}
function getDate(headers, key) {
const value = headers.get(key);
headers.delete(key);
if (value instanceof Date || value === undefined) {
return value;
}
else {
logger.error("Invalid header value for %s, expected date got %s", key, value);
}
return undefined;
}
function getSring(headers, key) {
const value = headers.get(key);
headers.delete(key);
if (typeof value === "string" || value === undefined) {
return value;
}
else {
logger.error("Invalid header value for %s, expected string got %s", key, value);
}
return undefined;
}
function getSringArray(headers, key) {
var _a;
const value = (_a = headers.get(key)) !== null && _a !== void 0 ? _a : [];
headers.delete(key);
if (typeof value === "string") {
return [value];
}
else if (Array.isArray(value)) {
return value.filter((elt) => {
const isString = typeof elt === "string";
if (!isString) {
logger.error("Invalid header value for %s, expected string[] got a %s", key, value);
}
return isString;
});
}
else {
logger.error("Invalid header value for %s, expected string or string[] got %s", key, value);
}
return [];
}
function getAddress(headers, key) {
var _a;
const headerValue = headers.get(key);
headers.delete(key);
function flatten(acc, addr) {
if (addr.group) {
addr.group.forEach((e) => flatten(acc, e));
}
if (addr.address) {
acc.push({ address: addr.address, name: addr.name });
}
return acc;
}
return headerValue
? {
value: (_a = headerValue.value.reduce(flatten, [])) !== null && _a !== void 0 ? _a : [],
html: headerValue.html,
text: headerValue.text,
}
: undefined;
}
function getSructured(headers, key) {
const value = headers.get(key);
headers.delete(key);
if (typeof value === "string") {
return [{ value, params: {} }];
}
else if (Array.isArray(value)) {
return value.map((n) => {
return { value: n, params: {} };
});
}
else if (typeof value === "object") {
return [value];
}
else if (value) {
logger.error("Invalid header value for %s, expected StructuredHeader or [] got %s", key, value);
}
return [];
}
/**
* <p>Generates a context unique filename for an attachment</p>
*
* <p>If a filename already exists, append a number to it</p>
*
* <ul>
* <li>file.txt</li>
* <li>file-1.txt</li>
* <li>file-2.txt</li>
* </ul>
*
* @param {String} fileName source filename
* @param {String} contentType source content type
* @returns {String} generated filename
*/
function generateFileNames(fileNames, fileName, contentType) {
let ext;
let defaultExt = "";
if (contentType) {
const ext = mime_1.default.getExtension(contentType);
defaultExt = ext ? "." + ext : "";
}
fileName = fileName || "attachment" + defaultExt;
// remove path if it is included in the filename
fileName =
fileName
.toString()
.split(/[/\\]+/)
.pop()
.replace(/^\.+/, "") || "attachment";
const fileRootName = fileName.replace(/(?:-\d+)+(\.[^.]*)$/, "$1") || "attachment";
if (fileRootName in fileNames) {
fileNames[fileRootName]++;
ext = fileName.substr((fileName.lastIndexOf(".") || 0) + 1);
if (ext === fileName) {
fileName += "-" + fileNames[fileRootName];
}
else {
fileName =
fileName.substr(0, fileName.length - ext.length - 1) +
"-" +
fileNames[fileRootName] +
"." +
ext;
}
}
else {
fileNames[fileRootName] = 0;
}
return fileName;
}
/**
* <p>Parses Received and X-Received header field value</p>
*
* <p>Pulls received date from the received and x-received header fields and
* update current node meta object with this date as long as it's later as the
* existing date of the meta object</p>
*
* <p>Example: <code>by 10.25.25.72 with SMTP id 69csp2404548lfz; Fri, 6 Feb 2015 15:15:32 -0800 (PST)</code>
* will become:
* </p>
*
* <pre>new Date('2015-02-06T23:15:32.000Z')</pre>
*
* @param {String} value Received string
* @returns {Date|Boolean} parsed received date
*/
function parseReceived(date, received, xReceived) {
let receivedDate;
function parse(value) {
const splitString = value.split(";");
return parseDateString(splitString[splitString.length - 1]);
}
if (received && received.length > 0) {
receivedDate = parse(received[0]);
}
if (!receivedDate && xReceived) {
receivedDate = parse(xReceived);
}
return !receivedDate || date > receivedDate ? date : receivedDate;
}
/**
* <p>Parses date string</o>
*
* <p>Receives possible date string in different formats and
* transforms it into a JS Date object</p>
*
* @param {String} value possible date string
* @returns {Date|Boolean} date object
*/
function parseDateString(value) {
let date;
date = new Date(value);
if (Object.prototype.toString.call(date) !== "[object Date]" ||
date.toString() === "Invalid Date") {
try {
date = strtotime(value);
}
catch (E) {
return false;
}
if (date) {
date = new Date(date * 1000);
}
else {
return false;
}
}
return date;
}