mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-10-09 09:59:45 +07:00
node_modules: update (#340)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
1 parent
791f27c1e4
commit
0635835dfc
50 files changed
+813
-244
No files matched your search
+3
-3
@@ -94,9 +94,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/nodemailer": {
|
"node_modules/nodemailer": {
|
||||||
"version": "10.0.13",
|
"version": "10.0.14",
|
||||||
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.13.tgz",
|
"resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-10.0.14.tgz",
|
||||||
"integrity": "sha512-SzG86OlvcW/NNhUFC6uROMwRTL4n7MswfQqC/T8mhkmnY1YVa23zUEMYi4ijSeXSl9GLz9ZeTJDUatEDuY5FeQ==",
|
"integrity": "sha512-eJoLFKg55fesSlzUekmfOv/SBXh4KUYT8oiQc2RLP0tfG0rbNMJBK7wgHJwXTJwqyKlSJMz9UzDZ4v+aHEvo8g==",
|
||||||
"license": "MIT-0",
|
"license": "MIT-0",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=20.0.0"
|
||||||
|
|||||||
+12
@@ -1,5 +1,17 @@
|
|||||||
# CHANGELOG
|
# CHANGELOG
|
||||||
|
|
||||||
|
## [10.0.14](https://github.com/nodemailer/nodemailer/compare/v10.0.13...v10.0.14) (2026-10-03)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
|
||||||
|
* **addressparser:** keep a quoted display name that holds no "@" out of the address ([3570d26](https://github.com/nodemailer/nodemailer/commit/3570d26c7b55f6e5df50d4076046600ad3fd040b))
|
||||||
|
* **addressparser:** keep the group recursion depth out of the options object ([a680254](https://github.com/nodemailer/nodemailer/commit/a68025405a1378c82e78f64ffad27b92c1035cbe))
|
||||||
|
* **addressparser:** stop a "[" from hiding the operators after it ([5619784](https://github.com/nodemailer/nodemailer/commit/561978491d5cc33d2052a6c89a499b838b960262))
|
||||||
|
* **dkim:** trim a header field name in linear time ([c6f7a55](https://github.com/nodemailer/nodemailer/commit/c6f7a55a2978bf031b8519ebf59baddf94c032c8))
|
||||||
|
* **mime-funcs:** read and write header parameters per rfc2045 and rfc2231 ([b8ccad7](https://github.com/nodemailer/nodemailer/commit/b8ccad723a7e49a674a4d70f32eb9fe77312008d))
|
||||||
|
* search only the new bytes for the end of the proxy CONNECT response ([81efd7b](https://github.com/nodemailer/nodemailer/commit/81efd7bfc559305f900d6a22b5c56aa94556002b))
|
||||||
|
|
||||||
## [10.0.13](https://github.com/nodemailer/nodemailer/compare/v10.0.12...v10.0.13) (2026-09-30)
|
## [10.0.13](https://github.com/nodemailer/nodemailer/compare/v10.0.12...v10.0.13) (2026-09-30)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
-1
@@ -39,7 +39,6 @@ export type Address = MailboxAddress | GroupAddress;
|
|||||||
*
|
*
|
||||||
* @param str Address field
|
* @param str Address field
|
||||||
* @param options Optional options object
|
* @param options Optional options object
|
||||||
* @param options._depth Internal recursion depth counter (do not set manually)
|
|
||||||
* @return An array of address objects
|
* @return An array of address objects
|
||||||
*/
|
*/
|
||||||
declare function addressparser(str: string | null | undefined, options: AddressParserOptions & {
|
declare function addressparser(str: string | null | undefined, options: AddressParserOptions & {
|
||||||
|
|||||||
+55
-17
@@ -428,7 +428,7 @@ function _handleAddress(tokens, depth) {
|
|||||||
// Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
|
// Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
|
||||||
let groupMembers = [];
|
let groupMembers = [];
|
||||||
if (data.group.length) {
|
if (data.group.length) {
|
||||||
const parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
|
const parsedGroup = _parseAddressList(data.group.join(','), depth + 1);
|
||||||
parsedGroup.forEach(member => {
|
parsedGroup.forEach(member => {
|
||||||
if (member.group) {
|
if (member.group) {
|
||||||
groupMembers = groupMembers.concat(member.group);
|
groupMembers = groupMembers.concat(member.group);
|
||||||
@@ -515,7 +515,13 @@ function _handleAddress(tokens, depth) {
|
|||||||
// Join values with spaces
|
// Join values with spaces
|
||||||
data.text = data.text.join(' ');
|
data.text = data.text.join(' ');
|
||||||
data.address = data.address.join(' ');
|
data.address = data.address.join(' ');
|
||||||
if (addressFromQuotedText && data.text) {
|
if (addressFromQuotedText && data.text.indexOf('@') >= 0) {
|
||||||
|
// A quoted run with no '@' in it is a display name and nothing else, so it stays
|
||||||
|
// where it is. Moving it over anyway made a comment the mailbox was read from:
|
||||||
|
// '"Display Name" <(a comment)>' handed on 'Display Name' as the address with the
|
||||||
|
// comment as the name, and a quoted name holding a ',' or a ';' became an address
|
||||||
|
// that reads as two recipients once a consumer writes it back into a header.
|
||||||
|
//
|
||||||
// The mailbox is still sitting in the text, so it moves over here and is quoted
|
// The mailbox is still sitting in the text, so it moves over here and is quoted
|
||||||
// before the recovery below rather than after it. Anything else the text holds
|
// before the recovery below rather than after it. Anything else the text holds
|
||||||
// came along with it: a comment ends the domain but leaves the atoms behind it in
|
// came along with it: a comment ends the domain but leaves the atoms behind it in
|
||||||
@@ -562,6 +568,7 @@ class Tokenizer {
|
|||||||
this.node = null;
|
this.node = null;
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
this.inDomainLiteral = false;
|
this.inDomainLiteral = false;
|
||||||
|
this.afterAt = false;
|
||||||
this.list = [];
|
this.list = [];
|
||||||
/**
|
/**
|
||||||
* Operator tokens and which tokens are expected to end the sequence
|
* Operator tokens and which tokens are expected to end the sequence
|
||||||
@@ -605,17 +612,23 @@ class Tokenizer {
|
|||||||
* Checks if a character is an operator or text and acts accordingly
|
* Checks if a character is an operator or text and acts accordingly
|
||||||
*
|
*
|
||||||
* @param chr Character from the address field
|
* @param chr Character from the address field
|
||||||
|
* @param nextChr Character following chr, null at the end of the field
|
||||||
*/
|
*/
|
||||||
checkChar(chr, nextChr) {
|
checkChar(chr, nextChr) {
|
||||||
// Track RFC 5322 domain-literals ("[" *dtext "]"). Operator characters such
|
// Track RFC 5322 domain-literals ("[" *dtext "]"). The ":" of an IPv6
|
||||||
// as the ":" of an IPv6 address-literal (user@[IPv6:2001:db8::1]) are dtext
|
// address-literal (user@[IPv6:2001:db8::1]) is dtext and must not be treated as
|
||||||
// and must not be treated as the group delimiter while inside the brackets.
|
// the group delimiter while inside the brackets. That is the only operator the
|
||||||
// Quoted strings and comments are handled separately via operatorExpecting,
|
// literal hides: letting it hide the others as well meant a "[" could turn the
|
||||||
// so only enter this state when no operator is open. The list separators ","
|
// comment, quoted string or angle-addr after it into text and pick a different
|
||||||
// and ";" are the exception: they always end the literal (and split the
|
// mailbox, '[ ( ] <a@victim.com> ) <b@evil.com>' yielded a@victim.com where
|
||||||
// address list) so that an unclosed "[" cannot swallow later recipients.
|
// RFC 5322 reads b@evil.com. A "[" also only opens a literal right after the "@"
|
||||||
|
// of an addr-spec, so the group delimiter of a display name holding one stays
|
||||||
|
// intact. Quoted strings and comments are handled separately via
|
||||||
|
// operatorExpecting, so only enter this state when no operator is open. The list
|
||||||
|
// separators "," and ";" always end the literal (and split the address list) so
|
||||||
|
// that an unclosed "[" cannot swallow later recipients.
|
||||||
if (!this.escaped && !this.operatorExpecting) {
|
if (!this.escaped && !this.operatorExpecting) {
|
||||||
if (!this.inDomainLiteral && chr === '[') {
|
if (!this.inDomainLiteral && chr === '[' && this.afterAt) {
|
||||||
this.inDomainLiteral = true;
|
this.inDomainLiteral = true;
|
||||||
}
|
}
|
||||||
else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
|
else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
|
||||||
@@ -635,17 +648,24 @@ class Tokenizer {
|
|||||||
}
|
}
|
||||||
this.list.push(this.node);
|
this.list.push(this.node);
|
||||||
this.node = null;
|
this.node = null;
|
||||||
|
if (chr !== ')') {
|
||||||
|
// a comment is folding whitespace, it does not part the "@" from the "["
|
||||||
|
this.afterAt = false;
|
||||||
|
}
|
||||||
this.operatorExpecting = '';
|
this.operatorExpecting = '';
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!this.operatorExpecting && !this.inDomainLiteral && chr in this.operators) {
|
else if (!this.operatorExpecting && !(this.inDomainLiteral && chr === ':') && chr in this.operators) {
|
||||||
this.node = {
|
this.node = {
|
||||||
type: 'operator',
|
type: 'operator',
|
||||||
value: chr
|
value: chr
|
||||||
};
|
};
|
||||||
this.list.push(this.node);
|
this.list.push(this.node);
|
||||||
this.node = null;
|
this.node = null;
|
||||||
|
if (chr !== '(') {
|
||||||
|
this.afterAt = false;
|
||||||
|
}
|
||||||
this.operatorExpecting = this.operators[chr];
|
this.operatorExpecting = this.operators[chr];
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
return;
|
return;
|
||||||
@@ -669,6 +689,11 @@ class Tokenizer {
|
|||||||
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
|
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
|
||||||
// skip command bytes
|
// skip command bytes
|
||||||
this.node.value += chr;
|
this.node.value += chr;
|
||||||
|
// text inside a quoted string or a comment is not part of an addr-spec. Tracked
|
||||||
|
// as it goes rather than read back off the value (see lastChars in _handleAddress)
|
||||||
|
if (!this.operatorExpecting && chr !== ' ' && chr !== '\t') {
|
||||||
|
this.afterAt = chr === '@';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
}
|
}
|
||||||
@@ -679,9 +704,19 @@ class Tokenizer {
|
|||||||
* malicious input that could cause stack overflow.
|
* malicious input that could cause stack overflow.
|
||||||
*/
|
*/
|
||||||
const MAX_NESTED_GROUP_DEPTH = 50;
|
const MAX_NESTED_GROUP_DEPTH = 50;
|
||||||
function addressparser(str, options) {
|
/**
|
||||||
options = options || {};
|
* Parses an address list, recursing into the groups it holds.
|
||||||
const depth = options._depth || 0;
|
*
|
||||||
|
* The depth is threaded through the calls rather than carried in an options object, so a
|
||||||
|
* caller supplied one can not seed it and lift the recursion limit: `{ _depth: -1e9 }`
|
||||||
|
* bought a billion levels of nesting and turned the guard below into the stack overflow it
|
||||||
|
* is there to prevent.
|
||||||
|
*
|
||||||
|
* @param str Address field
|
||||||
|
* @param depth Current recursion depth for nested group protection
|
||||||
|
* @return An array of address objects
|
||||||
|
*/
|
||||||
|
function _parseAddressList(str, depth) {
|
||||||
// Prevent stack overflow from deeply nested groups (DoS protection)
|
// Prevent stack overflow from deeply nested groups (DoS protection)
|
||||||
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
||||||
return [];
|
return [];
|
||||||
@@ -690,7 +725,7 @@ function addressparser(str, options) {
|
|||||||
const tokens = tokenizer.tokenize();
|
const tokens = tokenizer.tokenize();
|
||||||
const addresses = [];
|
const addresses = [];
|
||||||
let address = [];
|
let address = [];
|
||||||
let parsedAddresses = [];
|
const parsedAddresses = [];
|
||||||
tokens.forEach(token => {
|
tokens.forEach(token => {
|
||||||
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
||||||
if (address.length) {
|
if (address.length) {
|
||||||
@@ -731,8 +766,11 @@ function addressparser(str, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mergedAddresses.reverse();
|
mergedAddresses.reverse();
|
||||||
parsedAddresses = mergedAddresses;
|
return mergedAddresses;
|
||||||
if (options.flatten) {
|
}
|
||||||
|
function addressparser(str, options) {
|
||||||
|
const parsedAddresses = _parseAddressList(str, 0);
|
||||||
|
if (options?.flatten) {
|
||||||
const flatAddresses = [];
|
const flatAddresses = [];
|
||||||
const walkAddressList = (list) => {
|
const walkAddressList = (list) => {
|
||||||
list.forEach(entry => {
|
list.forEach(entry => {
|
||||||
|
|||||||
+28
-4
@@ -1,6 +1,33 @@
|
|||||||
"use strict";
|
"use strict";
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
const node_stream_1 = require("node:stream");
|
const node_stream_1 = require("node:stream");
|
||||||
|
/**
|
||||||
|
* Drops the SP and HTAB from both ends of a field name.
|
||||||
|
*
|
||||||
|
* An index scan rather than `/^[ \t]+|[ \t]+$/g`, which retries the trailing branch at
|
||||||
|
* every offset of a blank run that is followed by other text: the run is consumed greedily,
|
||||||
|
* `$` fails, and the engine gives the characters back one at a time before moving on to
|
||||||
|
* start the same walk one offset further in. A field name carrying a 128 KiB run of spaces
|
||||||
|
* between two atoms took about 6.7 seconds to trim, and the cost grows with its square.
|
||||||
|
*
|
||||||
|
* Not `.trim()`, which takes the other Unicode spaces along: an NBSP in front of a field
|
||||||
|
* name would come off and select the name into the signed set, where the relaxed
|
||||||
|
* canonicalization in sign.ts deliberately leaves everything but SP and HTAB alone.
|
||||||
|
*
|
||||||
|
* @param str Field name to trim
|
||||||
|
* @return The field name without the surrounding SP and HTAB
|
||||||
|
*/
|
||||||
|
function _trimFieldName(str) {
|
||||||
|
let start = 0;
|
||||||
|
let end = str.length;
|
||||||
|
while (start < end && (str.charCodeAt(start) === 0x20 || str.charCodeAt(start) === 0x09)) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end > start && (str.charCodeAt(end - 1) === 0x20 || str.charCodeAt(end - 1) === 0x09)) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return str.slice(start, end);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* MessageParser instance is a transform stream that separates message headers
|
* MessageParser instance is a transform stream that separates message headers
|
||||||
* from the rest of the body. Headers are emitted with the 'headers' event. Message
|
* from the rest of the body. Headers are emitted with the 'headers' event. Message
|
||||||
@@ -143,10 +170,7 @@ class MessageParser extends node_stream_1.Transform {
|
|||||||
return lines
|
return lines
|
||||||
.filter(line => /[^ \t\r]/.test(line))
|
.filter(line => /[^ \t\r]/.test(line))
|
||||||
.map(line => ({
|
.map(line => ({
|
||||||
key: line
|
key: _trimFieldName(line.substr(0, line.indexOf(':'))).toLowerCase(),
|
||||||
.substr(0, line.indexOf(':'))
|
|
||||||
.replace(/^[ \t]+|[ \t]+$/g, '')
|
|
||||||
.toLowerCase(),
|
|
||||||
line
|
line
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -36,6 +36,7 @@ export interface DKIMRelaxedHeaders {
|
|||||||
* Returns DKIM signature header line
|
* Returns DKIM signature header line
|
||||||
*
|
*
|
||||||
* @param headers Parsed headers object from MessageParser
|
* @param headers Parsed headers object from MessageParser
|
||||||
|
* @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
|
||||||
* @param bodyHash Base64 encoded hash of the message
|
* @param bodyHash Base64 encoded hash of the message
|
||||||
* @param options DKIM options
|
* @param options DKIM options
|
||||||
* @param options.domainName Domain name to be signed for
|
* @param options.domainName Domain name to be signed for
|
||||||
|
|||||||
+1
@@ -52,6 +52,7 @@ function unsupportedHashAlgoError(hashAlgo) {
|
|||||||
* Returns DKIM signature header line
|
* Returns DKIM signature header line
|
||||||
*
|
*
|
||||||
* @param headers Parsed headers object from MessageParser
|
* @param headers Parsed headers object from MessageParser
|
||||||
|
* @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
|
||||||
* @param bodyHash Base64 encoded hash of the message
|
* @param bodyHash Base64 encoded hash of the message
|
||||||
* @param options DKIM options
|
* @param options DKIM options
|
||||||
* @param options.domainName Domain name to be signed for
|
* @param options.domainName Domain name to be signed for
|
||||||
|
|||||||
+7
-1
@@ -193,9 +193,15 @@ declare class Mail<out T = SentMessageInfo, out D extends TransportOptions = Tra
|
|||||||
* Sends an email using the preselected transport object
|
* Sends an email using the preselected transport object
|
||||||
*
|
*
|
||||||
* @param data E-data description
|
* @param data E-data description
|
||||||
* @param callback Callback to run once the sending succeeded or failed
|
* @returns Promise that resolves with the result of the send
|
||||||
*/
|
*/
|
||||||
sendMail(data: SendMailOptions): Promise<T>;
|
sendMail(data: SendMailOptions): Promise<T>;
|
||||||
|
/**
|
||||||
|
* Sends an email using the preselected transport object
|
||||||
|
*
|
||||||
|
* @param data E-data description
|
||||||
|
* @param callback Callback to run once the sending succeeded or failed
|
||||||
|
*/
|
||||||
sendMail(data: SendMailOptions, callback: SendMailCallback<T>): void;
|
sendMail(data: SendMailOptions, callback: SendMailCallback<T>): void;
|
||||||
getVersionString(): string;
|
getVersionString(): string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
-1
@@ -59,6 +59,7 @@ export declare function quoteString(value?: string): string;
|
|||||||
* no need to encode the values in any way. If the value is plaintext but has
|
* no need to encode the values in any way. If the value is plaintext but has
|
||||||
* longer lines then allowed, then use format=flowed
|
* longer lines then allowed, then use format=flowed
|
||||||
*
|
*
|
||||||
|
* @param str Multi line string to check
|
||||||
* @param lineLength Max line length to check for
|
* @param lineLength Max line length to check for
|
||||||
* @returns Returns true if there is at least one line longer than lineLength chars
|
* @returns Returns true if there is at least one line longer than lineLength chars
|
||||||
*/
|
*/
|
||||||
@@ -100,9 +101,9 @@ export declare function buildHeaderValue(structured: StructuredHeaderValue): str
|
|||||||
* title*0*=utf-8''unicode
|
* title*0*=utf-8''unicode
|
||||||
* title*1*=%20string
|
* title*1*=%20string
|
||||||
*
|
*
|
||||||
|
* @param key Parameter name the generated keys are built from, for example title
|
||||||
* @param data String to be encoded
|
* @param data String to be encoded
|
||||||
* @param [maxLength=50] Max length for generated chunks
|
* @param [maxLength=50] Max length for generated chunks
|
||||||
* @param [fromCharset='UTF-8'] Source sharacter set
|
|
||||||
* @return A list of encoded keys and headers
|
* @return A list of encoded keys and headers
|
||||||
*/
|
*/
|
||||||
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
||||||
|
|||||||
+187
-73
@@ -88,6 +88,7 @@ function quoteString(value) {
|
|||||||
* no need to encode the values in any way. If the value is plaintext but has
|
* no need to encode the values in any way. If the value is plaintext but has
|
||||||
* longer lines then allowed, then use format=flowed
|
* longer lines then allowed, then use format=flowed
|
||||||
*
|
*
|
||||||
|
* @param str Multi line string to check
|
||||||
* @param lineLength Max line length to check for
|
* @param lineLength Max line length to check for
|
||||||
* @returns Returns true if there is at least one line longer than lineLength chars
|
* @returns Returns true if there is at least one line longer than lineLength chars
|
||||||
*/
|
*/
|
||||||
@@ -234,7 +235,10 @@ function buildHeaderValue(structured) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (/[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
|
else if (!value.length || /[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
|
||||||
|
// a parameter value is a token or a quoted-string and a token is never empty, so
|
||||||
|
// a valueless parameter such as the 'flag' of 'multipart/mixed; flag; boundary=b'
|
||||||
|
// goes out as 'flag=""' rather than as the 'flag=' that parses as neither
|
||||||
paramsArray.push(param + '=' + JSON.stringify(value));
|
paramsArray.push(param + '=' + JSON.stringify(value));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -256,9 +260,9 @@ function buildHeaderValue(structured) {
|
|||||||
* title*0*=utf-8''unicode
|
* title*0*=utf-8''unicode
|
||||||
* title*1*=%20string
|
* title*1*=%20string
|
||||||
*
|
*
|
||||||
|
* @param key Parameter name the generated keys are built from, for example title
|
||||||
* @param data String to be encoded
|
* @param data String to be encoded
|
||||||
* @param [maxLength=50] Max length for generated chunks
|
* @param [maxLength=50] Max length for generated chunks
|
||||||
* @param [fromCharset='UTF-8'] Source sharacter set
|
|
||||||
* @return A list of encoded keys and headers
|
* @return A list of encoded keys and headers
|
||||||
*/
|
*/
|
||||||
function buildHeaderParam(key, data, maxLength) {
|
function buildHeaderParam(key, data, maxLength) {
|
||||||
@@ -385,6 +389,26 @@ function buildHeaderParam(key, data, maxLength) {
|
|||||||
value: item.line
|
value: item.line
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* An RFC 2045 token: printable ASCII without SPACE, the control characters, DEL and the
|
||||||
|
* tspecials. A charset name is one, and the name a continuation carries is written into
|
||||||
|
* the encoded word the parameter value becomes, so it is checked against this before it
|
||||||
|
* goes in. Whitespace used to come off it only because the value was trimmed first.
|
||||||
|
*/
|
||||||
|
const TOKEN = /^[^\x00-\x20\x7f()<>@,;:\\"/[\]?=]+$/;
|
||||||
|
/**
|
||||||
|
* Whether a string can be a header parameter name.
|
||||||
|
*
|
||||||
|
* A parameter name is a token, so it is never empty. A "__proto__" name would target the
|
||||||
|
* prototype chain of the params object instead of an own property of it and read back as
|
||||||
|
* Object.prototype, so it is no name either.
|
||||||
|
*
|
||||||
|
* @param name Candidate parameter name, already lowercased
|
||||||
|
* @return true when the name can be used
|
||||||
|
*/
|
||||||
|
function _isParamName(name) {
|
||||||
|
return !!name && !(0, objects_js_1.isProtoKey)(name);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Parses a header value with key=value arguments into a structured
|
* Parses a header value with key=value arguments into a structured
|
||||||
* object.
|
* object.
|
||||||
@@ -405,117 +429,215 @@ function parseHeaderValue(str) {
|
|||||||
value: '',
|
value: '',
|
||||||
params: {}
|
params: {}
|
||||||
};
|
};
|
||||||
// Parameter names come from a caller supplied contentType/contentDisposition. A
|
// A duplicated parameter resolves to its first occurrence, the way a duplicated header
|
||||||
// "__proto__" name would target the prototype chain of the params object instead of
|
// does. Letting the last one win disagrees with the receivers that take the first, and
|
||||||
// an own property of it, and read back as Object.prototype, so it is dropped.
|
// the two readings of 'boundary="b"; boundary="c"' name different delimiters. The
|
||||||
|
// continuation join below is the only writer of the name it builds, so it tests the
|
||||||
|
// name with _isParamName directly rather than taking that rule along from here.
|
||||||
const setParam = (name, value) => {
|
const setParam = (name, value) => {
|
||||||
if (!(0, objects_js_1.isProtoKey)(name)) {
|
name = name.toLowerCase();
|
||||||
|
if (_isParamName(name) && !Object.prototype.hasOwnProperty.call(response.params, name)) {
|
||||||
response.params[name] = value;
|
response.params[name] = value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let key = false;
|
let key = false;
|
||||||
let value = '';
|
let value = '';
|
||||||
let type = 'value';
|
let stage = 'value';
|
||||||
let quote = false;
|
let quote = false;
|
||||||
let escaped = false;
|
let escaped = false;
|
||||||
let chr;
|
let chr;
|
||||||
|
// Whitespace seen outside a quoted string is held back until a significant character
|
||||||
|
// follows it, so the whitespace around a value is dropped without trimming spaces the
|
||||||
|
// sender quoted on purpose. Trimming the stored value instead loses the trailing space
|
||||||
|
// of 'filename*0="Annual Report "', which the next continuation section is appended to.
|
||||||
|
let pendingSpace = '';
|
||||||
|
let quoteClosed = false;
|
||||||
|
// Whitespace ahead of the first character of a value is padding and is dropped, the
|
||||||
|
// whitespace between two characters of it is content
|
||||||
|
const flushSpace = () => {
|
||||||
|
if (value.length) {
|
||||||
|
value += pendingSpace;
|
||||||
|
}
|
||||||
|
pendingSpace = '';
|
||||||
|
};
|
||||||
|
const addChr = (c) => {
|
||||||
|
flushSpace();
|
||||||
|
value += c;
|
||||||
|
};
|
||||||
|
const takeValue = () => {
|
||||||
|
const taken = value;
|
||||||
|
value = '';
|
||||||
|
pendingSpace = '';
|
||||||
|
quoteClosed = false;
|
||||||
|
return taken;
|
||||||
|
};
|
||||||
|
const storeValue = () => {
|
||||||
|
const taken = takeValue();
|
||||||
|
if (key === false) {
|
||||||
|
response.value = taken;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setParam(key, taken);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// A parameter name with no '=' is a valueless parameter, not the start of the next one.
|
||||||
|
// Without this the name keeps growing across the ';' and swallows whatever follows, which
|
||||||
|
// is how 'multipart/mixed; flag; boundary="AAA"' lost its boundary to a parameter named
|
||||||
|
// 'flag; boundary' and left the node declaring the generated boundary beside the asked
|
||||||
|
// for one, so a receiver reading the first of the two found no delimiter it matched.
|
||||||
|
const storeEmptyKey = () => {
|
||||||
|
setParam(takeValue().trim(), '');
|
||||||
|
};
|
||||||
for (let i = 0, len = str.length; i < len; i++) {
|
for (let i = 0, len = str.length; i < len; i++) {
|
||||||
chr = str.charAt(i);
|
chr = str.charAt(i);
|
||||||
if (type === 'key') {
|
if (stage === 'key') {
|
||||||
if (chr === '=') {
|
if (chr === '=') {
|
||||||
key = value.trim().toLowerCase();
|
key = takeValue().trim();
|
||||||
type = 'value';
|
stage = 'value';
|
||||||
value = '';
|
continue;
|
||||||
|
}
|
||||||
|
if (chr === ';') {
|
||||||
|
storeEmptyKey();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
value += chr;
|
value += chr;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (escaped) {
|
if (quoteClosed && chr !== ';') {
|
||||||
value += chr;
|
// Nothing behind a closed quoted string reaches the value. RFC 2045 says a
|
||||||
|
// parameter value is a token or a quoted string and not both, so what follows
|
||||||
|
// one is junk and only the ';' that ends the parameter still counts. Tested
|
||||||
|
// ahead of the branches rather than beside the append at the foot of them,
|
||||||
|
// where each branch above was a way around it: a second '"' reopened quoting
|
||||||
|
// and swallowed the rest of the header, so 'boundary="AAA" "; boundary=BBB"'
|
||||||
|
// read as a single boundary of 'AAA ', and the junk of
|
||||||
|
// 'boundary="AAA" (unterminated comment' still joined the declared boundary
|
||||||
|
// through the escape branch. quoteClosed is only ever set while no quote is
|
||||||
|
// open, and this is what keeps one from being opened afterwards.
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (chr === '\\') {
|
if (escaped) {
|
||||||
|
addChr(chr);
|
||||||
|
}
|
||||||
|
else if (quote && chr === '\\') {
|
||||||
|
// a backslash only escapes inside a quoted string, everywhere else it is an
|
||||||
|
// ordinary character. Treating it as an escape turns the parameter value
|
||||||
|
// 'C:\Users\me\report.txt' into 'C:Usersmereport.txt'
|
||||||
escaped = true;
|
escaped = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (quote && chr === quote) {
|
else if (quote && chr === quote) {
|
||||||
quote = false;
|
quote = false;
|
||||||
|
quoteClosed = true;
|
||||||
}
|
}
|
||||||
else if (!quote && chr === '"') {
|
else if (!quote && chr === '"') {
|
||||||
quote = chr;
|
quote = chr;
|
||||||
|
flushSpace();
|
||||||
}
|
}
|
||||||
else if (!quote && chr === ';') {
|
else if (!quote && chr === ';') {
|
||||||
if (key === false) {
|
storeValue();
|
||||||
response.value = value.trim();
|
stage = 'key';
|
||||||
|
}
|
||||||
|
else if (!quote && (chr === ' ' || chr === '\t')) {
|
||||||
|
pendingSpace += chr;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setParam(key, value.trim());
|
addChr(chr);
|
||||||
}
|
|
||||||
type = 'key';
|
|
||||||
value = '';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
value += chr;
|
|
||||||
}
|
}
|
||||||
escaped = false;
|
escaped = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'value') {
|
if (stage === 'value') {
|
||||||
if (key === false) {
|
storeValue();
|
||||||
response.value = value.trim();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setParam(key, value.trim());
|
// a key with no value, as in 'Header-Key: somevalue; key=value; emptykey'
|
||||||
}
|
storeEmptyKey();
|
||||||
}
|
|
||||||
else if (value.trim()) {
|
|
||||||
setParam(value.trim().toLowerCase(), '');
|
|
||||||
}
|
}
|
||||||
// handle parameter value continuations
|
// handle parameter value continuations
|
||||||
// https://tools.ietf.org/html/rfc2231#section-3
|
// https://tools.ietf.org/html/rfc2231#section-3
|
||||||
// preprocess values
|
// Sections are collected in a list and ordered below rather than written into an array
|
||||||
|
// at their own section number. An index write makes the array as long as the number the
|
||||||
|
// header asked for, and the join that follows walks all of it, so the 55 byte value
|
||||||
|
// "attachment; filename*0*=utf-8''a; filename*4000000000=b" held a core for over two
|
||||||
|
// minutes.
|
||||||
|
const continuations = new Map();
|
||||||
Object.keys(response.params).forEach(key => {
|
Object.keys(response.params).forEach(key => {
|
||||||
let actualKey, nr, match, value;
|
const match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/);
|
||||||
if ((match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/))) {
|
if (!match) {
|
||||||
actualKey = key.substr(0, match.index);
|
// not a continuation parameter, there is nothing to join
|
||||||
nr = Number(match[2] || match[3]) || 0;
|
|
||||||
if ((0, objects_js_1.isProtoKey)(actualKey)) {
|
|
||||||
// see setParam. Reading it back would yield Object.prototype, which is
|
|
||||||
// an object, so the initializer below would be skipped and the write
|
|
||||||
// that follows would throw out of a header build the caller can not catch
|
|
||||||
delete response.params[key];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!response.params[actualKey] || typeof response.params[actualKey] !== 'object') {
|
const actualKey = key.substr(0, match.index);
|
||||||
response.params[actualKey] = {
|
const nr = Number(match[2] || match[3]) || 0;
|
||||||
charset: false,
|
// RFC 2231 section 4.1: only a section whose name ends in '*' is percent encoded
|
||||||
values: []
|
const encoded = match[0].substr(-1) === '*';
|
||||||
};
|
|
||||||
}
|
|
||||||
value = response.params[key];
|
|
||||||
if (nr === 0 && match[0].substr(-1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
|
|
||||||
response.params[actualKey].charset = match[1] || 'iso-8859-1';
|
|
||||||
value = match[2];
|
|
||||||
}
|
|
||||||
response.params[actualKey].values[nr] = value;
|
|
||||||
// remove the old reference
|
// remove the old reference
|
||||||
|
let value = response.params[key];
|
||||||
delete response.params[key];
|
delete response.params[key];
|
||||||
|
if (!_isParamName(actualKey)) {
|
||||||
|
// the joined value can not be written back under this name. It is empty when the
|
||||||
|
// continuation suffix was all there was of it, as in the bare '*' of 'text/plain; a;*'
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
let continuation = continuations.get(actualKey);
|
||||||
|
if (!continuation) {
|
||||||
|
continuation = { charset: false, sections: [] };
|
||||||
|
continuations.set(actualKey, continuation);
|
||||||
|
}
|
||||||
|
const charsetMatch = nr === 0 && encoded ? value.match(/^([^']*)'[^']*'(.*)$/) : null;
|
||||||
|
if (charsetMatch) {
|
||||||
|
// the charset is a token, and anything else named as one is no charset a consumer
|
||||||
|
// could resolve, so it reads as the unnamed case rather than being carried into
|
||||||
|
// the encoded word below. A "\r\n" of a prefix would otherwise reach a consumer
|
||||||
|
// of the parsed value as the charset of a word that no decoder can act on
|
||||||
|
continuation.charset = TOKEN.test(charsetMatch[1]) ? charsetMatch[1] : 'iso-8859-1';
|
||||||
|
value = charsetMatch[2];
|
||||||
|
}
|
||||||
|
continuation.sections.push({ nr, value, encoded });
|
||||||
});
|
});
|
||||||
// concatenate split rfc2231 strings and convert encoded strings to mime encoded words
|
// concatenate split rfc2231 strings and convert encoded strings to mime encoded words
|
||||||
Object.keys(response.params).forEach(key => {
|
continuations.forEach((continuation, key) => {
|
||||||
let value;
|
if (Object.prototype.hasOwnProperty.call(response.params, key)) {
|
||||||
if (response.params[key] && Array.isArray(response.params[key].values)) {
|
// The same name was also given as a plain parameter, which the starred keys were
|
||||||
value = response.params[key].values.map((val) => val || '').join('');
|
// just deleted from around, so this write would be the only one in the function
|
||||||
if (response.params[key].charset) {
|
// to override a name already taken. 'filename=plain.txt; filename*0=evil.txt'
|
||||||
|
// resolves to the plain parameter either way round, so the reading does not come
|
||||||
|
// down to which of the two spellings the sender put first
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
continuation.sections.sort((a, b) => a.nr - b.nr);
|
||||||
|
if (!continuation.charset) {
|
||||||
|
// nothing said which charset the percent escapes of an encoded section are in,
|
||||||
|
// so every section is passed on as the text it already is
|
||||||
|
response.params[key] = continuation.sections.map(section => section.value).join('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
// convert "%AB" to "=?charset?Q?=AB?="
|
// convert "%AB" to "=?charset?Q?=AB?="
|
||||||
response.params[key] =
|
response.params[key] = '=?' + continuation.charset + '?Q?' + continuation.sections.map(_encodeContinuationSection).join('') + '?=';
|
||||||
'=?' +
|
});
|
||||||
response.params[key].charset +
|
return response;
|
||||||
'?Q?' +
|
}
|
||||||
value
|
/**
|
||||||
|
* Renders one parameter value continuation section as the payload of a Q encoded word.
|
||||||
|
*
|
||||||
|
* A section whose name ends in '*' is percent encoded and its escapes carry the bytes of
|
||||||
|
* the value, so they only have to be rewritten into the "=AB" spelling a Q encoded word
|
||||||
|
* uses. A section without the '*' is literal text (RFC 2231 section 4.1), so its '%' is a
|
||||||
|
* '%' and is escaped along with the characters a Q encoded word can not carry bare.
|
||||||
|
* Decoding a literal section invents bytes that never appeared on the wire: it is how the
|
||||||
|
* value 'filename*0*=utf-8''safe; filename*1=%2F..%2F..%2Fetc%2Fpasswd' was emitted as a
|
||||||
|
* filename every receiving client reads back as 'safe/../../etc/passwd'.
|
||||||
|
*
|
||||||
|
* @param section One collected continuation section
|
||||||
|
* @return The section as Q encoded word payload
|
||||||
|
*/
|
||||||
|
function _encodeContinuationSection(section) {
|
||||||
|
const specials = section.encoded ? /[=?_\s]/g : /[=?_\s%]/g;
|
||||||
|
return (section.value
|
||||||
// fix invalidly encoded chars
|
// fix invalidly encoded chars
|
||||||
.replace(/[=?_\s]/g, s => {
|
.replace(specials, s => {
|
||||||
const c = s.charCodeAt(0).toString(16);
|
const c = s.charCodeAt(0).toString(16);
|
||||||
if (s === ' ') {
|
if (s === ' ') {
|
||||||
return '_';
|
return '_';
|
||||||
@@ -523,15 +645,7 @@ function parseHeaderValue(str) {
|
|||||||
return '%' + (c.length < 2 ? '0' : '') + c;
|
return '%' + (c.length < 2 ? '0' : '') + c;
|
||||||
})
|
})
|
||||||
// change from urlencoding to percent encoding
|
// change from urlencoding to percent encoding
|
||||||
.replace(/%/g, '=') +
|
.replace(/%/g, '='));
|
||||||
'?=';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
response.params[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns file extension for a content type string. If no suitable extensions
|
* Returns file extension for a content type string. If no suitable extensions
|
||||||
|
|||||||
+2
-1
@@ -1294,6 +1294,7 @@ class MimeNode {
|
|||||||
*
|
*
|
||||||
* @param addresses An array of address objects
|
* @param addresses An array of address objects
|
||||||
* @param [uniqueList] An array to be populated with addresses
|
* @param [uniqueList] An array to be populated with addresses
|
||||||
|
* @param [seenAddresses] Addresses already added to uniqueList, shared with recursive calls to keep deduplication linear
|
||||||
* @return address string
|
* @return address string
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@@ -1431,7 +1432,7 @@ class MimeNode {
|
|||||||
/**
|
/**
|
||||||
* If needed, mime encodes the name part
|
* If needed, mime encodes the name part
|
||||||
*
|
*
|
||||||
* @param name Name part of an address
|
* @param value Name part of an address
|
||||||
* @returns Mime word encoded string if needed
|
* @returns Mime word encoded string if needed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|||||||
+13
-2
@@ -75,11 +75,22 @@ export declare function createTransport(transporter?: TransportConfig | Transpor
|
|||||||
/**
|
/**
|
||||||
* Creates a test account from the Ethereal service (https://ethereal.email)
|
* Creates a test account from the Ethereal service (https://ethereal.email)
|
||||||
*
|
*
|
||||||
* @param apiUrl Optional API endpoint, defaults to https://api.nodemailer.com
|
* @param callback Callback function to run with the account object
|
||||||
* @param callback Callback function to run with the account object. If not set, a Promise is returned
|
|
||||||
*/
|
*/
|
||||||
export declare function createTestAccount(callback: TestAccountCallback): void;
|
export declare function createTestAccount(callback: TestAccountCallback): void;
|
||||||
|
/**
|
||||||
|
* Creates a test account from the Ethereal service (https://ethereal.email)
|
||||||
|
*
|
||||||
|
* @param apiUrl API endpoint, defaults to https://api.nodemailer.com
|
||||||
|
* @param callback Callback function to run with the account object
|
||||||
|
*/
|
||||||
export declare function createTestAccount(apiUrl: string | false | null | undefined, callback: TestAccountCallback): void;
|
export declare function createTestAccount(apiUrl: string | false | null | undefined, callback: TestAccountCallback): void;
|
||||||
|
/**
|
||||||
|
* Creates a test account from the Ethereal service (https://ethereal.email)
|
||||||
|
*
|
||||||
|
* @param [apiUrl] API endpoint, defaults to https://api.nodemailer.com
|
||||||
|
* @returns Promise that resolves with the account object
|
||||||
|
*/
|
||||||
export declare function createTestAccount(apiUrl?: string | false | null): Promise<TestAccount>;
|
export declare function createTestAccount(apiUrl?: string | false | null): Promise<TestAccount>;
|
||||||
/**
|
/**
|
||||||
* Resolves the Ethereal web URL for a message sent through an Ethereal test account
|
* Resolves the Ethereal web URL for a message sent through an Ethereal test account
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
export declare const name = "nodemailer";
|
export declare const name = "nodemailer";
|
||||||
export declare const version = "10.0.13";
|
export declare const version = "10.0.14";
|
||||||
export declare const homepage = "https://nodemailer.com/";
|
export declare const homepage = "https://nodemailer.com/";
|
||||||
+1
-1
@@ -3,5 +3,5 @@
|
|||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.homepage = exports.version = exports.name = void 0;
|
exports.homepage = exports.version = exports.name = void 0;
|
||||||
exports.name = 'nodemailer';
|
exports.name = 'nodemailer';
|
||||||
exports.version = '10.0.13';
|
exports.version = '10.0.14';
|
||||||
exports.homepage = 'https://nodemailer.com/';
|
exports.homepage = 'https://nodemailer.com/';
|
||||||
+1
@@ -178,6 +178,7 @@ const basicToDigit = function (codePoint) {
|
|||||||
* @see `basicToDigit()`
|
* @see `basicToDigit()`
|
||||||
* @private
|
* @private
|
||||||
* @param digit The numeric value of a basic code point.
|
* @param digit The numeric value of a basic code point.
|
||||||
|
* @param flag Non-zero to use the uppercase form of the code point.
|
||||||
* @returns The basic code point whose value (when used for
|
* @returns The basic code point whose value (when used for
|
||||||
* representing integers) is `digit`, which needs to be in the range
|
* representing integers) is `digit`, which needs to be in the range
|
||||||
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
||||||
|
|||||||
+6
-1
@@ -85,9 +85,14 @@ declare class SESTransport extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Verifies SES configuration
|
* Verifies SES configuration
|
||||||
*
|
*
|
||||||
* @param callback Callback function
|
* @returns Promise that resolves to true if the configuration is usable
|
||||||
*/
|
*/
|
||||||
verify(): Promise<true>;
|
verify(): Promise<true>;
|
||||||
|
/**
|
||||||
|
* Verifies SES configuration
|
||||||
|
*
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
verify(callback: VerifyCallback): void;
|
verify(callback: VerifyCallback): void;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
+25
-1
@@ -187,6 +187,7 @@ export declare const parseConnectionUrl: (str?: string | null) => ConnectionUrlO
|
|||||||
* creates a default console logger
|
* creates a default console logger
|
||||||
*
|
*
|
||||||
* @param [options] Options object that might include 'logger' value
|
* @param [options] Options object that might include 'logger' value
|
||||||
|
* @param [defaults] Fields merged into every log entry, overridden by the fields of the entry itself
|
||||||
* @return bunyan compatible logger
|
* @return bunyan compatible logger
|
||||||
*/
|
*/
|
||||||
export declare const getLogger: (options?: GetLoggerOptions, defaults?: LogEntry) => Logger;
|
export declare const getLogger: (options?: GetLoggerOptions, defaults?: LogEntry) => Logger;
|
||||||
@@ -210,18 +211,41 @@ export declare const parseDataURI: (uri: unknown) => ParsedDataURI | null;
|
|||||||
*
|
*
|
||||||
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
* @param key Property name or an Array index
|
* @param key Property name or an Array index
|
||||||
* @param [options] Optional access policy: { disableFileAccess, disableUrlAccess }
|
|
||||||
* @param callback Callback function with (err, value)
|
* @param callback Callback function with (err, value)
|
||||||
*/
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, callback: ResolveContentCallback): void;
|
}, key: string | number, callback: ResolveContentCallback): void;
|
||||||
|
/**
|
||||||
|
* Resolves a String or a Buffer value for content value
|
||||||
|
*
|
||||||
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
|
* @param key Property name or an Array index
|
||||||
|
* @param options Access policy: { disableFileAccess, disableUrlAccess }
|
||||||
|
* @param callback Callback function with (err, value)
|
||||||
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback): void;
|
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback): void;
|
||||||
|
/**
|
||||||
|
* Resolves a String or a Buffer value for content value
|
||||||
|
*
|
||||||
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
|
* @param key Property name or an Array index
|
||||||
|
* @param [options] Optional access policy: { disableFileAccess, disableUrlAccess }
|
||||||
|
* @returns Promise that resolves with the value
|
||||||
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, options?: ResolveContentOptions | false): Promise<any>;
|
}, key: string | number, options?: ResolveContentOptions | false): Promise<any>;
|
||||||
|
/**
|
||||||
|
* Resolves a String or a Buffer value for content value
|
||||||
|
*
|
||||||
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
|
* @param key Property name or an Array index
|
||||||
|
* @param options Access policy: { disableFileAccess, disableUrlAccess }
|
||||||
|
* @param callback Callback function with (err, value), a Promise is returned if not set
|
||||||
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback | undefined): Promise<any> | void;
|
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback | undefined): Promise<any> | void;
|
||||||
|
|||||||
+1
@@ -362,6 +362,7 @@ exports._logFunc = _logFunc;
|
|||||||
* creates a default console logger
|
* creates a default console logger
|
||||||
*
|
*
|
||||||
* @param [options] Options object that might include 'logger' value
|
* @param [options] Options object that might include 'logger' value
|
||||||
|
* @param [defaults] Fields merged into every log entry, overridden by the fields of the entry itself
|
||||||
* @return bunyan compatible logger
|
* @return bunyan compatible logger
|
||||||
*/
|
*/
|
||||||
const getLogger = (options, defaults) => {
|
const getLogger = (options, defaults) => {
|
||||||
|
|||||||
+11
-3
@@ -21,13 +21,21 @@ export type HttpProxyClientCallback = Callback<net.Socket>;
|
|||||||
* socket.write("GET / HTTP/1.0\r\n\r\n");
|
* socket.write("GET / HTTP/1.0\r\n\r\n");
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @param proxyUrl proxy configuration, etg "http://proxy.host:3128/"
|
* @param proxyUrl proxy configuration, e.g. "http://proxy.host:3128/"
|
||||||
* @param destinationPort Port to open in destination host
|
* @param destinationPort Port to open in destination host
|
||||||
* @param destinationHost Destination hostname
|
* @param destinationHost Destination hostname
|
||||||
* @param [tlsOptions] Optional TLS options for an HTTPS proxy (e.g. { rejectUnauthorized: false })
|
* @param callback Callback to run with the socket object once connection is established
|
||||||
* @param callback Callback to run with the rocket object once connection is established
|
|
||||||
*/
|
*/
|
||||||
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, callback: HttpProxyClientCallback): void;
|
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, callback: HttpProxyClientCallback): void;
|
||||||
|
/**
|
||||||
|
* Establishes proxied connection to destinationPort through an HTTPS proxy
|
||||||
|
*
|
||||||
|
* @param proxyUrl proxy configuration, e.g. "https://proxy.host:3128/"
|
||||||
|
* @param destinationPort Port to open in destination host
|
||||||
|
* @param destinationHost Destination hostname
|
||||||
|
* @param tlsOptions TLS options for the proxy connection (e.g. { rejectUnauthorized: false })
|
||||||
|
* @param callback Callback to run with the socket object once connection is established
|
||||||
|
*/
|
||||||
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, tlsOptions: HttpProxyClientOptions | undefined, callback: HttpProxyClientCallback): void;
|
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, tlsOptions: HttpProxyClientOptions | undefined, callback: HttpProxyClientCallback): void;
|
||||||
/**
|
/**
|
||||||
* Socket timeout in milliseconds while the CONNECT handshake is in progress, defaults to 30 seconds.
|
* Socket timeout in milliseconds while the CONNECT handshake is in progress, defaults to 30 seconds.
|
||||||
|
|||||||
+17
-7
@@ -126,18 +126,28 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
|
|||||||
.join('\r\n') +
|
.join('\r\n') +
|
||||||
// End request
|
// End request
|
||||||
'\r\n\r\n');
|
'\r\n\r\n');
|
||||||
let headers = '';
|
// The response is collected as chunks and only the bytes that just arrived, together
|
||||||
|
// with the three before them, are searched for the end of the headers. Appending to a
|
||||||
|
// string and searching all of it again re-read the whole response on every chunk.
|
||||||
|
const chunks = [];
|
||||||
|
let received = 0;
|
||||||
|
let tail = '';
|
||||||
const onSocketData = (chunk) => {
|
const onSocketData = (chunk) => {
|
||||||
let match;
|
let match;
|
||||||
let remainder;
|
|
||||||
if (finished) {
|
if (finished) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
headers += chunk.toString('binary');
|
const window = tail + chunk.toString('binary');
|
||||||
if ((match = headers.match(/\r\n\r\n/))) {
|
const windowEnd = window.indexOf('\r\n\r\n');
|
||||||
|
chunks.push(chunk);
|
||||||
|
received += chunk.length;
|
||||||
|
tail = window.slice(-3);
|
||||||
|
if (windowEnd >= 0) {
|
||||||
socket.removeListener('data', onSocketData);
|
socket.removeListener('data', onSocketData);
|
||||||
remainder = headers.substr(match.index + match[0].length);
|
const headerEnd = received - window.length + windowEnd;
|
||||||
headers = headers.substr(0, match.index);
|
const response = Buffer.concat(chunks, received).toString('binary');
|
||||||
|
const headers = response.substr(0, headerEnd);
|
||||||
|
const remainder = response.substr(headerEnd + 4);
|
||||||
if (remainder) {
|
if (remainder) {
|
||||||
socket.unshift(Buffer.from(remainder, 'binary'));
|
socket.unshift(Buffer.from(remainder, 'binary'));
|
||||||
}
|
}
|
||||||
@@ -161,7 +171,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
|
|||||||
socket.setTimeout(0);
|
socket.setTimeout(0);
|
||||||
return done(null, socket);
|
return done(null, socket);
|
||||||
}
|
}
|
||||||
if (headers.length > MAX_RESPONSE_HEADER_BYTES) {
|
if (received > MAX_RESPONSE_HEADER_BYTES) {
|
||||||
socket.removeListener('data', onSocketData);
|
socket.removeListener('data', onSocketData);
|
||||||
const err = new Error('Proxy response headers too large');
|
const err = new Error('Proxy response headers too large');
|
||||||
err.code = errors.EPROXY;
|
err.code = errors.EPROXY;
|
||||||
|
|||||||
+1
-1
@@ -379,7 +379,7 @@ declare class SMTPConnection extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param envelope Envelope object, {from: addr, to: [addr]}
|
* @param envelope Envelope object, {from: addr, to: [addr]}
|
||||||
* @param message String, Buffer or a Stream
|
* @param message String, Buffer or a Stream
|
||||||
* @param callback Callback to return once sending is completed
|
* @param done Callback to return once sending is completed
|
||||||
*/
|
*/
|
||||||
send(envelope: SMTPEnvelope, message: string | Buffer | Readable, done: SMTPConnectionSendCallback): void;
|
send(envelope: SMTPEnvelope, message: string | Buffer | Readable, done: SMTPConnectionSendCallback): void;
|
||||||
/**
|
/**
|
||||||
|
|||||||
+15
-1
@@ -614,7 +614,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param envelope Envelope object, {from: addr, to: [addr]}
|
* @param envelope Envelope object, {from: addr, to: [addr]}
|
||||||
* @param message String, Buffer or a Stream
|
* @param message String, Buffer or a Stream
|
||||||
* @param callback Callback to return once sending is completed
|
* @param done Callback to return once sending is completed
|
||||||
*/
|
*/
|
||||||
send(envelope, message, done) {
|
send(envelope, message, done) {
|
||||||
// ensure that the callback is only called once. The public callback type has a
|
// ensure that the callback is only called once. The public callback type has a
|
||||||
@@ -827,6 +827,8 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* @event
|
* @event
|
||||||
* @param err Error object
|
* @param err Error object
|
||||||
* @param type Error name
|
* @param type Error name
|
||||||
|
* @param data Server response that triggered the error, false if there is none
|
||||||
|
* @param command SMTP command that was in flight
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_onError(err, type, data, command) {
|
_onError(err, type, data, command) {
|
||||||
@@ -1095,6 +1097,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* {from:'...', to:['...']}
|
* {from:'...', to:['...']}
|
||||||
* or
|
* or
|
||||||
* {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
|
* {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_setEnvelope(envelope, callback) {
|
_setEnvelope(envelope, callback) {
|
||||||
@@ -1489,6 +1492,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* hosts invalidly use a longer message than VXNlcm5hbWU6
|
* hosts invalidly use a longer message than VXNlcm5hbWU6
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_LOGIN_USER(str, callback) {
|
_actionAUTH_LOGIN_USER(str, callback) {
|
||||||
@@ -1510,6 +1514,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* base64 encoded again.
|
* base64 encoded again.
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_CRAM_MD5(str, callback) {
|
_actionAUTH_CRAM_MD5(str, callback) {
|
||||||
@@ -1534,6 +1539,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* the user can be considered logged in. Start waiting for a message to send
|
* the user can be considered logged in. Start waiting for a message to send
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_CRAM_MD5_PASS(str, callback) {
|
_actionAUTH_CRAM_MD5_PASS(str, callback) {
|
||||||
@@ -1555,6 +1561,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* response needs to be base64 encoded password.
|
* response needs to be base64 encoded password.
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_LOGIN_PASS(str, callback) {
|
_actionAUTH_LOGIN_PASS(str, callback) {
|
||||||
@@ -1574,6 +1581,8 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* the user can be considered logged in. Start waiting for a message to send
|
* the user can be considered logged in. Start waiting for a message to send
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param isRetry True if this is a retry after a failed login, or the callback itself
|
||||||
|
* @param [callback] Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTHComplete(str, isRetry, callback) {
|
_actionAUTHComplete(str, isRetry, callback) {
|
||||||
@@ -1616,6 +1625,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* Handle response for a MAIL FROM: command
|
* Handle response for a MAIL FROM: command
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionMAIL(str, callback) {
|
_actionMAIL(str, callback) {
|
||||||
@@ -1644,6 +1654,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* Handle response for a RCPT TO: command
|
* Handle response for a RCPT TO: command
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionRCPT(str, callback) {
|
_actionRCPT(str, callback) {
|
||||||
@@ -1695,6 +1706,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* Handle response for a DATA command
|
* Handle response for a DATA command
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionDATA(str, callback) {
|
_actionDATA(str, callback) {
|
||||||
@@ -1721,6 +1733,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* We expect a single response that defines if the sending succeeded or failed
|
* We expect a single response that defines if the sending succeeded or failed
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run with the final send result
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionSMTPStream(str, callback) {
|
_actionSMTPStream(str, callback) {
|
||||||
@@ -1737,6 +1750,7 @@ class SMTPConnection extends node_events_1.EventEmitter {
|
|||||||
* @param recipient The recipient this response applies to
|
* @param recipient The recipient this response applies to
|
||||||
* @param final Is this the final recipient?
|
* @param final Is this the final recipient?
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run with the final send result
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionLMTPStream(recipient, final, str, callback) {
|
_actionLMTPStream(recipient, final, str, callback) {
|
||||||
|
|||||||
+6
-1
@@ -110,9 +110,14 @@ declare class SMTPPool extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Verifies SMTP configuration
|
* Verifies SMTP configuration
|
||||||
*
|
*
|
||||||
* @param callback Callback function
|
* @returns Promise that resolves to true if the configuration is usable
|
||||||
*/
|
*/
|
||||||
verify(): Promise<true>;
|
verify(): Promise<true>;
|
||||||
|
/**
|
||||||
|
* Verifies SMTP configuration
|
||||||
|
*
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
verify(callback: VerifyCallback): void;
|
verify(callback: VerifyCallback): void;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
-1
@@ -125,9 +125,14 @@ declare class SMTPTransport extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Verifies SMTP configuration
|
* Verifies SMTP configuration
|
||||||
*
|
*
|
||||||
* @param callback Callback function
|
* @returns Promise that resolves to true if the configuration is usable
|
||||||
*/
|
*/
|
||||||
verify(): Promise<true>;
|
verify(): Promise<true>;
|
||||||
|
/**
|
||||||
|
* Verifies SMTP configuration
|
||||||
|
*
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
verify(callback: VerifyCallback): void;
|
verify(callback: VerifyCallback): void;
|
||||||
/**
|
/**
|
||||||
* Releases resources
|
* Releases resources
|
||||||
|
|||||||
+6
@@ -314,6 +314,12 @@ exports.services = {
|
|||||||
"host": "mailosaur.io",
|
"host": "mailosaur.io",
|
||||||
"port": 25
|
"port": 25
|
||||||
},
|
},
|
||||||
|
"MailSenpai": {
|
||||||
|
"description": "MailSenpai (SMTP Senpai, EU)",
|
||||||
|
"host": "relay.mailsenpai.com",
|
||||||
|
"port": 2525,
|
||||||
|
"secure": false
|
||||||
|
},
|
||||||
"Mailtrap": {
|
"Mailtrap": {
|
||||||
"description": "Mailtrap",
|
"description": "Mailtrap",
|
||||||
"host": "live.smtp.mailtrap.io",
|
"host": "live.smtp.mailtrap.io",
|
||||||
|
|||||||
-1
@@ -39,7 +39,6 @@ export type Address = MailboxAddress | GroupAddress;
|
|||||||
*
|
*
|
||||||
* @param str Address field
|
* @param str Address field
|
||||||
* @param options Optional options object
|
* @param options Optional options object
|
||||||
* @param options._depth Internal recursion depth counter (do not set manually)
|
|
||||||
* @return An array of address objects
|
* @return An array of address objects
|
||||||
*/
|
*/
|
||||||
declare function addressparser(str: string | null | undefined, options: AddressParserOptions & {
|
declare function addressparser(str: string | null | undefined, options: AddressParserOptions & {
|
||||||
|
|||||||
+55
-17
@@ -426,7 +426,7 @@ function _handleAddress(tokens, depth) {
|
|||||||
// Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
|
// Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
|
||||||
let groupMembers = [];
|
let groupMembers = [];
|
||||||
if (data.group.length) {
|
if (data.group.length) {
|
||||||
const parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
|
const parsedGroup = _parseAddressList(data.group.join(','), depth + 1);
|
||||||
parsedGroup.forEach(member => {
|
parsedGroup.forEach(member => {
|
||||||
if (member.group) {
|
if (member.group) {
|
||||||
groupMembers = groupMembers.concat(member.group);
|
groupMembers = groupMembers.concat(member.group);
|
||||||
@@ -513,7 +513,13 @@ function _handleAddress(tokens, depth) {
|
|||||||
// Join values with spaces
|
// Join values with spaces
|
||||||
data.text = data.text.join(' ');
|
data.text = data.text.join(' ');
|
||||||
data.address = data.address.join(' ');
|
data.address = data.address.join(' ');
|
||||||
if (addressFromQuotedText && data.text) {
|
if (addressFromQuotedText && data.text.indexOf('@') >= 0) {
|
||||||
|
// A quoted run with no '@' in it is a display name and nothing else, so it stays
|
||||||
|
// where it is. Moving it over anyway made a comment the mailbox was read from:
|
||||||
|
// '"Display Name" <(a comment)>' handed on 'Display Name' as the address with the
|
||||||
|
// comment as the name, and a quoted name holding a ',' or a ';' became an address
|
||||||
|
// that reads as two recipients once a consumer writes it back into a header.
|
||||||
|
//
|
||||||
// The mailbox is still sitting in the text, so it moves over here and is quoted
|
// The mailbox is still sitting in the text, so it moves over here and is quoted
|
||||||
// before the recovery below rather than after it. Anything else the text holds
|
// before the recovery below rather than after it. Anything else the text holds
|
||||||
// came along with it: a comment ends the domain but leaves the atoms behind it in
|
// came along with it: a comment ends the domain but leaves the atoms behind it in
|
||||||
@@ -560,6 +566,7 @@ class Tokenizer {
|
|||||||
this.node = null;
|
this.node = null;
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
this.inDomainLiteral = false;
|
this.inDomainLiteral = false;
|
||||||
|
this.afterAt = false;
|
||||||
this.list = [];
|
this.list = [];
|
||||||
/**
|
/**
|
||||||
* Operator tokens and which tokens are expected to end the sequence
|
* Operator tokens and which tokens are expected to end the sequence
|
||||||
@@ -603,17 +610,23 @@ class Tokenizer {
|
|||||||
* Checks if a character is an operator or text and acts accordingly
|
* Checks if a character is an operator or text and acts accordingly
|
||||||
*
|
*
|
||||||
* @param chr Character from the address field
|
* @param chr Character from the address field
|
||||||
|
* @param nextChr Character following chr, null at the end of the field
|
||||||
*/
|
*/
|
||||||
checkChar(chr, nextChr) {
|
checkChar(chr, nextChr) {
|
||||||
// Track RFC 5322 domain-literals ("[" *dtext "]"). Operator characters such
|
// Track RFC 5322 domain-literals ("[" *dtext "]"). The ":" of an IPv6
|
||||||
// as the ":" of an IPv6 address-literal (user@[IPv6:2001:db8::1]) are dtext
|
// address-literal (user@[IPv6:2001:db8::1]) is dtext and must not be treated as
|
||||||
// and must not be treated as the group delimiter while inside the brackets.
|
// the group delimiter while inside the brackets. That is the only operator the
|
||||||
// Quoted strings and comments are handled separately via operatorExpecting,
|
// literal hides: letting it hide the others as well meant a "[" could turn the
|
||||||
// so only enter this state when no operator is open. The list separators ","
|
// comment, quoted string or angle-addr after it into text and pick a different
|
||||||
// and ";" are the exception: they always end the literal (and split the
|
// mailbox, '[ ( ] <a@victim.com> ) <b@evil.com>' yielded a@victim.com where
|
||||||
// address list) so that an unclosed "[" cannot swallow later recipients.
|
// RFC 5322 reads b@evil.com. A "[" also only opens a literal right after the "@"
|
||||||
|
// of an addr-spec, so the group delimiter of a display name holding one stays
|
||||||
|
// intact. Quoted strings and comments are handled separately via
|
||||||
|
// operatorExpecting, so only enter this state when no operator is open. The list
|
||||||
|
// separators "," and ";" always end the literal (and split the address list) so
|
||||||
|
// that an unclosed "[" cannot swallow later recipients.
|
||||||
if (!this.escaped && !this.operatorExpecting) {
|
if (!this.escaped && !this.operatorExpecting) {
|
||||||
if (!this.inDomainLiteral && chr === '[') {
|
if (!this.inDomainLiteral && chr === '[' && this.afterAt) {
|
||||||
this.inDomainLiteral = true;
|
this.inDomainLiteral = true;
|
||||||
}
|
}
|
||||||
else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
|
else if (this.inDomainLiteral && (chr === ']' || chr === ',' || chr === ';')) {
|
||||||
@@ -633,17 +646,24 @@ class Tokenizer {
|
|||||||
}
|
}
|
||||||
this.list.push(this.node);
|
this.list.push(this.node);
|
||||||
this.node = null;
|
this.node = null;
|
||||||
|
if (chr !== ')') {
|
||||||
|
// a comment is folding whitespace, it does not part the "@" from the "["
|
||||||
|
this.afterAt = false;
|
||||||
|
}
|
||||||
this.operatorExpecting = '';
|
this.operatorExpecting = '';
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (!this.operatorExpecting && !this.inDomainLiteral && chr in this.operators) {
|
else if (!this.operatorExpecting && !(this.inDomainLiteral && chr === ':') && chr in this.operators) {
|
||||||
this.node = {
|
this.node = {
|
||||||
type: 'operator',
|
type: 'operator',
|
||||||
value: chr
|
value: chr
|
||||||
};
|
};
|
||||||
this.list.push(this.node);
|
this.list.push(this.node);
|
||||||
this.node = null;
|
this.node = null;
|
||||||
|
if (chr !== '(') {
|
||||||
|
this.afterAt = false;
|
||||||
|
}
|
||||||
this.operatorExpecting = this.operators[chr];
|
this.operatorExpecting = this.operators[chr];
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
return;
|
return;
|
||||||
@@ -667,6 +687,11 @@ class Tokenizer {
|
|||||||
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
|
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
|
||||||
// skip command bytes
|
// skip command bytes
|
||||||
this.node.value += chr;
|
this.node.value += chr;
|
||||||
|
// text inside a quoted string or a comment is not part of an addr-spec. Tracked
|
||||||
|
// as it goes rather than read back off the value (see lastChars in _handleAddress)
|
||||||
|
if (!this.operatorExpecting && chr !== ' ' && chr !== '\t') {
|
||||||
|
this.afterAt = chr === '@';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.escaped = false;
|
this.escaped = false;
|
||||||
}
|
}
|
||||||
@@ -677,9 +702,19 @@ class Tokenizer {
|
|||||||
* malicious input that could cause stack overflow.
|
* malicious input that could cause stack overflow.
|
||||||
*/
|
*/
|
||||||
const MAX_NESTED_GROUP_DEPTH = 50;
|
const MAX_NESTED_GROUP_DEPTH = 50;
|
||||||
function addressparser(str, options) {
|
/**
|
||||||
options = options || {};
|
* Parses an address list, recursing into the groups it holds.
|
||||||
const depth = options._depth || 0;
|
*
|
||||||
|
* The depth is threaded through the calls rather than carried in an options object, so a
|
||||||
|
* caller supplied one can not seed it and lift the recursion limit: `{ _depth: -1e9 }`
|
||||||
|
* bought a billion levels of nesting and turned the guard below into the stack overflow it
|
||||||
|
* is there to prevent.
|
||||||
|
*
|
||||||
|
* @param str Address field
|
||||||
|
* @param depth Current recursion depth for nested group protection
|
||||||
|
* @return An array of address objects
|
||||||
|
*/
|
||||||
|
function _parseAddressList(str, depth) {
|
||||||
// Prevent stack overflow from deeply nested groups (DoS protection)
|
// Prevent stack overflow from deeply nested groups (DoS protection)
|
||||||
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
||||||
return [];
|
return [];
|
||||||
@@ -688,7 +723,7 @@ function addressparser(str, options) {
|
|||||||
const tokens = tokenizer.tokenize();
|
const tokens = tokenizer.tokenize();
|
||||||
const addresses = [];
|
const addresses = [];
|
||||||
let address = [];
|
let address = [];
|
||||||
let parsedAddresses = [];
|
const parsedAddresses = [];
|
||||||
tokens.forEach(token => {
|
tokens.forEach(token => {
|
||||||
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
||||||
if (address.length) {
|
if (address.length) {
|
||||||
@@ -729,8 +764,11 @@ function addressparser(str, options) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
mergedAddresses.reverse();
|
mergedAddresses.reverse();
|
||||||
parsedAddresses = mergedAddresses;
|
return mergedAddresses;
|
||||||
if (options.flatten) {
|
}
|
||||||
|
function addressparser(str, options) {
|
||||||
|
const parsedAddresses = _parseAddressList(str, 0);
|
||||||
|
if (options?.flatten) {
|
||||||
const flatAddresses = [];
|
const flatAddresses = [];
|
||||||
const walkAddressList = (list) => {
|
const walkAddressList = (list) => {
|
||||||
list.forEach(entry => {
|
list.forEach(entry => {
|
||||||
|
|||||||
+28
-4
@@ -1,4 +1,31 @@
|
|||||||
import { Transform } from 'node:stream';
|
import { Transform } from 'node:stream';
|
||||||
|
/**
|
||||||
|
* Drops the SP and HTAB from both ends of a field name.
|
||||||
|
*
|
||||||
|
* An index scan rather than `/^[ \t]+|[ \t]+$/g`, which retries the trailing branch at
|
||||||
|
* every offset of a blank run that is followed by other text: the run is consumed greedily,
|
||||||
|
* `$` fails, and the engine gives the characters back one at a time before moving on to
|
||||||
|
* start the same walk one offset further in. A field name carrying a 128 KiB run of spaces
|
||||||
|
* between two atoms took about 6.7 seconds to trim, and the cost grows with its square.
|
||||||
|
*
|
||||||
|
* Not `.trim()`, which takes the other Unicode spaces along: an NBSP in front of a field
|
||||||
|
* name would come off and select the name into the signed set, where the relaxed
|
||||||
|
* canonicalization in sign.ts deliberately leaves everything but SP and HTAB alone.
|
||||||
|
*
|
||||||
|
* @param str Field name to trim
|
||||||
|
* @return The field name without the surrounding SP and HTAB
|
||||||
|
*/
|
||||||
|
function _trimFieldName(str) {
|
||||||
|
let start = 0;
|
||||||
|
let end = str.length;
|
||||||
|
while (start < end && (str.charCodeAt(start) === 0x20 || str.charCodeAt(start) === 0x09)) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
while (end > start && (str.charCodeAt(end - 1) === 0x20 || str.charCodeAt(end - 1) === 0x09)) {
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
return str.slice(start, end);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* MessageParser instance is a transform stream that separates message headers
|
* MessageParser instance is a transform stream that separates message headers
|
||||||
* from the rest of the body. Headers are emitted with the 'headers' event. Message
|
* from the rest of the body. Headers are emitted with the 'headers' event. Message
|
||||||
@@ -141,10 +168,7 @@ class MessageParser extends Transform {
|
|||||||
return lines
|
return lines
|
||||||
.filter(line => /[^ \t\r]/.test(line))
|
.filter(line => /[^ \t\r]/.test(line))
|
||||||
.map(line => ({
|
.map(line => ({
|
||||||
key: line
|
key: _trimFieldName(line.substr(0, line.indexOf(':'))).toLowerCase(),
|
||||||
.substr(0, line.indexOf(':'))
|
|
||||||
.replace(/^[ \t]+|[ \t]+$/g, '')
|
|
||||||
.toLowerCase(),
|
|
||||||
line
|
line
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|||||||
+1
@@ -36,6 +36,7 @@ export interface DKIMRelaxedHeaders {
|
|||||||
* Returns DKIM signature header line
|
* Returns DKIM signature header line
|
||||||
*
|
*
|
||||||
* @param headers Parsed headers object from MessageParser
|
* @param headers Parsed headers object from MessageParser
|
||||||
|
* @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
|
||||||
* @param bodyHash Base64 encoded hash of the message
|
* @param bodyHash Base64 encoded hash of the message
|
||||||
* @param options DKIM options
|
* @param options DKIM options
|
||||||
* @param options.domainName Domain name to be signed for
|
* @param options.domainName Domain name to be signed for
|
||||||
|
|||||||
+1
@@ -14,6 +14,7 @@ function unsupportedHashAlgoError(hashAlgo) {
|
|||||||
* Returns DKIM signature header line
|
* Returns DKIM signature header line
|
||||||
*
|
*
|
||||||
* @param headers Parsed headers object from MessageParser
|
* @param headers Parsed headers object from MessageParser
|
||||||
|
* @param hashAlgo Hash algorithm the body hash was calculated with, for example "sha256"
|
||||||
* @param bodyHash Base64 encoded hash of the message
|
* @param bodyHash Base64 encoded hash of the message
|
||||||
* @param options DKIM options
|
* @param options DKIM options
|
||||||
* @param options.domainName Domain name to be signed for
|
* @param options.domainName Domain name to be signed for
|
||||||
|
|||||||
+7
-1
@@ -193,9 +193,15 @@ declare class Mail<out T = SentMessageInfo, out D extends TransportOptions = Tra
|
|||||||
* Sends an email using the preselected transport object
|
* Sends an email using the preselected transport object
|
||||||
*
|
*
|
||||||
* @param data E-data description
|
* @param data E-data description
|
||||||
* @param callback Callback to run once the sending succeeded or failed
|
* @returns Promise that resolves with the result of the send
|
||||||
*/
|
*/
|
||||||
sendMail(data: SendMailOptions): Promise<T>;
|
sendMail(data: SendMailOptions): Promise<T>;
|
||||||
|
/**
|
||||||
|
* Sends an email using the preselected transport object
|
||||||
|
*
|
||||||
|
* @param data E-data description
|
||||||
|
* @param callback Callback to run once the sending succeeded or failed
|
||||||
|
*/
|
||||||
sendMail(data: SendMailOptions, callback: SendMailCallback<T>): void;
|
sendMail(data: SendMailOptions, callback: SendMailCallback<T>): void;
|
||||||
getVersionString(): string;
|
getVersionString(): string;
|
||||||
/**
|
/**
|
||||||
|
|||||||
+2
-1
@@ -59,6 +59,7 @@ export declare function quoteString(value?: string): string;
|
|||||||
* no need to encode the values in any way. If the value is plaintext but has
|
* no need to encode the values in any way. If the value is plaintext but has
|
||||||
* longer lines then allowed, then use format=flowed
|
* longer lines then allowed, then use format=flowed
|
||||||
*
|
*
|
||||||
|
* @param str Multi line string to check
|
||||||
* @param lineLength Max line length to check for
|
* @param lineLength Max line length to check for
|
||||||
* @returns Returns true if there is at least one line longer than lineLength chars
|
* @returns Returns true if there is at least one line longer than lineLength chars
|
||||||
*/
|
*/
|
||||||
@@ -100,9 +101,9 @@ export declare function buildHeaderValue(structured: StructuredHeaderValue): str
|
|||||||
* title*0*=utf-8''unicode
|
* title*0*=utf-8''unicode
|
||||||
* title*1*=%20string
|
* title*1*=%20string
|
||||||
*
|
*
|
||||||
|
* @param key Parameter name the generated keys are built from, for example title
|
||||||
* @param data String to be encoded
|
* @param data String to be encoded
|
||||||
* @param [maxLength=50] Max length for generated chunks
|
* @param [maxLength=50] Max length for generated chunks
|
||||||
* @param [fromCharset='UTF-8'] Source sharacter set
|
|
||||||
* @return A list of encoded keys and headers
|
* @return A list of encoded keys and headers
|
||||||
*/
|
*/
|
||||||
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
export declare function buildHeaderParam(key: string, data: string | Buffer, maxLength?: number): EncodedHeaderParam[];
|
||||||
|
|||||||
+187
-73
@@ -39,6 +39,7 @@ export function quoteString(value) {
|
|||||||
* no need to encode the values in any way. If the value is plaintext but has
|
* no need to encode the values in any way. If the value is plaintext but has
|
||||||
* longer lines then allowed, then use format=flowed
|
* longer lines then allowed, then use format=flowed
|
||||||
*
|
*
|
||||||
|
* @param str Multi line string to check
|
||||||
* @param lineLength Max line length to check for
|
* @param lineLength Max line length to check for
|
||||||
* @returns Returns true if there is at least one line longer than lineLength chars
|
* @returns Returns true if there is at least one line longer than lineLength chars
|
||||||
*/
|
*/
|
||||||
@@ -185,7 +186,10 @@ export function buildHeaderValue(structured) {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (/[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
|
else if (!value.length || /[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
|
||||||
|
// a parameter value is a token or a quoted-string and a token is never empty, so
|
||||||
|
// a valueless parameter such as the 'flag' of 'multipart/mixed; flag; boundary=b'
|
||||||
|
// goes out as 'flag=""' rather than as the 'flag=' that parses as neither
|
||||||
paramsArray.push(param + '=' + JSON.stringify(value));
|
paramsArray.push(param + '=' + JSON.stringify(value));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -207,9 +211,9 @@ export function buildHeaderValue(structured) {
|
|||||||
* title*0*=utf-8''unicode
|
* title*0*=utf-8''unicode
|
||||||
* title*1*=%20string
|
* title*1*=%20string
|
||||||
*
|
*
|
||||||
|
* @param key Parameter name the generated keys are built from, for example title
|
||||||
* @param data String to be encoded
|
* @param data String to be encoded
|
||||||
* @param [maxLength=50] Max length for generated chunks
|
* @param [maxLength=50] Max length for generated chunks
|
||||||
* @param [fromCharset='UTF-8'] Source sharacter set
|
|
||||||
* @return A list of encoded keys and headers
|
* @return A list of encoded keys and headers
|
||||||
*/
|
*/
|
||||||
export function buildHeaderParam(key, data, maxLength) {
|
export function buildHeaderParam(key, data, maxLength) {
|
||||||
@@ -336,6 +340,26 @@ export function buildHeaderParam(key, data, maxLength) {
|
|||||||
value: item.line
|
value: item.line
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
|
* An RFC 2045 token: printable ASCII without SPACE, the control characters, DEL and the
|
||||||
|
* tspecials. A charset name is one, and the name a continuation carries is written into
|
||||||
|
* the encoded word the parameter value becomes, so it is checked against this before it
|
||||||
|
* goes in. Whitespace used to come off it only because the value was trimmed first.
|
||||||
|
*/
|
||||||
|
const TOKEN = /^[^\x00-\x20\x7f()<>@,;:\\"/[\]?=]+$/;
|
||||||
|
/**
|
||||||
|
* Whether a string can be a header parameter name.
|
||||||
|
*
|
||||||
|
* A parameter name is a token, so it is never empty. A "__proto__" name would target the
|
||||||
|
* prototype chain of the params object instead of an own property of it and read back as
|
||||||
|
* Object.prototype, so it is no name either.
|
||||||
|
*
|
||||||
|
* @param name Candidate parameter name, already lowercased
|
||||||
|
* @return true when the name can be used
|
||||||
|
*/
|
||||||
|
function _isParamName(name) {
|
||||||
|
return !!name && !isProtoKey(name);
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Parses a header value with key=value arguments into a structured
|
* Parses a header value with key=value arguments into a structured
|
||||||
* object.
|
* object.
|
||||||
@@ -356,117 +380,215 @@ export function parseHeaderValue(str) {
|
|||||||
value: '',
|
value: '',
|
||||||
params: {}
|
params: {}
|
||||||
};
|
};
|
||||||
// Parameter names come from a caller supplied contentType/contentDisposition. A
|
// A duplicated parameter resolves to its first occurrence, the way a duplicated header
|
||||||
// "__proto__" name would target the prototype chain of the params object instead of
|
// does. Letting the last one win disagrees with the receivers that take the first, and
|
||||||
// an own property of it, and read back as Object.prototype, so it is dropped.
|
// the two readings of 'boundary="b"; boundary="c"' name different delimiters. The
|
||||||
|
// continuation join below is the only writer of the name it builds, so it tests the
|
||||||
|
// name with _isParamName directly rather than taking that rule along from here.
|
||||||
const setParam = (name, value) => {
|
const setParam = (name, value) => {
|
||||||
if (!isProtoKey(name)) {
|
name = name.toLowerCase();
|
||||||
|
if (_isParamName(name) && !Object.prototype.hasOwnProperty.call(response.params, name)) {
|
||||||
response.params[name] = value;
|
response.params[name] = value;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let key = false;
|
let key = false;
|
||||||
let value = '';
|
let value = '';
|
||||||
let type = 'value';
|
let stage = 'value';
|
||||||
let quote = false;
|
let quote = false;
|
||||||
let escaped = false;
|
let escaped = false;
|
||||||
let chr;
|
let chr;
|
||||||
|
// Whitespace seen outside a quoted string is held back until a significant character
|
||||||
|
// follows it, so the whitespace around a value is dropped without trimming spaces the
|
||||||
|
// sender quoted on purpose. Trimming the stored value instead loses the trailing space
|
||||||
|
// of 'filename*0="Annual Report "', which the next continuation section is appended to.
|
||||||
|
let pendingSpace = '';
|
||||||
|
let quoteClosed = false;
|
||||||
|
// Whitespace ahead of the first character of a value is padding and is dropped, the
|
||||||
|
// whitespace between two characters of it is content
|
||||||
|
const flushSpace = () => {
|
||||||
|
if (value.length) {
|
||||||
|
value += pendingSpace;
|
||||||
|
}
|
||||||
|
pendingSpace = '';
|
||||||
|
};
|
||||||
|
const addChr = (c) => {
|
||||||
|
flushSpace();
|
||||||
|
value += c;
|
||||||
|
};
|
||||||
|
const takeValue = () => {
|
||||||
|
const taken = value;
|
||||||
|
value = '';
|
||||||
|
pendingSpace = '';
|
||||||
|
quoteClosed = false;
|
||||||
|
return taken;
|
||||||
|
};
|
||||||
|
const storeValue = () => {
|
||||||
|
const taken = takeValue();
|
||||||
|
if (key === false) {
|
||||||
|
response.value = taken;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setParam(key, taken);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// A parameter name with no '=' is a valueless parameter, not the start of the next one.
|
||||||
|
// Without this the name keeps growing across the ';' and swallows whatever follows, which
|
||||||
|
// is how 'multipart/mixed; flag; boundary="AAA"' lost its boundary to a parameter named
|
||||||
|
// 'flag; boundary' and left the node declaring the generated boundary beside the asked
|
||||||
|
// for one, so a receiver reading the first of the two found no delimiter it matched.
|
||||||
|
const storeEmptyKey = () => {
|
||||||
|
setParam(takeValue().trim(), '');
|
||||||
|
};
|
||||||
for (let i = 0, len = str.length; i < len; i++) {
|
for (let i = 0, len = str.length; i < len; i++) {
|
||||||
chr = str.charAt(i);
|
chr = str.charAt(i);
|
||||||
if (type === 'key') {
|
if (stage === 'key') {
|
||||||
if (chr === '=') {
|
if (chr === '=') {
|
||||||
key = value.trim().toLowerCase();
|
key = takeValue().trim();
|
||||||
type = 'value';
|
stage = 'value';
|
||||||
value = '';
|
continue;
|
||||||
|
}
|
||||||
|
if (chr === ';') {
|
||||||
|
storeEmptyKey();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
value += chr;
|
value += chr;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (escaped) {
|
if (quoteClosed && chr !== ';') {
|
||||||
value += chr;
|
// Nothing behind a closed quoted string reaches the value. RFC 2045 says a
|
||||||
|
// parameter value is a token or a quoted string and not both, so what follows
|
||||||
|
// one is junk and only the ';' that ends the parameter still counts. Tested
|
||||||
|
// ahead of the branches rather than beside the append at the foot of them,
|
||||||
|
// where each branch above was a way around it: a second '"' reopened quoting
|
||||||
|
// and swallowed the rest of the header, so 'boundary="AAA" "; boundary=BBB"'
|
||||||
|
// read as a single boundary of 'AAA ', and the junk of
|
||||||
|
// 'boundary="AAA" (unterminated comment' still joined the declared boundary
|
||||||
|
// through the escape branch. quoteClosed is only ever set while no quote is
|
||||||
|
// open, and this is what keeps one from being opened afterwards.
|
||||||
|
escaped = false;
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
else if (chr === '\\') {
|
if (escaped) {
|
||||||
|
addChr(chr);
|
||||||
|
}
|
||||||
|
else if (quote && chr === '\\') {
|
||||||
|
// a backslash only escapes inside a quoted string, everywhere else it is an
|
||||||
|
// ordinary character. Treating it as an escape turns the parameter value
|
||||||
|
// 'C:\Users\me\report.txt' into 'C:Usersmereport.txt'
|
||||||
escaped = true;
|
escaped = true;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
else if (quote && chr === quote) {
|
else if (quote && chr === quote) {
|
||||||
quote = false;
|
quote = false;
|
||||||
|
quoteClosed = true;
|
||||||
}
|
}
|
||||||
else if (!quote && chr === '"') {
|
else if (!quote && chr === '"') {
|
||||||
quote = chr;
|
quote = chr;
|
||||||
|
flushSpace();
|
||||||
}
|
}
|
||||||
else if (!quote && chr === ';') {
|
else if (!quote && chr === ';') {
|
||||||
if (key === false) {
|
storeValue();
|
||||||
response.value = value.trim();
|
stage = 'key';
|
||||||
|
}
|
||||||
|
else if (!quote && (chr === ' ' || chr === '\t')) {
|
||||||
|
pendingSpace += chr;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setParam(key, value.trim());
|
addChr(chr);
|
||||||
}
|
|
||||||
type = 'key';
|
|
||||||
value = '';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
value += chr;
|
|
||||||
}
|
}
|
||||||
escaped = false;
|
escaped = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (type === 'value') {
|
if (stage === 'value') {
|
||||||
if (key === false) {
|
storeValue();
|
||||||
response.value = value.trim();
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
setParam(key, value.trim());
|
// a key with no value, as in 'Header-Key: somevalue; key=value; emptykey'
|
||||||
}
|
storeEmptyKey();
|
||||||
}
|
|
||||||
else if (value.trim()) {
|
|
||||||
setParam(value.trim().toLowerCase(), '');
|
|
||||||
}
|
}
|
||||||
// handle parameter value continuations
|
// handle parameter value continuations
|
||||||
// https://tools.ietf.org/html/rfc2231#section-3
|
// https://tools.ietf.org/html/rfc2231#section-3
|
||||||
// preprocess values
|
// Sections are collected in a list and ordered below rather than written into an array
|
||||||
|
// at their own section number. An index write makes the array as long as the number the
|
||||||
|
// header asked for, and the join that follows walks all of it, so the 55 byte value
|
||||||
|
// "attachment; filename*0*=utf-8''a; filename*4000000000=b" held a core for over two
|
||||||
|
// minutes.
|
||||||
|
const continuations = new Map();
|
||||||
Object.keys(response.params).forEach(key => {
|
Object.keys(response.params).forEach(key => {
|
||||||
let actualKey, nr, match, value;
|
const match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/);
|
||||||
if ((match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/))) {
|
if (!match) {
|
||||||
actualKey = key.substr(0, match.index);
|
// not a continuation parameter, there is nothing to join
|
||||||
nr = Number(match[2] || match[3]) || 0;
|
|
||||||
if (isProtoKey(actualKey)) {
|
|
||||||
// see setParam. Reading it back would yield Object.prototype, which is
|
|
||||||
// an object, so the initializer below would be skipped and the write
|
|
||||||
// that follows would throw out of a header build the caller can not catch
|
|
||||||
delete response.params[key];
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!response.params[actualKey] || typeof response.params[actualKey] !== 'object') {
|
const actualKey = key.substr(0, match.index);
|
||||||
response.params[actualKey] = {
|
const nr = Number(match[2] || match[3]) || 0;
|
||||||
charset: false,
|
// RFC 2231 section 4.1: only a section whose name ends in '*' is percent encoded
|
||||||
values: []
|
const encoded = match[0].substr(-1) === '*';
|
||||||
};
|
|
||||||
}
|
|
||||||
value = response.params[key];
|
|
||||||
if (nr === 0 && match[0].substr(-1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
|
|
||||||
response.params[actualKey].charset = match[1] || 'iso-8859-1';
|
|
||||||
value = match[2];
|
|
||||||
}
|
|
||||||
response.params[actualKey].values[nr] = value;
|
|
||||||
// remove the old reference
|
// remove the old reference
|
||||||
|
let value = response.params[key];
|
||||||
delete response.params[key];
|
delete response.params[key];
|
||||||
|
if (!_isParamName(actualKey)) {
|
||||||
|
// the joined value can not be written back under this name. It is empty when the
|
||||||
|
// continuation suffix was all there was of it, as in the bare '*' of 'text/plain; a;*'
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
let continuation = continuations.get(actualKey);
|
||||||
|
if (!continuation) {
|
||||||
|
continuation = { charset: false, sections: [] };
|
||||||
|
continuations.set(actualKey, continuation);
|
||||||
|
}
|
||||||
|
const charsetMatch = nr === 0 && encoded ? value.match(/^([^']*)'[^']*'(.*)$/) : null;
|
||||||
|
if (charsetMatch) {
|
||||||
|
// the charset is a token, and anything else named as one is no charset a consumer
|
||||||
|
// could resolve, so it reads as the unnamed case rather than being carried into
|
||||||
|
// the encoded word below. A "\r\n" of a prefix would otherwise reach a consumer
|
||||||
|
// of the parsed value as the charset of a word that no decoder can act on
|
||||||
|
continuation.charset = TOKEN.test(charsetMatch[1]) ? charsetMatch[1] : 'iso-8859-1';
|
||||||
|
value = charsetMatch[2];
|
||||||
|
}
|
||||||
|
continuation.sections.push({ nr, value, encoded });
|
||||||
});
|
});
|
||||||
// concatenate split rfc2231 strings and convert encoded strings to mime encoded words
|
// concatenate split rfc2231 strings and convert encoded strings to mime encoded words
|
||||||
Object.keys(response.params).forEach(key => {
|
continuations.forEach((continuation, key) => {
|
||||||
let value;
|
if (Object.prototype.hasOwnProperty.call(response.params, key)) {
|
||||||
if (response.params[key] && Array.isArray(response.params[key].values)) {
|
// The same name was also given as a plain parameter, which the starred keys were
|
||||||
value = response.params[key].values.map((val) => val || '').join('');
|
// just deleted from around, so this write would be the only one in the function
|
||||||
if (response.params[key].charset) {
|
// to override a name already taken. 'filename=plain.txt; filename*0=evil.txt'
|
||||||
|
// resolves to the plain parameter either way round, so the reading does not come
|
||||||
|
// down to which of the two spellings the sender put first
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
continuation.sections.sort((a, b) => a.nr - b.nr);
|
||||||
|
if (!continuation.charset) {
|
||||||
|
// nothing said which charset the percent escapes of an encoded section are in,
|
||||||
|
// so every section is passed on as the text it already is
|
||||||
|
response.params[key] = continuation.sections.map(section => section.value).join('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
// convert "%AB" to "=?charset?Q?=AB?="
|
// convert "%AB" to "=?charset?Q?=AB?="
|
||||||
response.params[key] =
|
response.params[key] = '=?' + continuation.charset + '?Q?' + continuation.sections.map(_encodeContinuationSection).join('') + '?=';
|
||||||
'=?' +
|
});
|
||||||
response.params[key].charset +
|
return response;
|
||||||
'?Q?' +
|
}
|
||||||
value
|
/**
|
||||||
|
* Renders one parameter value continuation section as the payload of a Q encoded word.
|
||||||
|
*
|
||||||
|
* A section whose name ends in '*' is percent encoded and its escapes carry the bytes of
|
||||||
|
* the value, so they only have to be rewritten into the "=AB" spelling a Q encoded word
|
||||||
|
* uses. A section without the '*' is literal text (RFC 2231 section 4.1), so its '%' is a
|
||||||
|
* '%' and is escaped along with the characters a Q encoded word can not carry bare.
|
||||||
|
* Decoding a literal section invents bytes that never appeared on the wire: it is how the
|
||||||
|
* value 'filename*0*=utf-8''safe; filename*1=%2F..%2F..%2Fetc%2Fpasswd' was emitted as a
|
||||||
|
* filename every receiving client reads back as 'safe/../../etc/passwd'.
|
||||||
|
*
|
||||||
|
* @param section One collected continuation section
|
||||||
|
* @return The section as Q encoded word payload
|
||||||
|
*/
|
||||||
|
function _encodeContinuationSection(section) {
|
||||||
|
const specials = section.encoded ? /[=?_\s]/g : /[=?_\s%]/g;
|
||||||
|
return (section.value
|
||||||
// fix invalidly encoded chars
|
// fix invalidly encoded chars
|
||||||
.replace(/[=?_\s]/g, s => {
|
.replace(specials, s => {
|
||||||
const c = s.charCodeAt(0).toString(16);
|
const c = s.charCodeAt(0).toString(16);
|
||||||
if (s === ' ') {
|
if (s === ' ') {
|
||||||
return '_';
|
return '_';
|
||||||
@@ -474,15 +596,7 @@ export function parseHeaderValue(str) {
|
|||||||
return '%' + (c.length < 2 ? '0' : '') + c;
|
return '%' + (c.length < 2 ? '0' : '') + c;
|
||||||
})
|
})
|
||||||
// change from urlencoding to percent encoding
|
// change from urlencoding to percent encoding
|
||||||
.replace(/%/g, '=') +
|
.replace(/%/g, '='));
|
||||||
'?=';
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
response.params[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Returns file extension for a content type string. If no suitable extensions
|
* Returns file extension for a content type string. If no suitable extensions
|
||||||
|
|||||||
+2
-1
@@ -1256,6 +1256,7 @@ class MimeNode {
|
|||||||
*
|
*
|
||||||
* @param addresses An array of address objects
|
* @param addresses An array of address objects
|
||||||
* @param [uniqueList] An array to be populated with addresses
|
* @param [uniqueList] An array to be populated with addresses
|
||||||
|
* @param [seenAddresses] Addresses already added to uniqueList, shared with recursive calls to keep deduplication linear
|
||||||
* @return address string
|
* @return address string
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
@@ -1393,7 +1394,7 @@ class MimeNode {
|
|||||||
/**
|
/**
|
||||||
* If needed, mime encodes the name part
|
* If needed, mime encodes the name part
|
||||||
*
|
*
|
||||||
* @param name Name part of an address
|
* @param value Name part of an address
|
||||||
* @returns Mime word encoded string if needed
|
* @returns Mime word encoded string if needed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|||||||
+13
-2
@@ -75,11 +75,22 @@ export declare function createTransport(transporter?: TransportConfig | Transpor
|
|||||||
/**
|
/**
|
||||||
* Creates a test account from the Ethereal service (https://ethereal.email)
|
* Creates a test account from the Ethereal service (https://ethereal.email)
|
||||||
*
|
*
|
||||||
* @param apiUrl Optional API endpoint, defaults to https://api.nodemailer.com
|
* @param callback Callback function to run with the account object
|
||||||
* @param callback Callback function to run with the account object. If not set, a Promise is returned
|
|
||||||
*/
|
*/
|
||||||
export declare function createTestAccount(callback: TestAccountCallback): void;
|
export declare function createTestAccount(callback: TestAccountCallback): void;
|
||||||
|
/**
|
||||||
|
* Creates a test account from the Ethereal service (https://ethereal.email)
|
||||||
|
*
|
||||||
|
* @param apiUrl API endpoint, defaults to https://api.nodemailer.com
|
||||||
|
* @param callback Callback function to run with the account object
|
||||||
|
*/
|
||||||
export declare function createTestAccount(apiUrl: string | false | null | undefined, callback: TestAccountCallback): void;
|
export declare function createTestAccount(apiUrl: string | false | null | undefined, callback: TestAccountCallback): void;
|
||||||
|
/**
|
||||||
|
* Creates a test account from the Ethereal service (https://ethereal.email)
|
||||||
|
*
|
||||||
|
* @param [apiUrl] API endpoint, defaults to https://api.nodemailer.com
|
||||||
|
* @returns Promise that resolves with the account object
|
||||||
|
*/
|
||||||
export declare function createTestAccount(apiUrl?: string | false | null): Promise<TestAccount>;
|
export declare function createTestAccount(apiUrl?: string | false | null): Promise<TestAccount>;
|
||||||
/**
|
/**
|
||||||
* Resolves the Ethereal web URL for a message sent through an Ethereal test account
|
* Resolves the Ethereal web URL for a message sent through an Ethereal test account
|
||||||
|
|||||||
+1
-1
@@ -1,3 +1,3 @@
|
|||||||
export declare const name = "nodemailer";
|
export declare const name = "nodemailer";
|
||||||
export declare const version = "10.0.13";
|
export declare const version = "10.0.14";
|
||||||
export declare const homepage = "https://nodemailer.com/";
|
export declare const homepage = "https://nodemailer.com/";
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
// Generated by scripts/build.js from package.json. Do not edit by hand.
|
// Generated by scripts/build.js from package.json. Do not edit by hand.
|
||||||
export const name = 'nodemailer';
|
export const name = 'nodemailer';
|
||||||
export const version = '10.0.13';
|
export const version = '10.0.14';
|
||||||
export const homepage = 'https://nodemailer.com/';
|
export const homepage = 'https://nodemailer.com/';
|
||||||
+1
@@ -175,6 +175,7 @@ const basicToDigit = function (codePoint) {
|
|||||||
* @see `basicToDigit()`
|
* @see `basicToDigit()`
|
||||||
* @private
|
* @private
|
||||||
* @param digit The numeric value of a basic code point.
|
* @param digit The numeric value of a basic code point.
|
||||||
|
* @param flag Non-zero to use the uppercase form of the code point.
|
||||||
* @returns The basic code point whose value (when used for
|
* @returns The basic code point whose value (when used for
|
||||||
* representing integers) is `digit`, which needs to be in the range
|
* representing integers) is `digit`, which needs to be in the range
|
||||||
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
||||||
|
|||||||
+6
-1
@@ -85,9 +85,14 @@ declare class SESTransport extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Verifies SES configuration
|
* Verifies SES configuration
|
||||||
*
|
*
|
||||||
* @param callback Callback function
|
* @returns Promise that resolves to true if the configuration is usable
|
||||||
*/
|
*/
|
||||||
verify(): Promise<true>;
|
verify(): Promise<true>;
|
||||||
|
/**
|
||||||
|
* Verifies SES configuration
|
||||||
|
*
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
verify(callback: VerifyCallback): void;
|
verify(callback: VerifyCallback): void;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
+25
-1
@@ -187,6 +187,7 @@ export declare const parseConnectionUrl: (str?: string | null) => ConnectionUrlO
|
|||||||
* creates a default console logger
|
* creates a default console logger
|
||||||
*
|
*
|
||||||
* @param [options] Options object that might include 'logger' value
|
* @param [options] Options object that might include 'logger' value
|
||||||
|
* @param [defaults] Fields merged into every log entry, overridden by the fields of the entry itself
|
||||||
* @return bunyan compatible logger
|
* @return bunyan compatible logger
|
||||||
*/
|
*/
|
||||||
export declare const getLogger: (options?: GetLoggerOptions, defaults?: LogEntry) => Logger;
|
export declare const getLogger: (options?: GetLoggerOptions, defaults?: LogEntry) => Logger;
|
||||||
@@ -210,18 +211,41 @@ export declare const parseDataURI: (uri: unknown) => ParsedDataURI | null;
|
|||||||
*
|
*
|
||||||
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
* @param key Property name or an Array index
|
* @param key Property name or an Array index
|
||||||
* @param [options] Optional access policy: { disableFileAccess, disableUrlAccess }
|
|
||||||
* @param callback Callback function with (err, value)
|
* @param callback Callback function with (err, value)
|
||||||
*/
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, callback: ResolveContentCallback): void;
|
}, key: string | number, callback: ResolveContentCallback): void;
|
||||||
|
/**
|
||||||
|
* Resolves a String or a Buffer value for content value
|
||||||
|
*
|
||||||
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
|
* @param key Property name or an Array index
|
||||||
|
* @param options Access policy: { disableFileAccess, disableUrlAccess }
|
||||||
|
* @param callback Callback function with (err, value)
|
||||||
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback): void;
|
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback): void;
|
||||||
|
/**
|
||||||
|
* Resolves a String or a Buffer value for content value
|
||||||
|
*
|
||||||
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
|
* @param key Property name or an Array index
|
||||||
|
* @param [options] Optional access policy: { disableFileAccess, disableUrlAccess }
|
||||||
|
* @returns Promise that resolves with the value
|
||||||
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, options?: ResolveContentOptions | false): Promise<any>;
|
}, key: string | number, options?: ResolveContentOptions | false): Promise<any>;
|
||||||
|
/**
|
||||||
|
* Resolves a String or a Buffer value for content value
|
||||||
|
*
|
||||||
|
* @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
|
||||||
|
* @param key Property name or an Array index
|
||||||
|
* @param options Access policy: { disableFileAccess, disableUrlAccess }
|
||||||
|
* @param callback Callback function with (err, value), a Promise is returned if not set
|
||||||
|
*/
|
||||||
export declare function resolveContent(data: {
|
export declare function resolveContent(data: {
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback | undefined): Promise<any> | void;
|
}, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback | undefined): Promise<any> | void;
|
||||||
|
|||||||
+1
@@ -318,6 +318,7 @@ export const _logFunc = (logger, level, defaults, data, message, ...args) => {
|
|||||||
* creates a default console logger
|
* creates a default console logger
|
||||||
*
|
*
|
||||||
* @param [options] Options object that might include 'logger' value
|
* @param [options] Options object that might include 'logger' value
|
||||||
|
* @param [defaults] Fields merged into every log entry, overridden by the fields of the entry itself
|
||||||
* @return bunyan compatible logger
|
* @return bunyan compatible logger
|
||||||
*/
|
*/
|
||||||
export const getLogger = (options, defaults) => {
|
export const getLogger = (options, defaults) => {
|
||||||
|
|||||||
+11
-3
@@ -21,13 +21,21 @@ export type HttpProxyClientCallback = Callback<net.Socket>;
|
|||||||
* socket.write("GET / HTTP/1.0\r\n\r\n");
|
* socket.write("GET / HTTP/1.0\r\n\r\n");
|
||||||
* });
|
* });
|
||||||
*
|
*
|
||||||
* @param proxyUrl proxy configuration, etg "http://proxy.host:3128/"
|
* @param proxyUrl proxy configuration, e.g. "http://proxy.host:3128/"
|
||||||
* @param destinationPort Port to open in destination host
|
* @param destinationPort Port to open in destination host
|
||||||
* @param destinationHost Destination hostname
|
* @param destinationHost Destination hostname
|
||||||
* @param [tlsOptions] Optional TLS options for an HTTPS proxy (e.g. { rejectUnauthorized: false })
|
* @param callback Callback to run with the socket object once connection is established
|
||||||
* @param callback Callback to run with the rocket object once connection is established
|
|
||||||
*/
|
*/
|
||||||
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, callback: HttpProxyClientCallback): void;
|
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, callback: HttpProxyClientCallback): void;
|
||||||
|
/**
|
||||||
|
* Establishes proxied connection to destinationPort through an HTTPS proxy
|
||||||
|
*
|
||||||
|
* @param proxyUrl proxy configuration, e.g. "https://proxy.host:3128/"
|
||||||
|
* @param destinationPort Port to open in destination host
|
||||||
|
* @param destinationHost Destination hostname
|
||||||
|
* @param tlsOptions TLS options for the proxy connection (e.g. { rejectUnauthorized: false })
|
||||||
|
* @param callback Callback to run with the socket object once connection is established
|
||||||
|
*/
|
||||||
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, tlsOptions: HttpProxyClientOptions | undefined, callback: HttpProxyClientCallback): void;
|
declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, tlsOptions: HttpProxyClientOptions | undefined, callback: HttpProxyClientCallback): void;
|
||||||
/**
|
/**
|
||||||
* Socket timeout in milliseconds while the CONNECT handshake is in progress, defaults to 30 seconds.
|
* Socket timeout in milliseconds while the CONNECT handshake is in progress, defaults to 30 seconds.
|
||||||
|
|||||||
+17
-7
@@ -88,18 +88,28 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
|
|||||||
.join('\r\n') +
|
.join('\r\n') +
|
||||||
// End request
|
// End request
|
||||||
'\r\n\r\n');
|
'\r\n\r\n');
|
||||||
let headers = '';
|
// The response is collected as chunks and only the bytes that just arrived, together
|
||||||
|
// with the three before them, are searched for the end of the headers. Appending to a
|
||||||
|
// string and searching all of it again re-read the whole response on every chunk.
|
||||||
|
const chunks = [];
|
||||||
|
let received = 0;
|
||||||
|
let tail = '';
|
||||||
const onSocketData = (chunk) => {
|
const onSocketData = (chunk) => {
|
||||||
let match;
|
let match;
|
||||||
let remainder;
|
|
||||||
if (finished) {
|
if (finished) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
headers += chunk.toString('binary');
|
const window = tail + chunk.toString('binary');
|
||||||
if ((match = headers.match(/\r\n\r\n/))) {
|
const windowEnd = window.indexOf('\r\n\r\n');
|
||||||
|
chunks.push(chunk);
|
||||||
|
received += chunk.length;
|
||||||
|
tail = window.slice(-3);
|
||||||
|
if (windowEnd >= 0) {
|
||||||
socket.removeListener('data', onSocketData);
|
socket.removeListener('data', onSocketData);
|
||||||
remainder = headers.substr(match.index + match[0].length);
|
const headerEnd = received - window.length + windowEnd;
|
||||||
headers = headers.substr(0, match.index);
|
const response = Buffer.concat(chunks, received).toString('binary');
|
||||||
|
const headers = response.substr(0, headerEnd);
|
||||||
|
const remainder = response.substr(headerEnd + 4);
|
||||||
if (remainder) {
|
if (remainder) {
|
||||||
socket.unshift(Buffer.from(remainder, 'binary'));
|
socket.unshift(Buffer.from(remainder, 'binary'));
|
||||||
}
|
}
|
||||||
@@ -123,7 +133,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
|
|||||||
socket.setTimeout(0);
|
socket.setTimeout(0);
|
||||||
return done(null, socket);
|
return done(null, socket);
|
||||||
}
|
}
|
||||||
if (headers.length > MAX_RESPONSE_HEADER_BYTES) {
|
if (received > MAX_RESPONSE_HEADER_BYTES) {
|
||||||
socket.removeListener('data', onSocketData);
|
socket.removeListener('data', onSocketData);
|
||||||
const err = new Error('Proxy response headers too large');
|
const err = new Error('Proxy response headers too large');
|
||||||
err.code = errors.EPROXY;
|
err.code = errors.EPROXY;
|
||||||
|
|||||||
+1
-1
@@ -379,7 +379,7 @@ declare class SMTPConnection extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param envelope Envelope object, {from: addr, to: [addr]}
|
* @param envelope Envelope object, {from: addr, to: [addr]}
|
||||||
* @param message String, Buffer or a Stream
|
* @param message String, Buffer or a Stream
|
||||||
* @param callback Callback to return once sending is completed
|
* @param done Callback to return once sending is completed
|
||||||
*/
|
*/
|
||||||
send(envelope: SMTPEnvelope, message: string | Buffer | Readable, done: SMTPConnectionSendCallback): void;
|
send(envelope: SMTPEnvelope, message: string | Buffer | Readable, done: SMTPConnectionSendCallback): void;
|
||||||
/**
|
/**
|
||||||
|
|||||||
+15
-1
@@ -576,7 +576,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param envelope Envelope object, {from: addr, to: [addr]}
|
* @param envelope Envelope object, {from: addr, to: [addr]}
|
||||||
* @param message String, Buffer or a Stream
|
* @param message String, Buffer or a Stream
|
||||||
* @param callback Callback to return once sending is completed
|
* @param done Callback to return once sending is completed
|
||||||
*/
|
*/
|
||||||
send(envelope, message, done) {
|
send(envelope, message, done) {
|
||||||
// ensure that the callback is only called once. The public callback type has a
|
// ensure that the callback is only called once. The public callback type has a
|
||||||
@@ -789,6 +789,8 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* @event
|
* @event
|
||||||
* @param err Error object
|
* @param err Error object
|
||||||
* @param type Error name
|
* @param type Error name
|
||||||
|
* @param data Server response that triggered the error, false if there is none
|
||||||
|
* @param command SMTP command that was in flight
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_onError(err, type, data, command) {
|
_onError(err, type, data, command) {
|
||||||
@@ -1057,6 +1059,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* {from:'...', to:['...']}
|
* {from:'...', to:['...']}
|
||||||
* or
|
* or
|
||||||
* {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
|
* {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_setEnvelope(envelope, callback) {
|
_setEnvelope(envelope, callback) {
|
||||||
@@ -1451,6 +1454,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* hosts invalidly use a longer message than VXNlcm5hbWU6
|
* hosts invalidly use a longer message than VXNlcm5hbWU6
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_LOGIN_USER(str, callback) {
|
_actionAUTH_LOGIN_USER(str, callback) {
|
||||||
@@ -1472,6 +1476,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* base64 encoded again.
|
* base64 encoded again.
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_CRAM_MD5(str, callback) {
|
_actionAUTH_CRAM_MD5(str, callback) {
|
||||||
@@ -1496,6 +1501,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* the user can be considered logged in. Start waiting for a message to send
|
* the user can be considered logged in. Start waiting for a message to send
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_CRAM_MD5_PASS(str, callback) {
|
_actionAUTH_CRAM_MD5_PASS(str, callback) {
|
||||||
@@ -1517,6 +1523,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* response needs to be base64 encoded password.
|
* response needs to be base64 encoded password.
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTH_LOGIN_PASS(str, callback) {
|
_actionAUTH_LOGIN_PASS(str, callback) {
|
||||||
@@ -1536,6 +1543,8 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* the user can be considered logged in. Start waiting for a message to send
|
* the user can be considered logged in. Start waiting for a message to send
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param isRetry True if this is a retry after a failed login, or the callback itself
|
||||||
|
* @param [callback] Callback to run once the authentication sequence completes
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionAUTHComplete(str, isRetry, callback) {
|
_actionAUTHComplete(str, isRetry, callback) {
|
||||||
@@ -1578,6 +1587,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* Handle response for a MAIL FROM: command
|
* Handle response for a MAIL FROM: command
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionMAIL(str, callback) {
|
_actionMAIL(str, callback) {
|
||||||
@@ -1606,6 +1616,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* Handle response for a RCPT TO: command
|
* Handle response for a RCPT TO: command
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionRCPT(str, callback) {
|
_actionRCPT(str, callback) {
|
||||||
@@ -1657,6 +1668,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* Handle response for a DATA command
|
* Handle response for a DATA command
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run once the envelope is processed
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionDATA(str, callback) {
|
_actionDATA(str, callback) {
|
||||||
@@ -1683,6 +1695,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* We expect a single response that defines if the sending succeeded or failed
|
* We expect a single response that defines if the sending succeeded or failed
|
||||||
*
|
*
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run with the final send result
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionSMTPStream(str, callback) {
|
_actionSMTPStream(str, callback) {
|
||||||
@@ -1699,6 +1712,7 @@ class SMTPConnection extends EventEmitter {
|
|||||||
* @param recipient The recipient this response applies to
|
* @param recipient The recipient this response applies to
|
||||||
* @param final Is this the final recipient?
|
* @param final Is this the final recipient?
|
||||||
* @param str Message from the server
|
* @param str Message from the server
|
||||||
|
* @param callback Callback to run with the final send result
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_actionLMTPStream(recipient, final, str, callback) {
|
_actionLMTPStream(recipient, final, str, callback) {
|
||||||
|
|||||||
+6
-1
@@ -110,9 +110,14 @@ declare class SMTPPool extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Verifies SMTP configuration
|
* Verifies SMTP configuration
|
||||||
*
|
*
|
||||||
* @param callback Callback function
|
* @returns Promise that resolves to true if the configuration is usable
|
||||||
*/
|
*/
|
||||||
verify(): Promise<true>;
|
verify(): Promise<true>;
|
||||||
|
/**
|
||||||
|
* Verifies SMTP configuration
|
||||||
|
*
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
verify(callback: VerifyCallback): void;
|
verify(callback: VerifyCallback): void;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
-1
@@ -125,9 +125,14 @@ declare class SMTPTransport extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* Verifies SMTP configuration
|
* Verifies SMTP configuration
|
||||||
*
|
*
|
||||||
* @param callback Callback function
|
* @returns Promise that resolves to true if the configuration is usable
|
||||||
*/
|
*/
|
||||||
verify(): Promise<true>;
|
verify(): Promise<true>;
|
||||||
|
/**
|
||||||
|
* Verifies SMTP configuration
|
||||||
|
*
|
||||||
|
* @param callback Callback function
|
||||||
|
*/
|
||||||
verify(callback: VerifyCallback): void;
|
verify(callback: VerifyCallback): void;
|
||||||
/**
|
/**
|
||||||
* Releases resources
|
* Releases resources
|
||||||
|
|||||||
+6
@@ -311,6 +311,12 @@ export const services = {
|
|||||||
"host": "mailosaur.io",
|
"host": "mailosaur.io",
|
||||||
"port": 25
|
"port": 25
|
||||||
},
|
},
|
||||||
|
"MailSenpai": {
|
||||||
|
"description": "MailSenpai (SMTP Senpai, EU)",
|
||||||
|
"host": "relay.mailsenpai.com",
|
||||||
|
"port": 2525,
|
||||||
|
"secure": false
|
||||||
|
},
|
||||||
"Mailtrap": {
|
"Mailtrap": {
|
||||||
"description": "Mailtrap",
|
"description": "Mailtrap",
|
||||||
"host": "live.smtp.mailtrap.io",
|
"host": "live.smtp.mailtrap.io",
|
||||||
|
|||||||
+7
@@ -279,6 +279,13 @@
|
|||||||
"port": 25
|
"port": 25
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"MailSenpai": {
|
||||||
|
"description": "MailSenpai (SMTP Senpai, EU)",
|
||||||
|
"host": "relay.mailsenpai.com",
|
||||||
|
"port": 2525,
|
||||||
|
"secure": false
|
||||||
|
},
|
||||||
|
|
||||||
"Mailtrap": {
|
"Mailtrap": {
|
||||||
"description": "Mailtrap",
|
"description": "Mailtrap",
|
||||||
"host": "live.smtp.mailtrap.io",
|
"host": "live.smtp.mailtrap.io",
|
||||||
|
|||||||
+5
-5
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nodemailer",
|
"name": "nodemailer",
|
||||||
"version": "10.0.13",
|
"version": "10.0.14",
|
||||||
"description": "Easy as cake e-mail sending from your Node.js applications",
|
"description": "Easy as cake e-mail sending from your Node.js applications",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/cjs/nodemailer.js",
|
"main": "./dist/cjs/nodemailer.js",
|
||||||
@@ -151,13 +151,13 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://nodemailer.com/",
|
"homepage": "https://nodemailer.com/",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@aws-sdk/client-sesv2": "3.1143.0",
|
"@aws-sdk/client-sesv2": "3.1146.0",
|
||||||
"@types/node": "20.19.43",
|
"@types/node": "20.19.43",
|
||||||
"bunyan": "1.8.15",
|
"bunyan": "1.8.15",
|
||||||
"c8": "12.0.0",
|
"c8": "12.0.0",
|
||||||
"eslint": "10.11.0",
|
"eslint": "10.12.0",
|
||||||
"eslint-config-prettier": "10.1.8",
|
"eslint-config-prettier": "10.1.8",
|
||||||
"globals": "17.12.0",
|
"globals": "17.13.0",
|
||||||
"libbase64": "1.3.1",
|
"libbase64": "1.3.1",
|
||||||
"libmime": "5.4.6",
|
"libmime": "5.4.6",
|
||||||
"libqp": "2.1.2",
|
"libqp": "2.1.2",
|
||||||
@@ -165,7 +165,7 @@
|
|||||||
"prettier": "3.9.9",
|
"prettier": "3.9.9",
|
||||||
"proxy": "1.0.2",
|
"proxy": "1.0.2",
|
||||||
"proxy-test-server": "1.0.0",
|
"proxy-test-server": "1.0.0",
|
||||||
"smtp-server": "3.19.15",
|
"smtp-server": "3.19.16",
|
||||||
"tsx": "4.23.15",
|
"tsx": "4.23.15",
|
||||||
"typescript": "6.0.3",
|
"typescript": "6.0.3",
|
||||||
"typescript-eslint": "8.71.0"
|
"typescript-eslint": "8.71.0"
|
||||||
|
|||||||
Reference in new issue
Block a user