emailjs-3.6.0-to-4.0.0
703 linhas
'use strict';
import { existsSync, open, read, closeSync, close } from 'fs';
import { hostname } from 'os';
Object.defineProperty(exports, '__esModule', { value: true });
import { Stream } from 'stream';
import { TextEncoder, TextDecoder } from 'util';
var fs = require('fs');
import { createHmac } from 'crypto';
var os = require('os');
import { EventEmitter } from 'events';
var stream = require('stream');
import { Socket } from 'net';
var util = require('util');
import { connect, TLSSocket, createSecureContext } from 'tls';
var crypto = require('crypto');
var events = require('events');
var net = require('net');
var tls = require('tls');
/*
/*
 * Operator tokens and which tokens are expected to end the sequence
 * Operator tokens and which tokens are expected to end the sequence
 */
 */
const OPERATORS = new Map([
const OPERATORS = new Map([
    ['"', '"'],
    ['"', '"'],
    ['(', ')'],
    ['(', ')'],
    ['<', '>'],
    ['<', '>'],
    [',', ''],
    [',', ''],
    // Groups are ended by semicolons
    // Groups are ended by semicolons
    [':', ';'],
    [':', ';'],
    // Semicolons are not a legal delimiter per the RFC2822 grammar other
    // Semicolons are not a legal delimiter per the RFC2822 grammar other
    // than for terminating a group, but they are also not valid for any
    // than for terminating a group, but they are also not valid for any
    // other use in this context.  Given that some mail clients have
    // other use in this context.  Given that some mail clients have
    // historically allowed the semicolon as a delimiter equivalent to the
    // historically allowed the semicolon as a delimiter equivalent to the
    // comma in their UI, it makes sense to treat them the same as a comma
    // comma in their UI, it makes sense to treat them the same as a comma
    // when used outside of a group.
    // when used outside of a group.
    [';', ''],
    [';', ''],
]);
]);
/**
/**
 * Tokenizes the original input string
 * Tokenizes the original input string
 *
 *
 * @param {string | string[] | undefined} address string(s) to tokenize
 * @param {string | string[] | undefined} address string(s) to tokenize
 * @return {AddressToken[]} An array of operator|text tokens
 * @return {AddressToken[]} An array of operator|text tokens
 */
 */
function tokenizeAddress(address = '') {
function tokenizeAddress(address = '') {
    var _a, _b;
    var _a, _b;
    const tokens = [];
    const tokens = [];
    let token = undefined;
    let token = undefined;
    let operator = undefined;
    let operator = undefined;
    for (const character of address.toString()) {
    for (const character of address.toString()) {
        if (((_a = operator === null || operator === void 0 ? void 0 : operator.length) !== null && _a !== void 0 ? _a : 0) > 0 && character === operator) {
        if (((_a = operator === null || operator === void 0 ? void 0 : operator.length) !== null && _a !== void 0 ? _a : 0) > 0 && character === operator) {
            tokens.push({ type: 'operator', value: character });
            tokens.push({ type: 'operator', value: character });
            token = undefined;
            token = undefined;
            operator = undefined;
            operator = undefined;
        }
        }
        else if (((_b = operator === null || operator === void 0 ? void 0 : operator.length) !== null && _b !== void 0 ? _b : 0) === 0 && OPERATORS.has(character)) {
        else if (((_b = operator === null || operator === void 0 ? void 0 : operator.length) !== null && _b !== void 0 ? _b : 0) === 0 && OPERATORS.has(character)) {
            tokens.push({ type: 'operator', value: character });
            tokens.push({ type: 'operator', value: character });
            token = undefined;
            token = undefined;
            operator = OPERATORS.get(character);
            operator = OPERATORS.get(character);
        }
        }
        else {
        else {
            if (token == null) {
            if (token == null) {
                token = { type: 'text', value: character };
                token = { type: 'text', value: character };
                tokens.push(token);
                tokens.push(token);
            }
            }
            else {
            else {
                token.value += character;
                token.value += character;
            }
            }
        }
        }
    }
    }
    return tokens
    return tokens
        .map((x) => {
        .map((x) => {
        x.value = x.value.trim();
        x.value = x.value.trim();
        return x;
        return x;
    })
    })
        .filter((x) => x.value.length > 0);
        .filter((x) => x.value.length > 0);
}
}
/**
/**
 * Converts tokens for a single address into an address object
 * Converts tokens for a single address into an address object
 *
 *
 * @param {AddressToken[]} tokens Tokens object
 * @param {AddressToken[]} tokens Tokens object
 * @return {AddressObject[]} addresses object array
 * @return {AddressObject[]} addresses object array
 */
 */
function convertAddressTokens(tokens) {
function convertAddressTokens(tokens) {
    const addressObjects = [];
    const addressObjects = [];
    const groups = [];
    const groups = [];
    let addresses = [];
    let addresses = [];
    let comments = [];
    let comments = [];
    let texts = [];
    let texts = [];
    let state = 'text';
    let state = 'text';
    let isGroup = false;
    let isGroup = false;
    function handleToken(token) {
    function handleToken(token) {
        if (token.type === 'operator') {
        if (token.type === 'operator') {
            switch (token.value) {
            switch (token.value) {
                case '<':
                case '<':
                    state = 'address';
                    state = 'address';
                    break;
                    break;
                case '(':
                case '(':
                    state = 'comment';
                    state = 'comment';
                    break;
                    break;
                case ':':
                case ':':
                    state = 'group';
                    state = 'group';
                    isGroup = true;
                    isGroup = true;
                    break;
                    break;
                default:
                default:
                    state = 'text';
                    state = 'text';
                    break;
                    break;
            }
            }
        }
        }
        else if (token.value.length > 0) {
        else if (token.value.length > 0) {
            switch (state) {
            switch (state) {
                case 'address':
                case 'address':
                    addresses.push(token.value);
                    addresses.push(token.value);
                    break;
                    break;
                case 'comment':
                case 'comment':
                    comments.push(token.value);
                    comments.push(token.value);
                    break;
                    break;
                case 'group':
                case 'group':
                    groups.push(token.value);
                    groups.push(token.value);
                    break;
                    break;
                default:
                default:
                    texts.push(token.value);
                    texts.push(token.value);
                    break;
                    break;
            }
            }
        }
        }
    }
    }
    // Filter out <addresses>, (comments) and regular text
    // Filter out <addresses>, (comments) and regular text
    for (const token of tokens) {
    for (const token of tokens) {
        handleToken(token);
        handleToken(token);
    }
    }
    // If there is no text but a comment, replace the two
    // If there is no text but a comment, replace the two
    if (texts.length === 0 && comments.length > 0) {
    if (texts.length === 0 && comments.length > 0) {
        texts = [...comments];
        texts = [...comments];
        comments = [];
        comments = [];
    }
    }
    // http://tools.ietf.org/html/rfc2822#appendix-A.1.3
    // http://tools.ietf.org/html/rfc2822#appendix-A.1.3
    if (isGroup) {
    if (isGroup) {
        addressObjects.push({
        addressObjects.push({
            name: texts.length === 0 ? undefined : texts.join(' '),
            name: texts.length === 0 ? undefined : texts.join(' '),
            group: groups.length > 0 ? addressparser(groups.join(',')) : [],
            group: groups.length > 0 ? addressparser(groups.join(',')) : [],
        });
        });
    }
    }
    else {
    else {
        // If no address was found, try to detect one from regular text
        // If no address was found, try to detect one from regular text
        if (addresses.length === 0 && texts.length > 0) {
        if (addresses.length === 0 && texts.length > 0) {
            for (let i = texts.length - 1; i >= 0; i--) {
            for (let i = texts.length - 1; i >= 0; i--) {
                if (texts[i].match(/^[^@\s]+@[^@\s]+$/)) {
                if (texts[i].match(/^[^@\s]+@[^@\s]+$/)) {
                    addresses = texts.splice(i, 1);
                    addresses = texts.splice(i, 1);
                    break;
                    break;
                }
                }
            }
            }
            // still no address
            // still no address
            if (addresses.length === 0) {
            if (addresses.length === 0) {
                for (let i = texts.length - 1; i >= 0; i--) {
                for (let i = texts.length - 1; i >= 0; i--) {
                    texts[i] = texts[i]
                    texts[i] = texts[i]
                        .replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, (address) => {
                        .replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, (address) => {
                        if (addresses.length === 0) {
                        if (addresses.length === 0) {
                            addresses = [address.trim()];
                            addresses = [address.trim()];
                            return ' ';
                            return ' ';
                        }
                        }
                        else {
                        else {
                            return address;
                            return address;
                        }
                        }
                    })
                    })
                        .trim();
                        .trim();
                    if (addresses.length > 0) {
                    if (addresses.length > 0) {
                        break;
                        break;
                    }
                    }
                }
                }
            }
            }
        }
        }
        // If there's still is no text but a comment exixts, replace the two
        // If there's still is no text but a comment exixts, replace the two
        if (texts.length === 0 && comments.length > 0) {
        if (texts.length === 0 && comments.length > 0) {
            texts = [...comments];
            texts = [...comments];
            comments = [];
            comments = [];
        }
        }
        // Keep only the first address occurence, push others to regular text
        // Keep only the first address occurence, push others to regular text
        if (addresses.length > 1) {
        if (addresses.length > 1) {
            texts = [...texts, ...addresses.splice(1)];
            texts = [...texts, ...addresses.splice(1)];
        }
        }
        if (addresses.length === 0 && isGroup) {
        if (addresses.length === 0 && isGroup) {
            return [];
            return [];
        }
        }
        else {
        else {
            // Join values with spaces
            // Join values with spaces
            let address = addresses.join(' ');
            let address = addresses.join(' ');
            let name = texts.length === 0 ? address : texts.join(' ');
            let name = texts.length === 0 ? address : texts.join(' ');
            if (address === name) {
            if (address === name) {
                if (address.match(/@/)) {
                if (address.match(/@/)) {
                    name = '';
                    name = '';
                }
                }
                else {
                else {
                    address = '';
                    address = '';
                }
                }
            }
            }
            addressObjects.push({ address, name });
            addressObjects.push({ address, name });
        }
        }
    }
    }
    return addressObjects;
    return addressObjects;
}
}
/**
/**
 * Parses structured e-mail addresses from an address field
 * Parses structured e-mail addresses from an address field
 *
 *
 * Example:
 * Example:
 *
 *
 *    "Name <address@domain>"
 *    "Name <address@domain>"
 *
 *
 * will be converted to
 * will be converted to
 *
 *
 *     [{name: "Name", address: "address@domain"}]
 *     [{name: "Name", address: "address@domain"}]
 *
 *
 * @param {string | string[] | undefined} address Address field
 * @param {string | string[] | undefined} address Address field
 * @return {AddressObject[]} An array of address objects
 * @return {AddressObject[]} An array of address objects
 */
 */
function addressparser(address) {
function addressparser(address) {
    const addresses = [];
    const addresses = [];
    let tokens = [];
    let tokens = [];
    for (const token of tokenizeAddress(address)) {
    for (const token of tokenizeAddress(address)) {
        if (token.type === 'operator' &&
        if (token.type === 'operator' &&
            (token.value === ',' || token.value === ';')) {
            (token.value === ',' || token.value === ';')) {
            if (tokens.length > 0) {
            if (tokens.length > 0) {
                addresses.push(...convertAddressTokens(tokens));
                addresses.push(...convertAddressTokens(tokens));
            }
            }
            tokens = [];
            tokens = [];
        }
        }
        else {
        else {
            tokens.push(token);
            tokens.push(token);
        }
        }
    }
    }
    if (tokens.length > 0) {
    if (tokens.length > 0) {
        addresses.push(...convertAddressTokens(tokens));
        addresses.push(...convertAddressTokens(tokens));
    }
    }
    return addresses;
    return addresses;
}
}
/**
/**
 * @param {Date} [date] an optional date to convert to RFC2822 format
 * @param {Date} [date] an optional date to convert to RFC2822 format
 * @param {boolean} [useUtc] whether to parse the date as UTC (default: false)
 * @param {boolean} [useUtc] whether to parse the date as UTC (default: false)
 * @returns {string} the converted date
 * @returns {string} the converted date
 */
 */
function getRFC2822Date(date = new Date(), useUtc = false) {
function getRFC2822Date(date = new Date(), useUtc = false) {
    if (useUtc) {
    if (useUtc) {
        return getRFC2822DateUTC(date);
        return getRFC2822DateUTC(date);
    }
    }
    const dates = date
    const dates = date
        .toString()
        .toString()
        .replace('GMT', '')
        .replace('GMT', '')
        .replace(/\s\(.*\)$/, '')
        .replace(/\s\(.*\)$/, '')
        .split(' ');
        .split(' ');
    dates[0] = dates[0] + ',';
    dates[0] = dates[0] + ',';
    const day = dates[1];
    const day = dates[1];
    dates[1] = dates[2];
    dates[1] = dates[2];
    dates[2] = day;
    dates[2] = day;
    return dates.join(' ');
    return dates.join(' ');
}
}
/**
/**
 * @param {Date} [date] an optional date to convert to RFC2822 format (UTC)
 * @param {Date} [date] an optional date to convert to RFC2822 format (UTC)
 * @returns {string} the converted date
 * @returns {string} the converted date
 */
 */
function getRFC2822DateUTC(date = new Date()) {
function getRFC2822DateUTC(date = new Date()) {
    const dates = date.toUTCString().split(' ');
    const dates = date.toUTCString().split(' ');
    dates.pop(); // remove timezone
    dates.pop(); // remove timezone
    dates.push('+0000');
    dates.push('+0000');
    return dates.join(' ');
    return dates.join(' ');
}
}
/**
/**
 * RFC 2822 regex
 * RFC 2822 regex
 * @see https://tools.ietf.org/html/rfc2822#section-3.3
 * @see https://tools.ietf.org/html/rfc2822#section-3.3
 * @see https://github.com/moment/moment/blob/a831fc7e2694281ce31e4f090bbcf90a690f0277/src/lib/create/from-string.js#L101
 * @see https://github.com/moment/moment/blob/a831fc7e2694281ce31e4f090bbcf90a690f0277/src/lib/create/from-string.js#L101
 */
 */
const rfc2822re = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/.compile();
const rfc2822re = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
/**
/**
 * @param {string} [date] a string to check for conformance to the [rfc2822](https://tools.ietf.org/html/rfc2822#section-3.3) standard
 * @param {string} [date] a string to check for conformance to the [rfc2822](https://tools.ietf.org/html/rfc2822#section-3.3) standard
 * @returns {boolean} the result of the conformance check
 * @returns {boolean} the result of the conformance check
 */
 */
function isRFC2822Date(date) {
function isRFC2822Date(date) {
    return rfc2822re.test(date);
    return rfc2822re.test(date);
}
}
// adapted from https://github.com/emailjs/emailjs-mime-codec/blob/6909c706b9f09bc0e5c3faf48f723cca53e5b352/src/mimecodec.js
// adapted from https://github.com/emailjs/emailjs-mime-codec/blob/6909c706b9f09bc0e5c3faf48f723cca53e5b352/src/mimecodec.js
const encoder = new util.TextEncoder();
const encoder = new TextEncoder();
/**
/**
 * @see https://tools.ietf.org/html/rfc2045#section-6.7
 * @see https://tools.ietf.org/html/rfc2045#section-6.7
 */
 */
const RANGES = [
const RANGES = [
    [0x09],
    [0x09],
    [0x0a],
    [0x0a],
    [0x0d],
    [0x0d],
    [0x20, 0x3c],
    [0x20, 0x3c],
    [0x3e, 0x7e], // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
    [0x3e, 0x7e], // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
];
];
const LOOKUP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
const LOOKUP = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
const MAX_CHUNK_LENGTH = 16383; // must be multiple of 3
const MAX_CHUNK_LENGTH = 16383; // must be multiple of 3
const MAX_MIME_WORD_LENGTH = 52;
const MAX_MIME_WORD_LENGTH = 52;
const MAX_B64_MIME_WORD_BYTE_LENGTH = 39;
const MAX_B64_MIME_WORD_BYTE_LENGTH = 39;
function tripletToBase64(num) {
function tripletToBase64(num) {
    return (LOOKUP[(num >> 18) & 0x3f] +
    return (LOOKUP[(num >> 18) & 0x3f] +
        LOOKUP[(num >> 12) & 0x3f] +
        LOOKUP[(num >> 12) & 0x3f] +
        LOOKUP[(num >> 6) & 0x3f] +
        LOOKUP[(num >> 6) & 0x3f] +
        LOOKUP[num & 0x3f]);
        LOOKUP[num & 0x3f]);
}
}
function encodeChunk(uint8, start, end) {
function encodeChunk(uint8, start, end) {
    let output = '';
    let output = '';
    for (let i = start; i < end; i += 3) {
    for (let i = start; i < end; i += 3) {
        output += tripletToBase64((uint8[i] << 16) + (uint8[i + 1] << 8) + uint8[i + 2]);
        output += tripletToBase64((uint8[i] << 16) + (uint8[i + 1] << 8) + uint8[i + 2]);
    }
    }
    return output;
    return output;
}
}
function encodeBase64(data) {
function encodeBase64(data) {
    const len = data.length;
    const len = data.length;
    const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
    const extraBytes = len % 3; // if we have 1 byte left, pad 2 bytes
    let output = '';
    let output = '';
    // go through the array every three bytes, we'll deal with trailing stuff later
    // go through the array every three bytes, we'll deal with trailing stuff later
    for (let i = 0, len2 = len - extraBytes; i < len2; i += MAX_CHUNK_LENGTH) {
    for (let i = 0, len2 = len - extraBytes; i < len2; i += MAX_CHUNK_LENGTH) {
        output += encodeChunk(data, i, i + MAX_CHUNK_LENGTH > len2 ? len2 : i + MAX_CHUNK_LENGTH);
        output += encodeChunk(data, i, i + MAX_CHUNK_LENGTH > len2 ? len2 : i + MAX_CHUNK_LENGTH);
    }
    }
    // pad the end with zeros, but make sure to not forget the extra bytes
    // pad the end with zeros, but make sure to not forget the extra bytes
    if (extraBytes === 1) {
    if (extraBytes === 1) {
        const tmp = data[len - 1];
        const tmp = data[len - 1];
        output += LOOKUP[tmp >> 2];
        output += LOOKUP[tmp >> 2];
        output += LOOKUP[(tmp << 4) & 0x3f];
        output += LOOKUP[(tmp << 4) & 0x3f];
        output += '==';
        output += '==';
    }
    }
    else if (extraBytes === 2) {
    else if (extraBytes === 2) {
        const tmp = (data[len - 2] << 8) + data[len - 1];
        const tmp = (data[len - 2] << 8) + data[len - 1];
        output += LOOKUP[tmp >> 10];
        output += LOOKUP[tmp >> 10];
        output += LOOKUP[(tmp >> 4) & 0x3f];
        output += LOOKUP[(tmp >> 4) & 0x3f];
        output += LOOKUP[(tmp << 2) & 0x3f];
        output += LOOKUP[(tmp << 2) & 0x3f];
        output += '=';
        output += '=';
    }
    }
    return output;
    return output;
}
}
/**
/**
 * Splits a mime encoded string. Needed for dividing mime words into smaller chunks
 * Splits a mime encoded string. Needed for dividing mime words into smaller chunks
 *
 *
 * @param {string} str Mime encoded string to be split up
 * @param {string} str Mime encoded string to be split up
 * @param {number} maxlen Maximum length of characters for one part (minimum 12)
 * @param {number} maxlen Maximum length of characters for one part (minimum 12)
 * @return {string[]} lines
 * @return {string[]} lines
 */
 */
function splitMimeEncodedString(str, maxlen = 12) {
function splitMimeEncodedString(str, maxlen = 12) {
    const minWordLength = 12; // require at least 12 symbols to fit possible 4 octet UTF-8 sequences
    const minWordLength = 12; // require at least 12 symbols to fit possible 4 octet UTF-8 sequences
    const maxWordLength = Math.max(maxlen, minWordLength);
    const maxWordLength = Math.max(maxlen, minWordLength);
    const lines = [];
    const lines = [];
    while (str.length) {
    while (str.length) {
        let curLine = str.substr(0, maxWordLength);
        let curLine = str.substr(0, maxWordLength);
        const match = curLine.match(/=[0-9A-F]?$/i); // skip incomplete escaped char
        const match = curLine.match(/=[0-9A-F]?$/i); // skip incomplete escaped char
        if (match) {
        if (match) {
            curLine = curLine.substr(0, match.index);
            curLine = curLine.substr(0, match.index);
        }
        }
        let done = false;
        let done = false;
        while (!done) {
        while (!done) {
            let chr;
            let chr;
            done = true;
            done = true;
            const match = str.substr(curLine.length).match(/^=([0-9A-F]{2})/i); // check if not middle of a unicode char sequence
            const match = str.substr(curLine.length).match(/^=([0-9A-F]{2})/i); // check if not middle of a unicode char sequence
            if (match) {
            if (match) {
                chr = parseInt(match[1], 16);
                chr = parseInt(match[1], 16);
                // invalid sequence, move one char back anc recheck
                // invalid sequence, move one char back anc recheck
                if (chr < 0xc2 && chr > 0x7f) {
                if (chr < 0xc2 && chr > 0x7f) {
                    curLine = curLine.substr(0, curLine.length - 3);
                    curLine = curLine.substr(0, curLine.length - 3);
                    done = false;
                    done = false;
                }
                }
            }
            }
        }
        }
        if (curLine.length) {
        if (curLine.length) {
            lines.push(curLine);
            lines.push(curLine);
        }
        }
        str = str.substr(curLine.length);
        str = str.substr(curLine.length);
    }
    }
    return lines;
    return lines;
}
}
/**
/**
 *
 *
 * @param {number} nr number
 * @param {number} nr number
 * @returns {boolean} if number is in range
 * @returns {boolean} if number is in range
 */
 */
function checkRanges(nr) {
function checkRanges(nr) {
    return RANGES.reduce((val, range) => val ||
    return RANGES.reduce((val, range) => val ||
        (range.length === 1 && nr === range[0]) ||
        (range.length === 1 && nr === range[0]) ||
        (range.length === 2 && nr >= range[0] && nr <= range[1]), false);
        (range.length === 2 && nr >= range[0] && nr <= range[1]), false);
}
}
/**
/**
 * Encodes all non printable and non ascii bytes to =XX form, where XX is the
 * Encodes all non printable and non ascii bytes to =XX form, where XX is the
 * byte value in hex. This function does not convert linebreaks etc. it
 * byte value in hex. This function does not convert linebreaks etc. it
 * only escapes character sequences
 * only escapes character sequences
 *
 *
 * NOTE: Encoding support depends on util.TextDecoder, which is severely limited
 * NOTE: Encoding support depends on util.TextDecoder, which is severely limited
 * prior to Node.js 13.
 * prior to Node.js 13.
 *
 *
 * @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings
 * @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings
 * @see https://github.com/nodejs/node/issues/19214
 * @see https://github.com/nodejs/node/issues/19214
 *
 *
 * @param {string|Uint8Array} data Either a string or an Uint8Array
 * @param {string|Uint8Array} data Either a string or an Uint8Array
 * @param {string} encoding WHATWG supported encoding
 * @param {string} encoding WHATWG supported encoding
 * @return {string} Mime encoded string
 * @return {string} Mime encoded string
 */
 */
function mimeEncode(data = '', encoding = 'utf-8') {
function mimeEncode(data = '', encoding = 'utf-8') {
    const decoder = new util.TextDecoder(encoding);
    const decoder = new TextDecoder(encoding);
    const buffer = typeof data === 'string'
    const buffer = typeof data === 'string'
        ? encoder.encode(data)
        ? encoder.encode(data)
        : encoder.encode(decoder.decode(data));
        : encoder.encode(decoder.decode(data));
    return buffer.reduce((aggregate, ord, index) => checkRanges(ord) &&
    return buffer.reduce((aggregate, ord, index) => checkRanges(ord) &&
        !((ord === 0x20 || ord === 0x09) &&
        !((ord === 0x20 || ord === 0x09) &&
            (index === buffer.length - 1 ||
            (index === buffer.length - 1 ||
                buffer[index + 1] === 0x0a ||
                buffer[index + 1] === 0x0a ||
                buffer[index + 1] === 0x0d))
                buffer[index + 1] === 0x0d))
        ? // if the char is in allowed range, then keep as is, unless it is a ws in the end of a line
        ? // if the char is in allowed range, then keep as is, unless it is a ws in the end of a line
            aggregate + String.fromCharCode(ord)
            aggregate + String.fromCharCode(ord)
        : `${aggregate}=${ord < 0x10 ? '0' : ''}${ord
        : `${aggregate}=${ord < 0x10 ? '0' : ''}${ord
            .toString(16)
            .toString(16)
            .toUpperCase()}`, '');
            .toUpperCase()}`, '');
}
}
/**
/**
 * Encodes a string or an Uint8Array to an UTF-8 MIME Word
 * Encodes a string or an Uint8Array to an UTF-8 MIME Word
 *
 *
 * NOTE: Encoding support depends on util.TextDecoder, which is severely limited
 * NOTE: Encoding support depends on util.TextDecoder, which is severely limited
 * prior to Node.js 13.
 * prior to Node.js 13.
 *
 *
 * @see https://tools.ietf.org/html/rfc2047
 * @see https://tools.ietf.org/html/rfc2047
 * @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings
 * @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings
 * @see https://github.com/nodejs/node/issues/19214
 * @see https://github.com/nodejs/node/issues/19214
 *
 *
 * @param {string|Uint8Array} data String to be encoded
 * @param {string|Uint8Array} data String to be encoded
 * @param {'Q' | 'B'} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
 * @param {'Q' | 'B'} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
 * @param {string} encoding WHATWG supported encoding
 * @param {string} encoding WHATWG supported encoding
 * @return {string} Single or several mime words joined together
 * @return {string} Single or several mime words joined together
 */
 */
function mimeWordEncode(data, mimeWordEncoding = 'Q', encoding = 'utf-8') {
function mimeWordEncode(data, mimeWordEncoding = 'Q', encoding = 'utf-8') {
    let parts = [];
    let parts = [];
    const decoder = new util.TextDecoder(encoding);
    const decoder = new TextDecoder(encoding);
    const str = typeof data === 'string' ? data : decoder.decode(data);
    const str = typeof data === 'string' ? data : decoder.decode(data);
    if (mimeWordEncoding === 'Q') {
    if (mimeWordEncoding === 'Q') {
        const encodedStr = mimeEncode(str, encoding).replace(/[^a-z0-9!*+\-/=]/gi, (chr) => chr === ' '
        const encodedStr = mimeEncode(str, encoding).replace(/[^a-z0-9!*+\-/=]/gi, (chr) => chr === ' '
            ? '_'
            ? '_'
            : '=' +
            : '=' +
                (chr.charCodeAt(0) < 0x10 ? '0' : '') +
                (chr.charCodeAt(0) < 0x10 ? '0' : '') +
                chr.charCodeAt(0).toString(16).toUpperCase());
                chr.charCodeAt(0).toString(16).toUpperCase());
        parts =
        parts =
            encodedStr.length < MAX_MIME_WORD_LENGTH
            encodedStr.length < MAX_MIME_WORD_LENGTH
                ? [encodedStr]
                ? [encodedStr]
                : splitMimeEncodedString(encodedStr, MAX_MIME_WORD_LENGTH);
                : splitMimeEncodedString(encodedStr, MAX_MIME_WORD_LENGTH);
    }
    }
    else {
    else {
        // Fits as much as possible into every line without breaking utf-8 multibyte characters' octets up across lines
        // Fits as much as possible into every line without breaking utf-8 multibyte characters' octets up across lines
        let j = 0;
        let j = 0;
        let i = 0;
        let i = 0;
        while (i < str.length) {
        while (i < str.length) {
            if (encoder.encode(str.substring(j, i)).length >
            if (encoder.encode(str.substring(j, i)).length >
                MAX_B64_MIME_WORD_BYTE_LENGTH) {
                MAX_B64_MIME_WORD_BYTE_LENGTH) {
                // we went one character too far, substring at the char before
                // we went one character too far, substring at the char before
                parts.push(str.substring(j, i - 1));
                parts.push(str.substring(j, i - 1));
                j = i - 1;
                j = i - 1;
            }
            }
            else {
            else {
                i++;
                i++;
            }
            }
        }
        }
        // add the remainder of the string
        // add the remainder of the string
        str.substring(j) && parts.push(str.substring(j));
        str.substring(j) && parts.push(str.substring(j));
        parts = parts.map((x) => encoder.encode(x)).map((x) => encodeBase64(x));
        parts = parts.map((x) => encoder.encode(x)).map((x) => encodeBase64(x));
    }
    }
    return parts
    return parts
        .map((p) => `=?UTF-8?${mimeWordEncoding}?${p}?= `)
        .map((p) => `=?UTF-8?${mimeWordEncoding}?${p}?= `)
        .join('')
        .join('')
        .trim();
        .trim();
}
}
const CRLF$1 = '\r\n';
const CRLF$1 = '\r\n';
/**
/**
 * MIME standard wants 76 char chunks when sending out.
 * MIME standard wants 76 char chunks when sending out.
 */
 */
const MIMECHUNK = 76;
const MIMECHUNK = 76;
/**
/**
 * meets both base64 and mime divisibility
 * meets both base64 and mime divisibility
 */
 */
const MIME64CHUNK = (MIMECHUNK * 6);
const MIME64CHUNK = (MIMECHUNK * 6);
/**
/**
 * size of the message stream buffer
 * size of the message stream buffer
 */
 */
const BUFFERSIZE = (MIMECHUNK * 24 * 7);
const BUFFERSIZE = (MIMECHUNK * 24 * 7);
let counter = 0;
let counter = 0;
function generateBoundary() {
function generateBoundary() {
    let text = '';
    let text = '';
    const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+_,-./:=?";
    const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'()+_,-./:=?";
    for (let i = 0; i < 69; i++) {
    for (let i = 0; i < 69; i++) {
        text += possible.charAt(Math.floor(Math.random() * possible.length));
        text += possible.charAt(Math.floor(Math.random() * possible.length));
    }
    }
    return text;
    return text;
}
}
function convertPersonToAddress(person) {
function convertPersonToAddress(person) {
    return addressparser(person)
    return addressparser(person)
        .map(({ name, address }) => {
        .map(({ name, address }) => {
        return name
        return name
            ? `${mimeWordEncode(name).replace(/,/g, '=2C')} <${address}>`
            ? `${mimeWordEncode(name).replace(/,/g, '=2C')} <${address}>`
            : address;
            : address;
    })
    })
        .join(', ');
        .join(', ');
}
}
function convertDashDelimitedTextToSnakeCase(text) {
function convertDashDelimitedTextToSnakeCase(text) {
    return text
    return text
        .toLowerCase()
        .toLowerCase()
        .replace(/^(.)|-(.)/g, (match) => match.toUpperCase());
        .replace(/^(.)|-(.)/g, (match) => match.toUpperCase());
}
}
class Message {
class Message {
    /**
    /**
     * Construct an rfc2822-compliant message object.
     * Construct an rfc2822-compliant message object.
     *
     *
     * Special notes:
     * Special notes:
     * - The `from` field is required.
     * - The `from` field is required.
     * - At least one `to`, `cc`, or `bcc` header is also required.
     * - At least one `to`, `cc`, or `bcc` header is also required.
     * - You can also add whatever other headers you want.
     * - You can also add whatever other headers you want.
     *
     *
     * @see https://tools.ietf.org/html/rfc2822
     * @see https://tools.ietf.org/html/rfc2822
     * @param {Partial<MessageHeaders>} headers Message headers
     * @param {Partial<MessageHeaders>} headers Message headers
     */
     */
    constructor(headers) {
    constructor(headers) {
        this.attachments = [];
        this.attachments = [];
        this.header = {
        this.header = {
            'message-id': `<${new Date().getTime()}.${counter++}.${process.pid}@${os.hostname()}>`,
            'message-id': `<${new Date().getTime()}.${counter++}.${process.pid}@${hostname()}>`,
            date: getRFC2822Date(),
            date: getRFC2822Date(),
        };
        };
        this.content = 'text/plain; charset=utf-8';
        this.content = 'text/plain; charset=utf-8';
        this.alternative = null;
        this.alternative = null;
        for (const header in headers) {
        for (const header in headers) {
            // allow user to override default content-type to override charset or send a single non-text message
            // allow user to override default content-type to override charset or send a single non-text message
            if (/^content-type$/i.test(header)) {
            if (/^content-type$/i.test(header)) {
                this.content = headers[header];
                this.content = headers[header];
            }
            }
            else if (header === 'text') {
            else if (header === 'text') {
                this.text = headers[header];
                this.text = headers[header];
            }
            }
            else if (header === 'attachment' &&
            else if (header === 'attachment' &&
                typeof headers[header] === 'object') {
                typeof headers[header] === 'object') {
                const attachment = headers[header];
                const attachment = headers[header];
                if (Array.isArray(attachment)) {
                if (Array.isArray(attachment)) {
                    for (let i = 0; i < attachment.length; i++) {
                    for (let i = 0; i < attachment.length; i++) {
                        this.attach(attachment[i]);
                        this.attach(attachment[i]);
                    }
                    }
                }
                }
                else if (attachment != null) {
                else if (attachment != null) {
                    this.attach(attachment);
                    this.attach(attachment);
                }
                }
            }
            }
            else if (header === 'subject') {
            else if (header === 'subject') {
                this.header.subject = mimeWordEncode(headers.subject);
                this.header.subject = mimeWordEncode(headers.subject);
            }
            }
            else if (/^(cc|bcc|to|from)/i.test(header)) {
            else if (/^(cc|bcc|to|from)/i.test(header)) {
                this.header[header.toLowerCase()] = convertPersonToAddress(headers[header]);
                this.header[header.toLowerCase()] = convertPersonToAddress(headers[header]);
            }
            }
            else {
            else {
                // allow any headers the user wants to set??
                // allow any headers the user wants to set??
                this.header[header.toLowerCase()] = headers[header];
                this.header[header.toLowerCase()] = headers[header];
            }
            }
        }
        }
    }
    }
    /**
    /**
     * Attach a file to the message.
     * Attach a file to the message.
     *
     *
     * Can be called multiple times, each adding a new attachment.
     * Can be called multiple times, each adding a new attachment.
     *
     *
     * @public
     * @public
     * @param {MessageAttachment} options attachment options
     * @param {MessageAttachment} options attachment options
     * @returns {Message} the current instance for chaining
     * @returns {Message} the current instance for chaining
     */
     */
    attach(options) {
    attach(options) {
        // sender can specify an attachment as an alternative
        // sender can specify an attachment as an alternative
        if (options.alternative) {
        if (options.alternative) {
            this.alternative = options;
            this.alternative = options;
            this.alternative.charset = options.charset || 'utf-8';
            this.alternative.charset = options.charset || 'utf-8';
            this.alternative.type = options.type || 'text/html';
            this.alternative.type = options.type || 'text/html';
            this.alternative.inline = true;
            this.alternative.inline = true;
        }
        }
        else {
        else {
            this.attachments.push(options);
            this.attachments.push(options);
        }
        }
        return this;
        return this;
    }
    }
    /**
    /**
     * @public
     * @public
     * @returns {{ isValid: boolean, validationError: (string | undefined) }} an object specifying whether this message is validly formatted, and the first validation error if it is not.
     * @returns {{ isValid: boolean, validationError: (string | undefined) }} an object specifying whether this message is validly formatted, and the first validation error if it is not.
     */
     */
    checkValidity() {
    checkValidity() {
        if (typeof this.header.from !== 'string' &&
        if (typeof this.header.from !== 'string' &&
            Array.isArray(this.header.from) === false) {
            Array.isArray(this.header.from) === false) {
            return {
            return {
                isValid: false,
                isValid: false,
                validationError: 'Message must have a `from` header',
                validationError: 'Message must have a `from` header',
            };
            };
        }
        }
        if (typeof this.header.to !== 'string' &&
        if (typeof this.header.to !== 'string' &&
            Array.isArray(this.header.to) === false &&
            Array.isArray(this.header.to) === false &&
            typeof this.header.cc !== 'string' &&
            typeof this.header.cc !== 'string' &&
            Array.isArray(this.header.cc) === false &&
            Array.isArray(this.header.cc) === false &&
            typeof this.header.bcc !== 'string' &&
            typeof this.header.bcc !== 'string' &&
            Array.isArray(this.header.bcc) === false) {
            Array.isArray(this.header.bcc) === false) {
            return {
            return {
                isValid: false,
                isValid: false,
                validationError: 'Message must have at least one `to`, `cc`, or `bcc` header',
                validationError: 'Message must have at least one `to`, `cc`, or `bcc` header',
            };
            };
        }
        }
        if (this.attachments.length > 0) {
        if (this.attachments.length > 0) {
            const failed = [];
            const failed = [];
            this.attachments.forEach((attachment) => {
            this.attachments.forEach((attachment) => {
                if (attachment.path) {
                if (attachment.path) {
                    if (fs.existsSync(attachment.path) === false) {
                    if (existsSync(attachment.path) === false) {
                        failed.push(`${attachment.path} does not exist`);
                        failed.push(`${attachment.path} does not exist`);
                    }
                    }
                }
                }
                else if (attachment.stream) {
                else if (attachment.stream) {
                    if (!attachment.stream.readable) {
                    if (!attachment.stream.readable) {
                        failed.push('attachment stream is not readable');
                        failed.push('attachment stream is not readable');
                    }
                    }
                }
                }
                else if (!attachment.data) {
                else if (!attachment.data) {
                    failed.push('attachment has no data associated with it');
                    failed.push('attachment has no data associated with it');
                }
                }
            });
            });
            return {
            return {
                isValid: failed.length === 0,
                isValid: failed.length === 0,
                validationError: failed.join(', '),
                validationError: failed.join(', '),
            };
            };
        }
        }
        return { isValid: true, validationError: undefined };
        return { isValid: true, validationError: undefined };
    }
    }
    /**
    /**
     * @public
     * @public
     * @deprecated does not conform to the `errback` style followed by the rest of the library, and will be removed in the next major version. use `checkValidity` instead.
     * @deprecated does not conform to the `errback` style followed by the rest of the library, and will be removed in the next major version. use `checkValidity` instead.
     * @param {function(isValid: boolean, invalidReason: (string | undefined)): void} callback .
     * @param {function(isValid: boolean, invalidReason: (string | undefined)): void} callback .
     * @returns {void}
     * @returns {void}
     */
     */
    valid(callback) {
    valid(callback) {
        const { isValid, validationError } = this.checkValidity();
        const { isValid, validationError } = this.checkValidity();
        callback(isValid, validationError);
        callback(isValid, validationError);
    }
    }
    /**
    /**
     * @public
     * @public
     * @returns {MessageStream} a stream of the current message
     * @returns {MessageStream} a stream of the current message
     */
     */
    stream() {
    stream() {
        return new MessageStream(this);
        return new MessageStream(this);
    }
    }
    /**
    /**
     * @public
     * @public
     * @param {function(Error, string): void} callback the function to call with the error and buffer
     * @param {function(Error, string): void} callback the function to call with the error and buffer
     * @returns {void}
     * @returns {void}
     */
     */
    read(callback) {
    read(callback) {
        let buffer = '';
        let buffer = '';
        const str = this.stream();
        const str = this.stream();
        str.on('data', (data) => (buffer += data));
        str.on('data', (data) => (buffer += data));
        str.on('end', (err) => callback(err, buffer));
        str.on('end', (err) => callback(err, buffer));
        str.on('error', (err) => callback(err, buffer));
        str.on('error', (err) => callback(err, buffer));
    }
    }
    readAsync() {
    readAsync() {
        return new Promise((resolve, reject) => {
        return new Promise((resolve, reject) => {
            this.read((err, buffer) => {
            this.read((err, buffer) => {
                if (err != null) {
                if (err != null) {
                    reject(err);
                    reject(err);
                }
                }
                else {
                else {
                    resolve(buffer);
                    resolve(buffer);
                }
                }
            });
            });
        });
        });
    }
    }
}
}
class MessageStream extends stream.Stream {
class MessageStream extends Stream {
    /**
    /**
     * @param {Message} message the message to stream
     * @param {Message} message the message to stream
     */
     */
    constructor(message) {
    constructor(message) {
        super();
        super();
        this.message = message;
        this.message = message;
        this.readable = true;
        this.readable = true;
        this.paused = false;
        this.paused = false;
        this.buffer = Buffer.alloc(MIMECHUNK * 24 * 7);
        this.buffer = Buffer.alloc(MIMECHUNK * 24 * 7);
        this.bufferIndex = 0;
        this.bufferIndex = 0;
        /**
        /**
         * @param {string} [data] the data to output
         * @param {string} [data] the data to output
         * @param {Function} [callback] the function
         * @param {Function} [callback] the function
         * @param {any[]} [args] array of arguments to pass to the callback
         * @param {any[]} [args] array of arguments to pass to the callback
         * @returns {void}
         * @returns {void}
         */
         */
        const output = (data) => {
        const output = (data) => {
            // can we buffer the data?
            // can we buffer the data?
            if (this.buffer != null) {
            if (this.buffer != null) {
                const bytes = Buffer.byteLength(data);
                const bytes = Buffer.byteLength(data);
                if (bytes + this.bufferIndex < this.buffer.length) {
                if (bytes + this.bufferIndex < this.buffer.length) {
                    this.buffer.write(data, this.bufferIndex);
                    this.buffer.write(data, this.bufferIndex);
                    this.bufferIndex += bytes;
                    this.bufferIndex += bytes;
                }
                }
                // we can't buffer the data, so ship it out!
                // we can't buffer the data, so ship it out!
                else if (bytes > this.buffer.length) {
                else if (bytes > this.buffer.length) {
                    if (this.bufferIndex) {
                    if (this.bufferIndex) {
                        this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
                        this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
                        this.bufferIndex = 0;
                        this.bufferIndex = 0;
                    }
                    }
                    const loops = Math.ceil(data.length / this.buffer.length);
                    const loops = Math.ceil(data.length / this.buffer.length);
                    let loop = 0;
                    let loop = 0;
                    while (loop < loops) {
                    while (loop < loops) {
                        this.emit('data', data.substring(this.buffer.length * loop, this.buffer.length * (loop + 1)));
                        this.emit('data', data.substring(this.buffer.length * loop, this.buffer.length * (loop + 1)));
                        loop++;
                        loop++;
                    }
                    }
                } // we need to clean out the buffer, it is getting full
                } // we need to clean out the buffer, it is getting full
                else {
                else {
                    if (!this.paused) {
                    if (!this.paused) {
                        this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
                        this.emit('data', this.buffer.toString('utf-8', 0, this.bufferIndex));
                        this.buffer.write(data, 0);
                        this.buffer.write(data, 0);
                        this.bufferIndex = bytes;
                        this.bufferIndex = bytes;
                    }
                    }
                    el