lint: enable @typescript-eslint plugin

This commit is contained in:
Zack Schuster 2020-05-24 07:47:49 -07:00
parent e6f4cc28fa
commit 9d80365677
11 changed files with 251 additions and 219 deletions

View File

@ -15,9 +15,19 @@
],
"extends": [
"eslint:recommended",
"plugin:prettier/recommended"
"plugin:prettier/recommended",
"plugin:@typescript-eslint/recommended"
],
"rules": {
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-explicit-any": [
"error",
{
"ignoreRestArgs": true
}
],
"curly": [
"error",
"all"

View File

@ -1,8 +1,8 @@
import addressparser from 'addressparser';
import { Message } from './message';
import type { MessageAttachment, MessageHeaders } from './message'; // eslint-disable-line no-unused-vars
import type { MessageAttachment, MessageHeaders } from './message';
import { SMTPConnection, SMTPState } from './smtp';
import type { SMTPConnectionOptions } from './smtp'; // eslint-disable-line no-unused-vars
import type { SMTPConnectionOptions } from './smtp';
export interface MessageStack {
callback: (error: Error | null, message: Message) => void;
@ -57,7 +57,12 @@ export class Client {
message,
to: addressparser(message.header.to),
from: addressparser(message.header.from)[0].address,
callback: (callback || function () {}).bind(this),
callback: (
callback ||
function () {
/* ø */
}
).bind(this),
} as MessageStack;
if (message.header.cc) {
@ -241,7 +246,7 @@ export class Client {
throw new TypeError('stack.to must be array');
}
const to = stack.to.shift()!.address;
const to = stack.to.shift()?.address;
this.smtp.rcpt(
this._sendsmtp(stack, stack.to.length ? this._sendrcpt : this._senddata),
`<${to}>`

View File

@ -17,7 +17,7 @@ export const SMTPErrorStates = {
class SMTPError extends Error {
public code: number | null = null;
public smtp: any = null;
public smtp: unknown = null;
public previous: Error | null = null;
constructor(message: string) {
@ -29,7 +29,7 @@ export function makeSMTPError(
message: string,
code: number,
error?: Error | null,
smtp?: any
smtp?: unknown
) {
const msg = error?.message ? `${message} (${error.message})` : message;
const err = new SMTPError(msg);

View File

@ -1,7 +1,8 @@
import fs from 'fs';
import type { PathLike } from 'fs';
import { hostname } from 'os';
import { Stream } from 'stream';
import type { Duplex } from 'stream'; // eslint-disable-line no-unused-vars
import type { Duplex } from 'stream';
import addressparser from 'addressparser';
import { mimeWordEncode } from 'emailjs-mime-codec';
@ -25,35 +26,44 @@ export const MIME64CHUNK = (MIMECHUNK * 6) as 456;
export const BUFFERSIZE = (MIMECHUNK * 24 * 7) as 12768;
export interface MessageAttachmentHeaders {
[index: string]: any;
[index: string]: string | undefined;
'content-type'?: string;
'content-transfer-encoding'?: string;
'content-transfer-encoding'?: BufferEncoding | '7bit' | '8bit';
'content-disposition'?: string;
}
export interface AlternateMessageAttachment {
[index: string]: any;
[index: string]:
| string
| boolean
| MessageAttachment
| MessageAttachment[]
| MessageAttachmentHeaders
| Duplex
| PathLike
| undefined;
name?: string;
headers?: MessageAttachmentHeaders;
inline: boolean;
alternative?: MessageAttachment | boolean;
related?: MessageAttachment[];
data: any;
encoded?: any;
data: string;
encoded?: boolean;
stream?: Duplex;
path?: PathLike;
}
export interface MessageAttachment extends AlternateMessageAttachment {
name: string;
type: string;
charset: string;
method: string;
path: string;
stream: Duplex;
}
export interface MessageHeaders {
[index: string]: any;
[index: string]: string | null | MessageAttachment | MessageAttachment[];
'content-type': string;
'message-id': string;
'return-path': string | null;
date: string;
from: string;
to: string;
@ -64,7 +74,7 @@ export interface MessageHeaders {
attachment: MessageAttachment | MessageAttachment[];
}
let counter: number = 0;
let counter = 0;
function generate_boundary() {
let text = '';
@ -95,14 +105,14 @@ function convertDashDelimitedTextToSnakeCase(text: string) {
}
export class Message {
public readonly attachments: any[] = [];
public readonly attachments: MessageAttachment[] = [];
public readonly header: Partial<MessageHeaders> = {
'message-id': `<${new Date().getTime()}.${counter++}.${
process.pid
}@${hostname()}>`,
date: getRFC2822Date(),
};
public readonly content = 'text/plain; charset=utf-8';
public readonly content: string = 'text/plain; charset=utf-8';
public readonly text?: string;
public alternative: AlternateMessageAttachment | null = null;
@ -110,7 +120,7 @@ export class Message {
for (const header in headers) {
// allow user to override default content-type to override charset or send a single non-text message
if (/^content-type$/i.test(header)) {
this.content = headers[header];
this.content = headers[header] as string;
} else if (header === 'text') {
this.text = headers[header] as string;
} else if (
@ -129,7 +139,7 @@ export class Message {
this.header.subject = mimeWordEncode(headers.subject);
} else if (/^(cc|bcc|to|from)/i.test(header)) {
this.header[header.toLowerCase()] = convertPersonToAddress(
headers[header]
headers[header] as string
);
} else {
// allow any headers the user wants to set??
@ -216,6 +226,7 @@ export class Message {
* @returns {*} a stream of the current message
*/
public stream() {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
return new MessageStream(this);
}
@ -245,51 +256,57 @@ class MessageStream extends Stream {
constructor(private message: Message) {
super();
const output_mixed = () => {
const boundary = generate_boundary();
output(
`Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
);
if (this.message.alternative == null) {
output_text(this.message);
output_message(boundary, this.message.attachments, 0, close);
} else {
output_alternative(
// typescript bug; should narrow to { alternative: AlternateMessageAttachment }
this.message as Parameters<typeof output_alternative>[0],
() => output_message(boundary, this.message.attachments, 0, close)
);
}
};
/**
* @param {string} boundary the boundary text between outputs
* @param {MessageAttachment[]} list the list of potential messages to output
* @param {number} index the index of the list item to output
* @param {function(): void} callback the function to call if index is greater than upper bound
* @param {string} [data] the data to output
* @param {Function} [callback] the function
* @param {any[]} [args] array of arguments to pass to the callback
* @returns {void}
*/
const output_message = (
boundary: string,
list: MessageAttachment[],
index: number,
callback: () => void
) => {
if (index < list.length) {
output(`--${boundary}${CRLF}`);
if (list[index].related) {
output_related(list[index], () =>
output_message(boundary, list, index + 1, callback)
);
} else {
output_attachment(list[index], () =>
output_message(boundary, list, index + 1, callback)
);
const output = (data: string) => {
// can we buffer the data?
if (this.buffer != null) {
const bytes = Buffer.byteLength(data);
if (bytes + this.bufferIndex < this.buffer.length) {
this.buffer.write(data, this.bufferIndex);
this.bufferIndex += bytes;
}
// we can't buffer the data, so ship it out!
else if (bytes > this.buffer.length) {
if (this.bufferIndex) {
this.emit(
'data',
this.buffer.toString('utf-8', 0, this.bufferIndex)
);
this.bufferIndex = 0;
}
const loops = Math.ceil(data.length / this.buffer.length);
let loop = 0;
while (loop < loops) {
this.emit(
'data',
data.substring(
this.buffer.length * loop,
this.buffer.length * (loop + 1)
)
);
loop++;
}
} // we need to clean out the buffer, it is getting full
else {
if (!this.paused) {
this.emit(
'data',
this.buffer.toString('utf-8', 0, this.bufferIndex)
);
this.buffer.write(data, 0);
this.bufferIndex = bytes;
} else {
// we can't empty out the buffer, so let's wait till we resume before adding to it
this.once('resume', () => output(data));
}
}
} else {
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
callback();
}
};
@ -309,7 +326,9 @@ class MessageStream extends Stream {
'content-transfer-encoding': 'base64',
'content-disposition': attachment.inline
? 'inline'
: `attachment; filename="${mimeWordEncode(attachment.name)}"`,
: `attachment; filename="${mimeWordEncode(
attachment.name as string
)}"`,
};
// allow sender to override default headers
@ -323,7 +342,7 @@ class MessageStream extends Stream {
data = data.concat([
convertDashDelimitedTextToSnakeCase(header),
': ',
headers[header],
headers[header] as string,
CRLF,
]);
}
@ -331,34 +350,21 @@ class MessageStream extends Stream {
output(data.concat([CRLF]).join(''));
};
const output_attachment = (
attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void
) => {
const build = attachment.path
? output_file
: attachment.stream
? output_stream
: output_data;
output_attachment_headers(attachment);
build(attachment, callback);
};
/**
* @param {MessageAttachment} attachment the metadata to use as headers
* @param {function(): void} callback the function to call after output is finished
* @param {string} data the data to output as base64
* @param {function(): void} [callback] the function to call after output is finished
* @returns {void}
*/
const output_data = (
attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void
) => {
output_base64(
attachment.encoded
? attachment.data
: Buffer.from(attachment.data).toString('base64'),
callback
);
const output_base64 = (data: string, callback?: () => void) => {
const loops = Math.ceil(data.length / MIMECHUNK);
let loop = 0;
while (loop < loops) {
output(data.substring(MIMECHUNK * loop, MIMECHUNK * (loop + 1)) + CRLF);
loop++;
}
if (callback) {
callback();
}
};
const output_file = (
@ -415,7 +421,7 @@ class MessageStream extends Stream {
}
};
fs.open(attachment.path, 'r', opened);
fs.open(attachment.path as PathLike, 'r', opened);
};
/**
@ -427,19 +433,22 @@ class MessageStream extends Stream {
attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void
) => {
if (attachment.stream.readable) {
if (attachment.stream != null && attachment.stream.readable) {
let previous = Buffer.alloc(0);
attachment.stream.resume();
(attachment as MessageAttachment).stream.on('end', () => {
attachment.stream.on('end', () => {
output_base64(previous.toString('base64'), callback);
this.removeListener('pause', attachment.stream.pause);
this.removeListener('resume', attachment.stream.resume);
this.removeListener('error', attachment.stream.resume);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.removeListener('pause', attachment.stream!.pause);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.removeListener('resume', attachment.stream!.resume);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.removeListener('error', attachment.stream!.resume);
});
(attachment as MessageAttachment).stream.on('data', (buff) => {
attachment.stream.on('data', (buff) => {
// do we have bytes from a previous stream data event?
let buffer = Buffer.isBuffer(buff) ? buff : Buffer.from(buff);
@ -466,23 +475,84 @@ class MessageStream extends Stream {
}
};
const output_attachment = (
attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void
) => {
const build = attachment.path
? output_file
: attachment.stream
? output_stream
: output_data;
output_attachment_headers(attachment);
build(attachment, callback);
};
/**
* @param {string} data the data to output as base64
* @param {function(): void} [callback] the function to call after output is finished
* @param {string} boundary the boundary text between outputs
* @param {MessageAttachment[]} list the list of potential messages to output
* @param {number} index the index of the list item to output
* @param {function(): void} callback the function to call if index is greater than upper bound
* @returns {void}
*/
const output_base64 = (data: string, callback?: () => void) => {
const loops = Math.ceil(data.length / MIMECHUNK);
let loop = 0;
while (loop < loops) {
output(data.substring(MIMECHUNK * loop, MIMECHUNK * (loop + 1)) + CRLF);
loop++;
}
if (callback) {
const output_message = (
boundary: string,
list: MessageAttachment[],
index: number,
callback: () => void
) => {
if (index < list.length) {
output(`--${boundary}${CRLF}`);
if (list[index].related) {
output_related(list[index], () =>
output_message(boundary, list, index + 1, callback)
);
} else {
output_attachment(list[index], () =>
output_message(boundary, list, index + 1, callback)
);
}
} else {
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
callback();
}
};
const output_mixed = () => {
const boundary = generate_boundary();
output(
`Content-Type: multipart/mixed; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
);
if (this.message.alternative == null) {
output_text(this.message);
output_message(boundary, this.message.attachments, 0, close);
} else {
output_alternative(
// typescript bug; should narrow to { alternative: AlternateMessageAttachment }
this.message as Parameters<typeof output_alternative>[0],
() => output_message(boundary, this.message.attachments, 0, close)
);
}
};
/**
* @param {MessageAttachment} attachment the metadata to use as headers
* @param {function(): void} callback the function to call after output is finished
* @returns {void}
*/
const output_data = (
attachment: MessageAttachment | AlternateMessageAttachment,
callback: () => void
) => {
output_base64(
attachment.encoded
? attachment.data
: Buffer.from(attachment.data).toString('base64'),
callback
);
};
/**
* @param {Message} message the message to output
* @returns {void}
@ -503,6 +573,27 @@ class MessageStream extends Stream {
output(data.join(''));
};
/**
* @param {MessageAttachment} message the message to output
* @param {function(): void} callback the function to call after output is finished
* @returns {void}
*/
const output_related = (
message: AlternateMessageAttachment,
callback: () => void
) => {
const boundary = generate_boundary();
output(
`Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
);
output_attachment(message, () => {
output_message(boundary, message.related ?? [], 0, () => {
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
callback();
});
});
};
/**
* @param {Message} message the message to output
* @param {function(): void} callback the function to call after output is finished
@ -534,25 +625,24 @@ class MessageStream extends Stream {
}
};
/**
* @param {MessageAttachment} message the message to output
* @param {function(): void} callback the function to call after output is finished
* @returns {void}
*/
const output_related = (
message: AlternateMessageAttachment,
callback: () => void
) => {
const boundary = generate_boundary();
output(
`Content-Type: multipart/related; boundary="${boundary}"${CRLF}${CRLF}--${boundary}${CRLF}`
);
output_attachment(message, () => {
output_message(boundary, message.related ?? [], 0, () => {
output(`${CRLF}--${boundary}--${CRLF}${CRLF}`);
callback();
});
});
const close = (err?: Error) => {
if (err) {
this.emit('error', err);
} else {
this.emit(
'data',
this.buffer?.toString('utf-8', 0, this.bufferIndex) ?? ''
);
this.emit('end');
}
this.buffer = null;
this.bufferIndex = 0;
this.readable = false;
this.removeAllListeners('resume');
this.removeAllListeners('pause');
this.removeAllListeners('error');
this.removeAllListeners('data');
this.removeAllListeners('end');
};
/**
@ -579,13 +669,12 @@ class MessageStream extends Stream {
// do not output BCC in the headers (regex) nor custom Object.prototype functions...
if (
!/bcc/i.test(header) &&
// eslint-disable-next-line no-prototype-builtins
this.message.header.hasOwnProperty(header)
Object.prototype.hasOwnProperty.call(this.message.header, header)
) {
data = data.concat([
convertDashDelimitedTextToSnakeCase(header),
': ',
this.message.header[header],
this.message.header[header] as string,
CRLF,
]);
}
@ -595,80 +684,6 @@ class MessageStream extends Stream {
output_header_data();
};
/**
* @param {string} [data] the data to output
* @param {Function} [callback] the function
* @param {any[]} [args] array of arguments to pass to the callback
* @returns {void}
*/
const output = (data: string) => {
// can we buffer the data?
if (this.buffer != null) {
const bytes = Buffer.byteLength(data);
if (bytes + this.bufferIndex < this.buffer.length) {
this.buffer.write(data, this.bufferIndex);
this.bufferIndex += bytes;
}
// we can't buffer the data, so ship it out!
else if (bytes > this.buffer.length) {
if (this.bufferIndex) {
this.emit(
'data',
this.buffer.toString('utf-8', 0, this.bufferIndex)
);
this.bufferIndex = 0;
}
const loops = Math.ceil(data.length / this.buffer.length);
let loop = 0;
while (loop < loops) {
this.emit(
'data',
data.substring(
this.buffer.length * loop,
this.buffer.length * (loop + 1)
)
);
loop++;
}
} // we need to clean out the buffer, it is getting full
else {
if (!this.paused) {
this.emit(
'data',
this.buffer.toString('utf-8', 0, this.bufferIndex)
);
this.buffer.write(data, 0);
this.bufferIndex = bytes;
} else {
// we can't empty out the buffer, so let's wait till we resume before adding to it
this.once('resume', () => output(data));
}
}
}
};
const close = (err?: any) => {
if (err) {
this.emit('error', err);
} else {
this.emit(
'data',
this.buffer?.toString('utf-8', 0, this.bufferIndex) ?? ''
);
this.emit('end');
}
this.buffer = null;
this.bufferIndex = 0;
this.readable = false;
this.removeAllListeners('resume');
this.removeAllListeners('pause');
this.removeAllListeners('error');
this.removeAllListeners('data');
this.removeAllListeners('end');
};
this.once('destroy', close);
process.nextTick(output_header);
}

View File

@ -1,6 +1,6 @@
import { makeSMTPError, SMTPErrorStates } from './error';
import type { Socket } from 'net'; // eslint-disable-line no-unused-vars
import type { TLSSocket } from 'tls'; // eslint-disable-line no-unused-vars
import type { Socket } from 'net';
import type { TLSSocket } from 'tls';
export class SMTPResponse {
public readonly stop: (err?: Error) => void;

View File

@ -62,7 +62,7 @@ const log = (...args: any[]) => {
*/
const caller = (callback?: (...rest: any[]) => void, ...args: any[]) => {
if (typeof callback === 'function') {
callback.apply(null, args);
callback(...args);
}
};
@ -331,7 +331,7 @@ export class SMTPConnection extends EventEmitter {
* @param {*} callback function to call after response
* @returns {void}
*/
public send(str: string, callback: any) {
public send(str: string, callback: (...args: any[]) => void) {
if (this.sock && this._state === SMTPState.CONNECTED) {
this.log(str);
@ -432,7 +432,7 @@ export class SMTPConnection extends EventEmitter {
* @returns {void}
*/
public starttls(callback: (...rest: any[]) => void) {
const response = (err: Error, msg: { data: any }) => {
const response = (err: Error, msg: { data: unknown }) => {
if (this.sock == null) {
throw new Error('null socket');
}
@ -640,7 +640,8 @@ export class SMTPConnection extends EventEmitter {
) {
// is this code callable...?
if (!this.features) {
const response = (err: Error, data: any) => caller(callback, err, data);
const response = (err: Error, data: unknown) =>
caller(callback, err, data);
this.ehlo((err, data) => {
if (err) {
this.helo(response, domain);
@ -681,7 +682,7 @@ export class SMTPConnection extends EventEmitter {
const domain = options?.domain || this.domain;
const initiate = (err: Error | null | undefined, data: any) => {
const initiate = (err: Error | null | undefined, data: unknown) => {
if (err) {
caller(callback, err);
return;
@ -742,7 +743,7 @@ export class SMTPConnection extends EventEmitter {
* @param {*} data data
* @returns {void}
*/
const failed = (err: Error, data: any) => {
const failed = (err: Error, data: unknown) => {
this.loggedin = false;
this.close(); // if auth is bad, close the connection, it won't get better by itself
caller(
@ -761,7 +762,7 @@ export class SMTPConnection extends EventEmitter {
* @param {*} data data
* @returns {void}
*/
const response = (err: Error | null | undefined, data: any) => {
const response = (err: Error | null | undefined, data: unknown) => {
if (err) {
failed(err, data);
} else {
@ -778,7 +779,7 @@ export class SMTPConnection extends EventEmitter {
*/
const attempt = (
err: Error | null | undefined,
data: any,
data: unknown,
msg: string
) => {
if (err) {
@ -802,7 +803,7 @@ export class SMTPConnection extends EventEmitter {
* @param {string} msg msg
* @returns {void}
*/
const attempt_user = (err: Error, data: any) => {
const attempt_user = (err: Error, data: unknown) => {
if (err) {
failed(err, data);
} else {
@ -858,7 +859,7 @@ export class SMTPConnection extends EventEmitter {
* @param {boolean} [force=false] whether or not to force destroy the connection
* @returns {void}
*/
public close(force: boolean = false) {
public close(force = false) {
if (this.sock) {
if (force) {
this.log('smtp connection destroyed!');

1
smtp/typings.d.ts vendored
View File

@ -1,3 +1,4 @@
/* eslint-disable no-var */
declare module 'addressparser' {
var addressparser: (address?: string) => { name: string; address: string }[];
export = addressparser;

View File

@ -1,4 +1,4 @@
import type { Readable } from 'stream'; // eslint-disable-line no-unused-vars
import type { Readable } from 'stream';
import test from 'ava';
import mailparser from 'mailparser';
import smtp from 'smtp-server';

View File

@ -1,4 +1,4 @@
import type { Readable } from 'stream'; // eslint-disable-line no-unused-vars
import type { Readable } from 'stream';
import test from 'ava';
import mailparser from 'mailparser';
import smtp from 'smtp-server';

View File

@ -9,7 +9,7 @@ const toD = (dt: number, utc = false) => getRFC2822Date(new Date(dt), utc);
test('rfc2822 non-UTC', async (t) => {
// RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
// thanks to moment.js for the listing: https://github.com/moment/moment/blob/a831fc7e2694281ce31e4f090bbcf90a690f0277/src/lib/create/from-string.js#L101
var rfc2822re = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
const rfc2822re = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/;
t.regex(toD(0), rfc2822re);
t.regex(toD(329629726785), rfc2822re);
t.regex(toD(729629726785), rfc2822re);

View File

@ -1,4 +1,4 @@
import type { Readable } from 'stream'; // eslint-disable-line no-unused-vars
import type { Readable } from 'stream';
import { readFileSync, createReadStream } from 'fs';
import { join } from 'path';