UNPKG

@timshel_npm/maildev

Version:

SMTP Server with async API and Web Interface for viewing and testing emails during development

254 lines (253 loc) 10.2 kB
"use strict"; 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 = 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.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; }