!"#$%&'()*+,-./0123456789:;
+ [0x3e, 0x7e] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
+ ];
+ let result = '';
+ let ord;
+
+ for (let i = 0, len = buffer.length; i < len; i++) {
+ ord = buffer[i];
+ // if the char is in allowed range, then keep as is, unless it is a WS in the end of a line
+ if (checkRanges(ord, ranges) && !((ord === 0x20 || ord === 0x09) && (i === len - 1 || buffer[i + 1] === 0x0a || buffer[i + 1] === 0x0d))) {
+ result += String.fromCharCode(ord);
+ continue;
+ }
+ result += '=' + (ord < 0x10 ? '0' : '') + ord.toString(16).toUpperCase();
+ }
+
+ return result;
+}
+
+/**
+ * Adds soft line breaks to a Quoted-Printable string
+ *
+ * @param {String} str Quoted-Printable encoded string that might need line wrapping
+ * @param {Number} [lineLength=76] Maximum allowed length for a line
+ * @returns {String} Soft-wrapped Quoted-Printable encoded string
+ */
+function wrap(str, lineLength) {
+ str = (str || '').toString();
+ lineLength = lineLength || 76;
+
+ if (str.length <= lineLength) {
+ return str;
+ }
+
+ let pos = 0;
+ let len = str.length;
+ let match, code, line;
+ let lineMargin = Math.floor(lineLength / 3);
+ let result = '';
+
+ // insert soft linebreaks where needed
+ while (pos < len) {
+ line = str.substr(pos, lineLength);
+ if ((match = line.match(/\r\n/))) {
+ line = line.substr(0, match.index + match[0].length);
+ result += line;
+ pos += line.length;
+ continue;
+ }
+
+ if (line.substr(-1) === '\n') {
+ // nothing to change here
+ result += line;
+ pos += line.length;
+ continue;
+ } else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) {
+ // truncate to nearest line break
+ line = line.substr(0, line.length - (match[0].length - 1));
+ result += line;
+ pos += line.length;
+ continue;
+ } else if (line.length > lineLength - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) {
+ // truncate to nearest space
+ line = line.substr(0, line.length - (match[0].length - 1));
+ } else if (line.match(/[=][\da-f]{0,2}$/i)) {
+ // push incomplete encoding sequences to the next line
+ if ((match = line.match(/[=][\da-f]{0,1}$/i))) {
+ line = line.substr(0, line.length - match[0].length);
+ }
+
+ // ensure that utf-8 sequences are not split
+ while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/[=][\da-f]{2}$/gi))) {
+ code = parseInt(match[0].substr(1, 2), 16);
+ if (code < 128) {
+ break;
+ }
+
+ line = line.substr(0, line.length - 3);
+
+ if (code >= 0xc0) {
+ break;
+ }
+ }
+ }
+
+ if (pos + line.length < len && line.substr(-1) !== '\n') {
+ if (line.length === lineLength && line.match(/[=][\da-f]{2}$/i)) {
+ line = line.substr(0, line.length - 3);
+ } else if (line.length === lineLength) {
+ line = line.substr(0, line.length - 1);
+ }
+ pos += line.length;
+ line += '=\r\n';
+ } else {
+ pos += line.length;
+ }
+
+ result += line;
+ }
+
+ return result;
+}
+
+/**
+ * Helper function to check if a number is inside provided ranges
+ *
+ * @param {Number} nr Number to check for
+ * @param {Array} ranges An Array of allowed values
+ * @returns {Boolean} True if the value was found inside allowed ranges, false otherwise
+ */
+function checkRanges(nr, ranges) {
+ for (let i = ranges.length - 1; i >= 0; i--) {
+ if (!ranges[i].length) {
+ continue;
+ }
+ if (ranges[i].length === 1 && nr === ranges[i][0]) {
+ return true;
+ }
+ if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/**
+ * Creates a transform stream for encoding data to Quoted-Printable encoding
+ *
+ * @constructor
+ * @param {Object} options Stream options
+ * @param {Number} [options.lineLength=76] Maximum lenght for lines, set to false to disable wrapping
+ */
+class Encoder extends Transform {
+ constructor(options) {
+ super();
+
+ // init Transform
+ this.options = options || {};
+
+ if (this.options.lineLength !== false) {
+ this.options.lineLength = this.options.lineLength || 76;
+ }
+
+ this._curLine = '';
+
+ this.inputBytes = 0;
+ this.outputBytes = 0;
+ }
+
+ _transform(chunk, encoding, done) {
+ let qp;
+
+ if (encoding !== 'buffer') {
+ chunk = Buffer.from(chunk, encoding);
+ }
+
+ if (!chunk || !chunk.length) {
+ return done();
+ }
+
+ this.inputBytes += chunk.length;
+
+ if (this.options.lineLength) {
+ qp = this._curLine + encode(chunk);
+ qp = wrap(qp, this.options.lineLength);
+ qp = qp.replace(/(^|\n)([^\n]*)$/, (match, lineBreak, lastLine) => {
+ this._curLine = lastLine;
+ return lineBreak;
+ });
+
+ if (qp) {
+ this.outputBytes += qp.length;
+ this.push(qp);
+ }
+ } else {
+ qp = encode(chunk);
+ this.outputBytes += qp.length;
+ this.push(qp, 'ascii');
+ }
+
+ done();
+ }
+
+ _flush(done) {
+ if (this._curLine) {
+ this.outputBytes += this._curLine.length;
+ this.push(this._curLine, 'ascii');
+ }
+ done();
+ }
+}
+
+// expose to the world
+module.exports = {
+ encode,
+ wrap,
+ Encoder
+};
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/index.js b/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/index.js
new file mode 100644
index 0000000..0e59221
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/index.js
@@ -0,0 +1,208 @@
+'use strict';
+
+const spawn = require('child_process').spawn;
+const packageData = require('../../package.json');
+const LeWindows = require('./le-windows');
+const LeUnix = require('./le-unix');
+const shared = require('../shared');
+
+/**
+ * Generates a Transport object for Sendmail
+ *
+ * Possible options can be the following:
+ *
+ * * **path** optional path to sendmail binary
+ * * **newline** either 'windows' or 'unix'
+ * * **args** an array of arguments for the sendmail binary
+ *
+ * @constructor
+ * @param {Object} optional config parameter for Sendmail
+ */
+class SendmailTransport {
+ constructor(options) {
+ options = options || {};
+
+ // use a reference to spawn for mocking purposes
+ this._spawn = spawn;
+
+ this.options = options || {};
+
+ this.name = 'Sendmail';
+ this.version = packageData.version;
+
+ this.path = 'sendmail';
+ this.args = false;
+ this.winbreak = false;
+
+ this.logger = shared.getLogger(this.options, {
+ component: this.options.component || 'sendmail'
+ });
+
+ if (options) {
+ if (typeof options === 'string') {
+ this.path = options;
+ } else if (typeof options === 'object') {
+ if (options.path) {
+ this.path = options.path;
+ }
+ if (Array.isArray(options.args)) {
+ this.args = options.args;
+ }
+ this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
+ }
+ }
+ }
+
+ /**
+ * Compiles a mailcomposer message and forwards it to handler that sends it.
+ *
+ * @param {Object} emailMessage MailComposer object
+ * @param {Function} callback Callback function to run when the sending is completed
+ */
+ send(mail, done) {
+ // Sendmail strips this header line by itself
+ mail.message.keepBcc = true;
+
+ let envelope = mail.data.envelope || mail.message.getEnvelope();
+ let messageId = mail.message.messageId();
+ let args;
+ let sendmail;
+ let returned;
+ let transform;
+
+ if (this.args) {
+ // force -i to keep single dots
+ args = ['-i'].concat(this.args).concat(envelope.to);
+ } else {
+ args = ['-i'].concat(envelope.from ? ['-f', envelope.from] : []).concat(envelope.to);
+ }
+
+ let callback = err => {
+ if (returned) {
+ // ignore any additional responses, already done
+ return;
+ }
+ returned = true;
+ if (typeof done === 'function') {
+ if (err) {
+ return done(err);
+ } else {
+ return done(null, {
+ envelope: mail.data.envelope || mail.message.getEnvelope(),
+ messageId,
+ response: 'Messages queued for delivery'
+ });
+ }
+ }
+ };
+
+ try {
+ sendmail = this._spawn(this.path, args);
+ } catch (E) {
+ this.logger.error(
+ {
+ err: E,
+ tnx: 'spawn',
+ messageId
+ },
+ 'Error occurred while spawning sendmail. %s',
+ E.message
+ );
+ return callback(E);
+ }
+
+ if (sendmail) {
+ sendmail.on('error', err => {
+ this.logger.error(
+ {
+ err,
+ tnx: 'spawn',
+ messageId
+ },
+ 'Error occurred when sending message %s. %s',
+ messageId,
+ err.message
+ );
+ callback(err);
+ });
+
+ sendmail.once('exit', code => {
+ if (!code) {
+ return callback();
+ }
+ let err;
+ if (code === 127) {
+ err = new Error('Sendmail command not found, process exited with code ' + code);
+ } else {
+ err = new Error('Sendmail exited with code ' + code);
+ }
+
+ this.logger.error(
+ {
+ err,
+ tnx: 'stdin',
+ messageId
+ },
+ 'Error sending message %s to sendmail. %s',
+ messageId,
+ err.message
+ );
+ callback(err);
+ });
+ sendmail.once('close', callback);
+
+ sendmail.stdin.on('error', err => {
+ this.logger.error(
+ {
+ err,
+ tnx: 'stdin',
+ messageId
+ },
+ 'Error occurred when piping message %s to sendmail. %s',
+ messageId,
+ err.message
+ );
+ callback(err);
+ });
+
+ let recipients = [].concat(envelope.to || []);
+ if (recipients.length > 3) {
+ recipients.push('...and ' + recipients.splice(2).length + ' more');
+ }
+ this.logger.info(
+ {
+ tnx: 'send',
+ messageId
+ },
+ 'Sending message %s to <%s>',
+ messageId,
+ recipients.join(', ')
+ );
+
+ transform = this.winbreak ? new LeWindows() : new LeUnix();
+ let sourceStream = mail.message.createReadStream();
+
+ transform.once('error', err => {
+ this.logger.error(
+ {
+ err,
+ tnx: 'stdin',
+ messageId
+ },
+ 'Error occurred when generating message %s. %s',
+ messageId,
+ err.message
+ );
+ sendmail.kill('SIGINT'); // do not deliver the message
+ callback(err);
+ });
+
+ sourceStream.once('error', err => transform.emit('error', err));
+ sourceStream.pipe(transform).pipe(sendmail.stdin);
+ } else {
+ return callback(new Error('sendmail was not found'));
+ }
+ }
+}
+
+module.exports = SendmailTransport;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/le-unix.js b/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/le-unix.js
new file mode 100644
index 0000000..5feacd3
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/le-unix.js
@@ -0,0 +1,43 @@
+'use strict';
+
+const stream = require('stream');
+const Transform = stream.Transform;
+
+/**
+ * Ensures that only is used for linebreaks
+ *
+ * @param {Object} options Stream options
+ */
+class LeWindows extends Transform {
+ constructor(options) {
+ super(options);
+ // init Transform
+ this.options = options || {};
+ }
+
+ /**
+ * Escapes dots
+ */
+ _transform(chunk, encoding, done) {
+ let buf;
+ let lastPos = 0;
+
+ for (let i = 0, len = chunk.length; i < len; i++) {
+ if (chunk[i] === 0x0d) {
+ // \n
+ buf = chunk.slice(lastPos, i);
+ lastPos = i + 1;
+ this.push(buf);
+ }
+ }
+ if (lastPos && lastPos < chunk.length) {
+ buf = chunk.slice(lastPos);
+ this.push(buf);
+ } else if (!lastPos) {
+ this.push(chunk);
+ }
+ done();
+ }
+}
+
+module.exports = LeWindows;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/le-windows.js b/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/le-windows.js
new file mode 100644
index 0000000..b156a7c
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/sendmail-transport/le-windows.js
@@ -0,0 +1,52 @@
+'use strict';
+
+const stream = require('stream');
+const Transform = stream.Transform;
+
+/**
+ * Ensures that only sequences are used for linebreaks
+ *
+ * @param {Object} options Stream options
+ */
+class LeWindows extends Transform {
+ constructor(options) {
+ super(options);
+ // init Transform
+ this.options = options || {};
+ this.lastByte = false;
+ }
+
+ /**
+ * Escapes dots
+ */
+ _transform(chunk, encoding, done) {
+ let buf;
+ let lastPos = 0;
+
+ for (let i = 0, len = chunk.length; i < len; i++) {
+ if (chunk[i] === 0x0a) {
+ // \n
+ if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
+ if (i > lastPos) {
+ buf = chunk.slice(lastPos, i);
+ this.push(buf);
+ }
+ this.push(Buffer.from('\r\n'));
+ lastPos = i + 1;
+ }
+ }
+ }
+
+ if (lastPos && lastPos < chunk.length) {
+ buf = chunk.slice(lastPos);
+ this.push(buf);
+ } else if (!lastPos) {
+ this.push(chunk);
+ }
+
+ this.lastByte = chunk[chunk.length - 1];
+ done();
+ }
+}
+
+module.exports = LeWindows;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/ses-transport/index.js b/html/RentForCamp/node_modules/nodemailer/lib/ses-transport/index.js
new file mode 100644
index 0000000..37dced9
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/ses-transport/index.js
@@ -0,0 +1,312 @@
+'use strict';
+
+const EventEmitter = require('events');
+const packageData = require('../../package.json');
+const shared = require('../shared');
+const LeWindows = require('../sendmail-transport/le-windows');
+
+/**
+ * Generates a Transport object for AWS SES
+ *
+ * Possible options can be the following:
+ *
+ * * **sendingRate** optional Number specifying how many messages per second should be delivered to SES
+ * * **maxConnections** optional Number specifying max number of parallel connections to SES
+ *
+ * @constructor
+ * @param {Object} optional config parameter
+ */
+class SESTransport extends EventEmitter {
+ constructor(options) {
+ super();
+ options = options || {};
+
+ this.options = options || {};
+ this.ses = this.options.SES;
+
+ this.name = 'SESTransport';
+ this.version = packageData.version;
+
+ this.logger = shared.getLogger(this.options, {
+ component: this.options.component || 'ses-transport'
+ });
+
+ // parallel sending connections
+ this.maxConnections = Number(this.options.maxConnections) || Infinity;
+ this.connections = 0;
+
+ // max messages per second
+ this.sendingRate = Number(this.options.sendingRate) || Infinity;
+ this.sendingRateTTL = null;
+ this.rateInterval = 1000;
+ this.rateMessages = [];
+
+ this.pending = [];
+
+ this.idling = true;
+
+ setImmediate(() => {
+ if (this.idling) {
+ this.emit('idle');
+ }
+ });
+ }
+
+ /**
+ * Schedules a sending of a message
+ *
+ * @param {Object} emailMessage MailComposer object
+ * @param {Function} callback Callback function to run when the sending is completed
+ */
+ send(mail, callback) {
+ if (this.connections >= this.maxConnections) {
+ this.idling = false;
+ return this.pending.push({
+ mail,
+ callback
+ });
+ }
+
+ if (!this._checkSendingRate()) {
+ this.idling = false;
+ return this.pending.push({
+ mail,
+ callback
+ });
+ }
+
+ this._send(mail, (...args) => {
+ setImmediate(() => callback(...args));
+ this._sent();
+ });
+ }
+
+ _checkRatedQueue() {
+ if (this.connections >= this.maxConnections || !this._checkSendingRate()) {
+ return;
+ }
+
+ if (!this.pending.length) {
+ if (!this.idling) {
+ this.idling = true;
+ this.emit('idle');
+ }
+ return;
+ }
+
+ let next = this.pending.shift();
+ this._send(next.mail, (...args) => {
+ setImmediate(() => next.callback(...args));
+ this._sent();
+ });
+ }
+
+ _checkSendingRate() {
+ clearTimeout(this.sendingRateTTL);
+
+ let now = Date.now();
+ let oldest = false;
+ // delete older messages
+ for (let i = this.rateMessages.length - 1; i >= 0; i--) {
+ if (this.rateMessages[i].ts >= now - this.rateInterval && (!oldest || this.rateMessages[i].ts < oldest)) {
+ oldest = this.rateMessages[i].ts;
+ }
+
+ if (this.rateMessages[i].ts < now - this.rateInterval && !this.rateMessages[i].pending) {
+ this.rateMessages.splice(i, 1);
+ }
+ }
+
+ if (this.rateMessages.length < this.sendingRate) {
+ return true;
+ }
+
+ let delay = Math.max(oldest + 1001, now + 20);
+ this.sendingRateTTL = setTimeout(() => this._checkRatedQueue(), now - delay);
+
+ try {
+ this.sendingRateTTL.unref();
+ } catch (E) {
+ // Ignore. Happens on envs with non-node timer implementation
+ }
+
+ return false;
+ }
+
+ _sent() {
+ this.connections--;
+ this._checkRatedQueue();
+ }
+
+ /**
+ * Returns true if there are free slots in the queue
+ */
+ isIdle() {
+ return this.idling;
+ }
+
+ /**
+ * Compiles a mailcomposer message and forwards it to SES
+ *
+ * @param {Object} emailMessage MailComposer object
+ * @param {Function} callback Callback function to run when the sending is completed
+ */
+ _send(mail, callback) {
+ let statObject = {
+ ts: Date.now(),
+ pending: true
+ };
+ this.connections++;
+ this.rateMessages.push(statObject);
+
+ let envelope = mail.data.envelope || mail.message.getEnvelope();
+ let messageId = mail.message.messageId();
+
+ let recipients = [].concat(envelope.to || []);
+ if (recipients.length > 3) {
+ recipients.push('...and ' + recipients.splice(2).length + ' more');
+ }
+ this.logger.info(
+ {
+ tnx: 'send',
+ messageId
+ },
+ 'Sending message %s to <%s>',
+ messageId,
+ recipients.join(', ')
+ );
+
+ let getRawMessage = next => {
+ // do not use Message-ID and Date in DKIM signature
+ if (!mail.data._dkim) {
+ mail.data._dkim = {};
+ }
+ if (mail.data._dkim.skipFields && typeof mail.data._dkim.skipFields === 'string') {
+ mail.data._dkim.skipFields += ':date:message-id';
+ } else {
+ mail.data._dkim.skipFields = 'date:message-id';
+ }
+
+ let sourceStream = mail.message.createReadStream();
+ let stream = sourceStream.pipe(new LeWindows());
+ let chunks = [];
+ let chunklen = 0;
+
+ stream.on('readable', () => {
+ let chunk;
+ while ((chunk = stream.read()) !== null) {
+ chunks.push(chunk);
+ chunklen += chunk.length;
+ }
+ });
+
+ sourceStream.once('error', err => stream.emit('error', err));
+
+ stream.once('error', err => {
+ next(err);
+ });
+
+ stream.once('end', () => next(null, Buffer.concat(chunks, chunklen)));
+ };
+
+ setImmediate(() =>
+ getRawMessage((err, raw) => {
+ if (err) {
+ this.logger.error(
+ {
+ err,
+ tnx: 'send',
+ messageId
+ },
+ 'Failed creating message for %s. %s',
+ messageId,
+ err.message
+ );
+ statObject.pending = false;
+ return callback(err);
+ }
+
+ let sesMessage = {
+ RawMessage: {
+ // required
+ Data: raw // required
+ },
+ Source: envelope.from,
+ Destinations: envelope.to
+ };
+
+ Object.keys(mail.data.ses || {}).forEach(key => {
+ sesMessage[key] = mail.data.ses[key];
+ });
+
+ this.ses.sendRawEmail(sesMessage, (err, data) => {
+ if (err) {
+ this.logger.error(
+ {
+ err,
+ tnx: 'send'
+ },
+ 'Send error for %s: %s',
+ messageId,
+ err.message
+ );
+ statObject.pending = false;
+ return callback(err);
+ }
+
+ let region = (this.ses.config && this.ses.config.region) || 'us-east-1';
+ if (region === 'us-east-1') {
+ region = 'email';
+ }
+
+ statObject.pending = false;
+ callback(null, {
+ envelope: {
+ from: envelope.from,
+ to: envelope.to
+ },
+ messageId: '<' + data.MessageId + (!/@/.test(data.MessageId) ? '@' + region + '.amazonses.com' : '') + '>',
+ response: data.MessageId,
+ raw
+ });
+ });
+ })
+ );
+ }
+
+ /**
+ * Verifies SES configuration
+ *
+ * @param {Function} callback Callback function
+ */
+ verify(callback) {
+ let promise;
+
+ if (!callback) {
+ promise = new Promise((resolve, reject) => {
+ callback = shared.callbackPromise(resolve, reject);
+ });
+ }
+
+ this.ses.sendRawEmail(
+ {
+ RawMessage: {
+ // required
+ Data: 'From: invalid@invalid\r\nTo: invalid@invalid\r\n Subject: Invalid\r\n\r\nInvalid'
+ },
+ Source: 'invalid@invalid',
+ Destinations: ['invalid@invalid']
+ },
+ err => {
+ if (err && err.code !== 'InvalidParameterValue') {
+ return callback(err);
+ }
+ return callback(null, true);
+ }
+ );
+
+ return promise;
+ }
+}
+
+module.exports = SESTransport;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/shared/index.js b/html/RentForCamp/node_modules/nodemailer/lib/shared/index.js
new file mode 100644
index 0000000..7be5253
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/shared/index.js
@@ -0,0 +1,507 @@
+/* eslint no-console: 0 */
+
+'use strict';
+
+const urllib = require('url');
+const util = require('util');
+const fs = require('fs');
+const fetch = require('../fetch');
+const dns = require('dns');
+const net = require('net');
+
+const DNS_TTL = 5 * 60 * 1000;
+
+const resolver = (family, hostname, callback) => {
+ dns['resolve' + family](hostname, (err, addresses) => {
+ if (err) {
+ switch (err.code) {
+ case dns.NODATA:
+ case dns.NOTFOUND:
+ case dns.NOTIMP:
+ return callback(null, []);
+ }
+ return callback(err);
+ }
+ return callback(null, Array.isArray(addresses) ? addresses : [].concat(addresses || []));
+ });
+};
+
+const dnsCache = (module.exports.dnsCache = new Map());
+module.exports.resolveHostname = (options, callback) => {
+ options = options || {};
+
+ if (!options.host || net.isIP(options.host)) {
+ // nothing to do here
+ let value = {
+ host: options.host,
+ servername: options.servername || false
+ };
+ return callback(null, value);
+ }
+
+ let cached;
+
+ if (dnsCache.has(options.host)) {
+ cached = dnsCache.get(options.host);
+ if (!cached.expires || cached.expires >= Date.now()) {
+ return callback(null, {
+ host: cached.value.host,
+ servername: cached.value.servername,
+ _cached: true
+ });
+ }
+ }
+
+ resolver(4, options.host, (err, addresses) => {
+ if (err) {
+ if (cached) {
+ // ignore error, use expired value
+ return callback(null, cached.value);
+ }
+ return callback(err);
+ }
+ if (addresses && addresses.length) {
+ let value = {
+ host: addresses[0] || options.host,
+ servername: options.servername || options.host
+ };
+ dnsCache.set(options.host, {
+ value,
+ expires: Date.now() + DNS_TTL
+ });
+ return callback(null, value);
+ }
+
+ resolver(6, options.host, (err, addresses) => {
+ if (err) {
+ if (cached) {
+ // ignore error, use expired value
+ return callback(null, cached.value);
+ }
+ return callback(err);
+ }
+ if (addresses && addresses.length) {
+ let value = {
+ host: addresses[0] || options.host,
+ servername: options.servername || options.host
+ };
+ dnsCache.set(options.host, {
+ value,
+ expires: Date.now() + DNS_TTL
+ });
+ return callback(null, value);
+ }
+
+ try {
+ dns.lookup(options.host, {}, (err, address) => {
+ if (err) {
+ if (cached) {
+ // ignore error, use expired value
+ return callback(null, cached.value);
+ }
+ return callback(err);
+ }
+
+ if (!address && cached) {
+ // nothing was found, fallback to cached value
+ return callback(null, cached.value);
+ }
+
+ let value = {
+ host: address || options.host,
+ servername: options.servername || options.host
+ };
+ dnsCache.set(options.host, {
+ value,
+ expires: Date.now() + DNS_TTL
+ });
+ return callback(null, value);
+ });
+ } catch (err) {
+ if (cached) {
+ // ignore error, use expired value
+ return callback(null, cached.value);
+ }
+ return callback(err);
+ }
+ });
+ });
+};
+/**
+ * Parses connection url to a structured configuration object
+ *
+ * @param {String} str Connection url
+ * @return {Object} Configuration object
+ */
+module.exports.parseConnectionUrl = str => {
+ str = str || '';
+ let options = {};
+
+ [urllib.parse(str, true)].forEach(url => {
+ let auth;
+
+ switch (url.protocol) {
+ case 'smtp:':
+ options.secure = false;
+ break;
+ case 'smtps:':
+ options.secure = true;
+ break;
+ case 'direct:':
+ options.direct = true;
+ break;
+ }
+
+ if (!isNaN(url.port) && Number(url.port)) {
+ options.port = Number(url.port);
+ }
+
+ if (url.hostname) {
+ options.host = url.hostname;
+ }
+
+ if (url.auth) {
+ auth = url.auth.split(':');
+
+ if (!options.auth) {
+ options.auth = {};
+ }
+
+ options.auth.user = auth.shift();
+ options.auth.pass = auth.join(':');
+ }
+
+ Object.keys(url.query || {}).forEach(key => {
+ let obj = options;
+ let lKey = key;
+ let value = url.query[key];
+
+ if (!isNaN(value)) {
+ value = Number(value);
+ }
+
+ switch (value) {
+ case 'true':
+ value = true;
+ break;
+ case 'false':
+ value = false;
+ break;
+ }
+
+ // tls is nested object
+ if (key.indexOf('tls.') === 0) {
+ lKey = key.substr(4);
+ if (!options.tls) {
+ options.tls = {};
+ }
+ obj = options.tls;
+ } else if (key.indexOf('.') >= 0) {
+ // ignore nested properties besides tls
+ return;
+ }
+
+ if (!(lKey in obj)) {
+ obj[lKey] = value;
+ }
+ });
+ });
+
+ return options;
+};
+
+module.exports._logFunc = (logger, level, defaults, data, message, ...args) => {
+ let entry = {};
+
+ Object.keys(defaults || {}).forEach(key => {
+ if (key !== 'level') {
+ entry[key] = defaults[key];
+ }
+ });
+
+ Object.keys(data || {}).forEach(key => {
+ if (key !== 'level') {
+ entry[key] = data[key];
+ }
+ });
+
+ logger[level](entry, message, ...args);
+};
+
+/**
+ * Returns a bunyan-compatible logger interface. Uses either provided logger or
+ * creates a default console logger
+ *
+ * @param {Object} [options] Options object that might include 'logger' value
+ * @return {Object} bunyan compatible logger
+ */
+module.exports.getLogger = (options, defaults) => {
+ options = options || {};
+
+ let response = {};
+ let levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
+
+ if (!options.logger) {
+ // use vanity logger
+ levels.forEach(level => {
+ response[level] = () => false;
+ });
+ return response;
+ }
+
+ let logger = options.logger;
+
+ if (options.logger === true) {
+ // create console logger
+ logger = createDefaultLogger(levels);
+ }
+
+ levels.forEach(level => {
+ response[level] = (data, message, ...args) => {
+ module.exports._logFunc(logger, level, defaults, data, message, ...args);
+ };
+ });
+
+ return response;
+};
+
+/**
+ * Wrapper for creating a callback that either resolves or rejects a promise
+ * based on input
+ *
+ * @param {Function} resolve Function to run if callback is called
+ * @param {Function} reject Function to run if callback ends with an error
+ */
+module.exports.callbackPromise = (resolve, reject) =>
+ function() {
+ let args = Array.from(arguments);
+ let err = args.shift();
+ if (err) {
+ reject(err);
+ } else {
+ resolve(...args);
+ }
+ };
+
+/**
+ * Resolves a String or a Buffer value for content value. Useful if the value
+ * is a Stream or a file or an URL. If the value is a Stream, overwrites
+ * the stream object with the resolved value (you can't stream a value twice).
+ *
+ * This is useful when you want to create a plugin that needs a content value,
+ * for example the `html` or `text` value as a String or a Buffer but not as
+ * a file path or an URL.
+ *
+ * @param {Object} data An object or an Array you want to resolve an element for
+ * @param {String|Number} key Property name or an Array index
+ * @param {Function} callback Callback function with (err, value)
+ */
+module.exports.resolveContent = (data, key, callback) => {
+ let promise;
+
+ if (!callback) {
+ promise = new Promise((resolve, reject) => {
+ callback = module.exports.callbackPromise(resolve, reject);
+ });
+ }
+
+ let content = (data && data[key] && data[key].content) || data[key];
+ let contentStream;
+ let encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
+ .toString()
+ .toLowerCase()
+ .replace(/[-_\s]/g, '');
+
+ if (!content) {
+ return callback(null, content);
+ }
+
+ if (typeof content === 'object') {
+ if (typeof content.pipe === 'function') {
+ return resolveStream(content, (err, value) => {
+ if (err) {
+ return callback(err);
+ }
+ // we can't stream twice the same content, so we need
+ // to replace the stream object with the streaming result
+ data[key] = value;
+ callback(null, value);
+ });
+ } else if (/^https?:\/\//i.test(content.path || content.href)) {
+ contentStream = fetch(content.path || content.href);
+ return resolveStream(contentStream, callback);
+ } else if (/^data:/i.test(content.path || content.href)) {
+ let parts = (content.path || content.href).match(/^data:((?:[^;]*;)*(?:[^,]*)),(.*)$/i);
+ if (!parts) {
+ return callback(null, Buffer.from(0));
+ }
+ return callback(null, /\bbase64$/i.test(parts[1]) ? Buffer.from(parts[2], 'base64') : Buffer.from(decodeURIComponent(parts[2])));
+ } else if (content.path) {
+ return resolveStream(fs.createReadStream(content.path), callback);
+ }
+ }
+
+ if (typeof data[key].content === 'string' && !['utf8', 'usascii', 'ascii'].includes(encoding)) {
+ content = Buffer.from(data[key].content, encoding);
+ }
+
+ // default action, return as is
+ setImmediate(() => callback(null, content));
+
+ return promise;
+};
+
+/**
+ * Copies properties from source objects to target objects
+ */
+module.exports.assign = function(/* target, ... sources */) {
+ let args = Array.from(arguments);
+ let target = args.shift() || {};
+
+ args.forEach(source => {
+ Object.keys(source || {}).forEach(key => {
+ if (['tls', 'auth'].includes(key) && source[key] && typeof source[key] === 'object') {
+ // tls and auth are special keys that need to be enumerated separately
+ // other objects are passed as is
+ if (!target[key]) {
+ // ensure that target has this key
+ target[key] = {};
+ }
+ Object.keys(source[key]).forEach(subKey => {
+ target[key][subKey] = source[key][subKey];
+ });
+ } else {
+ target[key] = source[key];
+ }
+ });
+ });
+ return target;
+};
+
+module.exports.encodeXText = str => {
+ // ! 0x21
+ // + 0x2B
+ // = 0x3D
+ // ~ 0x7E
+ if (!/[^\x21-\x2A\x2C-\x3C\x3E-\x7E]/.test(str)) {
+ return str;
+ }
+ let buf = Buffer.from(str);
+ let result = '';
+ for (let i = 0, len = buf.length; i < len; i++) {
+ let c = buf[i];
+ if (c < 0x21 || c > 0x7e || c === 0x2b || c === 0x3d) {
+ result += '+' + (c < 0x10 ? '0' : '') + c.toString(16).toUpperCase();
+ } else {
+ result += String.fromCharCode(c);
+ }
+ }
+ return result;
+};
+
+/**
+ * Streams a stream value into a Buffer
+ *
+ * @param {Object} stream Readable stream
+ * @param {Function} callback Callback function with (err, value)
+ */
+function resolveStream(stream, callback) {
+ let responded = false;
+ let chunks = [];
+ let chunklen = 0;
+
+ stream.on('error', err => {
+ if (responded) {
+ return;
+ }
+
+ responded = true;
+ callback(err);
+ });
+
+ stream.on('readable', () => {
+ let chunk;
+ while ((chunk = stream.read()) !== null) {
+ chunks.push(chunk);
+ chunklen += chunk.length;
+ }
+ });
+
+ stream.on('end', () => {
+ if (responded) {
+ return;
+ }
+ responded = true;
+
+ let value;
+
+ try {
+ value = Buffer.concat(chunks, chunklen);
+ } catch (E) {
+ return callback(E);
+ }
+ callback(null, value);
+ });
+}
+
+/**
+ * Generates a bunyan-like logger that prints to console
+ *
+ * @returns {Object} Bunyan logger instance
+ */
+function createDefaultLogger(levels) {
+ let levelMaxLen = 0;
+ let levelNames = new Map();
+ levels.forEach(level => {
+ if (level.length > levelMaxLen) {
+ levelMaxLen = level.length;
+ }
+ });
+
+ levels.forEach(level => {
+ let levelName = level.toUpperCase();
+ if (levelName.length < levelMaxLen) {
+ levelName += ' '.repeat(levelMaxLen - levelName.length);
+ }
+ levelNames.set(level, levelName);
+ });
+
+ let print = (level, entry, message, ...args) => {
+ let prefix = '';
+ if (entry) {
+ if (entry.tnx === 'server') {
+ prefix = 'S: ';
+ } else if (entry.tnx === 'client') {
+ prefix = 'C: ';
+ }
+
+ if (entry.sid) {
+ prefix = '[' + entry.sid + '] ' + prefix;
+ }
+
+ if (entry.cid) {
+ prefix = '[#' + entry.cid + '] ' + prefix;
+ }
+ }
+
+ message = util.format(message, ...args);
+ message.split(/\r?\n/).forEach(line => {
+ console.log(
+ '[%s] %s %s',
+ new Date()
+ .toISOString()
+ .substr(0, 19)
+ .replace(/T/, ' '),
+ levelNames.get(level),
+ prefix + line
+ );
+ });
+ };
+
+ let logger = {};
+ levels.forEach(level => {
+ logger[level] = print.bind(null, level);
+ });
+
+ return logger;
+}
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/data-stream.js b/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/data-stream.js
new file mode 100644
index 0000000..5efa087
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/data-stream.js
@@ -0,0 +1,108 @@
+'use strict';
+
+const stream = require('stream');
+const Transform = stream.Transform;
+
+/**
+ * Escapes dots in the beginning of lines. Ends the stream with .
+ * Also makes sure that only sequences are used for linebreaks
+ *
+ * @param {Object} options Stream options
+ */
+class DataStream extends Transform {
+ constructor(options) {
+ super(options);
+ // init Transform
+ this.options = options || {};
+ this._curLine = '';
+
+ this.inByteCount = 0;
+ this.outByteCount = 0;
+ this.lastByte = false;
+ }
+
+ /**
+ * Escapes dots
+ */
+ _transform(chunk, encoding, done) {
+ let chunks = [];
+ let chunklen = 0;
+ let i,
+ len,
+ lastPos = 0;
+ let buf;
+
+ if (!chunk || !chunk.length) {
+ return done();
+ }
+
+ if (typeof chunk === 'string') {
+ chunk = Buffer.from(chunk);
+ }
+
+ this.inByteCount += chunk.length;
+
+ for (i = 0, len = chunk.length; i < len; i++) {
+ if (chunk[i] === 0x2e) {
+ // .
+ if ((i && chunk[i - 1] === 0x0a) || (!i && (!this.lastByte || this.lastByte === 0x0a))) {
+ buf = chunk.slice(lastPos, i + 1);
+ chunks.push(buf);
+ chunks.push(Buffer.from('.'));
+ chunklen += buf.length + 1;
+ lastPos = i + 1;
+ }
+ } else if (chunk[i] === 0x0a) {
+ // .
+ if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
+ if (i > lastPos) {
+ buf = chunk.slice(lastPos, i);
+ chunks.push(buf);
+ chunklen += buf.length + 2;
+ } else {
+ chunklen += 2;
+ }
+ chunks.push(Buffer.from('\r\n'));
+ lastPos = i + 1;
+ }
+ }
+ }
+
+ if (chunklen) {
+ // add last piece
+ if (lastPos < chunk.length) {
+ buf = chunk.slice(lastPos);
+ chunks.push(buf);
+ chunklen += buf.length;
+ }
+
+ this.outByteCount += chunklen;
+ this.push(Buffer.concat(chunks, chunklen));
+ } else {
+ this.outByteCount += chunk.length;
+ this.push(chunk);
+ }
+
+ this.lastByte = chunk[chunk.length - 1];
+ done();
+ }
+
+ /**
+ * Finalizes the stream with a dot on a single line
+ */
+ _flush(done) {
+ let buf;
+ if (this.lastByte === 0x0a) {
+ buf = Buffer.from('.\r\n');
+ } else if (this.lastByte === 0x0d) {
+ buf = Buffer.from('\n.\r\n');
+ } else {
+ buf = Buffer.from('\r\n.\r\n');
+ }
+ this.outByteCount += buf.length;
+ this.push(buf);
+ done();
+ }
+}
+
+module.exports = DataStream;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js b/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js
new file mode 100644
index 0000000..5e9b25d
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js
@@ -0,0 +1,134 @@
+'use strict';
+
+/**
+ * Minimal HTTP/S proxy client
+ */
+
+const net = require('net');
+const tls = require('tls');
+const urllib = require('url');
+
+/**
+ * Establishes proxied connection to destinationPort
+ *
+ * httpProxyClient("http://localhost:3128/", 80, "google.com", function(err, socket){
+ * socket.write("GET / HTTP/1.0\r\n\r\n");
+ * });
+ *
+ * @param {String} proxyUrl proxy configuration, etg "http://proxy.host:3128/"
+ * @param {Number} destinationPort Port to open in destination host
+ * @param {String} destinationHost Destination hostname
+ * @param {Function} callback Callback to run with the rocket object once connection is established
+ */
+function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
+ let proxy = urllib.parse(proxyUrl);
+
+ // create a socket connection to the proxy server
+ let options;
+ let connect;
+ let socket;
+
+ options = {
+ host: proxy.hostname,
+ port: Number(proxy.port) ? Number(proxy.port) : proxy.protocol === 'https:' ? 443 : 80
+ };
+
+ if (proxy.protocol === 'https:') {
+ // we can use untrusted proxies as long as we verify actual SMTP certificates
+ options.rejectUnauthorized = false;
+ connect = tls.connect.bind(tls);
+ } else {
+ connect = net.connect.bind(net);
+ }
+
+ // Error harness for initial connection. Once connection is established, the responsibility
+ // to handle errors is passed to whoever uses this socket
+ let finished = false;
+ let tempSocketErr = function(err) {
+ if (finished) {
+ return;
+ }
+ finished = true;
+ try {
+ socket.destroy();
+ } catch (E) {
+ // ignore
+ }
+ callback(err);
+ };
+
+ socket = connect(
+ options,
+ () => {
+ if (finished) {
+ return;
+ }
+
+ let reqHeaders = {
+ Host: destinationHost + ':' + destinationPort,
+ Connection: 'close'
+ };
+ if (proxy.auth) {
+ reqHeaders['Proxy-Authorization'] = 'Basic ' + Buffer.from(proxy.auth).toString('base64');
+ }
+
+ socket.write(
+ // HTTP method
+ 'CONNECT ' +
+ destinationHost +
+ ':' +
+ destinationPort +
+ ' HTTP/1.1\r\n' +
+ // HTTP request headers
+ Object.keys(reqHeaders)
+ .map(key => key + ': ' + reqHeaders[key])
+ .join('\r\n') +
+ // End request
+ '\r\n\r\n'
+ );
+
+ let headers = '';
+ let onSocketData = chunk => {
+ let match;
+ let remainder;
+
+ if (finished) {
+ return;
+ }
+
+ headers += chunk.toString('binary');
+ if ((match = headers.match(/\r\n\r\n/))) {
+ socket.removeListener('data', onSocketData);
+
+ remainder = headers.substr(match.index + match[0].length);
+ headers = headers.substr(0, match.index);
+ if (remainder) {
+ socket.unshift(Buffer.from(remainder, 'binary'));
+ }
+
+ // proxy connection is now established
+ finished = true;
+
+ // check response code
+ match = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i);
+ if (!match || (match[1] || '').charAt(0) !== '2') {
+ try {
+ socket.destroy();
+ } catch (E) {
+ // ignore
+ }
+ return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
+ }
+
+ socket.removeListener('error', tempSocketErr);
+ return callback(null, socket);
+ }
+ };
+ socket.on('data', onSocketData);
+ }
+ );
+
+ socket.once('error', tempSocketErr);
+}
+
+module.exports = httpProxyClient;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/index.js b/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/index.js
new file mode 100644
index 0000000..f0b35c7
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/smtp-connection/index.js
@@ -0,0 +1,1743 @@
+'use strict';
+
+const packageInfo = require('../../package.json');
+const EventEmitter = require('events').EventEmitter;
+const net = require('net');
+const tls = require('tls');
+const os = require('os');
+const crypto = require('crypto');
+const DataStream = require('./data-stream');
+const PassThrough = require('stream').PassThrough;
+const shared = require('../shared');
+
+// default timeout values in ms
+const CONNECTION_TIMEOUT = 2 * 60 * 1000; // how much to wait for the connection to be established
+const SOCKET_TIMEOUT = 10 * 60 * 1000; // how much to wait for socket inactivity before disconnecting the client
+const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is established but SMTP greeting is not receieved
+
+/**
+ * Generates a SMTP connection object
+ *
+ * Optional options object takes the following possible properties:
+ *
+ * * **port** - is the port to connect to (defaults to 587 or 465)
+ * * **host** - is the hostname or IP address to connect to (defaults to 'localhost')
+ * * **secure** - use SSL
+ * * **ignoreTLS** - ignore server support for STARTTLS
+ * * **requireTLS** - forces the client to use STARTTLS
+ * * **name** - the name of the client server
+ * * **localAddress** - outbound address to bind to (see: http://nodejs.org/api/net.html#net_net_connect_options_connectionlistener)
+ * * **greetingTimeout** - Time to wait in ms until greeting message is received from the server (defaults to 10000)
+ * * **connectionTimeout** - how many milliseconds to wait for the connection to establish
+ * * **socketTimeout** - Time of inactivity until the connection is closed (defaults to 1 hour)
+ * * **lmtp** - if true, uses LMTP instead of SMTP protocol
+ * * **logger** - bunyan compatible logger interface
+ * * **debug** - if true pass SMTP traffic to the logger
+ * * **tls** - options for createCredentials
+ * * **socket** - existing socket to use instead of creating a new one (see: http://nodejs.org/api/net.html#net_class_net_socket)
+ * * **secured** - boolean indicates that the provided socket has already been upgraded to tls
+ *
+ * @constructor
+ * @namespace SMTP Client module
+ * @param {Object} [options] Option properties
+ */
+class SMTPConnection extends EventEmitter {
+ constructor(options) {
+ super(options);
+
+ this.id = crypto
+ .randomBytes(8)
+ .toString('base64')
+ .replace(/\W/g, '');
+ this.stage = 'init';
+
+ this.options = options || {};
+
+ this.secureConnection = !!this.options.secure;
+ this.alreadySecured = !!this.options.secured;
+
+ this.port = Number(this.options.port) || (this.secureConnection ? 465 : 587);
+ this.host = this.options.host || 'localhost';
+
+ if (typeof this.options.secure === 'undefined' && this.port === 465) {
+ // if secure option is not set but port is 465, then default to secure
+ this.secureConnection = true;
+ }
+
+ this.name = this.options.name || this._getHostname();
+
+ this.logger = shared.getLogger(this.options, {
+ component: this.options.component || 'smtp-connection',
+ sid: this.id
+ });
+
+ this.customAuth = new Map();
+ Object.keys(this.options.customAuth || {}).forEach(key => {
+ let mapKey = (key || '')
+ .toString()
+ .trim()
+ .toUpperCase();
+ if (!mapKey) {
+ return;
+ }
+ this.customAuth.set(mapKey, this.options.customAuth[key]);
+ });
+
+ /**
+ * Expose version nr, just for the reference
+ * @type {String}
+ */
+ this.version = packageInfo.version;
+
+ /**
+ * If true, then the user is authenticated
+ * @type {Boolean}
+ */
+ this.authenticated = false;
+
+ /**
+ * If set to true, this instance is no longer active
+ * @private
+ */
+ this.destroyed = false;
+
+ /**
+ * Defines if the current connection is secure or not. If not,
+ * STARTTLS can be used if available
+ * @private
+ */
+ this.secure = !!this.secureConnection;
+
+ /**
+ * Store incomplete messages coming from the server
+ * @private
+ */
+ this._remainder = '';
+
+ /**
+ * Unprocessed responses from the server
+ * @type {Array}
+ */
+ this._responseQueue = [];
+
+ this.lastServerResponse = false;
+
+ /**
+ * The socket connecting to the server
+ * @publick
+ */
+ this._socket = false;
+
+ /**
+ * Lists supported auth mechanisms
+ * @private
+ */
+ this._supportedAuth = [];
+
+ /**
+ * Includes current envelope (from, to)
+ * @private
+ */
+ this._envelope = false;
+
+ /**
+ * Lists supported extensions
+ * @private
+ */
+ this._supportedExtensions = [];
+
+ /**
+ * Defines the maximum allowed size for a single message
+ * @private
+ */
+ this._maxAllowedSize = 0;
+
+ /**
+ * Function queue to run if a data chunk comes from the server
+ * @private
+ */
+ this._responseActions = [];
+ this._recipientQueue = [];
+
+ /**
+ * Timeout variable for waiting the greeting
+ * @private
+ */
+ this._greetingTimeout = false;
+
+ /**
+ * Timeout variable for waiting the connection to start
+ * @private
+ */
+ this._connectionTimeout = false;
+
+ /**
+ * If the socket is deemed already closed
+ * @private
+ */
+ this._destroyed = false;
+
+ /**
+ * If the socket is already being closed
+ * @private
+ */
+ this._closing = false;
+ }
+
+ /**
+ * Creates a connection to a SMTP server and sets up connection
+ * listener
+ */
+ connect(connectCallback) {
+ if (typeof connectCallback === 'function') {
+ this.once('connect', () => {
+ this.logger.debug(
+ {
+ tnx: 'smtp'
+ },
+ 'SMTP handshake finished'
+ );
+ connectCallback();
+ });
+
+ const isDestroyedMessage = this._isDestroyedMessage('connect');
+ if (isDestroyedMessage) {
+ return connectCallback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'CONN'));
+ }
+ }
+
+ let opts = {
+ port: this.port,
+ host: this.host
+ };
+
+ if (this.options.localAddress) {
+ opts.localAddress = this.options.localAddress;
+ }
+
+ let setupConnectionHandlers = () => {
+ this._connectionTimeout = setTimeout(() => {
+ this._onError('Connection timeout', 'ETIMEDOUT', false, 'CONN');
+ }, this.options.connectionTimeout || CONNECTION_TIMEOUT);
+
+ this._socket.on('error', err => {
+ this._onError(err, 'ECONNECTION', false, 'CONN');
+ });
+ };
+
+ if (this.options.connection) {
+ // connection is already opened
+ this._socket = this.options.connection;
+ if (this.secureConnection && !this.alreadySecured) {
+ setImmediate(() =>
+ this._upgradeConnection(err => {
+ if (err) {
+ this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'CONN');
+ return;
+ }
+ this._onConnect();
+ })
+ );
+ } else {
+ setImmediate(() => this._onConnect());
+ }
+ return;
+ } else if (this.options.socket) {
+ // socket object is set up but not yet connected
+ this._socket = this.options.socket;
+ return shared.resolveHostname(opts, (err, resolved) => {
+ if (err) {
+ return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
+ }
+ this.logger.debug(
+ {
+ tnx: 'dns',
+ source: opts.host,
+ resolved: resolved.host,
+ cached: !!resolved._cached
+ },
+ 'Resolved %s as %s [cache %s]',
+ opts.host,
+ resolved.host,
+ resolved._cached ? 'hit' : 'miss'
+ );
+ Object.keys(resolved).forEach(key => {
+ if (key.charAt(0) !== '_' && resolved[key]) {
+ opts[key] = resolved[key];
+ }
+ });
+ try {
+ this._socket.connect(
+ this.port,
+ this.host,
+ () => {
+ this._socket.setKeepAlive(true);
+ this._onConnect();
+ }
+ );
+ setupConnectionHandlers();
+ } catch (E) {
+ return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
+ }
+ });
+ } else if (this.secureConnection) {
+ // connect using tls
+ if (this.options.tls) {
+ Object.keys(this.options.tls).forEach(key => {
+ opts[key] = this.options.tls[key];
+ });
+ }
+ return shared.resolveHostname(opts, (err, resolved) => {
+ if (err) {
+ return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
+ }
+ this.logger.debug(
+ {
+ tnx: 'dns',
+ source: opts.host,
+ resolved: resolved.host,
+ cached: !!resolved._cached
+ },
+ 'Resolved %s as %s [cache %s]',
+ opts.host,
+ resolved.host,
+ resolved._cached ? 'hit' : 'miss'
+ );
+ Object.keys(resolved).forEach(key => {
+ if (key.charAt(0) !== '_' && resolved[key]) {
+ opts[key] = resolved[key];
+ }
+ });
+ try {
+ this._socket = tls.connect(
+ opts,
+ () => {
+ this._socket.setKeepAlive(true);
+ this._onConnect();
+ }
+ );
+ setupConnectionHandlers();
+ } catch (E) {
+ return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
+ }
+ });
+ } else {
+ // connect using plaintext
+ return shared.resolveHostname(opts, (err, resolved) => {
+ if (err) {
+ return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
+ }
+ this.logger.debug(
+ {
+ tnx: 'dns',
+ source: opts.host,
+ resolved: resolved.host,
+ cached: !!resolved._cached
+ },
+ 'Resolved %s as %s [cache %s]',
+ opts.host,
+ resolved.host,
+ resolved._cached ? 'hit' : 'miss'
+ );
+ Object.keys(resolved).forEach(key => {
+ if (key.charAt(0) !== '_' && resolved[key]) {
+ opts[key] = resolved[key];
+ }
+ });
+ try {
+ this._socket = net.connect(
+ opts,
+ () => {
+ this._socket.setKeepAlive(true);
+ this._onConnect();
+ }
+ );
+ setupConnectionHandlers();
+ } catch (E) {
+ return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
+ }
+ });
+ }
+ }
+
+ /**
+ * Sends QUIT
+ */
+ quit() {
+ this._sendCommand('QUIT');
+ this._responseActions.push(this.close);
+ }
+
+ /**
+ * Closes the connection to the server
+ */
+ close() {
+ clearTimeout(this._connectionTimeout);
+ clearTimeout(this._greetingTimeout);
+ this._responseActions = [];
+
+ // allow to run this function only once
+ if (this._closing) {
+ return;
+ }
+ this._closing = true;
+
+ let closeMethod = 'end';
+
+ if (this.stage === 'init') {
+ // Close the socket immediately when connection timed out
+ closeMethod = 'destroy';
+ }
+
+ this.logger.debug(
+ {
+ tnx: 'smtp'
+ },
+ 'Closing connection to the server using "%s"',
+ closeMethod
+ );
+
+ let socket = (this._socket && this._socket.socket) || this._socket;
+
+ if (socket && !socket.destroyed) {
+ try {
+ this._socket[closeMethod]();
+ } catch (E) {
+ // just ignore
+ }
+ }
+
+ this._destroy();
+ }
+
+ /**
+ * Authenticate user
+ */
+ login(authData, callback) {
+ const isDestroyedMessage = this._isDestroyedMessage('login');
+ if (isDestroyedMessage) {
+ return callback(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
+ }
+
+ this._auth = authData || {};
+ // Select SASL authentication method
+ this._authMethod =
+ (this._auth.method || '')
+ .toString()
+ .trim()
+ .toUpperCase() || false;
+
+ if (!this._authMethod && this._auth.oauth2 && !this._auth.credentials) {
+ this._authMethod = 'XOAUTH2';
+ } else if (!this._authMethod || (this._authMethod === 'XOAUTH2' && !this._auth.oauth2)) {
+ // use first supported
+ this._authMethod = (this._supportedAuth[0] || 'PLAIN').toUpperCase().trim();
+ }
+
+ if (this._authMethod !== 'XOAUTH2' && (!this._auth.credentials || !this._auth.credentials.user || !this._auth.credentials.pass)) {
+ if (this._auth.user && this._auth.pass) {
+ this._auth.credentials = {
+ user: this._auth.user,
+ pass: this._auth.pass,
+ options: this._auth.options
+ };
+ } else {
+ return callback(this._formatError('Missing credentials for "' + this._authMethod + '"', 'EAUTH', false, 'API'));
+ }
+ }
+
+ if (this.customAuth.has(this._authMethod)) {
+ let handler = this.customAuth.get(this._authMethod);
+ let lastResponse;
+ let returned = false;
+
+ let resolve = () => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ this.logger.info(
+ {
+ tnx: 'smtp',
+ username: this._auth.user,
+ action: 'authenticated',
+ method: this._authMethod
+ },
+ 'User %s authenticated',
+ JSON.stringify(this._auth.user)
+ );
+ this.authenticated = true;
+ callback(null, true);
+ };
+
+ let reject = err => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ callback(this._formatError(err, 'EAUTH', lastResponse, 'AUTH ' + this._authMethod));
+ };
+
+ let handlerResponse = handler({
+ auth: this._auth,
+ method: this._authMethod,
+
+ extensions: [].concat(this._supportedExtensions),
+ authMethods: [].concat(this._supportedAuth),
+ maxAllowedSize: this._maxAllowedSize || false,
+
+ sendCommand: (cmd, done) => {
+ let promise;
+
+ if (!done) {
+ promise = new Promise((resolve, reject) => {
+ done = shared.callbackPromise(resolve, reject);
+ });
+ }
+
+ this._responseActions.push(str => {
+ lastResponse = str;
+
+ let codes = str.match(/^(\d+)(?:\s(\d+\.\d+\.\d+))?\s/);
+ let data = {
+ command: cmd,
+ response: str
+ };
+ if (codes) {
+ data.status = Number(codes[1]) || 0;
+ if (codes[2]) {
+ data.code = codes[2];
+ }
+ data.text = str.substr(codes[0].length);
+ } else {
+ data.text = str;
+ data.status = 0; // just in case we need to perform numeric comparisons
+ }
+ done(null, data);
+ });
+ setImmediate(() => this._sendCommand(cmd));
+
+ return promise;
+ },
+
+ resolve,
+ reject
+ });
+
+ if (handlerResponse && typeof handlerResponse.catch === 'function') {
+ // a promise was returned
+ handlerResponse.then(resolve).catch(reject);
+ }
+
+ return;
+ }
+
+ switch (this._authMethod) {
+ case 'XOAUTH2':
+ this._handleXOauth2Token(false, callback);
+ return;
+ case 'LOGIN':
+ this._responseActions.push(str => {
+ this._actionAUTH_LOGIN_USER(str, callback);
+ });
+ this._sendCommand('AUTH LOGIN');
+ return;
+ case 'PLAIN':
+ this._responseActions.push(str => {
+ this._actionAUTHComplete(str, callback);
+ });
+ this._sendCommand(
+ 'AUTH PLAIN ' +
+ Buffer.from(
+ //this._auth.user+'\u0000'+
+ '\u0000' + // skip authorization identity as it causes problems with some servers
+ this._auth.credentials.user +
+ '\u0000' +
+ this._auth.credentials.pass,
+ 'utf-8'
+ ).toString('base64')
+ );
+ return;
+ case 'CRAM-MD5':
+ this._responseActions.push(str => {
+ this._actionAUTH_CRAM_MD5(str, callback);
+ });
+ this._sendCommand('AUTH CRAM-MD5');
+ return;
+ }
+
+ return callback(this._formatError('Unknown authentication method "' + this._authMethod + '"', 'EAUTH', false, 'API'));
+ }
+
+ /**
+ * Sends a message
+ *
+ * @param {Object} envelope Envelope object, {from: addr, to: [addr]}
+ * @param {Object} message String, Buffer or a Stream
+ * @param {Function} callback Callback to return once sending is completed
+ */
+ send(envelope, message, done) {
+ if (!message) {
+ return done(this._formatError('Empty message', 'EMESSAGE', false, 'API'));
+ }
+
+ const isDestroyedMessage = this._isDestroyedMessage('send message');
+ if (isDestroyedMessage) {
+ return done(this._formatError(isDestroyedMessage, 'ECONNECTION', false, 'API'));
+ }
+
+ // reject larger messages than allowed
+ if (this._maxAllowedSize && envelope.size > this._maxAllowedSize) {
+ return setImmediate(() => {
+ done(this._formatError('Message size larger than allowed ' + this._maxAllowedSize, 'EMESSAGE', false, 'MAIL FROM'));
+ });
+ }
+
+ // ensure that callback is only called once
+ let returned = false;
+ let callback = function() {
+ if (returned) {
+ return;
+ }
+ returned = true;
+
+ done(...arguments);
+ };
+
+ if (typeof message.on === 'function') {
+ message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
+ }
+
+ let startTime = Date.now();
+ this._setEnvelope(envelope, (err, info) => {
+ if (err) {
+ return callback(err);
+ }
+ let envelopeTime = Date.now();
+ let stream = this._createSendStream((err, str) => {
+ if (err) {
+ return callback(err);
+ }
+
+ info.envelopeTime = envelopeTime - startTime;
+ info.messageTime = Date.now() - envelopeTime;
+ info.messageSize = stream.outByteCount;
+ info.response = str;
+
+ return callback(null, info);
+ });
+ if (typeof message.pipe === 'function') {
+ message.pipe(stream);
+ } else {
+ stream.write(message);
+ stream.end();
+ }
+ });
+ }
+
+ /**
+ * Resets connection state
+ *
+ * @param {Function} callback Callback to return once connection is reset
+ */
+ reset(callback) {
+ this._sendCommand('RSET');
+ this._responseActions.push(str => {
+ if (str.charAt(0) !== '2') {
+ return callback(this._formatError('Could not reset session state. response=' + str, 'EPROTOCOL', str, 'RSET'));
+ }
+ this._envelope = false;
+ return callback(null, true);
+ });
+ }
+
+ /**
+ * Connection listener that is run when the connection to
+ * the server is opened
+ *
+ * @event
+ */
+ _onConnect() {
+ clearTimeout(this._connectionTimeout);
+
+ this.logger.info(
+ {
+ tnx: 'network',
+ localAddress: this._socket.localAddress,
+ localPort: this._socket.localPort,
+ remoteAddress: this._socket.remoteAddress,
+ remotePort: this._socket.remotePort
+ },
+ '%s established to %s:%s',
+ this.secure ? 'Secure connection' : 'Connection',
+ this._socket.remoteAddress,
+ this._socket.remotePort
+ );
+
+ if (this._destroyed) {
+ // Connection was established after we already had canceled it
+ this.close();
+ return;
+ }
+
+ this.stage = 'connected';
+
+ // clear existing listeners for the socket
+ this._socket.removeAllListeners('data');
+ this._socket.removeAllListeners('timeout');
+ this._socket.removeAllListeners('close');
+ this._socket.removeAllListeners('end');
+
+ this._socket.on('data', chunk => this._onData(chunk));
+ this._socket.once('close', errored => this._onClose(errored));
+ this._socket.once('end', () => this._onEnd());
+
+ this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT);
+ this._socket.on('timeout', () => this._onTimeout());
+
+ this._greetingTimeout = setTimeout(() => {
+ // if still waiting for greeting, give up
+ if (this._socket && !this._destroyed && this._responseActions[0] === this._actionGreeting) {
+ this._onError('Greeting never received', 'ETIMEDOUT', false, 'CONN');
+ }
+ }, this.options.greetingTimeout || GREETING_TIMEOUT);
+
+ this._responseActions.push(this._actionGreeting);
+
+ // we have a 'data' listener set up so resume socket if it was paused
+ this._socket.resume();
+ }
+
+ /**
+ * 'data' listener for data coming from the server
+ *
+ * @event
+ * @param {Buffer} chunk Data chunk coming from the server
+ */
+ _onData(chunk) {
+ if (this._destroyed || !chunk || !chunk.length) {
+ return;
+ }
+
+ let data = (chunk || '').toString('binary');
+ let lines = (this._remainder + data).split(/\r?\n/);
+ let lastline;
+
+ this._remainder = lines.pop();
+
+ for (let i = 0, len = lines.length; i < len; i++) {
+ if (this._responseQueue.length) {
+ lastline = this._responseQueue[this._responseQueue.length - 1];
+ if (/^\d+-/.test(lastline.split('\n').pop())) {
+ this._responseQueue[this._responseQueue.length - 1] += '\n' + lines[i];
+ continue;
+ }
+ }
+ this._responseQueue.push(lines[i]);
+ }
+
+ this._processResponse();
+ }
+
+ /**
+ * 'error' listener for the socket
+ *
+ * @event
+ * @param {Error} err Error object
+ * @param {String} type Error name
+ */
+ _onError(err, type, data, command) {
+ clearTimeout(this._connectionTimeout);
+ clearTimeout(this._greetingTimeout);
+
+ if (this._destroyed) {
+ // just ignore, already closed
+ // this might happen when a socket is canceled because of reached timeout
+ // but the socket timeout error itself receives only after
+ return;
+ }
+
+ err = this._formatError(err, type, data, command);
+
+ this.logger.error(data, err.message);
+
+ this.emit('error', err);
+ this.close();
+ }
+
+ _formatError(message, type, response, command) {
+ let err;
+
+ if (/Error\]$/i.test(Object.prototype.toString.call(message))) {
+ err = message;
+ } else {
+ err = new Error(message);
+ }
+
+ if (type && type !== 'Error') {
+ err.code = type;
+ }
+
+ if (response) {
+ err.response = response;
+ err.message += ': ' + response;
+ }
+
+ let responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
+ if (responseCode) {
+ err.responseCode = responseCode;
+ }
+
+ if (command) {
+ err.command = command;
+ }
+
+ return err;
+ }
+
+ /**
+ * 'close' listener for the socket
+ *
+ * @event
+ */
+ _onClose() {
+ this.logger.info(
+ {
+ tnx: 'network'
+ },
+ 'Connection closed'
+ );
+
+ if (this.upgrading && !this._destroyed) {
+ return this._onError(new Error('Connection closed unexpectedly'), 'ETLS', false, 'CONN');
+ } else if (![this._actionGreeting, this.close].includes(this._responseActions[0]) && !this._destroyed) {
+ return this._onError(new Error('Connection closed unexpectedly'), 'ECONNECTION', false, 'CONN');
+ }
+
+ this._destroy();
+ }
+
+ /**
+ * 'end' listener for the socket
+ *
+ * @event
+ */
+ _onEnd() {
+ this._destroy();
+ }
+
+ /**
+ * 'timeout' listener for the socket
+ *
+ * @event
+ */
+ _onTimeout() {
+ return this._onError(new Error('Timeout'), 'ETIMEDOUT', false, 'CONN');
+ }
+
+ /**
+ * Destroys the client, emits 'end'
+ */
+ _destroy() {
+ if (this._destroyed) {
+ return;
+ }
+ this._destroyed = true;
+ this.emit('end');
+ }
+
+ /**
+ * Upgrades the connection to TLS
+ *
+ * @param {Function} callback Callback function to run when the connection
+ * has been secured
+ */
+ _upgradeConnection(callback) {
+ // do not remove all listeners or it breaks node v0.10 as there's
+ // apparently a 'finish' event set that would be cleared as well
+
+ // we can safely keep 'error', 'end', 'close' etc. events
+ this._socket.removeAllListeners('data'); // incoming data is going to be gibberish from this point onwards
+ this._socket.removeAllListeners('timeout'); // timeout will be re-set for the new socket object
+
+ let socketPlain = this._socket;
+ let opts = {
+ socket: this._socket,
+ host: this.host
+ };
+
+ Object.keys(this.options.tls || {}).forEach(key => {
+ opts[key] = this.options.tls[key];
+ });
+
+ this.upgrading = true;
+ this._socket = tls.connect(
+ opts,
+ () => {
+ this.secure = true;
+ this.upgrading = false;
+ this._socket.on('data', chunk => this._onData(chunk));
+
+ socketPlain.removeAllListeners('close');
+ socketPlain.removeAllListeners('end');
+
+ return callback(null, true);
+ }
+ );
+
+ this._socket.on('error', err => this._onError(err, 'ESOCKET', false, 'CONN'));
+ this._socket.once('close', errored => this._onClose(errored));
+ this._socket.once('end', () => this._onEnd());
+
+ this._socket.setTimeout(this.options.socketTimeout || SOCKET_TIMEOUT); // 10 min.
+ this._socket.on('timeout', () => this._onTimeout());
+
+ // resume in case the socket was paused
+ socketPlain.resume();
+ }
+
+ /**
+ * Processes queued responses from the server
+ *
+ * @param {Boolean} force If true, ignores _processing flag
+ */
+ _processResponse() {
+ if (!this._responseQueue.length) {
+ return false;
+ }
+
+ let str = (this.lastServerResponse = (this._responseQueue.shift() || '').toString());
+
+ if (/^\d+-/.test(str.split('\n').pop())) {
+ // keep waiting for the final part of multiline response
+ return;
+ }
+
+ if (this.options.debug || this.options.transactionLog) {
+ this.logger.debug(
+ {
+ tnx: 'server'
+ },
+ str.replace(/\r?\n$/, '')
+ );
+ }
+
+ if (!str.trim()) {
+ // skip unexpected empty lines
+ setImmediate(() => this._processResponse(true));
+ }
+
+ let action = this._responseActions.shift();
+
+ if (typeof action === 'function') {
+ action.call(this, str);
+ setImmediate(() => this._processResponse(true));
+ } else {
+ return this._onError(new Error('Unexpected Response'), 'EPROTOCOL', str, 'CONN');
+ }
+ }
+
+ /**
+ * Send a command to the server, append \r\n
+ *
+ * @param {String} str String to be sent to the server
+ */
+ _sendCommand(str) {
+ if (this._destroyed) {
+ // Connection already closed, can't send any more data
+ return;
+ }
+
+ if (this._socket.destroyed) {
+ return this.close();
+ }
+
+ if (this.options.debug || this.options.transactionLog) {
+ this.logger.debug(
+ {
+ tnx: 'client'
+ },
+ (str || '').toString().replace(/\r?\n$/, '')
+ );
+ }
+
+ this._socket.write(Buffer.from(str + '\r\n', 'utf-8'));
+ }
+
+ /**
+ * Initiates a new message by submitting envelope data, starting with
+ * MAIL FROM: command
+ *
+ * @param {Object} envelope Envelope object in the form of
+ * {from:'...', to:['...']}
+ * or
+ * {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
+ */
+ _setEnvelope(envelope, callback) {
+ let args = [];
+ let useSmtpUtf8 = false;
+
+ this._envelope = envelope || {};
+ this._envelope.from = ((this._envelope.from && this._envelope.from.address) || this._envelope.from || '').toString().trim();
+
+ this._envelope.to = [].concat(this._envelope.to || []).map(to => ((to && to.address) || to || '').toString().trim());
+
+ if (!this._envelope.to.length) {
+ return callback(this._formatError('No recipients defined', 'EENVELOPE', false, 'API'));
+ }
+
+ if (this._envelope.from && /[\r\n<>]/.test(this._envelope.from)) {
+ return callback(this._formatError('Invalid sender ' + JSON.stringify(this._envelope.from), 'EENVELOPE', false, 'API'));
+ }
+
+ // check if the sender address uses only ASCII characters,
+ // otherwise require usage of SMTPUTF8 extension
+ if (/[\x80-\uFFFF]/.test(this._envelope.from)) {
+ useSmtpUtf8 = true;
+ }
+
+ for (let i = 0, len = this._envelope.to.length; i < len; i++) {
+ if (!this._envelope.to[i] || /[\r\n<>]/.test(this._envelope.to[i])) {
+ return callback(this._formatError('Invalid recipient ' + JSON.stringify(this._envelope.to[i]), 'EENVELOPE', false, 'API'));
+ }
+
+ // check if the recipients addresses use only ASCII characters,
+ // otherwise require usage of SMTPUTF8 extension
+ if (/[\x80-\uFFFF]/.test(this._envelope.to[i])) {
+ useSmtpUtf8 = true;
+ }
+ }
+
+ // clone the recipients array for latter manipulation
+ this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
+ this._envelope.rejected = [];
+ this._envelope.rejectedErrors = [];
+ this._envelope.accepted = [];
+
+ if (this._envelope.dsn) {
+ try {
+ this._envelope.dsn = this._setDsnEnvelope(this._envelope.dsn);
+ } catch (err) {
+ return callback(this._formatError('Invalid DSN ' + err.message, 'EENVELOPE', false, 'API'));
+ }
+ }
+
+ this._responseActions.push(str => {
+ this._actionMAIL(str, callback);
+ });
+
+ // If the server supports SMTPUTF8 and the envelope includes an internationalized
+ // email address then append SMTPUTF8 keyword to the MAIL FROM command
+ if (useSmtpUtf8 && this._supportedExtensions.includes('SMTPUTF8')) {
+ args.push('SMTPUTF8');
+ this._usingSmtpUtf8 = true;
+ }
+
+ // If the server supports 8BITMIME and the message might contain non-ascii bytes
+ // then append the 8BITMIME keyword to the MAIL FROM command
+ if (this._envelope.use8BitMime && this._supportedExtensions.includes('8BITMIME')) {
+ args.push('BODY=8BITMIME');
+ this._using8BitMime = true;
+ }
+
+ if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
+ args.push('SIZE=' + this._envelope.size);
+ }
+
+ // If the server supports DSN and the envelope includes an DSN prop
+ // then append DSN params to the MAIL FROM command
+ if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
+ if (this._envelope.dsn.ret) {
+ args.push('RET=' + shared.encodeXText(this._envelope.dsn.ret));
+ }
+ if (this._envelope.dsn.envid) {
+ args.push('ENVID=' + shared.encodeXText(this._envelope.dsn.envid));
+ }
+ }
+
+ this._sendCommand('MAIL FROM:<' + this._envelope.from + '>' + (args.length ? ' ' + args.join(' ') : ''));
+ }
+
+ _setDsnEnvelope(params) {
+ let ret = (params.ret || params.return || '').toString().toUpperCase() || null;
+ if (ret) {
+ switch (ret) {
+ case 'HDRS':
+ case 'HEADERS':
+ ret = 'HDRS';
+ break;
+ case 'FULL':
+ case 'BODY':
+ ret = 'FULL';
+ break;
+ }
+ }
+
+ if (ret && !['FULL', 'HDRS'].includes(ret)) {
+ throw new Error('ret: ' + JSON.stringify(ret));
+ }
+
+ let envid = (params.envid || params.id || '').toString() || null;
+
+ let notify = params.notify || null;
+ if (notify) {
+ if (typeof notify === 'string') {
+ notify = notify.split(',');
+ }
+ notify = notify.map(n => n.trim().toUpperCase());
+ let validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
+ let invaliNotify = notify.filter(n => !validNotify.includes(n));
+ if (invaliNotify.length || (notify.length > 1 && notify.includes('NEVER'))) {
+ throw new Error('notify: ' + JSON.stringify(notify.join(',')));
+ }
+ notify = notify.join(',');
+ }
+
+ let orcpt = (params.orcpt || params.recipient || '').toString() || null;
+ if (orcpt && orcpt.indexOf(';') < 0) {
+ orcpt = 'rfc822;' + orcpt;
+ }
+
+ return {
+ ret,
+ envid,
+ notify,
+ orcpt
+ };
+ }
+
+ _getDsnRcptToArgs() {
+ let args = [];
+ // If the server supports DSN and the envelope includes an DSN prop
+ // then append DSN params to the RCPT TO command
+ if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
+ if (this._envelope.dsn.notify) {
+ args.push('NOTIFY=' + shared.encodeXText(this._envelope.dsn.notify));
+ }
+ if (this._envelope.dsn.orcpt) {
+ args.push('ORCPT=' + shared.encodeXText(this._envelope.dsn.orcpt));
+ }
+ }
+ return args.length ? ' ' + args.join(' ') : '';
+ }
+
+ _createSendStream(callback) {
+ let dataStream = new DataStream();
+ let logStream;
+
+ if (this.options.lmtp) {
+ this._envelope.accepted.forEach((recipient, i) => {
+ let final = i === this._envelope.accepted.length - 1;
+ this._responseActions.push(str => {
+ this._actionLMTPStream(recipient, final, str, callback);
+ });
+ });
+ } else {
+ this._responseActions.push(str => {
+ this._actionSMTPStream(str, callback);
+ });
+ }
+
+ dataStream.pipe(
+ this._socket,
+ {
+ end: false
+ }
+ );
+
+ if (this.options.debug) {
+ logStream = new PassThrough();
+ logStream.on('readable', () => {
+ let chunk;
+ while ((chunk = logStream.read())) {
+ this.logger.debug(
+ {
+ tnx: 'message'
+ },
+ chunk.toString('binary').replace(/\r?\n$/, '')
+ );
+ }
+ });
+ dataStream.pipe(logStream);
+ }
+
+ dataStream.once('end', () => {
+ this.logger.info(
+ {
+ tnx: 'message',
+ inByteCount: dataStream.inByteCount,
+ outByteCount: dataStream.outByteCount
+ },
+ '<%s bytes encoded mime message (source size %s bytes)>',
+ dataStream.outByteCount,
+ dataStream.inByteCount
+ );
+ });
+
+ return dataStream;
+ }
+
+ /** ACTIONS **/
+
+ /**
+ * Will be run after the connection is created and the server sends
+ * a greeting. If the incoming message starts with 220 initiate
+ * SMTP session by sending EHLO command
+ *
+ * @param {String} str Message from the server
+ */
+ _actionGreeting(str) {
+ clearTimeout(this._greetingTimeout);
+
+ if (str.substr(0, 3) !== '220') {
+ this._onError(new Error('Invalid greeting. response=' + str), 'EPROTOCOL', str, 'CONN');
+ return;
+ }
+
+ if (this.options.lmtp) {
+ this._responseActions.push(this._actionLHLO);
+ this._sendCommand('LHLO ' + this.name);
+ } else {
+ this._responseActions.push(this._actionEHLO);
+ this._sendCommand('EHLO ' + this.name);
+ }
+ }
+
+ /**
+ * Handles server response for LHLO command. If it yielded in
+ * error, emit 'error', otherwise treat this as an EHLO response
+ *
+ * @param {String} str Message from the server
+ */
+ _actionLHLO(str) {
+ if (str.charAt(0) !== '2') {
+ this._onError(new Error('Invalid LHLO. response=' + str), 'EPROTOCOL', str, 'LHLO');
+ return;
+ }
+
+ this._actionEHLO(str);
+ }
+
+ /**
+ * Handles server response for EHLO command. If it yielded in
+ * error, try HELO instead, otherwise initiate TLS negotiation
+ * if STARTTLS is supported by the server or move into the
+ * authentication phase.
+ *
+ * @param {String} str Message from the server
+ */
+ _actionEHLO(str) {
+ let match;
+
+ if (str.substr(0, 3) === '421') {
+ this._onError(new Error('Server terminates connection. response=' + str), 'ECONNECTION', str, 'EHLO');
+ return;
+ }
+
+ if (str.charAt(0) !== '2') {
+ if (this.options.requireTLS) {
+ this._onError(new Error('EHLO failed but HELO does not support required STARTTLS. response=' + str), 'ECONNECTION', str, 'EHLO');
+ return;
+ }
+
+ // Try HELO instead
+ this._responseActions.push(this._actionHELO);
+ this._sendCommand('HELO ' + this.name);
+ return;
+ }
+
+ // Detect if the server supports STARTTLS
+ if (!this.secure && !this.options.ignoreTLS && (/[ -]STARTTLS\b/im.test(str) || this.options.requireTLS)) {
+ this._sendCommand('STARTTLS');
+ this._responseActions.push(this._actionSTARTTLS);
+ return;
+ }
+
+ // Detect if the server supports SMTPUTF8
+ if (/[ -]SMTPUTF8\b/im.test(str)) {
+ this._supportedExtensions.push('SMTPUTF8');
+ }
+
+ // Detect if the server supports DSN
+ if (/[ -]DSN\b/im.test(str)) {
+ this._supportedExtensions.push('DSN');
+ }
+
+ // Detect if the server supports 8BITMIME
+ if (/[ -]8BITMIME\b/im.test(str)) {
+ this._supportedExtensions.push('8BITMIME');
+ }
+
+ // Detect if the server supports PIPELINING
+ if (/[ -]PIPELINING\b/im.test(str)) {
+ this._supportedExtensions.push('PIPELINING');
+ }
+
+ // Detect if the server supports PLAIN auth
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)PLAIN/i.test(str)) {
+ this._supportedAuth.push('PLAIN');
+ }
+
+ // Detect if the server supports LOGIN auth
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)LOGIN/i.test(str)) {
+ this._supportedAuth.push('LOGIN');
+ }
+
+ // Detect if the server supports CRAM-MD5 auth
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)CRAM-MD5/i.test(str)) {
+ this._supportedAuth.push('CRAM-MD5');
+ }
+
+ // Detect if the server supports XOAUTH2 auth
+ if (/AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)XOAUTH2/i.test(str)) {
+ this._supportedAuth.push('XOAUTH2');
+ }
+
+ // Detect if the server supports SIZE extensions (and the max allowed size)
+ if ((match = str.match(/[ -]SIZE(?:[ \t]+(\d+))?/im))) {
+ this._supportedExtensions.push('SIZE');
+ this._maxAllowedSize = Number(match[1]) || 0;
+ }
+
+ this.emit('connect');
+ }
+
+ /**
+ * Handles server response for HELO command. If it yielded in
+ * error, emit 'error', otherwise move into the authentication phase.
+ *
+ * @param {String} str Message from the server
+ */
+ _actionHELO(str) {
+ if (str.charAt(0) !== '2') {
+ this._onError(new Error('Invalid HELO. response=' + str), 'EPROTOCOL', str, 'HELO');
+ return;
+ }
+
+ this.emit('connect');
+ }
+
+ /**
+ * Handles server response for STARTTLS command. If there's an error
+ * try HELO instead, otherwise initiate TLS upgrade. If the upgrade
+ * succeedes restart the EHLO
+ *
+ * @param {String} str Message from the server
+ */
+ _actionSTARTTLS(str) {
+ if (str.charAt(0) !== '2') {
+ if (this.options.opportunisticTLS) {
+ this.logger.info(
+ {
+ tnx: 'smtp'
+ },
+ 'Failed STARTTLS upgrade, continuing unencrypted'
+ );
+ return this.emit('connect');
+ }
+ this._onError(new Error('Error upgrading connection with STARTTLS'), 'ETLS', str, 'STARTTLS');
+ return;
+ }
+
+ this._upgradeConnection((err, secured) => {
+ if (err) {
+ this._onError(new Error('Error initiating TLS - ' + (err.message || err)), 'ETLS', false, 'STARTTLS');
+ return;
+ }
+
+ this.logger.info(
+ {
+ tnx: 'smtp'
+ },
+ 'Connection upgraded with STARTTLS'
+ );
+
+ if (secured) {
+ // restart session
+ if (this.options.lmtp) {
+ this._responseActions.push(this._actionLHLO);
+ this._sendCommand('LHLO ' + this.name);
+ } else {
+ this._responseActions.push(this._actionEHLO);
+ this._sendCommand('EHLO ' + this.name);
+ }
+ } else {
+ this.emit('connect');
+ }
+ });
+ }
+
+ /**
+ * Handle the response for AUTH LOGIN command. We are expecting
+ * '334 VXNlcm5hbWU6' (base64 for 'Username:'). Data to be sent as
+ * response needs to be base64 encoded username. We do not need
+ * exact match but settle with 334 response in general as some
+ * hosts invalidly use a longer message than VXNlcm5hbWU6
+ *
+ * @param {String} str Message from the server
+ */
+ _actionAUTH_LOGIN_USER(str, callback) {
+ if (!/^334[ -]/.test(str)) {
+ // expecting '334 VXNlcm5hbWU6'
+ callback(this._formatError('Invalid login sequence while waiting for "334 VXNlcm5hbWU6"', 'EAUTH', str, 'AUTH LOGIN'));
+ return;
+ }
+
+ this._responseActions.push(str => {
+ this._actionAUTH_LOGIN_PASS(str, callback);
+ });
+
+ this._sendCommand(Buffer.from(this._auth.credentials.user + '', 'utf-8').toString('base64'));
+ }
+
+ /**
+ * Handle the response for AUTH CRAM-MD5 command. We are expecting
+ * '334 '. Data to be sent as response needs to be
+ * base64 decoded challenge string, MD5 hashed using the password as
+ * a HMAC key, prefixed by the username and a space, and finally all
+ * base64 encoded again.
+ *
+ * @param {String} str Message from the server
+ */
+ _actionAUTH_CRAM_MD5(str, callback) {
+ let challengeMatch = str.match(/^334\s+(.+)$/);
+ let challengeString = '';
+
+ if (!challengeMatch) {
+ return callback(this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5'));
+ } else {
+ challengeString = challengeMatch[1];
+ }
+
+ // Decode from base64
+ let base64decoded = Buffer.from(challengeString, 'base64').toString('ascii'),
+ hmac_md5 = crypto.createHmac('md5', this._auth.credentials.pass);
+
+ hmac_md5.update(base64decoded);
+
+ let hex_hmac = hmac_md5.digest('hex');
+ let prepended = this._auth.credentials.user + ' ' + hex_hmac;
+
+ this._responseActions.push(str => {
+ this._actionAUTH_CRAM_MD5_PASS(str, callback);
+ });
+
+ this._sendCommand(Buffer.from(prepended).toString('base64'));
+ }
+
+ /**
+ * Handles the response to CRAM-MD5 authentication, if there's no error,
+ * the user can be considered logged in. Start waiting for a message to send
+ *
+ * @param {String} str Message from the server
+ */
+ _actionAUTH_CRAM_MD5_PASS(str, callback) {
+ if (!str.match(/^235\s+/)) {
+ return callback(this._formatError('Invalid login sequence while waiting for "235"', 'EAUTH', str, 'AUTH CRAM-MD5'));
+ }
+
+ this.logger.info(
+ {
+ tnx: 'smtp',
+ username: this._auth.user,
+ action: 'authenticated',
+ method: this._authMethod
+ },
+ 'User %s authenticated',
+ JSON.stringify(this._auth.user)
+ );
+ this.authenticated = true;
+ callback(null, true);
+ }
+
+ /**
+ * Handle the response for AUTH LOGIN command. We are expecting
+ * '334 UGFzc3dvcmQ6' (base64 for 'Password:'). Data to be sent as
+ * response needs to be base64 encoded password.
+ *
+ * @param {String} str Message from the server
+ */
+ _actionAUTH_LOGIN_PASS(str, callback) {
+ if (!/^334[ -]/.test(str)) {
+ // expecting '334 UGFzc3dvcmQ6'
+ return callback(this._formatError('Invalid login sequence while waiting for "334 UGFzc3dvcmQ6"', 'EAUTH', str, 'AUTH LOGIN'));
+ }
+
+ this._responseActions.push(str => {
+ this._actionAUTHComplete(str, callback);
+ });
+
+ this._sendCommand(Buffer.from(this._auth.credentials.pass + '', 'utf-8').toString('base64'));
+ }
+
+ /**
+ * Handles the response for authentication, if there's no error,
+ * the user can be considered logged in. Start waiting for a message to send
+ *
+ * @param {String} str Message from the server
+ */
+ _actionAUTHComplete(str, isRetry, callback) {
+ if (!callback && typeof isRetry === 'function') {
+ callback = isRetry;
+ isRetry = false;
+ }
+
+ if (str.substr(0, 3) === '334') {
+ this._responseActions.push(str => {
+ if (isRetry || this._authMethod !== 'XOAUTH2') {
+ this._actionAUTHComplete(str, true, callback);
+ } else {
+ // fetch a new OAuth2 access token
+ setImmediate(() => this._handleXOauth2Token(true, callback));
+ }
+ });
+ this._sendCommand('');
+ return;
+ }
+
+ if (str.charAt(0) !== '2') {
+ this.logger.info(
+ {
+ tnx: 'smtp',
+ username: this._auth.user,
+ action: 'authfail',
+ method: this._authMethod
+ },
+ 'User %s failed to authenticate',
+ JSON.stringify(this._auth.user)
+ );
+ return callback(this._formatError('Invalid login', 'EAUTH', str, 'AUTH ' + this._authMethod));
+ }
+
+ this.logger.info(
+ {
+ tnx: 'smtp',
+ username: this._auth.user,
+ action: 'authenticated',
+ method: this._authMethod
+ },
+ 'User %s authenticated',
+ JSON.stringify(this._auth.user)
+ );
+ this.authenticated = true;
+ callback(null, true);
+ }
+
+ /**
+ * Handle response for a MAIL FROM: command
+ *
+ * @param {String} str Message from the server
+ */
+ _actionMAIL(str, callback) {
+ let message, curRecipient;
+ if (Number(str.charAt(0)) !== 2) {
+ if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
+ message = 'Internationalized mailbox name not allowed';
+ } else {
+ message = 'Mail command failed';
+ }
+ return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
+ }
+
+ if (!this._envelope.rcptQueue.length) {
+ return callback(this._formatError('Can\x27t send mail - no recipients defined', 'EENVELOPE', false, 'API'));
+ } else {
+ this._recipientQueue = [];
+
+ if (this._supportedExtensions.includes('PIPELINING')) {
+ while (this._envelope.rcptQueue.length) {
+ curRecipient = this._envelope.rcptQueue.shift();
+ this._recipientQueue.push(curRecipient);
+ this._responseActions.push(str => {
+ this._actionRCPT(str, callback);
+ });
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
+ }
+ } else {
+ curRecipient = this._envelope.rcptQueue.shift();
+ this._recipientQueue.push(curRecipient);
+ this._responseActions.push(str => {
+ this._actionRCPT(str, callback);
+ });
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
+ }
+ }
+ }
+
+ /**
+ * Handle response for a RCPT TO: command
+ *
+ * @param {String} str Message from the server
+ */
+ _actionRCPT(str, callback) {
+ let message,
+ err,
+ curRecipient = this._recipientQueue.shift();
+ if (Number(str.charAt(0)) !== 2) {
+ // this is a soft error
+ if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
+ message = 'Internationalized mailbox name not allowed';
+ } else {
+ message = 'Recipient command failed';
+ }
+ this._envelope.rejected.push(curRecipient);
+ // store error for the failed recipient
+ err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
+ err.recipient = curRecipient;
+ this._envelope.rejectedErrors.push(err);
+ } else {
+ this._envelope.accepted.push(curRecipient);
+ }
+
+ if (!this._envelope.rcptQueue.length && !this._recipientQueue.length) {
+ if (this._envelope.rejected.length < this._envelope.to.length) {
+ this._responseActions.push(str => {
+ this._actionDATA(str, callback);
+ });
+ this._sendCommand('DATA');
+ } else {
+ err = this._formatError('Can\x27t send mail - all recipients were rejected', 'EENVELOPE', str, 'RCPT TO');
+ err.rejected = this._envelope.rejected;
+ err.rejectedErrors = this._envelope.rejectedErrors;
+ return callback(err);
+ }
+ } else if (this._envelope.rcptQueue.length) {
+ curRecipient = this._envelope.rcptQueue.shift();
+ this._recipientQueue.push(curRecipient);
+ this._responseActions.push(str => {
+ this._actionRCPT(str, callback);
+ });
+ this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
+ }
+ }
+
+ /**
+ * Handle response for a DATA command
+ *
+ * @param {String} str Message from the server
+ */
+ _actionDATA(str, callback) {
+ // response should be 354 but according to this issue https://github.com/eleith/emailjs/issues/24
+ // some servers might use 250 instead, so lets check for 2 or 3 as the first digit
+ if (!/^[23]/.test(str)) {
+ return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
+ }
+
+ let response = {
+ accepted: this._envelope.accepted,
+ rejected: this._envelope.rejected
+ };
+
+ if (this._envelope.rejectedErrors.length) {
+ response.rejectedErrors = this._envelope.rejectedErrors;
+ }
+
+ callback(null, response);
+ }
+
+ /**
+ * Handle response for a DATA stream when using SMTP
+ * We expect a single response that defines if the sending succeeded or failed
+ *
+ * @param {String} str Message from the server
+ */
+ _actionSMTPStream(str, callback) {
+ if (Number(str.charAt(0)) !== 2) {
+ // Message failed
+ return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
+ } else {
+ // Message sent succesfully
+ return callback(null, str);
+ }
+ }
+
+ /**
+ * Handle response for a DATA stream
+ * We expect a separate response for every recipient. All recipients can either
+ * succeed or fail separately
+ *
+ * @param {String} recipient The recipient this response applies to
+ * @param {Boolean} final Is this the final recipient?
+ * @param {String} str Message from the server
+ */
+ _actionLMTPStream(recipient, final, str, callback) {
+ let err;
+ if (Number(str.charAt(0)) !== 2) {
+ // Message failed
+ err = this._formatError('Message failed for recipient ' + recipient, 'EMESSAGE', str, 'DATA');
+ err.recipient = recipient;
+ this._envelope.rejected.push(recipient);
+ this._envelope.rejectedErrors.push(err);
+ for (let i = 0, len = this._envelope.accepted.length; i < len; i++) {
+ if (this._envelope.accepted[i] === recipient) {
+ this._envelope.accepted.splice(i, 1);
+ }
+ }
+ }
+ if (final) {
+ return callback(null, str);
+ }
+ }
+
+ _handleXOauth2Token(isRetry, callback) {
+ this._auth.oauth2.getToken(isRetry, (err, accessToken) => {
+ if (err) {
+ this.logger.info(
+ {
+ tnx: 'smtp',
+ username: this._auth.user,
+ action: 'authfail',
+ method: this._authMethod
+ },
+ 'User %s failed to authenticate',
+ JSON.stringify(this._auth.user)
+ );
+ return callback(this._formatError(err, 'EAUTH', false, 'AUTH XOAUTH2'));
+ }
+ this._responseActions.push(str => {
+ this._actionAUTHComplete(str, isRetry, callback);
+ });
+ this._sendCommand('AUTH XOAUTH2 ' + this._auth.oauth2.buildXOAuth2Token(accessToken));
+ });
+ }
+
+ /**
+ *
+ * @param {string} command
+ * @private
+ */
+ _isDestroyedMessage(command) {
+ if (this._destroyed) {
+ return 'Cannot ' + command + ' - smtp connection is already destroyed.';
+ }
+
+ if (this._socket) {
+ if (this._socket.destroyed) {
+ return 'Cannot ' + command + ' - smtp connection socket is already destroyed.';
+ }
+
+ if (!this._socket.writable) {
+ return 'Cannot ' + command + ' - smtp connection socket is already half-closed.';
+ }
+ }
+ }
+
+ _getHostname() {
+ // defaul hostname is machine hostname or [IP]
+ let defaultHostname = os.hostname() || '';
+
+ // ignore if not FQDN
+ if (defaultHostname.indexOf('.') < 0) {
+ defaultHostname = '[127.0.0.1]';
+ }
+
+ // IP should be enclosed in []
+ if (defaultHostname.match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/)) {
+ defaultHostname = '[' + defaultHostname + ']';
+ }
+
+ return defaultHostname;
+ }
+}
+
+module.exports = SMTPConnection;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/smtp-pool/index.js b/html/RentForCamp/node_modules/nodemailer/lib/smtp-pool/index.js
new file mode 100644
index 0000000..27be7ec
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/smtp-pool/index.js
@@ -0,0 +1,600 @@
+'use strict';
+
+const EventEmitter = require('events');
+const PoolResource = require('./pool-resource');
+const SMTPConnection = require('../smtp-connection');
+const wellKnown = require('../well-known');
+const shared = require('../shared');
+const packageData = require('../../package.json');
+
+/**
+ * Creates a SMTP pool transport object for Nodemailer
+ *
+ * @constructor
+ * @param {Object} options SMTP Connection options
+ */
+class SMTPPool extends EventEmitter {
+ constructor(options) {
+ super();
+
+ options = options || {};
+ if (typeof options === 'string') {
+ options = {
+ url: options
+ };
+ }
+
+ let urlData;
+ let service = options.service;
+
+ if (typeof options.getSocket === 'function') {
+ this.getSocket = options.getSocket;
+ }
+
+ if (options.url) {
+ urlData = shared.parseConnectionUrl(options.url);
+ service = service || urlData.service;
+ }
+
+ this.options = shared.assign(
+ false, // create new object
+ options, // regular options
+ urlData, // url options
+ service && wellKnown(service) // wellknown options
+ );
+
+ this.options.maxConnections = this.options.maxConnections || 5;
+ this.options.maxMessages = this.options.maxMessages || 100;
+
+ this.logger = shared.getLogger(this.options, {
+ component: this.options.component || 'smtp-pool'
+ });
+
+ // temporary object
+ let connection = new SMTPConnection(this.options);
+
+ this.name = 'SMTP (pool)';
+ this.version = packageData.version + '[client:' + connection.version + ']';
+
+ this._rateLimit = {
+ counter: 0,
+ timeout: null,
+ waiting: [],
+ checkpoint: false,
+ delta: Number(this.options.rateDelta) || 1000,
+ limit: Number(this.options.rateLimit) || 0
+ };
+ this._closed = false;
+ this._queue = [];
+ this._connections = [];
+ this._connectionCounter = 0;
+
+ this.idling = true;
+
+ setImmediate(() => {
+ if (this.idling) {
+ this.emit('idle');
+ }
+ });
+ }
+
+ /**
+ * Placeholder function for creating proxy sockets. This method immediatelly returns
+ * without a socket
+ *
+ * @param {Object} options Connection options
+ * @param {Function} callback Callback function to run with the socket keys
+ */
+ getSocket(options, callback) {
+ // return immediatelly
+ return setImmediate(() => callback(null, false));
+ }
+
+ /**
+ * Queues an e-mail to be sent using the selected settings
+ *
+ * @param {Object} mail Mail object
+ * @param {Function} callback Callback function
+ */
+ send(mail, callback) {
+ if (this._closed) {
+ return false;
+ }
+
+ this._queue.push({
+ mail,
+ callback
+ });
+
+ if (this.idling && this._queue.length >= this.options.maxConnections) {
+ this.idling = false;
+ }
+
+ setImmediate(() => this._processMessages());
+
+ return true;
+ }
+
+ /**
+ * Closes all connections in the pool. If there is a message being sent, the connection
+ * is closed later
+ */
+ close() {
+ let connection;
+ let len = this._connections.length;
+ this._closed = true;
+
+ // clear rate limit timer if it exists
+ clearTimeout(this._rateLimit.timeout);
+
+ if (!len && !this._queue.length) {
+ return;
+ }
+
+ // remove all available connections
+ for (let i = len - 1; i >= 0; i--) {
+ if (this._connections[i] && this._connections[i].available) {
+ connection = this._connections[i];
+ connection.close();
+ this.logger.info(
+ {
+ tnx: 'connection',
+ cid: connection.id,
+ action: 'removed'
+ },
+ 'Connection #%s removed',
+ connection.id
+ );
+ }
+ }
+
+ if (len && !this._connections.length) {
+ this.logger.debug(
+ {
+ tnx: 'connection'
+ },
+ 'All connections removed'
+ );
+ }
+
+ if (!this._queue.length) {
+ return;
+ }
+
+ // make sure that entire queue would be cleaned
+ let invokeCallbacks = () => {
+ if (!this._queue.length) {
+ this.logger.debug(
+ {
+ tnx: 'connection'
+ },
+ 'Pending queue entries cleared'
+ );
+ return;
+ }
+ let entry = this._queue.shift();
+ if (entry && typeof entry.callback === 'function') {
+ try {
+ entry.callback(new Error('Connection pool was closed'));
+ } catch (E) {
+ this.logger.error(
+ {
+ err: E,
+ tnx: 'callback',
+ cid: connection.id
+ },
+ 'Callback error for #%s: %s',
+ connection.id,
+ E.message
+ );
+ }
+ }
+ setImmediate(invokeCallbacks);
+ };
+ setImmediate(invokeCallbacks);
+ }
+
+ /**
+ * Check the queue and available connections. If there is a message to be sent and there is
+ * an available connection, then use this connection to send the mail
+ */
+ _processMessages() {
+ let connection;
+ let i, len;
+
+ // do nothing if already closed
+ if (this._closed) {
+ return;
+ }
+
+ // do nothing if queue is empty
+ if (!this._queue.length) {
+ if (!this.idling) {
+ // no pending jobs
+ this.idling = true;
+ this.emit('idle');
+ }
+ return;
+ }
+
+ // find first available connection
+ for (i = 0, len = this._connections.length; i < len; i++) {
+ if (this._connections[i].available) {
+ connection = this._connections[i];
+ break;
+ }
+ }
+
+ if (!connection && this._connections.length < this.options.maxConnections) {
+ connection = this._createConnection();
+ }
+
+ if (!connection) {
+ // no more free connection slots available
+ this.idling = false;
+ return;
+ }
+
+ // check if there is free space in the processing queue
+ if (!this.idling && this._queue.length < this.options.maxConnections) {
+ this.idling = true;
+ this.emit('idle');
+ }
+
+ let entry = (connection.queueEntry = this._queue.shift());
+ entry.messageId = (connection.queueEntry.mail.message.getHeader('message-id') || '').replace(/[<>\s]/g, '');
+
+ connection.available = false;
+
+ this.logger.debug(
+ {
+ tnx: 'pool',
+ cid: connection.id,
+ messageId: entry.messageId,
+ action: 'assign'
+ },
+ 'Assigned message <%s> to #%s (%s)',
+ entry.messageId,
+ connection.id,
+ connection.messages + 1
+ );
+
+ if (this._rateLimit.limit) {
+ this._rateLimit.counter++;
+ if (!this._rateLimit.checkpoint) {
+ this._rateLimit.checkpoint = Date.now();
+ }
+ }
+
+ connection.send(entry.mail, (err, info) => {
+ // only process callback if current handler is not changed
+ if (entry === connection.queueEntry) {
+ try {
+ entry.callback(err, info);
+ } catch (E) {
+ this.logger.error(
+ {
+ err: E,
+ tnx: 'callback',
+ cid: connection.id
+ },
+ 'Callback error for #%s: %s',
+ connection.id,
+ E.message
+ );
+ }
+ connection.queueEntry = false;
+ }
+ });
+ }
+
+ /**
+ * Creates a new pool resource
+ */
+ _createConnection() {
+ let connection = new PoolResource(this);
+
+ connection.id = ++this._connectionCounter;
+
+ this.logger.info(
+ {
+ tnx: 'pool',
+ cid: connection.id,
+ action: 'conection'
+ },
+ 'Created new pool resource #%s',
+ connection.id
+ );
+
+ // resource comes available
+ connection.on('available', () => {
+ this.logger.debug(
+ {
+ tnx: 'connection',
+ cid: connection.id,
+ action: 'available'
+ },
+ 'Connection #%s became available',
+ connection.id
+ );
+
+ if (this._closed) {
+ // if already closed run close() that will remove this connections from connections list
+ this.close();
+ } else {
+ // check if there's anything else to send
+ this._processMessages();
+ }
+ });
+
+ // resource is terminated with an error
+ connection.once('error', err => {
+ if (err.code !== 'EMAXLIMIT') {
+ this.logger.error(
+ {
+ err,
+ tnx: 'pool',
+ cid: connection.id
+ },
+ 'Pool Error for #%s: %s',
+ connection.id,
+ err.message
+ );
+ } else {
+ this.logger.debug(
+ {
+ tnx: 'pool',
+ cid: connection.id,
+ action: 'maxlimit'
+ },
+ 'Max messages limit exchausted for #%s',
+ connection.id
+ );
+ }
+
+ if (connection.queueEntry) {
+ try {
+ connection.queueEntry.callback(err);
+ } catch (E) {
+ this.logger.error(
+ {
+ err: E,
+ tnx: 'callback',
+ cid: connection.id
+ },
+ 'Callback error for #%s: %s',
+ connection.id,
+ E.message
+ );
+ }
+ connection.queueEntry = false;
+ }
+
+ // remove the erroneus connection from connections list
+ this._removeConnection(connection);
+
+ this._continueProcessing();
+ });
+
+ connection.once('close', () => {
+ this.logger.info(
+ {
+ tnx: 'connection',
+ cid: connection.id,
+ action: 'closed'
+ },
+ 'Connection #%s was closed',
+ connection.id
+ );
+
+ this._removeConnection(connection);
+
+ if (connection.queueEntry) {
+ // If the connection closed when sending, add the message to the queue again
+ // Note that we must wait a bit.. because the callback of the 'error' handler might be called
+ // in the next event loop
+ setTimeout(() => {
+ if (connection.queueEntry) {
+ this.logger.debug(
+ {
+ tnx: 'pool',
+ cid: connection.id,
+ messageId: connection.queueEntry.messageId,
+ action: 'requeue'
+ },
+ 'Re-queued message <%s> for #%s',
+ connection.queueEntry.messageId,
+ connection.id
+ );
+ this._queue.unshift(connection.queueEntry);
+ connection.queueEntry = false;
+ }
+ this._continueProcessing();
+ }, 50);
+ } else {
+ this._continueProcessing();
+ }
+ });
+
+ this._connections.push(connection);
+
+ return connection;
+ }
+
+ /**
+ * Continue to process message if the pool hasn't closed
+ */
+ _continueProcessing() {
+ if (this._closed) {
+ this.close();
+ } else {
+ setTimeout(() => this._processMessages(), 100);
+ }
+ }
+
+ /**
+ * Remove resource from pool
+ *
+ * @param {Object} connection The PoolResource to remove
+ */
+ _removeConnection(connection) {
+ let index = this._connections.indexOf(connection);
+
+ if (index !== -1) {
+ this._connections.splice(index, 1);
+ }
+ }
+
+ /**
+ * Checks if connections have hit current rate limit and if so, queues the availability callback
+ *
+ * @param {Function} callback Callback function to run once rate limiter has been cleared
+ */
+ _checkRateLimit(callback) {
+ if (!this._rateLimit.limit) {
+ return callback();
+ }
+
+ let now = Date.now();
+
+ if (this._rateLimit.counter < this._rateLimit.limit) {
+ return callback();
+ }
+
+ this._rateLimit.waiting.push(callback);
+
+ if (this._rateLimit.checkpoint <= now - this._rateLimit.delta) {
+ return this._clearRateLimit();
+ } else if (!this._rateLimit.timeout) {
+ this._rateLimit.timeout = setTimeout(() => this._clearRateLimit(), this._rateLimit.delta - (now - this._rateLimit.checkpoint));
+ this._rateLimit.checkpoint = now;
+ }
+ }
+
+ /**
+ * Clears current rate limit limitation and runs paused callback
+ */
+ _clearRateLimit() {
+ clearTimeout(this._rateLimit.timeout);
+ this._rateLimit.timeout = null;
+ this._rateLimit.counter = 0;
+ this._rateLimit.checkpoint = false;
+
+ // resume all paused connections
+ while (this._rateLimit.waiting.length) {
+ let cb = this._rateLimit.waiting.shift();
+ setImmediate(cb);
+ }
+ }
+
+ /**
+ * Returns true if there are free slots in the queue
+ */
+ isIdle() {
+ return this.idling;
+ }
+
+ /**
+ * Verifies SMTP configuration
+ *
+ * @param {Function} callback Callback function
+ */
+ verify(callback) {
+ let promise;
+
+ if (!callback) {
+ promise = new Promise((resolve, reject) => {
+ callback = shared.callbackPromise(resolve, reject);
+ });
+ }
+
+ let auth = new PoolResource(this).auth;
+
+ this.getSocket(this.options, (err, socketOptions) => {
+ if (err) {
+ return callback(err);
+ }
+
+ let options = this.options;
+ if (socketOptions && socketOptions.connection) {
+ this.logger.info(
+ {
+ tnx: 'proxy',
+ remoteAddress: socketOptions.connection.remoteAddress,
+ remotePort: socketOptions.connection.remotePort,
+ destHost: options.host || '',
+ destPort: options.port || '',
+ action: 'connected'
+ },
+ 'Using proxied socket from %s:%s to %s:%s',
+ socketOptions.connection.remoteAddress,
+ socketOptions.connection.remotePort,
+ options.host || '',
+ options.port || ''
+ );
+ options = shared.assign(false, options);
+ Object.keys(socketOptions).forEach(key => {
+ options[key] = socketOptions[key];
+ });
+ }
+
+ let connection = new SMTPConnection(options);
+ let returned = false;
+
+ connection.once('error', err => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ connection.close();
+ return callback(err);
+ });
+
+ connection.once('end', () => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ return callback(new Error('Connection closed'));
+ });
+
+ let finalize = () => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ connection.quit();
+ return callback(null, true);
+ };
+
+ connection.connect(() => {
+ if (returned) {
+ return;
+ }
+
+ if (auth) {
+ connection.login(auth, err => {
+ if (returned) {
+ return;
+ }
+
+ if (err) {
+ returned = true;
+ connection.close();
+ return callback(err);
+ }
+
+ finalize();
+ });
+ } else {
+ finalize();
+ }
+ });
+ });
+
+ return promise;
+ }
+}
+
+// expose to the world
+module.exports = SMTPPool;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/smtp-pool/pool-resource.js b/html/RentForCamp/node_modules/nodemailer/lib/smtp-pool/pool-resource.js
new file mode 100644
index 0000000..d1c542a
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/smtp-pool/pool-resource.js
@@ -0,0 +1,253 @@
+'use strict';
+
+const SMTPConnection = require('../smtp-connection');
+const assign = require('../shared').assign;
+const XOAuth2 = require('../xoauth2');
+const EventEmitter = require('events');
+
+/**
+ * Creates an element for the pool
+ *
+ * @constructor
+ * @param {Object} options SMTPPool instance
+ */
+class PoolResource extends EventEmitter {
+ constructor(pool) {
+ super();
+
+ this.pool = pool;
+ this.options = pool.options;
+ this.logger = this.pool.logger;
+
+ if (this.options.auth) {
+ switch ((this.options.auth.type || '').toString().toUpperCase()) {
+ case 'OAUTH2': {
+ let oauth2 = new XOAuth2(this.options.auth, this.logger);
+ oauth2.provisionCallback = (this.pool.mailer && this.pool.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
+ this.auth = {
+ type: 'OAUTH2',
+ user: this.options.auth.user,
+ oauth2,
+ method: 'XOAUTH2'
+ };
+ oauth2.on('token', token => this.pool.mailer.emit('token', token));
+ oauth2.on('error', err => this.emit('error', err));
+ break;
+ }
+ default:
+ if (!this.options.auth.user && !this.options.auth.pass) {
+ break;
+ }
+ this.auth = {
+ type: (this.options.auth.type || '').toString().toUpperCase() || 'LOGIN',
+ user: this.options.auth.user,
+ credentials: {
+ user: this.options.auth.user || '',
+ pass: this.options.auth.pass,
+ options: this.options.auth.options
+ },
+ method: (this.options.auth.method || '').trim().toUpperCase() || false
+ };
+ }
+ }
+
+ this._connection = false;
+ this._connected = false;
+
+ this.messages = 0;
+ this.available = true;
+ }
+
+ /**
+ * Initiates a connection to the SMTP server
+ *
+ * @param {Function} callback Callback function to run once the connection is established or failed
+ */
+ connect(callback) {
+ this.pool.getSocket(this.options, (err, socketOptions) => {
+ if (err) {
+ return callback(err);
+ }
+
+ let returned = false;
+ let options = this.options;
+ if (socketOptions && socketOptions.connection) {
+ this.logger.info(
+ {
+ tnx: 'proxy',
+ remoteAddress: socketOptions.connection.remoteAddress,
+ remotePort: socketOptions.connection.remotePort,
+ destHost: options.host || '',
+ destPort: options.port || '',
+ action: 'connected'
+ },
+ 'Using proxied socket from %s:%s to %s:%s',
+ socketOptions.connection.remoteAddress,
+ socketOptions.connection.remotePort,
+ options.host || '',
+ options.port || ''
+ );
+
+ options = assign(false, options);
+ Object.keys(socketOptions).forEach(key => {
+ options[key] = socketOptions[key];
+ });
+ }
+
+ this.connection = new SMTPConnection(options);
+
+ this.connection.once('error', err => {
+ this.emit('error', err);
+ if (returned) {
+ return;
+ }
+ returned = true;
+ return callback(err);
+ });
+
+ this.connection.once('end', () => {
+ this.close();
+ if (returned) {
+ return;
+ }
+ returned = true;
+
+ let timer = setTimeout(() => {
+ if (returned) {
+ return;
+ }
+ // still have not returned, this means we have an unexpected connection close
+ let err = new Error('Unexpected socket close');
+ if (this.connection && this.connection._socket && this.connection._socket.upgrading) {
+ // starttls connection errors
+ err.code = 'ETLS';
+ }
+ callback(err);
+ }, 1000);
+
+ try {
+ timer.unref();
+ } catch (E) {
+ // Ignore. Happens on envs with non-node timer implementation
+ }
+ });
+
+ this.connection.connect(() => {
+ if (returned) {
+ return;
+ }
+
+ if (this.auth) {
+ this.connection.login(this.auth, err => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+
+ if (err) {
+ this.connection.close();
+ this.emit('error', err);
+ return callback(err);
+ }
+
+ this._connected = true;
+ callback(null, true);
+ });
+ } else {
+ returned = true;
+ this._connected = true;
+ return callback(null, true);
+ }
+ });
+ });
+ }
+
+ /**
+ * Sends an e-mail to be sent using the selected settings
+ *
+ * @param {Object} mail Mail object
+ * @param {Function} callback Callback function
+ */
+ send(mail, callback) {
+ if (!this._connected) {
+ return this.connect(err => {
+ if (err) {
+ return callback(err);
+ }
+ return this.send(mail, callback);
+ });
+ }
+
+ let envelope = mail.message.getEnvelope();
+ let messageId = mail.message.messageId();
+
+ let recipients = [].concat(envelope.to || []);
+ if (recipients.length > 3) {
+ recipients.push('...and ' + recipients.splice(2).length + ' more');
+ }
+ this.logger.info(
+ {
+ tnx: 'send',
+ messageId,
+ cid: this.id
+ },
+ 'Sending message %s using #%s to <%s>',
+ messageId,
+ this.id,
+ recipients.join(', ')
+ );
+
+ if (mail.data.dsn) {
+ envelope.dsn = mail.data.dsn;
+ }
+
+ this.connection.send(envelope, mail.message.createReadStream(), (err, info) => {
+ this.messages++;
+
+ if (err) {
+ this.connection.close();
+ this.emit('error', err);
+ return callback(err);
+ }
+
+ info.envelope = {
+ from: envelope.from,
+ to: envelope.to
+ };
+ info.messageId = messageId;
+
+ setImmediate(() => {
+ let err;
+ if (this.messages >= this.options.maxMessages) {
+ err = new Error('Resource exhausted');
+ err.code = 'EMAXLIMIT';
+ this.connection.close();
+ this.emit('error', err);
+ } else {
+ this.pool._checkRateLimit(() => {
+ this.available = true;
+ this.emit('available');
+ });
+ }
+ });
+
+ callback(null, info);
+ });
+ }
+
+ /**
+ * Closes the connection
+ */
+ close() {
+ this._connected = false;
+ if (this.auth && this.auth.oauth2) {
+ this.auth.oauth2.removeAllListeners();
+ }
+ if (this.connection) {
+ this.connection.close();
+ }
+ this.emit('close');
+ }
+}
+
+module.exports = PoolResource;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/smtp-transport/index.js b/html/RentForCamp/node_modules/nodemailer/lib/smtp-transport/index.js
new file mode 100644
index 0000000..a14e5d6
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/smtp-transport/index.js
@@ -0,0 +1,408 @@
+'use strict';
+
+const EventEmitter = require('events');
+const SMTPConnection = require('../smtp-connection');
+const wellKnown = require('../well-known');
+const shared = require('../shared');
+const XOAuth2 = require('../xoauth2');
+const packageData = require('../../package.json');
+
+/**
+ * Creates a SMTP transport object for Nodemailer
+ *
+ * @constructor
+ * @param {Object} options Connection options
+ */
+class SMTPTransport extends EventEmitter {
+ constructor(options) {
+ super();
+
+ options = options || {};
+ if (typeof options === 'string') {
+ options = {
+ url: options
+ };
+ }
+
+ let urlData;
+ let service = options.service;
+
+ if (typeof options.getSocket === 'function') {
+ this.getSocket = options.getSocket;
+ }
+
+ if (options.url) {
+ urlData = shared.parseConnectionUrl(options.url);
+ service = service || urlData.service;
+ }
+
+ this.options = shared.assign(
+ false, // create new object
+ options, // regular options
+ urlData, // url options
+ service && wellKnown(service) // wellknown options
+ );
+
+ this.logger = shared.getLogger(this.options, {
+ component: this.options.component || 'smtp-transport'
+ });
+
+ // temporary object
+ let connection = new SMTPConnection(this.options);
+
+ this.name = 'SMTP';
+ this.version = packageData.version + '[client:' + connection.version + ']';
+
+ if (this.options.auth) {
+ this.auth = this.getAuth({});
+ }
+ }
+
+ /**
+ * Placeholder function for creating proxy sockets. This method immediatelly returns
+ * without a socket
+ *
+ * @param {Object} options Connection options
+ * @param {Function} callback Callback function to run with the socket keys
+ */
+ getSocket(options, callback) {
+ // return immediatelly
+ return setImmediate(() => callback(null, false));
+ }
+
+ getAuth(authOpts) {
+ if (!authOpts) {
+ return this.auth;
+ }
+
+ let hasAuth = false;
+ let authData = {};
+
+ if (this.options.auth && typeof this.options.auth === 'object') {
+ Object.keys(this.options.auth).forEach(key => {
+ hasAuth = true;
+ authData[key] = this.options.auth[key];
+ });
+ }
+
+ if (authOpts && typeof authOpts === 'object') {
+ Object.keys(authOpts).forEach(key => {
+ hasAuth = true;
+ authData[key] = authOpts[key];
+ });
+ }
+
+ if (!hasAuth) {
+ return false;
+ }
+
+ switch ((authData.type || '').toString().toUpperCase()) {
+ case 'OAUTH2': {
+ if (!authData.service && !authData.user) {
+ return false;
+ }
+ let oauth2 = new XOAuth2(authData, this.logger);
+ oauth2.provisionCallback = (this.mailer && this.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
+ oauth2.on('token', token => this.mailer.emit('token', token));
+ oauth2.on('error', err => this.emit('error', err));
+ return {
+ type: 'OAUTH2',
+ user: authData.user,
+ oauth2,
+ method: 'XOAUTH2'
+ };
+ }
+ default:
+ return {
+ type: (authData.type || '').toString().toUpperCase() || 'LOGIN',
+ user: authData.user,
+ credentials: {
+ user: authData.user || '',
+ pass: authData.pass,
+ options: authData.options
+ },
+ method: (authData.method || '').trim().toUpperCase() || false
+ };
+ }
+ }
+
+ /**
+ * Sends an e-mail using the selected settings
+ *
+ * @param {Object} mail Mail object
+ * @param {Function} callback Callback function
+ */
+ send(mail, callback) {
+ this.getSocket(this.options, (err, socketOptions) => {
+ if (err) {
+ return callback(err);
+ }
+
+ let returned = false;
+ let options = this.options;
+ if (socketOptions && socketOptions.connection) {
+ this.logger.info(
+ {
+ tnx: 'proxy',
+ remoteAddress: socketOptions.connection.remoteAddress,
+ remotePort: socketOptions.connection.remotePort,
+ destHost: options.host || '',
+ destPort: options.port || '',
+ action: 'connected'
+ },
+ 'Using proxied socket from %s:%s to %s:%s',
+ socketOptions.connection.remoteAddress,
+ socketOptions.connection.remotePort,
+ options.host || '',
+ options.port || ''
+ );
+
+ // only copy options if we need to modify it
+ options = shared.assign(false, options);
+ Object.keys(socketOptions).forEach(key => {
+ options[key] = socketOptions[key];
+ });
+ }
+
+ let connection = new SMTPConnection(options);
+
+ connection.once('error', err => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ connection.close();
+ return callback(err);
+ });
+
+ connection.once('end', () => {
+ if (returned) {
+ return;
+ }
+
+ let timer = setTimeout(() => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ // still have not returned, this means we have an unexpected connection close
+ let err = new Error('Unexpected socket close');
+ if (connection && connection._socket && connection._socket.upgrading) {
+ // starttls connection errors
+ err.code = 'ETLS';
+ }
+ callback(err);
+ }, 1000);
+
+ try {
+ timer.unref();
+ } catch (E) {
+ // Ignore. Happens on envs with non-node timer implementation
+ }
+ });
+
+ let sendMessage = () => {
+ let envelope = mail.message.getEnvelope();
+ let messageId = mail.message.messageId();
+
+ let recipients = [].concat(envelope.to || []);
+ if (recipients.length > 3) {
+ recipients.push('...and ' + recipients.splice(2).length + ' more');
+ }
+
+ if (mail.data.dsn) {
+ envelope.dsn = mail.data.dsn;
+ }
+
+ this.logger.info(
+ {
+ tnx: 'send',
+ messageId
+ },
+ 'Sending message %s to <%s>',
+ messageId,
+ recipients.join(', ')
+ );
+
+ connection.send(envelope, mail.message.createReadStream(), (err, info) => {
+ returned = true;
+ connection.close();
+ if (err) {
+ this.logger.error(
+ {
+ err,
+ tnx: 'send'
+ },
+ 'Send error for %s: %s',
+ messageId,
+ err.message
+ );
+ return callback(err);
+ }
+ info.envelope = {
+ from: envelope.from,
+ to: envelope.to
+ };
+ info.messageId = messageId;
+ try {
+ return callback(null, info);
+ } catch (E) {
+ this.logger.error(
+ {
+ err: E,
+ tnx: 'callback'
+ },
+ 'Callback error for %s: %s',
+ messageId,
+ E.message
+ );
+ }
+ });
+ };
+
+ connection.connect(() => {
+ if (returned) {
+ return;
+ }
+
+ let auth = this.getAuth(mail.data.auth);
+
+ if (auth) {
+ connection.login(auth, err => {
+ if (auth && auth !== this.auth && auth.oauth2) {
+ auth.oauth2.removeAllListeners();
+ }
+ if (returned) {
+ return;
+ }
+
+ if (err) {
+ returned = true;
+ connection.close();
+ return callback(err);
+ }
+
+ sendMessage();
+ });
+ } else {
+ sendMessage();
+ }
+ });
+ });
+ }
+
+ /**
+ * Verifies SMTP configuration
+ *
+ * @param {Function} callback Callback function
+ */
+ verify(callback) {
+ let promise;
+
+ if (!callback) {
+ promise = new Promise((resolve, reject) => {
+ callback = shared.callbackPromise(resolve, reject);
+ });
+ }
+
+ this.getSocket(this.options, (err, socketOptions) => {
+ if (err) {
+ return callback(err);
+ }
+
+ let options = this.options;
+ if (socketOptions && socketOptions.connection) {
+ this.logger.info(
+ {
+ tnx: 'proxy',
+ remoteAddress: socketOptions.connection.remoteAddress,
+ remotePort: socketOptions.connection.remotePort,
+ destHost: options.host || '',
+ destPort: options.port || '',
+ action: 'connected'
+ },
+ 'Using proxied socket from %s:%s to %s:%s',
+ socketOptions.connection.remoteAddress,
+ socketOptions.connection.remotePort,
+ options.host || '',
+ options.port || ''
+ );
+
+ options = shared.assign(false, options);
+ Object.keys(socketOptions).forEach(key => {
+ options[key] = socketOptions[key];
+ });
+ }
+
+ let connection = new SMTPConnection(options);
+ let returned = false;
+
+ connection.once('error', err => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ connection.close();
+ return callback(err);
+ });
+
+ connection.once('end', () => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ return callback(new Error('Connection closed'));
+ });
+
+ let finalize = () => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ connection.quit();
+ return callback(null, true);
+ };
+
+ connection.connect(() => {
+ if (returned) {
+ return;
+ }
+
+ let authData = this.getAuth({});
+
+ if (authData) {
+ connection.login(authData, err => {
+ if (returned) {
+ return;
+ }
+
+ if (err) {
+ returned = true;
+ connection.close();
+ return callback(err);
+ }
+
+ finalize();
+ });
+ } else {
+ finalize();
+ }
+ });
+ });
+
+ return promise;
+ }
+
+ /**
+ * Releases resources
+ */
+ close() {
+ if (this.auth && this.auth.oauth2) {
+ this.auth.oauth2.removeAllListeners();
+ }
+ this.emit('close');
+ }
+}
+
+// expose to the world
+module.exports = SMTPTransport;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/stream-transport/index.js b/html/RentForCamp/node_modules/nodemailer/lib/stream-transport/index.js
new file mode 100644
index 0000000..a39fac4
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/stream-transport/index.js
@@ -0,0 +1,142 @@
+'use strict';
+
+const packageData = require('../../package.json');
+const shared = require('../shared');
+const LeWindows = require('../sendmail-transport/le-windows');
+const LeUnix = require('../sendmail-transport/le-unix');
+
+/**
+ * Generates a Transport object for streaming
+ *
+ * Possible options can be the following:
+ *
+ * * **buffer** if true, then returns the message as a Buffer object instead of a stream
+ * * **newline** either 'windows' or 'unix'
+ *
+ * @constructor
+ * @param {Object} optional config parameter
+ */
+class StreamTransport {
+ constructor(options) {
+ options = options || {};
+
+ this.options = options || {};
+
+ this.name = 'StreamTransport';
+ this.version = packageData.version;
+
+ this.logger = shared.getLogger(this.options, {
+ component: this.options.component || 'stream-transport'
+ });
+
+ this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
+ }
+
+ /**
+ * Compiles a mailcomposer message and forwards it to handler that sends it
+ *
+ * @param {Object} emailMessage MailComposer object
+ * @param {Function} callback Callback function to run when the sending is completed
+ */
+ send(mail, done) {
+ // We probably need this in the output
+ mail.message.keepBcc = true;
+
+ let envelope = mail.data.envelope || mail.message.getEnvelope();
+ let messageId = mail.message.messageId();
+
+ let recipients = [].concat(envelope.to || []);
+ if (recipients.length > 3) {
+ recipients.push('...and ' + recipients.splice(2).length + ' more');
+ }
+ this.logger.info(
+ {
+ tnx: 'send',
+ messageId
+ },
+ 'Sending message %s to <%s> using %s line breaks',
+ messageId,
+ recipients.join(', '),
+ this.winbreak ? '' : ''
+ );
+
+ setImmediate(() => {
+ let sourceStream;
+ let stream;
+ let transform;
+
+ try {
+ transform = this.winbreak ? new LeWindows() : new LeUnix();
+ sourceStream = mail.message.createReadStream();
+ stream = sourceStream.pipe(transform);
+ sourceStream.on('error', err => stream.emit('error', err));
+ } catch (E) {
+ this.logger.error(
+ {
+ err: E,
+ tnx: 'send',
+ messageId
+ },
+ 'Creating send stream failed for %s. %s',
+ messageId,
+ E.message
+ );
+ return done(E);
+ }
+
+ if (!this.options.buffer) {
+ stream.once('error', err => {
+ this.logger.error(
+ {
+ err,
+ tnx: 'send',
+ messageId
+ },
+ 'Failed creating message for %s. %s',
+ messageId,
+ err.message
+ );
+ });
+ return done(null, {
+ envelope: mail.data.envelope || mail.message.getEnvelope(),
+ messageId,
+ message: stream
+ });
+ }
+
+ let chunks = [];
+ let chunklen = 0;
+ stream.on('readable', () => {
+ let chunk;
+ while ((chunk = stream.read()) !== null) {
+ chunks.push(chunk);
+ chunklen += chunk.length;
+ }
+ });
+
+ stream.once('error', err => {
+ this.logger.error(
+ {
+ err,
+ tnx: 'send',
+ messageId
+ },
+ 'Failed creating message for %s. %s',
+ messageId,
+ err.message
+ );
+ return done(err);
+ });
+
+ stream.on('end', () =>
+ done(null, {
+ envelope: mail.data.envelope || mail.message.getEnvelope(),
+ messageId,
+ message: Buffer.concat(chunks, chunklen)
+ })
+ );
+ });
+ }
+}
+
+module.exports = StreamTransport;
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/well-known/index.js b/html/RentForCamp/node_modules/nodemailer/lib/well-known/index.js
new file mode 100644
index 0000000..ca4c537
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/well-known/index.js
@@ -0,0 +1,47 @@
+'use strict';
+
+const services = require('./services.json');
+const normalized = {};
+
+Object.keys(services).forEach(key => {
+ let service = services[key];
+
+ normalized[normalizeKey(key)] = normalizeService(service);
+
+ [].concat(service.aliases || []).forEach(alias => {
+ normalized[normalizeKey(alias)] = normalizeService(service);
+ });
+
+ [].concat(service.domains || []).forEach(domain => {
+ normalized[normalizeKey(domain)] = normalizeService(service);
+ });
+});
+
+function normalizeKey(key) {
+ return key.replace(/[^a-zA-Z0-9.-]/g, '').toLowerCase();
+}
+
+function normalizeService(service) {
+ let filter = ['domains', 'aliases'];
+ let response = {};
+
+ Object.keys(service).forEach(key => {
+ if (filter.indexOf(key) < 0) {
+ response[key] = service[key];
+ }
+ });
+
+ return response;
+}
+
+/**
+ * Resolves SMTP config for given key. Key can be a name (like 'Gmail'), alias (like 'Google Mail') or
+ * an email address (like 'test@googlemail.com').
+ *
+ * @param {String} key [description]
+ * @returns {Object} SMTP config or false if not found
+ */
+module.exports = function(key) {
+ key = normalizeKey(key.split('@').pop());
+ return normalized[key] || false;
+};
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/well-known/services.json b/html/RentForCamp/node_modules/nodemailer/lib/well-known/services.json
new file mode 100644
index 0000000..fec47eb
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/well-known/services.json
@@ -0,0 +1,265 @@
+{
+ "1und1": {
+ "host": "smtp.1und1.de",
+ "port": 465,
+ "secure": true,
+ "authMethod": "LOGIN"
+ },
+
+ "AOL": {
+ "domains": ["aol.com"],
+ "host": "smtp.aol.com",
+ "port": 587
+ },
+
+ "DebugMail": {
+ "host": "debugmail.io",
+ "port": 25
+ },
+
+ "DynectEmail": {
+ "aliases": ["Dynect"],
+ "host": "smtp.dynect.net",
+ "port": 25
+ },
+
+ "FastMail": {
+ "domains": ["fastmail.fm"],
+ "host": "smtp.fastmail.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "GandiMail": {
+ "aliases": ["Gandi", "Gandi Mail"],
+ "host": "mail.gandi.net",
+ "port": 587
+ },
+
+ "Gmail": {
+ "aliases": ["Google Mail"],
+ "domains": ["gmail.com", "googlemail.com"],
+ "host": "smtp.gmail.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "Godaddy": {
+ "host": "smtpout.secureserver.net",
+ "port": 25
+ },
+
+ "GodaddyAsia": {
+ "host": "smtp.asia.secureserver.net",
+ "port": 25
+ },
+
+ "GodaddyEurope": {
+ "host": "smtp.europe.secureserver.net",
+ "port": 25
+ },
+
+ "hot.ee": {
+ "host": "mail.hot.ee"
+ },
+
+ "Hotmail": {
+ "aliases": ["Outlook", "Outlook.com", "Hotmail.com"],
+ "domains": ["hotmail.com", "outlook.com"],
+ "host": "smtp.live.com",
+ "port": 587,
+ "tls": {
+ "ciphers": "SSLv3"
+ }
+ },
+
+ "iCloud": {
+ "aliases": ["Me", "Mac"],
+ "domains": ["me.com", "mac.com"],
+ "host": "smtp.mail.me.com",
+ "port": 587
+ },
+
+ "mail.ee": {
+ "host": "smtp.mail.ee"
+ },
+
+ "Mail.ru": {
+ "host": "smtp.mail.ru",
+ "port": 465,
+ "secure": true
+ },
+
+ "Maildev": {
+ "port": 1025,
+ "ignoreTLS": true
+ },
+
+ "Mailgun": {
+ "host": "smtp.mailgun.org",
+ "port": 465,
+ "secure": true
+ },
+
+ "Mailjet": {
+ "host": "in.mailjet.com",
+ "port": 587
+ },
+
+ "Mailosaur": {
+ "host": "mailosaur.io",
+ "port": 25
+ },
+
+ "Mailtrap": {
+ "host": "smtp.mailtrap.io",
+ "port": 2525
+ },
+
+ "Mandrill": {
+ "host": "smtp.mandrillapp.com",
+ "port": 587
+ },
+
+ "Naver": {
+ "host": "smtp.naver.com",
+ "port": 587
+ },
+
+ "One": {
+ "host": "send.one.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "OpenMailBox": {
+ "aliases": ["OMB", "openmailbox.org"],
+ "host": "smtp.openmailbox.org",
+ "port": 465,
+ "secure": true
+ },
+
+ "Outlook365": {
+ "host": "smtp.office365.com",
+ "port": 587,
+ "secure": false
+ },
+
+ "Postmark": {
+ "aliases": ["PostmarkApp"],
+ "host": "smtp.postmarkapp.com",
+ "port": 2525
+ },
+
+ "qiye.aliyun": {
+ "host": "smtp.mxhichina.com",
+ "port": "465",
+ "secure": true
+ },
+
+ "QQ": {
+ "domains": ["qq.com"],
+ "host": "smtp.qq.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "QQex": {
+ "aliases": ["QQ Enterprise"],
+ "domains": ["exmail.qq.com"],
+ "host": "smtp.exmail.qq.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "SendCloud": {
+ "host": "smtpcloud.sohu.com",
+ "port": 25
+ },
+
+ "SendGrid": {
+ "host": "smtp.sendgrid.net",
+ "port": 587
+ },
+
+ "SendinBlue": {
+ "host": "smtp-relay.sendinblue.com",
+ "port": 587
+ },
+
+ "SendPulse": {
+ "host": "smtp-pulse.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "SES": {
+ "host": "email-smtp.us-east-1.amazonaws.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "SES-US-EAST-1": {
+ "host": "email-smtp.us-east-1.amazonaws.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "SES-US-WEST-2": {
+ "host": "email-smtp.us-west-2.amazonaws.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "SES-EU-WEST-1": {
+ "host": "email-smtp.eu-west-1.amazonaws.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "Sparkpost": {
+ "aliases": ["SparkPost", "SparkPost Mail"],
+ "domains": ["sparkpost.com"],
+ "host": "smtp.sparkpostmail.com",
+ "port": 587,
+ "secure": false
+ },
+
+ "Tipimail": {
+ "host": "smtp.tipimail.com",
+ "port": 587
+ },
+
+ "Yahoo": {
+ "domains": ["yahoo.com"],
+ "host": "smtp.mail.yahoo.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "Yandex": {
+ "domains": ["yandex.ru"],
+ "host": "smtp.yandex.ru",
+ "port": 465,
+ "secure": true
+ },
+
+ "Zoho": {
+ "host": "smtp.zoho.com",
+ "port": 465,
+ "secure": true,
+ "authMethod": "LOGIN"
+ },
+
+ "126": {
+ "host": "smtp.126.com",
+ "port": 465,
+ "secure": true
+ },
+
+ "163": {
+ "host": "smtp.163.com",
+ "port": 465,
+ "secure": true
+ }
+}
diff --git a/html/RentForCamp/node_modules/nodemailer/lib/xoauth2/index.js b/html/RentForCamp/node_modules/nodemailer/lib/xoauth2/index.js
new file mode 100644
index 0000000..6baba58
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/lib/xoauth2/index.js
@@ -0,0 +1,366 @@
+'use strict';
+
+const Stream = require('stream').Stream;
+const fetch = require('../fetch');
+const crypto = require('crypto');
+const shared = require('../shared');
+
+/**
+ * XOAUTH2 access_token generator for Gmail.
+ * Create client ID for web applications in Google API console to use it.
+ * See Offline Access for receiving the needed refreshToken for an user
+ * https://developers.google.com/accounts/docs/OAuth2WebServer#offline
+ *
+ * Usage for generating access tokens with a custom method using provisionCallback:
+ * provisionCallback(user, renew, callback)
+ * * user is the username to get the token for
+ * * renew is a boolean that if true indicates that existing token failed and needs to be renewed
+ * * callback is the callback to run with (error, accessToken [, expires])
+ * * accessToken is a string
+ * * expires is an optional expire time in milliseconds
+ * If provisionCallback is used, then Nodemailer does not try to attempt generating the token by itself
+ *
+ * @constructor
+ * @param {Object} options Client information for token generation
+ * @param {String} options.user User e-mail address
+ * @param {String} options.clientId Client ID value
+ * @param {String} options.clientSecret Client secret value
+ * @param {String} options.refreshToken Refresh token for an user
+ * @param {String} options.accessUrl Endpoint for token generation, defaults to 'https://accounts.google.com/o/oauth2/token'
+ * @param {String} options.accessToken An existing valid accessToken
+ * @param {String} options.privateKey Private key for JSW
+ * @param {Number} options.expires Optional Access Token expire time in ms
+ * @param {Number} options.timeout Optional TTL for Access Token in seconds
+ * @param {Function} options.provisionCallback Function to run when a new access token is required
+ */
+class XOAuth2 extends Stream {
+ constructor(options, logger) {
+ super();
+
+ this.options = options || {};
+
+ if (options && options.serviceClient) {
+ if (!options.privateKey || !options.user) {
+ setImmediate(() => this.emit('error', new Error('Options "privateKey" and "user" are required for service account!')));
+ return;
+ }
+
+ let serviceRequestTimeout = Math.min(Math.max(Number(this.options.serviceRequestTimeout) || 0, 0), 3600);
+ this.options.serviceRequestTimeout = serviceRequestTimeout || 5 * 60;
+ }
+
+ this.logger = shared.getLogger(
+ {
+ logger
+ },
+ {
+ component: this.options.component || 'OAuth2'
+ }
+ );
+
+ this.provisionCallback = typeof this.options.provisionCallback === 'function' ? this.options.provisionCallback : false;
+
+ this.options.accessUrl = this.options.accessUrl || 'https://accounts.google.com/o/oauth2/token';
+ this.options.customHeaders = this.options.customHeaders || {};
+ this.options.customParams = this.options.customParams || {};
+
+ this.accessToken = this.options.accessToken || false;
+
+ if (this.options.expires && Number(this.options.expires)) {
+ this.expires = this.options.expires;
+ } else {
+ let timeout = Math.max(Number(this.options.timeout) || 0, 0);
+ this.expires = (timeout && Date.now() + timeout * 1000) || 0;
+ }
+ }
+
+ /**
+ * Returns or generates (if previous has expired) a XOAuth2 token
+ *
+ * @param {Boolean} renew If false then use cached access token (if available)
+ * @param {Function} callback Callback function with error object and token string
+ */
+ getToken(renew, callback) {
+ if (!renew && this.accessToken && (!this.expires || this.expires > Date.now())) {
+ return callback(null, this.accessToken);
+ }
+
+ let generateCallback = (...args) => {
+ if (args[0]) {
+ this.logger.error(
+ {
+ err: args[0],
+ tnx: 'OAUTH2',
+ user: this.options.user,
+ action: 'renew'
+ },
+ 'Failed generating new Access Token for %s',
+ this.options.user
+ );
+ } else {
+ this.logger.info(
+ {
+ tnx: 'OAUTH2',
+ user: this.options.user,
+ action: 'renew'
+ },
+ 'Generated new Access Token for %s',
+ this.options.user
+ );
+ }
+ callback(...args);
+ };
+
+ if (this.provisionCallback) {
+ this.provisionCallback(this.options.user, !!renew, (err, accessToken, expires) => {
+ if (!err && accessToken) {
+ this.accessToken = accessToken;
+ this.expires = expires || 0;
+ }
+ generateCallback(err, accessToken);
+ });
+ } else {
+ this.generateToken(generateCallback);
+ }
+ }
+
+ /**
+ * Updates token values
+ *
+ * @param {String} accessToken New access token
+ * @param {Number} timeout Access token lifetime in seconds
+ *
+ * Emits 'token': { user: User email-address, accessToken: the new accessToken, timeout: TTL in seconds}
+ */
+ updateToken(accessToken, timeout) {
+ this.accessToken = accessToken;
+ timeout = Math.max(Number(timeout) || 0, 0);
+ this.expires = (timeout && Date.now() + timeout * 1000) || 0;
+
+ this.emit('token', {
+ user: this.options.user,
+ accessToken: accessToken || '',
+ expires: this.expires
+ });
+ }
+
+ /**
+ * Generates a new XOAuth2 token with the credentials provided at initialization
+ *
+ * @param {Function} callback Callback function with error object and token string
+ */
+ generateToken(callback) {
+ let urlOptions;
+ let loggedUrlOptions;
+ if (this.options.serviceClient) {
+ // service account - https://developers.google.com/identity/protocols/OAuth2ServiceAccount
+ let iat = Math.floor(Date.now() / 1000); // unix time
+ let tokenData = {
+ iss: this.options.serviceClient,
+ scope: this.options.scope || 'https://mail.google.com/',
+ sub: this.options.user,
+ aud: this.options.accessUrl,
+ iat,
+ exp: iat + this.options.serviceRequestTimeout
+ };
+ let token = this.jwtSignRS256(tokenData);
+
+ urlOptions = {
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
+ assertion: token
+ };
+
+ loggedUrlOptions = {
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
+ assertion: tokenData
+ };
+ } else {
+ if (!this.options.refreshToken) {
+ return callback(new Error('Can\x27t create new access token for user'));
+ }
+
+ // web app - https://developers.google.com/identity/protocols/OAuth2WebServer
+ urlOptions = {
+ client_id: this.options.clientId || '',
+ client_secret: this.options.clientSecret || '',
+ refresh_token: this.options.refreshToken,
+ grant_type: 'refresh_token'
+ };
+
+ loggedUrlOptions = {
+ client_id: this.options.clientId || '',
+ client_secret: (this.options.clientSecret || '').substr(0, 6) + '...',
+ refresh_token: (this.options.refreshToken || '').substr(0, 6) + '...',
+ grant_type: 'refresh_token'
+ };
+ }
+
+ Object.keys(this.options.customParams).forEach(key => {
+ urlOptions[key] = this.options.customParams[key];
+ loggedUrlOptions[key] = this.options.customParams[key];
+ });
+
+ this.logger.debug(
+ {
+ tnx: 'OAUTH2',
+ user: this.options.user,
+ action: 'generate'
+ },
+ 'Requesting token using: %s',
+ JSON.stringify(loggedUrlOptions)
+ );
+
+ this.postRequest(this.options.accessUrl, urlOptions, this.options, (error, body) => {
+ let data;
+
+ if (error) {
+ return callback(error);
+ }
+
+ try {
+ data = JSON.parse(body.toString());
+ } catch (E) {
+ return callback(E);
+ }
+
+ if (!data || typeof data !== 'object') {
+ this.logger.debug(
+ {
+ tnx: 'OAUTH2',
+ user: this.options.user,
+ action: 'post'
+ },
+ 'Response: %s',
+ (body || '').toString()
+ );
+ return callback(new Error('Invalid authentication response'));
+ }
+
+ let logData = {};
+ Object.keys(data).forEach(key => {
+ if (key !== 'access_token') {
+ logData[key] = data[key];
+ } else {
+ logData[key] = (data[key] || '').toString().substr(0, 6) + '...';
+ }
+ });
+
+ this.logger.debug(
+ {
+ tnx: 'OAUTH2',
+ user: this.options.user,
+ action: 'post'
+ },
+ 'Response: %s',
+ JSON.stringify(logData)
+ );
+
+ if (data.error) {
+ return callback(new Error(data.error));
+ }
+
+ if (data.access_token) {
+ this.updateToken(data.access_token, data.expires_in);
+ return callback(null, this.accessToken);
+ }
+
+ return callback(new Error('No access token'));
+ });
+ }
+
+ /**
+ * Converts an access_token and user id into a base64 encoded XOAuth2 token
+ *
+ * @param {String} [accessToken] Access token string
+ * @return {String} Base64 encoded token for IMAP or SMTP login
+ */
+ buildXOAuth2Token(accessToken) {
+ let authData = ['user=' + (this.options.user || ''), 'auth=Bearer ' + (accessToken || this.accessToken), '', ''];
+ return Buffer.from(authData.join('\x01'), 'utf-8').toString('base64');
+ }
+
+ /**
+ * Custom POST request handler.
+ * This is only needed to keep paths short in Windows – usually this module
+ * is a dependency of a dependency and if it tries to require something
+ * like the request module the paths get way too long to handle for Windows.
+ * As we do only a simple POST request we do not actually require complicated
+ * logic support (no redirects, no nothing) anyway.
+ *
+ * @param {String} url Url to POST to
+ * @param {String|Buffer} payload Payload to POST
+ * @param {Function} callback Callback function with (err, buff)
+ */
+ postRequest(url, payload, params, callback) {
+ let returned = false;
+
+ let chunks = [];
+ let chunklen = 0;
+
+ let req = fetch(url, {
+ method: 'post',
+ headers: params.customHeaders,
+ body: payload,
+ allowErrorResponse: true
+ });
+
+ req.on('readable', () => {
+ let chunk;
+ while ((chunk = req.read()) !== null) {
+ chunks.push(chunk);
+ chunklen += chunk.length;
+ }
+ });
+
+ req.once('error', err => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ return callback(err);
+ });
+
+ req.once('end', () => {
+ if (returned) {
+ return;
+ }
+ returned = true;
+ return callback(null, Buffer.concat(chunks, chunklen));
+ });
+ }
+
+ /**
+ * Encodes a buffer or a string into Base64url format
+ *
+ * @param {Buffer|String} data The data to convert
+ * @return {String} The encoded string
+ */
+ toBase64URL(data) {
+ if (typeof data === 'string') {
+ data = Buffer.from(data);
+ }
+
+ return data
+ .toString('base64')
+ .replace(/[=]+/g, '') // remove '='s
+ .replace(/\+/g, '-') // '+' → '-'
+ .replace(/\//g, '_'); // '/' → '_'
+ }
+
+ /**
+ * Creates a JSON Web Token signed with RS256 (SHA256 + RSA)
+ *
+ * @param {Object} payload The payload to include in the generated token
+ * @return {String} The generated and signed token
+ */
+ jwtSignRS256(payload) {
+ payload = ['{"alg":"RS256","typ":"JWT"}', JSON.stringify(payload)].map(val => this.toBase64URL(val)).join('.');
+ let signature = crypto
+ .createSign('RSA-SHA256')
+ .update(payload)
+ .sign(this.options.privateKey);
+ return payload + '.' + this.toBase64URL(signature);
+ }
+}
+
+module.exports = XOAuth2;
diff --git a/html/RentForCamp/node_modules/nodemailer/package.json b/html/RentForCamp/node_modules/nodemailer/package.json
new file mode 100644
index 0000000..b2971b5
--- /dev/null
+++ b/html/RentForCamp/node_modules/nodemailer/package.json
@@ -0,0 +1,113 @@
+{
+ "_args": [
+ [
+ {
+ "raw": "nodemailer",
+ "scope": null,
+ "escapedName": "nodemailer",
+ "name": "nodemailer",
+ "rawSpec": "",
+ "spec": "latest",
+ "type": "tag"
+ },
+ "/Users/gerrit/Documents/dev/nodejs/rentfor.camp/html/RentForCamp"
+ ]
+ ],
+ "_from": "nodemailer@latest",
+ "_hasShrinkwrap": false,
+ "_id": "nodemailer@5.1.1",
+ "_inCache": true,
+ "_location": "/nodemailer",
+ "_nodeVersion": "10.14.1",
+ "_npmOperationalInternal": {
+ "host": "s3://npm-registry-packages",
+ "tmp": "tmp/nodemailer_5.1.1_1547067848297_0.7169709724410838"
+ },
+ "_npmUser": {
+ "name": "andris",
+ "email": "andris@kreata.ee"
+ },
+ "_npmVersion": "6.4.1",
+ "_phantomChildren": {},
+ "_requested": {
+ "raw": "nodemailer",
+ "scope": null,
+ "escapedName": "nodemailer",
+ "name": "nodemailer",
+ "rawSpec": "",
+ "spec": "latest",
+ "type": "tag"
+ },
+ "_requiredBy": [
+ "#USER",
+ "/"
+ ],
+ "_resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-5.1.1.tgz",
+ "_shasum": "0c48d1ecab02e86d9ff6c620ee75ed944b763505",
+ "_shrinkwrap": null,
+ "_spec": "nodemailer",
+ "_where": "/Users/gerrit/Documents/dev/nodejs/rentfor.camp/html/RentForCamp",
+ "author": {
+ "name": "Andris Reinman"
+ },
+ "bugs": {
+ "url": "https://github.com/nodemailer/nodemailer/issues"
+ },
+ "dependencies": {},
+ "description": "Easy as cake e-mail sending from your Node.js applications",
+ "devDependencies": {
+ "bunyan": "1.8.12",
+ "chai": "4.2.0",
+ "eslint-config-nodemailer": "1.2.0",
+ "eslint-config-prettier": "3.3.0",
+ "grunt": "1.0.3",
+ "grunt-cli": "1.3.2",
+ "grunt-eslint": "21.0.0",
+ "grunt-mocha-test": "0.13.3",
+ "libbase64": "1.0.3",
+ "libmime": "4.0.1",
+ "libqp": "1.1.0",
+ "mocha": "5.2.0",
+ "nodemailer-ntlm-auth": "1.0.1",
+ "proxy": "0.2.4",
+ "proxy-test-server": "1.0.0",
+ "sinon": "7.2.2",
+ "smtp-server": "3.5.0"
+ },
+ "directories": {},
+ "dist": {
+ "integrity": "sha512-hKGCoeNdFL2W7S76J/Oucbw0/qRlfG815tENdhzcqTpSjKgAN91mFOqU2lQUflRRxFM7iZvCyaFcAR9noc/CqQ==",
+ "shasum": "0c48d1ecab02e86d9ff6c620ee75ed944b763505",
+ "tarball": "https://registry.npmjs.org/nodemailer/-/nodemailer-5.1.1.tgz",
+ "fileCount": 39,
+ "unpackedSize": 459879,
+ "npm-signature": "-----BEGIN PGP SIGNATURE-----\r\nVersion: OpenPGP.js v3.0.4\r\nComment: https://openpgpjs.org\r\n\r\nwsFcBAEBCAAQBQJcNmHJCRA9TVsSAnZWagAA3r0P+wa1qWoMA6gNCbt1Yiix\n+5hUsLnW+IbCG0DT5waZaTQ8+QUQsxH7ule7k3CnQ8zBV6peYxGLFc5WZPHc\nbkm1+Ui1QIM63X5i7/z5QXBeydiOLj42SPdgDrm96707PaWNrPaYGLYfYipG\nB4mPY0sFkVPygb17X3vos80+BRzGjtGriyseoDOhjKneFYFHPnaOylRQsdPH\nH+E5ZK6kezSs9NQMZGY/Pbo7Wyp+2fYeFaN3uO0S0ZEGihas2RBt2ejTsTh2\n0RE0t9a7koGjjh+JiHIoM7qT7J3MA4x1rj9LObbyGbILZWm8n8gcTWsacZTp\nK4CcCPJJL6ZcpykuAeOBeIfFhkSFFaY0A8dt12xie/XdzeCKqfDdexXvSYec\nCLO44Ogu98qrpWdPN1cQJA0f7gkSAGb/xeUOKKwW6WSHSpbsy4+5zxFYpVlu\nWhJmJM8+iTzU1TAbXJkH6H+Nj7UZ0DhB86vdnr9tScsUj3LmjlR+maENP8wo\nBjRFnY6zbj0grJxbzZ+46DaXl1/Gs4U6z9iATYqeWbgc+EAiUIo7+tVuIc+8\nrXfWwrryxgTIK76kFVsxdOdIxI5v7a+Kx32l7rz/CQgSgEC/CgAcqxL/viH4\nLWnLtFqDKay9T3sJg0KQ1jyVbCbaCqFn8taYMqAGKA/duuqViGimqK34JLfM\n/XtC\r\n=j3WI\r\n-----END PGP SIGNATURE-----\r\n"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "gitHead": "1cb2e42e574ca6411747f1aae4c230fe5ec7bbf7",
+ "homepage": "https://nodemailer.com/",
+ "keywords": [
+ "Nodemailer"
+ ],
+ "license": "MIT",
+ "main": "lib/nodemailer.js",
+ "maintainers": [
+ {
+ "name": "andris",
+ "email": "andris@node.ee"
+ }
+ ],
+ "name": "nodemailer",
+ "optionalDependencies": {},
+ "readme": "ERROR: No README data found!",
+ "repository": {
+ "type": "git",
+ "url": "git+https://github.com/nodemailer/nodemailer.git"
+ },
+ "scripts": {
+ "test": "grunt"
+ },
+ "version": "5.1.1"
+}
diff --git a/html/RentForCamp/package.json b/html/RentForCamp/package.json
index 1113f35..6c36205 100644
--- a/html/RentForCamp/package.json
+++ b/html/RentForCamp/package.json
@@ -10,6 +10,7 @@
"cookie-parser": "~1.4.3",
"dateformat": "^3.0.3",
"debug": "~2.6.9",
+ "email-validator": "^2.0.4",
"express": "~4.16.0",
"express-session": "^1.15.6",
"eyespect": "^0.1.10",
@@ -19,6 +20,7 @@
"less-middleware": "~2.2.1",
"morgan": "~1.9.0",
"mysql": "^2.16.0",
+ "nodemailer": "^5.1.1",
"pug": "2.0.0-beta11"
}
}
diff --git a/html/RentForCamp/routes/index.js b/html/RentForCamp/routes/index.js
index f6174d6..b6d4abc 100644
--- a/html/RentForCamp/routes/index.js
+++ b/html/RentForCamp/routes/index.js
@@ -50,12 +50,20 @@ router.get('/terms/', function(req, res, next) {
});
router.post('/request/', function(req, res, next) {
+ var app = req.app
+ var mailer = app.get('MAILER')
+
const paramStart = req.body.start
const paramEnd = req.body.end
const paramName = req.body.name
const paramContact = req.body.contact
- //TODO: Mail hier!
+ mailer.mail({
+ name: paramName,
+ contact: paramContact,
+ start: paramStart,
+ end: paramEnd
+ })
res.render('request', { title: 'Dein.Equipment: Anfrage gesendet' })
})