@timshel_npm/maildev 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +287 -0
  3. package/bin/maildev +13 -0
  4. package/dist/app/components/angular/angular.js +20282 -0
  5. package/dist/app/components/angular/angular.min.js +201 -0
  6. package/dist/app/components/angular/angular.min.js.map +8 -0
  7. package/dist/app/components/angular-cookies/angular-cookies.js +202 -0
  8. package/dist/app/components/angular-cookies/angular-cookies.min.js +8 -0
  9. package/dist/app/components/angular-cookies/angular-cookies.min.js.map +8 -0
  10. package/dist/app/components/angular-resource/angular-resource.js +546 -0
  11. package/dist/app/components/angular-resource/angular-resource.min.js +12 -0
  12. package/dist/app/components/angular-resource/angular-resource.min.js.map +8 -0
  13. package/dist/app/components/angular-route/angular-route.js +891 -0
  14. package/dist/app/components/angular-route/angular-route.min.js +14 -0
  15. package/dist/app/components/angular-route/angular-route.min.js.map +8 -0
  16. package/dist/app/components/angular-sanitize/angular-sanitize.js +615 -0
  17. package/dist/app/components/angular-sanitize/angular-sanitize.min.js +14 -0
  18. package/dist/app/components/angular-sanitize/angular-sanitize.min.js.map +8 -0
  19. package/dist/app/components/socket.io/socket.io.min.js +7 -0
  20. package/dist/app/favicon.ico +0 -0
  21. package/dist/app/index.html +176 -0
  22. package/dist/app/scripts/app.js +68 -0
  23. package/dist/app/scripts/components/address.js +15 -0
  24. package/dist/app/scripts/controllers/item.js +191 -0
  25. package/dist/app/scripts/controllers/main.js +259 -0
  26. package/dist/app/scripts/services.js +84 -0
  27. package/dist/app/styles/style.css +37 -0
  28. package/dist/app/views/address.html +3 -0
  29. package/dist/app/views/item.html +310 -0
  30. package/dist/app/views/main.html +18 -0
  31. package/dist/app/webfonts/fa-brands-400.eot +0 -0
  32. package/dist/app/webfonts/fa-brands-400.svg +3717 -0
  33. package/dist/app/webfonts/fa-brands-400.ttf +0 -0
  34. package/dist/app/webfonts/fa-brands-400.woff +0 -0
  35. package/dist/app/webfonts/fa-brands-400.woff2 +0 -0
  36. package/dist/app/webfonts/fa-regular-400.eot +0 -0
  37. package/dist/app/webfonts/fa-regular-400.svg +801 -0
  38. package/dist/app/webfonts/fa-regular-400.ttf +0 -0
  39. package/dist/app/webfonts/fa-regular-400.woff +0 -0
  40. package/dist/app/webfonts/fa-regular-400.woff2 +0 -0
  41. package/dist/app/webfonts/fa-solid-900.eot +0 -0
  42. package/dist/app/webfonts/fa-solid-900.svg +5028 -0
  43. package/dist/app/webfonts/fa-solid-900.ttf +0 -0
  44. package/dist/app/webfonts/fa-solid-900.woff +0 -0
  45. package/dist/app/webfonts/fa-solid-900.woff2 +0 -0
  46. package/dist/index.d.ts +26 -0
  47. package/dist/index.js +50 -0
  48. package/dist/lib/auth.d.ts +4 -0
  49. package/dist/lib/auth.js +26 -0
  50. package/dist/lib/helpers/bcc.d.ts +5 -0
  51. package/dist/lib/helpers/bcc.js +16 -0
  52. package/dist/lib/helpers/smtp.d.ts +4 -0
  53. package/dist/lib/helpers/smtp.js +16 -0
  54. package/dist/lib/helpers/strtotime.d.ts +2 -0
  55. package/dist/lib/helpers/strtotime.js +1301 -0
  56. package/dist/lib/logger.d.ts +2 -0
  57. package/dist/lib/logger.js +30 -0
  58. package/dist/lib/mailbuffer.d.ts +21 -0
  59. package/dist/lib/mailbuffer.js +102 -0
  60. package/dist/lib/mailparser.d.ts +2 -0
  61. package/dist/lib/mailparser.js +253 -0
  62. package/dist/lib/mailserver.d.ts +115 -0
  63. package/dist/lib/mailserver.js +497 -0
  64. package/dist/lib/options.d.ts +3 -0
  65. package/dist/lib/options.js +150 -0
  66. package/dist/lib/outgoing.d.ts +40 -0
  67. package/dist/lib/outgoing.js +162 -0
  68. package/dist/lib/routes.d.ts +2 -0
  69. package/dist/lib/routes.js +139 -0
  70. package/dist/lib/type.d.ts +228 -0
  71. package/dist/lib/type.js +2 -0
  72. package/dist/lib/utils.d.ts +4 -0
  73. package/dist/lib/utils.js +65 -0
  74. package/dist/lib/web.d.ts +32 -0
  75. package/dist/lib/web.js +90 -0
  76. package/package.json +109 -0
@@ -0,0 +1,497 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __await = (this && this.__await) || function (v) { return this instanceof __await ? (this.v = v, this) : new __await(v); }
12
+ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _arguments, generator) {
13
+ if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
14
+ var g = generator.apply(thisArg, _arguments || []), i, q = [];
15
+ return i = Object.create((typeof AsyncIterator === "function" ? AsyncIterator : Object).prototype), verb("next"), verb("throw"), verb("return", awaitReturn), i[Symbol.asyncIterator] = function () { return this; }, i;
16
+ function awaitReturn(f) { return function (v) { return Promise.resolve(v).then(f, reject); }; }
17
+ function verb(n, f) { if (g[n]) { i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; if (f) i[n] = f(i[n]); } }
18
+ function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
19
+ function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
20
+ function fulfill(value) { resume("next", value); }
21
+ function reject(value) { resume("throw", value); }
22
+ function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.MailServer = void 0;
26
+ const bcc_1 = require("./helpers/bcc");
27
+ const smtp_1 = require("./helpers/smtp");
28
+ const mailbuffer_1 = require("./mailbuffer");
29
+ const mailparser_1 = require("./mailparser");
30
+ const outgoing_1 = require("./outgoing");
31
+ const smtp_server_1 = require("smtp-server");
32
+ const fs_1 = require("fs");
33
+ const events = require("events");
34
+ const fs = require("fs");
35
+ const os = require("os");
36
+ const path = require("path");
37
+ const utils = require("./utils");
38
+ const logger = require("./logger");
39
+ const createDOMPurify = require("dompurify");
40
+ const { JSDOM } = require("jsdom");
41
+ const defaultPort = 1025;
42
+ const defaultHost = "0.0.0.0";
43
+ const reservedEventName = ["close", "delete"];
44
+ class MailServer {
45
+ next(subject) {
46
+ if (reservedEventName.includes(subject)) {
47
+ throw new Error(`Invalid subject ${subject}; ${reservedEventName} are reserved for internal usage`);
48
+ }
49
+ return new Promise((resolve) => {
50
+ this.once(subject, resolve);
51
+ });
52
+ }
53
+ /**
54
+ * Use an internal array to store received email even if not consummed
55
+ * Use `.return()` to close it
56
+ **/
57
+ iterator(subject) {
58
+ if (reservedEventName.includes(subject)) {
59
+ throw new Error(`Invalid subject ${subject}; ${reservedEventName} are reserved for internal usage`);
60
+ }
61
+ let closed = false;
62
+ const next = [];
63
+ const self = this;
64
+ const closing = () => {
65
+ closed = true;
66
+ };
67
+ let nextCallback;
68
+ function buildNext() {
69
+ return new Promise((resolve) => {
70
+ nextCallback = (mail) => {
71
+ next.push(buildNext());
72
+ resolve(mail);
73
+ };
74
+ self.once(subject, nextCallback);
75
+ });
76
+ }
77
+ self.once("close", closing);
78
+ next.push(buildNext());
79
+ // We use an internal generator otherwise the setup phase was not always run
80
+ function inner(subject) {
81
+ return __asyncGenerator(this, arguments, function* inner_1() {
82
+ try {
83
+ do {
84
+ const email = (yield __await(next.shift()));
85
+ yield yield __await(email);
86
+ } while (!closed);
87
+ }
88
+ finally {
89
+ self.removeListener("close", closing);
90
+ if (nextCallback) {
91
+ self.removeListener(subject, nextCallback);
92
+ }
93
+ }
94
+ });
95
+ }
96
+ return inner(subject);
97
+ }
98
+ /**
99
+ * Return a struct which store received emails.
100
+ * Then allow to obtain a `Promise<Mail>` dependant on a predicate `(Mail) => boolean`.
101
+ * Allow to wait for `Mail` independant of their order of arrival.
102
+ */
103
+ buffer(subject, defaultTimeout = 10000) {
104
+ return new mailbuffer_1.MailBuffer(this, subject, defaultTimeout);
105
+ }
106
+ constructor(options, mailEventSubjectMapper = (m) => { var _a; return (_a = m.to[0]) === null || _a === void 0 ? void 0 : _a.address; }) {
107
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l;
108
+ this.store = [];
109
+ this.eventEmitter = new events.EventEmitter();
110
+ /**
111
+ * Extend Event Emitter methods
112
+ * events:
113
+ * 'new' - emitted when new email has arrived
114
+ */
115
+ this.emit = this.eventEmitter.emit.bind(this.eventEmitter);
116
+ this.on = this.eventEmitter.on.bind(this.eventEmitter);
117
+ this.off = this.eventEmitter.off.bind(this.eventEmitter);
118
+ this.once = this.eventEmitter.once.bind(this.eventEmitter);
119
+ this.prependListener = this.eventEmitter.once.bind(this.eventEmitter);
120
+ this.prependOnceListener = this.eventEmitter.once.bind(this.eventEmitter);
121
+ this.removeListener = this.eventEmitter.removeListener.bind(this.eventEmitter);
122
+ this.removeAllListeners = this.eventEmitter.removeAllListeners.bind(this.eventEmitter);
123
+ const defaultMailDir = path.join(os.tmpdir(), `maildev-${process.pid.toString()}`);
124
+ const secure = (_a = options === null || options === void 0 ? void 0 : options.isSecure) !== null && _a !== void 0 ? _a : false;
125
+ const smtpServerConfig = {
126
+ secure,
127
+ cert: (options === null || options === void 0 ? void 0 : options.ssl) ? fs.readFileSync((_b = options === null || options === void 0 ? void 0 : options.ssl) === null || _b === void 0 ? void 0 : _b.certPath) : null,
128
+ key: (options === null || options === void 0 ? void 0 : options.ssl) ? fs.readFileSync((_c = options === null || options === void 0 ? void 0 : options.ssl) === null || _c === void 0 ? void 0 : _c.keyPath) : null,
129
+ onAuth: (0, smtp_1.createOnAuthCallback)((_d = options === null || options === void 0 ? void 0 : options.auth) === null || _d === void 0 ? void 0 : _d.user, (_e = options === null || options === void 0 ? void 0 : options.auth) === null || _e === void 0 ? void 0 : _e.pass),
130
+ onData: (stream, session, callback) => handleDataStream(this, stream, session, callback),
131
+ logger: false,
132
+ hideSTARTTLS: true,
133
+ hidePIPELINING: (_f = options === null || options === void 0 ? void 0 : options.hidePIPELINING) !== null && _f !== void 0 ? _f : false,
134
+ hide8BITMIME: (_g = options === null || options === void 0 ? void 0 : options.hide8BITMIME) !== null && _g !== void 0 ? _g : false,
135
+ hideSMTPUTF8: (_h = options === null || options === void 0 ? void 0 : options.hideSMTPUTF8) !== null && _h !== void 0 ? _h : false,
136
+ disabledCommands: (options === null || options === void 0 ? void 0 : options.auth) ? (secure ? [] : ["STARTTLS"]) : ["AUTH"],
137
+ };
138
+ this.port = (_j = options === null || options === void 0 ? void 0 : options.port) !== null && _j !== void 0 ? _j : defaultPort;
139
+ this.host = (_k = options === null || options === void 0 ? void 0 : options.host) !== null && _k !== void 0 ? _k : defaultHost;
140
+ this.mailDir = (_l = options === null || options === void 0 ? void 0 : options.mailDir) !== null && _l !== void 0 ? _l : defaultMailDir;
141
+ this.mailEventSubjectMapper = mailEventSubjectMapper;
142
+ this.smtp = new smtp_server_1.SMTPServer(smtpServerConfig);
143
+ this.smtp.on("error", onSmtpError);
144
+ createMailDir(this.mailDir);
145
+ if (options === null || options === void 0 ? void 0 : options.outgoing) {
146
+ this.outgoing = new outgoing_1.Outgoing(options === null || options === void 0 ? void 0 : options.outgoing);
147
+ }
148
+ if (options === null || options === void 0 ? void 0 : options.mailDir) {
149
+ this.loadMailsFromDirectory();
150
+ }
151
+ }
152
+ /**
153
+ * Start the mailServer
154
+ */
155
+ listen() {
156
+ const self = this;
157
+ return new Promise((resolve, reject) => {
158
+ self.smtp.listen(self.port, self.host, (err) => {
159
+ if (err) {
160
+ reject(err);
161
+ }
162
+ logger.info("MailDev SMTP Server running at %s:%s", self.host, self.port);
163
+ resolve();
164
+ });
165
+ });
166
+ }
167
+ /**
168
+ * Stop the mailserver
169
+ */
170
+ close() {
171
+ var _a;
172
+ this.emit("close");
173
+ (_a = this.outgoing) === null || _a === void 0 ? void 0 : _a.close();
174
+ return new Promise((resolve) => {
175
+ this.smtp.close(resolve);
176
+ });
177
+ }
178
+ isOutgoingEnabled() {
179
+ return this.outgoing !== undefined;
180
+ }
181
+ getOutgoingHost() {
182
+ var _a;
183
+ return (_a = this.outgoing) === null || _a === void 0 ? void 0 : _a.getOutgoingHost();
184
+ }
185
+ /**
186
+ * Set Auto Relay Mode, automatic send email to recipient
187
+ */
188
+ setAutoRelayMode(enabled, emailAddress, rules) {
189
+ if (this.outgoing) {
190
+ this.outgoing.setAutoRelayMode(enabled, emailAddress, rules);
191
+ }
192
+ else {
193
+ throw new Error("Outgoing not configured");
194
+ }
195
+ }
196
+ relayMail(mail, isAutoRelay = true) {
197
+ const self = this;
198
+ const outgoing = this.outgoing;
199
+ return outgoing
200
+ ? new Promise((resolve, reject) => {
201
+ self
202
+ .getRawEmail(mail.id)
203
+ .then((rawEmailStream) => {
204
+ outgoing.relayMail(mail, rawEmailStream, isAutoRelay, (err) => {
205
+ if (err) {
206
+ reject(err);
207
+ }
208
+ else {
209
+ resolve();
210
+ }
211
+ });
212
+ })
213
+ .catch((err) => {
214
+ logger.error("Mail Stream Error: ", err);
215
+ reject(err);
216
+ });
217
+ })
218
+ : Promise.reject(new Error("Outgoing not configured"));
219
+ }
220
+ /**
221
+ * Get an email by id
222
+ */
223
+ getEmail(id) {
224
+ return __awaiter(this, void 0, void 0, function* () {
225
+ const envelope = this.store.find(function (elt) {
226
+ return elt.id === id;
227
+ });
228
+ if (envelope) {
229
+ return getDiskEmail(this.mailDir, envelope.id, envelope).then((mail) => {
230
+ if (mail.html) {
231
+ // sanitize html
232
+ const window = new JSDOM("").window;
233
+ const DOMPurify = createDOMPurify(window);
234
+ mail.html = DOMPurify.sanitize(mail.html, {
235
+ WHOLE_DOCUMENT: true, // preserve html,head,body elements
236
+ SANITIZE_DOM: false, // ignore DOM cloberring to preserve form id/name attributes
237
+ ADD_TAGS: ["link"], // allow link element to preserve external style sheets
238
+ ADD_ATTR: ["target"], // Preserve explicit target attributes on links
239
+ });
240
+ }
241
+ return mail;
242
+ });
243
+ }
244
+ else {
245
+ throw new Error(`No email with id: ${id}`);
246
+ }
247
+ });
248
+ }
249
+ /**
250
+ * Returns a readable stream of the raw email
251
+ */
252
+ getRawEmail(id) {
253
+ return __awaiter(this, void 0, void 0, function* () {
254
+ const emlPath = path.join(this.mailDir, id + ".eml");
255
+ if (fs.existsSync(emlPath)) {
256
+ return fs.createReadStream(emlPath);
257
+ }
258
+ else {
259
+ throw new Error(`No email with id: ${id}`);
260
+ }
261
+ });
262
+ }
263
+ /**
264
+ * Returns the html of a given email
265
+ */
266
+ getEmailHTML(id_1) {
267
+ return __awaiter(this, arguments, void 0, function* (id, baseUrl = "") {
268
+ baseUrl = baseUrl ? "//" + baseUrl : "";
269
+ const mail = yield this.getEmail(id);
270
+ if (typeof mail.html === "string") {
271
+ var html = mail.html;
272
+ const getFileUrl = function (filename) {
273
+ return baseUrl + "/email/" + id + "/attachment/" + encodeURIComponent(filename);
274
+ };
275
+ for (const attachment of mail.attachments) {
276
+ if (attachment.contentId) {
277
+ const regex = new RegExp("src=(\"|')cid:" + attachment.contentId + "(\"|')", "g");
278
+ const replacement = 'src="' + getFileUrl(attachment.generatedFileName) + '"';
279
+ html = html.replace(regex, replacement);
280
+ }
281
+ }
282
+ return html;
283
+ }
284
+ else {
285
+ throw new Error(`No html in email ${id}`);
286
+ }
287
+ });
288
+ }
289
+ /**
290
+ * Set all emails to read
291
+ */
292
+ readAllEmail() {
293
+ return this.store.reduce(function (count, elt) {
294
+ if (!elt.isRead) {
295
+ count++;
296
+ }
297
+ return count;
298
+ }, 0);
299
+ }
300
+ getAllEnvelope() {
301
+ return this.store.slice();
302
+ }
303
+ getAllEmail() {
304
+ const emails = this.store.map((elt) => {
305
+ return this.getEmail(elt.id);
306
+ });
307
+ return Promise.all(emails);
308
+ }
309
+ deleteEmail(id) {
310
+ return __awaiter(this, void 0, void 0, function* () {
311
+ const self = this;
312
+ const emailIndex = this.store.findIndex((elt) => elt.id === id);
313
+ if (emailIndex < 0) {
314
+ throw new Error(`Email ${id} not found`);
315
+ }
316
+ const mail = this.store[emailIndex];
317
+ logger.warn("Deleting email - %s", mail.id);
318
+ this.store.splice(emailIndex, 1);
319
+ return Promise.all([
320
+ fs_1.promises.unlink(path.join(this.mailDir, id + ".eml")).catch((err) => {
321
+ throw new Error(`Error when deleteing ${id}`);
322
+ }),
323
+ fs_1.promises.rm(path.join(this.mailDir, id), { recursive: true, force: true }).catch((err) => {
324
+ throw new Error(`Error when deleteing ${id} attachments: ${err}`);
325
+ }),
326
+ ]).then(() => {
327
+ self.eventEmitter.emit("delete", { id, index: emailIndex });
328
+ return true;
329
+ });
330
+ });
331
+ }
332
+ deleteAllEmail() {
333
+ return __awaiter(this, void 0, void 0, function* () {
334
+ logger.warn("Deleting all email");
335
+ this.clearMailDir();
336
+ this.store.length = 0;
337
+ this.eventEmitter.emit("delete", { id: "all" });
338
+ return true;
339
+ });
340
+ }
341
+ /**
342
+ * Delete everything in the mail directory
343
+ */
344
+ clearMailDir() {
345
+ return __awaiter(this, void 0, void 0, function* () {
346
+ const self = this;
347
+ const files = yield fs_1.promises.readdir(this.mailDir);
348
+ const rms = files.map(function (file) {
349
+ return fs_1.promises.rm(path.join(self.mailDir, file), { recursive: true });
350
+ });
351
+ return Promise.all(rms);
352
+ });
353
+ }
354
+ /**
355
+ * Returns the content type and a readable stream of the file
356
+ */
357
+ getEmailAttachment(id, filename) {
358
+ return __awaiter(this, void 0, void 0, function* () {
359
+ const mail = yield this.getEmail(id);
360
+ if (mail.attachments.length === 0) {
361
+ throw new Error("Email has no attachments");
362
+ }
363
+ for (const attachment of mail.attachments) {
364
+ if (attachment.generatedFileName === filename) {
365
+ return attachment;
366
+ }
367
+ }
368
+ throw new Error(`Attachment ${filename} not found`);
369
+ });
370
+ }
371
+ /**
372
+ * Download a given email
373
+ */
374
+ getEmailEml(id) {
375
+ return __awaiter(this, void 0, void 0, function* () {
376
+ const filename = id + ".eml";
377
+ const stream = yield this.getRawEmail(id);
378
+ return ["message/rfc822", filename, stream];
379
+ });
380
+ }
381
+ loadMailsFromDirectory() {
382
+ return __awaiter(this, void 0, void 0, function* () {
383
+ const self = this;
384
+ const persistencePath = yield fs_1.promises.realpath(this.mailDir);
385
+ const files = yield fs_1.promises.readdir(persistencePath).catch((err) => {
386
+ logger.error(`Error during reading of the mailDir ${persistencePath}`);
387
+ throw new Error(`Error during reading of the mailDir ${persistencePath}`);
388
+ });
389
+ this.store.length = 0;
390
+ const saved = files.map(function (file) {
391
+ return __awaiter(this, void 0, void 0, function* () {
392
+ const id = path.parse(file).name;
393
+ const email = yield getDiskEmail(self.mailDir, id, undefined);
394
+ return saveEmailToStore(self, email);
395
+ });
396
+ });
397
+ return Promise.all(saved);
398
+ });
399
+ }
400
+ }
401
+ exports.MailServer = MailServer;
402
+ /**
403
+ * Handle mailServer error
404
+ */
405
+ function onSmtpError(err) {
406
+ if (err.code === "ECONNRESET" && err.syscall === "read") {
407
+ logger.warn(`Ignoring "${err.message}" error thrown by SMTP server. Likely the client connection closed prematurely. Full error details below.`);
408
+ logger.error(err);
409
+ }
410
+ else
411
+ throw err;
412
+ }
413
+ /**
414
+ * Create mail directory
415
+ */
416
+ function createMailDir(mailDir) {
417
+ fs.mkdirSync(mailDir, { recursive: true });
418
+ logger.info("MailDev using directory %s", mailDir);
419
+ }
420
+ function getDiskEmail(mailDir, id, envelope) {
421
+ return __awaiter(this, void 0, void 0, function* () {
422
+ const emlPath = path.join(mailDir, id + ".eml");
423
+ const data = yield fs_1.promises.readFile(emlPath, "utf8");
424
+ const parsedMail = yield (0, mailparser_1.parse)(data);
425
+ if (envelope === undefined) {
426
+ envelope = {
427
+ id: id,
428
+ from: parsedMail.from,
429
+ to: parsedMail.to,
430
+ date: parsedMail.date,
431
+ subject: parsedMail.subject,
432
+ hasAttachment: parsedMail.attachments.length > 0,
433
+ isRead: false,
434
+ };
435
+ }
436
+ return buildMail(mailDir, envelope, parsedMail);
437
+ });
438
+ }
439
+ function buildMail(mailDir, envelope, parsedMail) {
440
+ return __awaiter(this, void 0, void 0, function* () {
441
+ const emlPath = path.join(mailDir, envelope.id + ".eml");
442
+ const stat = yield fs_1.promises.stat(emlPath);
443
+ return Object.assign({ id: envelope.id, envelope, calculatedBcc: (0, bcc_1.calculateBcc)(envelope.to, parsedMail.to, parsedMail.cc), size: stat.size, sizeHuman: utils.formatBytes(stat.size) }, parsedMail);
444
+ });
445
+ }
446
+ /**
447
+ * SMTP Server stream and helper functions
448
+ */
449
+ // Save an email object on stream end
450
+ function saveEmailToStore(mailServer, mail) {
451
+ return __awaiter(this, void 0, void 0, function* () {
452
+ var _a;
453
+ logger.log("Saving email: %s, id: %s", mail.subject, mail.id);
454
+ mailServer.store.push(mail.envelope);
455
+ mailServer.eventEmitter.emit("new", mail);
456
+ const subject = mailServer === null || mailServer === void 0 ? void 0 : mailServer.mailEventSubjectMapper(mail);
457
+ if (subject) {
458
+ if (reservedEventName.includes(subject)) {
459
+ logger.error(`Invalid subject ${subject}; ${reservedEventName} are reserved for internal usage`);
460
+ }
461
+ else {
462
+ mailServer.eventEmitter.emit(subject, mail);
463
+ }
464
+ }
465
+ if ((_a = mailServer === null || mailServer === void 0 ? void 0 : mailServer.outgoing) === null || _a === void 0 ? void 0 : _a.isAutoRelayEnabled()) {
466
+ yield mailServer.relayMail(mail).catch((err) => {
467
+ logger.error("Error when relaying email", err);
468
+ });
469
+ }
470
+ });
471
+ }
472
+ /**
473
+ * Handle smtp-server onData stream
474
+ */
475
+ function handleDataStream(mailServer, stream, session, callback) {
476
+ return __awaiter(this, void 0, void 0, function* () {
477
+ const id = utils.makeId();
478
+ const emlStream = fs.createWriteStream(path.join(mailServer.mailDir, id + ".eml"));
479
+ stream.on("end", function () {
480
+ emlStream.end();
481
+ callback(null, "Message queued as " + id);
482
+ });
483
+ stream.pipe(emlStream);
484
+ const parsed = yield (0, mailparser_1.parse)(stream);
485
+ const envelope = {
486
+ id: id,
487
+ from: session.envelope.mailFrom,
488
+ to: session.envelope.rcptTo,
489
+ date: parsed.date,
490
+ subject: parsed.subject,
491
+ hasAttachment: parsed.attachments.length > 0,
492
+ isRead: false,
493
+ };
494
+ const mail = yield buildMail(mailServer.mailDir, envelope, parsed);
495
+ return saveEmailToStore(mailServer, mail);
496
+ });
497
+ }
@@ -0,0 +1,3 @@
1
+ import type { MailDevOptions } from "../index";
2
+ export declare function appendOptions(program: any, options: any): any;
3
+ export declare function cliOptions(): MailDevOptions;
@@ -0,0 +1,150 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.appendOptions = appendOptions;
4
+ exports.cliOptions = cliOptions;
5
+ const program = require("commander").program;
6
+ const pkg = require("../../package.json");
7
+ const fs = require("fs");
8
+ const options = [
9
+ // General config
10
+ ["-v, --verbose"],
11
+ ["--silent"],
12
+ ["--log-mail-contents", "Log a JSON representation of each incoming email"],
13
+ // SMTP server parameters
14
+ ["-s, --smtp <port>", "MAILDEV_SMTP_PORT", "SMTP port to catch emails", 1025, (x) => parseInt(x)],
15
+ ["--ip <ip address>", "MAILDEV_IP", "IP Address to bind SMTP service to", "0.0.0.0"],
16
+ ["--mail-directory <path>", "MAILDEV_MAIL_DIRECTORY", "Directory for persisting mails"],
17
+ ["--incoming-user <user>", "MAILDEV_INCOMING_USER", "SMTP user for incoming emails"],
18
+ ["--incoming-pass <pass>", "MAILDEV_INCOMING_PASS", "SMTP password for incoming emails"],
19
+ ["--incoming-secure", "MAILDEV_INCOMING_SECURE", "Use SMTP SSL for incoming emails", false],
20
+ ["--incoming-cert <path>", "MAILDEV_INCOMING_CERT", "Cert file location for incoming SSL"],
21
+ ["--incoming-key <path>", "MAILDEV_INCOMING_KEY", "Key file location for incoming SSL"],
22
+ [
23
+ "--hide-extensions <extensions>",
24
+ "MAILDEV_HIDE_EXTENSIONS",
25
+ "Comma separated list of SMTP extensions to NOT advertise (SMTPUTF8, PIPELINING, 8BITMIME)",
26
+ [],
27
+ (val) => val.split(","),
28
+ ],
29
+ // Outgoing parameters
30
+ [
31
+ "--auto-relay [email]",
32
+ "MAILDEV_AUTO_RELAY",
33
+ "Use auto-relay mode. Optional relay email address",
34
+ ],
35
+ ["--outgoing-host <host>", "MAILDEV_OUTGOING_HOST", "SMTP host for outgoing emails"],
36
+ [
37
+ "--outgoing-port <port>",
38
+ "MAILDEV_OUTGOING_PORT",
39
+ "SMTP port for outgoing emails",
40
+ (x) => parseInt(x),
41
+ ],
42
+ ["--outgoing-user <user>", "MAILDEV_OUTGOING_USER", "SMTP user for outgoing emails"],
43
+ ["--outgoing-pass <password>", "MAILDEV_OUTGOING_PASS", "SMTP password for outgoing emails"],
44
+ ["--outgoing-secure", "MAILDEV_OUTGOING_SECURE", "Use SMTP SSL for outgoing emails", false],
45
+ ["--auto-relay-rules <file>", "MAILDEV_AUTO_RELAY_RULES", "Filter rules for auto relay mode"],
46
+ // Web app config
47
+ [
48
+ "--disable-web",
49
+ "MAILDEV_DISABLE_WEB",
50
+ "Disable the use of the web interface. Useful for unit testing",
51
+ false,
52
+ ],
53
+ ["-w, --web <port>", "MAILDEV_WEB_PORT", "Port to run the Web GUI", 1080, (x) => parseInt(x)],
54
+ [
55
+ "--web-ip <ip address>",
56
+ "MAILDEV_WEB_IP",
57
+ "IP Address to bind HTTP service to, defaults to --ip",
58
+ ],
59
+ ["--web-user <user>", "MAILDEV_WEB_USER", "HTTP user for GUI"],
60
+ ["--web-pass <password>", "MAILDEV_WEB_PASS", "HTTP password for GUI"],
61
+ ["--base-pathname <path>", "MAILDEV_BASE_PATHNAME", "Base path for URLs"],
62
+ ["--https", "MAILDEV_HTTPS", "Switch from http to https protocol", false],
63
+ ["--https-key <file>", "MAILDEV_HTTPS_KEY", "The file path to the ssl private key"],
64
+ ["--https-cert <file>", "MAILDEV_HTTPS_CERT", "The file path to the ssl cert file"],
65
+ ];
66
+ function appendOptions(program, options) {
67
+ return options.reduce(function (chain, option) {
68
+ var _a;
69
+ const flag = option[0];
70
+ const envVariable = typeof option[1] === "string" ? option[1] : undefined;
71
+ const description = typeof option[2] === "string" ? option[2] : undefined;
72
+ const defaultValue = envVariable ? ((_a = process.env[envVariable]) !== null && _a !== void 0 ? _a : option[3]) : option[3];
73
+ const fn = option[4];
74
+ return fn
75
+ ? chain.option(flag, description, fn, defaultValue)
76
+ : chain.option(flag, description, defaultValue);
77
+ }, program);
78
+ }
79
+ function cliOptions() {
80
+ var _a, _b, _c;
81
+ const config = appendOptions(program.version(pkg.version).allowUnknownOption(true), options)
82
+ .parse(process.argv)
83
+ .opts();
84
+ let web;
85
+ if (!(config === null || config === void 0 ? void 0 : config.disableWeb)) {
86
+ var secure;
87
+ if (config === null || config === void 0 ? void 0 : config.https) {
88
+ if (!(config === null || config === void 0 ? void 0 : config.httpsKey) || fs.existsSync(config === null || config === void 0 ? void 0 : config.httpsKey) === false) {
89
+ throw new Error("Unable to find https secure key. Please specify key file via -https-key argument");
90
+ }
91
+ if (!(config === null || config === void 0 ? void 0 : config.httpsCert) || fs.existsSync(config === null || config === void 0 ? void 0 : config.httpsCert) === false) {
92
+ throw new Error("Unable to find https secure cert. Please specify cert file via -https-cert argument");
93
+ }
94
+ secure = {
95
+ cert: config === null || config === void 0 ? void 0 : config.httpsCert,
96
+ key: config === null || config === void 0 ? void 0 : config.httpsKey,
97
+ };
98
+ }
99
+ web = {
100
+ disabled: false,
101
+ port: config === null || config === void 0 ? void 0 : config.web,
102
+ host: config === null || config === void 0 ? void 0 : config.webIp,
103
+ basePathname: config === null || config === void 0 ? void 0 : config.basePathname,
104
+ auth: (config === null || config === void 0 ? void 0 : config.webUser) && (config === null || config === void 0 ? void 0 : config.webPass)
105
+ ? { user: config === null || config === void 0 ? void 0 : config.webUser, pass: config === null || config === void 0 ? void 0 : config.webPass }
106
+ : undefined,
107
+ ssl: secure,
108
+ };
109
+ }
110
+ else {
111
+ web = { disabled: true };
112
+ }
113
+ return {
114
+ verbose: config === null || config === void 0 ? void 0 : config.verbose,
115
+ silent: config === null || config === void 0 ? void 0 : config.silent,
116
+ logMailContents: config === null || config === void 0 ? void 0 : config.logMailContents,
117
+ port: config === null || config === void 0 ? void 0 : config.smtp,
118
+ host: config === null || config === void 0 ? void 0 : config.ip,
119
+ mailDir: config === null || config === void 0 ? void 0 : config.mailDirectory,
120
+ auth: (config === null || config === void 0 ? void 0 : config.incomingUser) && (config === null || config === void 0 ? void 0 : config.incomingPass)
121
+ ? {
122
+ user: config === null || config === void 0 ? void 0 : config.incomingUser,
123
+ pass: config === null || config === void 0 ? void 0 : config.incomingPass,
124
+ }
125
+ : undefined,
126
+ isSecure: config === null || config === void 0 ? void 0 : config.incomingSecure,
127
+ ssl: (config === null || config === void 0 ? void 0 : config.incomingCert) && (config === null || config === void 0 ? void 0 : config.incomingKey)
128
+ ? {
129
+ certPath: config === null || config === void 0 ? void 0 : config.incomingCert,
130
+ keyPath: config === null || config === void 0 ? void 0 : config.incomingKey,
131
+ }
132
+ : undefined,
133
+ hide8BITMIME: ((_a = config === null || config === void 0 ? void 0 : config.hideExtensions) !== null && _a !== void 0 ? _a : []).includes("8BITMIME"),
134
+ hidePIPELINING: ((_b = config === null || config === void 0 ? void 0 : config.hideExtensions) !== null && _b !== void 0 ? _b : []).includes("PIPELINING"),
135
+ hideSMTPUTF8: ((_c = config === null || config === void 0 ? void 0 : config.hideExtensions) !== null && _c !== void 0 ? _c : []).includes("SMTPUTF8"),
136
+ outgoing: (config === null || config === void 0 ? void 0 : config.autoRelay) || (config === null || config === void 0 ? void 0 : config.autoRelayRules)
137
+ ? {
138
+ host: config === null || config === void 0 ? void 0 : config.outgoingHost,
139
+ port: config === null || config === void 0 ? void 0 : config.outgoingPort,
140
+ secure: config === null || config === void 0 ? void 0 : config.outgoingSecure,
141
+ auth: (config === null || config === void 0 ? void 0 : config.outgoingUser) && (config === null || config === void 0 ? void 0 : config.outgoingPass)
142
+ ? { user: config === null || config === void 0 ? void 0 : config.outgoingUser, pass: config === null || config === void 0 ? void 0 : config.outgoingPass }
143
+ : undefined,
144
+ autoRelayAddress: config === null || config === void 0 ? void 0 : config.autoRelay,
145
+ autoRelayRules: config === null || config === void 0 ? void 0 : config.autoRelayRules,
146
+ }
147
+ : undefined,
148
+ web,
149
+ };
150
+ }