Compare commits

...

4 Commits

Author SHA1 Message Date
Zack Schuster 615de80130 test: add validation units 2022-05-11 16:39:49 -07:00
Zack Schuster 5841918d7d chore: jsdoc improvements 2022-05-11 16:39:12 -07:00
Zack Schuster 8706678fbc chore: fix nits 2022-05-11 16:35:21 -07:00
Zack Schuster 8503c7f63a chore: upgrade deps 2022-05-11 15:45:15 -07:00
13 changed files with 124 additions and 101 deletions

View File

@ -22,15 +22,15 @@
"@types/mailparser": "3.4.0",
"@types/node": "12.12.6",
"@types/smtp-server": "3.5.7",
"@typescript-eslint/eslint-plugin": "5.22.0",
"@typescript-eslint/parser": "5.22.0",
"@typescript-eslint/eslint-plugin": "5.23.0",
"@typescript-eslint/parser": "5.23.0",
"ava": "4.2.0",
"eslint": "8.15.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-prettier": "4.0.0",
"mailparser": "3.5.0",
"prettier": "2.6.2",
"rollup": "2.72.0",
"rollup": "2.72.1",
"smtp-server": "3.11.0",
"ts-node": "10.7.0",
"tslib": "2.4.0",

View File

@ -31,7 +31,7 @@ const OPERATORS = new Map([
/**
* Tokenizes the original input string
*
* @param {string | string[] | undefined} address string(s) to tokenize
* @param {string | string[]} [address=''] string(s) to tokenize
* @return {AddressToken[]} An array of operator & text tokens
*/
function tokenizeAddress(address: string | string[] = '') {
@ -208,7 +208,7 @@ function convertAddressTokens(tokens: AddressToken[]) {
*
* [{name: "Name", address: "address@domain"}]
*
* @param {string | string[] | undefined} address
* @param {string | string[] | undefined} [address]
* @return {AddressObject[]}
*/
export function addressparser(address?: string | string[]) {

View File

@ -35,7 +35,7 @@ export class SMTPClient {
*
* NOTE: `host` is trimmed before being used to establish a connection; however, the original untrimmed value will still be visible in configuration.
*
* @param {SMTPConnectionOptions} server smtp options
* @param {Partial<SMTPConnectionOptions>} server smtp options
*/
constructor(server: Partial<SMTPConnectionOptions>) {
this.smtp = new SMTPConnection(server);
@ -223,7 +223,7 @@ export class SMTPClient {
/**
* @protected
* @param {MessageAttachment | MessageAttachment[]} attachment
* @param {MessageAttachment | MessageAttachment[]} [attachment]
* @returns {boolean}
*/
protected _containsInlinedHtml(
@ -240,7 +240,7 @@ export class SMTPClient {
/**
* @protected
* @param {MessageAttachment} attachment
* @param {MessageAttachment} [attachment]
* @returns {boolean}
*/
protected _isAttachmentInlinedHtml(attachment?: MessageAttachment) {

View File

@ -785,11 +785,15 @@ export class SMTPConnection extends EventEmitter {
/**
* handle bad responses from command differently
* @param {Error} err
* @param {unknown} data
* @param {Error | SMTPError | null} err
* @param {(
* string |
* { code: (string | number), data: string, message: string } |
* null
* )} [data]
* @returns {void}
*/
const failed = (err: Error, data: unknown) => {
const failed: SMTPCommandCallback = (err, data) => {
this.loggedin = false;
this.close(); // if auth is bad, close the connection, it won't get better by itself
callback(

View File

@ -1,6 +1,6 @@
/**
* @param {Date} [date] an optional date to convert to RFC2822 format
* @param {boolean} [useUtc] whether to parse the date as UTC (default: false)
* @param {Date} [date=new Date()] an optional date to convert to RFC2822 format
* @param {boolean} [useUtc=false] whether to parse the date as UTC (default: false)
* @returns {string} the converted date
*/
export function getRFC2822Date(date = new Date(), useUtc = false) {
@ -24,7 +24,7 @@ export function getRFC2822Date(date = new Date(), useUtc = false) {
}
/**
* @param {Date} [date] an optional date to convert to RFC2822 format (UTC)
* @param {Date} [date=new Date()] an optional date to convert to RFC2822 format (UTC)
* @returns {string} the converted date
*/
export function getRFC2822DateUTC(date = new Date()) {

View File

@ -163,7 +163,7 @@ export class Message {
this.header.subject = mimeWordEncode(headers.subject as string);
} else if (/^(cc|bcc|to|from)/i.test(header)) {
this.header[header.toLowerCase()] = convertPersonToAddress(
headers[header] as string | string[]
headers[header as 'cc' | 'bcc' | 'to' | 'from'] ?? ''
);
} else {
// allow any headers the user wants to set??

View File

@ -71,7 +71,7 @@ function encodeBase64(data: Uint8Array) {
* Splits a mime encoded string. Needed for dividing mime words into smaller chunks
*
* @param {string} str Mime encoded string to be split up
* @param {number} maxlen Maximum length of characters for one part (minimum 12)
* @param {number} [maxlen=12] Maximum length of characters for one part (minimum 12)
* @return {string[]} lines
*/
function splitMimeEncodedString(str: string, maxlen = 12) {
@ -127,17 +127,17 @@ function checkRanges(nr: number) {
/**
* Encodes all non printable and non ascii bytes to =XX form, where XX is the
* byte value in hex. This function does not convert linebreaks etc. it
* only escapes character sequences
* byte value in hex. This function only escapes character sequences; it does not
* convert linebreaks etc.
*
* NOTE: Encoding support depends on util.TextDecoder, which is severely limited
* NOTE: Encoding support depends on `util.TextDecoder`, which lacks full ICU support
* prior to Node.js 13.
*
* @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings
* @see https://github.com/nodejs/node/issues/19214
*
* @param {string|Uint8Array} data Either a string or an Uint8Array
* @param {string} encoding WHATWG supported encoding
* @param {string | Uint8Array} [data='']
* @param {string} [encoding='utf-8'] WHATWG supported encoding
* @return {string} Mime encoded string
*/
export function mimeEncode(data: string | Uint8Array = '', encoding = 'utf-8') {
@ -168,16 +168,16 @@ export function mimeEncode(data: string | Uint8Array = '', encoding = 'utf-8') {
/**
* Encodes a string or an Uint8Array to an UTF-8 MIME Word
*
* NOTE: Encoding support depends on util.TextDecoder, which is severely limited
* NOTE: Encoding support depends on `util.TextDecoder`, which lacks full ICU support
* prior to Node.js 13.
*
* @see https://tools.ietf.org/html/rfc2047
* @see https://nodejs.org/api/util.html#util_whatwg_supported_encodings
* @see https://github.com/nodejs/node/issues/19214
*
* @param {string|Uint8Array} data String to be encoded
* @param {'Q' | 'B'} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
* @param {string} encoding WHATWG supported encoding
* @param {string | Uint8Array} data
* @param {'Q' | 'B'} [mimeWordEncoding='Q']
* @param {string} [encoding='utf-8'] WHATWG supported encoding
* @return {string} Single or several mime words joined together
*/
export function mimeWordEncode(

View File

@ -5,6 +5,11 @@ import type { TLSSocket } from 'tls';
export class SMTPResponseMonitor {
public readonly stop: (err?: Error) => void;
/**
* @param {Socket | TLSSocket} stream
* @param {number} timeout
* @param {function(Error): void} onerror
*/
constructor(
stream: Socket | TLSSocket,
timeout: number,

View File

@ -17,7 +17,7 @@ function connect({
return new Promise<void>((resolve, reject) => {
const server = new SMTPServer({
authMethods,
secure: secure,
secure,
onAuth(auth, _session, callback) {
const { accessToken, method, username, password } = auth;
if (

View File

@ -122,8 +122,12 @@ test('client accepts array sender', async (t) => {
});
msg.header.from = [msg.header.from as string];
const { isValid } = msg.checkValidity();
t.true(isValid);
const message = await client.sendAsync(msg);
t.is(message.text, msg.text);
t.deepEqual(message.header.from, ['zelda@gmail.com']);
t.is(message.header.to, 'gannon1@gmail.com');
t.is(message.header.cc, undefined);
t.is(message.header.bcc, undefined);
});
test('client rejects message without `from` header', async (t) => {

View File

@ -380,6 +380,21 @@ test('text + two attachments message (streams)', async (t) => {
t.is(mail.to?.text, msg.to);
});
test('message validation succeeds when `from` header is an array', async (t) => {
const msg = new Message({
from: ['zelda@gmail.com'],
to: 'gannon1@gmail.com',
});
t.false(Array.isArray(msg.header.from));
msg.header.from = [msg.header.from as string];
const { isValid } = msg.checkValidity();
t.true(Array.isArray(msg.header.from));
t.true(isValid);
});
test('message validation fails without `from` header', async (t) => {
const msg = new Message({});
const { isValid, validationError } = msg.checkValidity();

View File

@ -84,23 +84,18 @@ test('synchronous queue failures are handled gracefully by client', async (t) =>
})
);
// @ts-expect-error need to check protected prop
const { ready, sending, smtp } = tlsClient;
const state = smtp.state();
t.log(
`SMTPClient ${JSON.stringify(
{
// @ts-expect-error need to check protected prop
ready: tlsClient.ready,
// @ts-expect-error need to check protected prop
sending: tlsClient.sending,
state: tlsClient.smtp.state(),
},
null,
'\t'
).replace(/"/g, '')}`
`SMTPClient ${JSON.stringify({ ready, sending, state }, null, '\t').replace(
/"/g,
''
)}`
);
// @ts-expect-error need to check protected prop
t.false(tlsClient.ready);
// @ts-expect-error need to check protected prop
t.false(tlsClient.sending);
t.is(tlsClient.smtp.state(), 0);
t.false(ready);
t.false(sending);
t.is(state, 0);
});

114
yarn.lock
View File

@ -152,14 +152,14 @@
"@types/node" "*"
"@types/nodemailer" "*"
"@typescript-eslint/eslint-plugin@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.22.0.tgz#7b52a0de2e664044f28b36419210aea4ab619e2a"
integrity sha512-YCiy5PUzpAeOPGQ7VSGDEY2NeYUV1B0swde2e0HzokRsHBYjSdF6DZ51OuRZxVPHx0032lXGLvOMls91D8FXlg==
"@typescript-eslint/eslint-plugin@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.23.0.tgz#bc4cbcf91fbbcc2e47e534774781b82ae25cc3d8"
integrity sha512-hEcSmG4XodSLiAp1uxv/OQSGsDY6QN3TcRU32gANp+19wGE1QQZLRS8/GV58VRUoXhnkuJ3ZxNQ3T6Z6zM59DA==
dependencies:
"@typescript-eslint/scope-manager" "5.22.0"
"@typescript-eslint/type-utils" "5.22.0"
"@typescript-eslint/utils" "5.22.0"
"@typescript-eslint/scope-manager" "5.23.0"
"@typescript-eslint/type-utils" "5.23.0"
"@typescript-eslint/utils" "5.23.0"
debug "^4.3.2"
functional-red-black-tree "^1.0.1"
ignore "^5.1.8"
@ -167,69 +167,69 @@
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/parser@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.22.0.tgz#7bedf8784ef0d5d60567c5ba4ce162460e70c178"
integrity sha512-piwC4krUpRDqPaPbFaycN70KCP87+PC5WZmrWs+DlVOxxmF+zI6b6hETv7Quy4s9wbkV16ikMeZgXsvzwI3icQ==
"@typescript-eslint/parser@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.23.0.tgz#443778e1afc9a8ff180f91b5e260ac3bec5e2de1"
integrity sha512-V06cYUkqcGqpFjb8ttVgzNF53tgbB/KoQT/iB++DOIExKmzI9vBJKjZKt/6FuV9c+zrDsvJKbJ2DOCYwX91cbw==
dependencies:
"@typescript-eslint/scope-manager" "5.22.0"
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/typescript-estree" "5.22.0"
"@typescript-eslint/scope-manager" "5.23.0"
"@typescript-eslint/types" "5.23.0"
"@typescript-eslint/typescript-estree" "5.23.0"
debug "^4.3.2"
"@typescript-eslint/scope-manager@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.22.0.tgz#590865f244ebe6e46dc3e9cab7976fc2afa8af24"
integrity sha512-yA9G5NJgV5esANJCO0oF15MkBO20mIskbZ8ijfmlKIvQKg0ynVKfHZ15/nhAJN5m8Jn3X5qkwriQCiUntC9AbA==
"@typescript-eslint/scope-manager@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.23.0.tgz#4305e61c2c8e3cfa3787d30f54e79430cc17ce1b"
integrity sha512-EhjaFELQHCRb5wTwlGsNMvzK9b8Oco4aYNleeDlNuL6qXWDF47ch4EhVNPh8Rdhf9tmqbN4sWDk/8g+Z/J8JVw==
dependencies:
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/visitor-keys" "5.22.0"
"@typescript-eslint/types" "5.23.0"
"@typescript-eslint/visitor-keys" "5.23.0"
"@typescript-eslint/type-utils@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.22.0.tgz#0c0e93b34210e334fbe1bcb7250c470f4a537c19"
integrity sha512-iqfLZIsZhK2OEJ4cQ01xOq3NaCuG5FQRKyHicA3xhZxMgaxQazLUHbH/B2k9y5i7l3+o+B5ND9Mf1AWETeMISA==
"@typescript-eslint/type-utils@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.23.0.tgz#f852252f2fc27620d5bb279d8fed2a13d2e3685e"
integrity sha512-iuI05JsJl/SUnOTXA9f4oI+/4qS/Zcgk+s2ir+lRmXI+80D8GaGwoUqs4p+X+4AxDolPpEpVUdlEH4ADxFy4gw==
dependencies:
"@typescript-eslint/utils" "5.22.0"
"@typescript-eslint/utils" "5.23.0"
debug "^4.3.2"
tsutils "^3.21.0"
"@typescript-eslint/types@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.22.0.tgz#50a4266e457a5d4c4b87ac31903b28b06b2c3ed0"
integrity sha512-T7owcXW4l0v7NTijmjGWwWf/1JqdlWiBzPqzAWhobxft0SiEvMJB56QXmeCQjrPuM8zEfGUKyPQr/L8+cFUBLw==
"@typescript-eslint/types@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.23.0.tgz#8733de0f58ae0ed318dbdd8f09868cdbf9f9ad09"
integrity sha512-NfBsV/h4dir/8mJwdZz7JFibaKC3E/QdeMEDJhiAE3/eMkoniZ7MjbEMCGXw6MZnZDMN3G9S0mH/6WUIj91dmw==
"@typescript-eslint/typescript-estree@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.22.0.tgz#e2116fd644c3e2fda7f4395158cddd38c0c6df97"
integrity sha512-EyBEQxvNjg80yinGE2xdhpDYm41so/1kOItl0qrjIiJ1kX/L/L8WWGmJg8ni6eG3DwqmOzDqOhe6763bF92nOw==
"@typescript-eslint/typescript-estree@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.23.0.tgz#dca5f10a0a85226db0796e8ad86addc9aee52065"
integrity sha512-xE9e0lrHhI647SlGMl+m+3E3CKPF1wzvvOEWnuE3CCjjT7UiRnDGJxmAcVKJIlFgK6DY9RB98eLr1OPigPEOGg==
dependencies:
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/visitor-keys" "5.22.0"
"@typescript-eslint/types" "5.23.0"
"@typescript-eslint/visitor-keys" "5.23.0"
debug "^4.3.2"
globby "^11.0.4"
is-glob "^4.0.3"
semver "^7.3.5"
tsutils "^3.21.0"
"@typescript-eslint/utils@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.22.0.tgz#1f2c4897e2cf7e44443c848a13c60407861babd8"
integrity sha512-HodsGb037iobrWSUMS7QH6Hl1kppikjA1ELiJlNSTYf/UdMEwzgj0WIp+lBNb6WZ3zTwb0tEz51j0Wee3iJ3wQ==
"@typescript-eslint/utils@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.23.0.tgz#4691c3d1b414da2c53d8943310df36ab1c50648a"
integrity sha512-dbgaKN21drqpkbbedGMNPCtRPZo1IOUr5EI9Jrrh99r5UW5Q0dz46RKXeSBoPV+56R6dFKpbrdhgUNSJsDDRZA==
dependencies:
"@types/json-schema" "^7.0.9"
"@typescript-eslint/scope-manager" "5.22.0"
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/typescript-estree" "5.22.0"
"@typescript-eslint/scope-manager" "5.23.0"
"@typescript-eslint/types" "5.23.0"
"@typescript-eslint/typescript-estree" "5.23.0"
eslint-scope "^5.1.1"
eslint-utils "^3.0.0"
"@typescript-eslint/visitor-keys@5.22.0":
version "5.22.0"
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.22.0.tgz#f49c0ce406944ffa331a1cfabeed451ea4d0909c"
integrity sha512-DbgTqn2Dv5RFWluG88tn0pP6Ex0ROF+dpDO1TNNZdRtLjUr6bdznjA6f/qNqJLjd2PgguAES2Zgxh/JzwzETDg==
"@typescript-eslint/visitor-keys@5.23.0":
version "5.23.0"
resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.23.0.tgz#057c60a7ca64667a39f991473059377a8067c87b"
integrity sha512-Vd4mFNchU62sJB8pX19ZSPog05B0Y0CE2UxAZPT5k4iqhRYjPnqyY3woMxCd0++t9OTqkgjST+1ydLBi7e2Fvg==
dependencies:
"@typescript-eslint/types" "5.22.0"
"@typescript-eslint/types" "5.23.0"
eslint-visitor-keys "^3.0.0"
acorn-jsx@^5.3.2:
@ -256,9 +256,9 @@ aggregate-error@^3.0.0:
indent-string "^4.0.0"
aggregate-error@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.0.tgz#83dbdb53a0d500721281d22e19eee9bc352a89cd"
integrity sha512-8DGp7zUt1E9k0NE2q4jlXHk+V3ORErmwolEdRz9iV+LKJ40WhMHh92cxAvhqV2I+zEn/gotIoqoMs0NjF3xofg==
version "4.0.1"
resolved "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz#25091fe1573b9e0be892aeda15c7c66a545f758e"
integrity sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==
dependencies:
clean-stack "^4.0.0"
indent-string "^5.0.0"
@ -1003,9 +1003,9 @@ glob@^7.1.3:
path-is-absolute "^1.0.0"
globals@^13.6.0, globals@^13.9.0:
version "13.13.0"
resolved "https://registry.npmjs.org/globals/-/globals-13.13.0.tgz#ac32261060d8070e2719dd6998406e27d2b5727b"
integrity sha512-EQ7Q18AJlPwp3vUDL4mKA0KXrXyNIQyWon6T6XQiBQF0XHvRsiCSrWmmeATpUzdJN2HhWZU6Pdl0a9zdep5p6A==
version "13.14.0"
resolved "https://registry.npmjs.org/globals/-/globals-13.14.0.tgz#daf3ff9b4336527cf56e98330b6f64bea9aff9df"
integrity sha512-ERO68sOYwm5UuLvSJTY7w7NP2c8S4UcXs3X1GBX8cwOr+ShOcDBbCY5mH4zxz0jsYCdJ8ve8Mv9n2YGJMB1aeg==
dependencies:
type-fest "^0.20.2"
@ -1678,10 +1678,10 @@ rimraf@^3.0.2:
dependencies:
glob "^7.1.3"
rollup@2.72.0:
version "2.72.0"
resolved "https://registry.npmjs.org/rollup/-/rollup-2.72.0.tgz#f94280b003bcf9f2f1f2594059a9db5abced371e"
integrity sha512-KqtR2YcO35/KKijg4nx4STO3569aqCUeGRkKWnJ6r+AvBBrVY9L4pmf4NHVrQr4mTOq6msbohflxr2kpihhaOA==
rollup@2.72.1:
version "2.72.1"
resolved "https://registry.npmjs.org/rollup/-/rollup-2.72.1.tgz#861c94790537b10008f0ca0fbc60e631aabdd045"
integrity sha512-NTc5UGy/NWFGpSqF1lFY8z9Adri6uhyMLI6LvPAXdBKoPRFhIIiBUpt+Qg2awixqO3xvzSijjhnb4+QEZwJmxA==
optionalDependencies:
fsevents "~2.3.2"