2019-09-18 23:24:09 +00:00
|
|
|
import { Socket } from 'net';
|
|
|
|
import { TLSSocket } from 'tls';
|
|
|
|
|
2020-04-21 03:20:06 +00:00
|
|
|
import { makeSMTPError, SMTPErrorStates } from './error';
|
2019-09-18 23:24:09 +00:00
|
|
|
|
|
|
|
export class SMTPResponse {
|
|
|
|
private buffer = '';
|
|
|
|
public stop: (err?: Error) => void;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param [stream] The open socket to stream a response from
|
|
|
|
* @param [timeout] The time to wait (in milliseconds) before closing the socket
|
|
|
|
* @param [onerror] The function to call on error
|
|
|
|
*/
|
|
|
|
constructor(private stream: Socket | TLSSocket, timeout: number, onerror: (err: Error) => void) {
|
|
|
|
const watch = (data: Parameters<SMTPResponse['watch']>[0]) => this.watch(data);
|
|
|
|
const end = () => this.end();
|
|
|
|
const close = () => this.close();
|
|
|
|
const error = (data: Parameters<SMTPResponse['error']>[0]) => this.error(data);
|
|
|
|
const timedout = (data: Parameters<SMTPResponse['timedout']>[0]) => this.timedout(data);
|
|
|
|
|
|
|
|
this.stream.on('data', watch);
|
|
|
|
this.stream.on('end', end);
|
|
|
|
this.stream.on('close', close);
|
|
|
|
this.stream.on('error', error);
|
|
|
|
this.stream.setTimeout(timeout, timedout);
|
|
|
|
|
|
|
|
this.stop = err => {
|
|
|
|
this.stream.removeAllListeners('response');
|
|
|
|
this.stream.removeListener('data', watch);
|
|
|
|
this.stream.removeListener('end', end);
|
|
|
|
this.stream.removeListener('close', close);
|
|
|
|
this.stream.removeListener('error', error);
|
|
|
|
|
|
|
|
if (err != null && typeof onerror === 'function') {
|
|
|
|
onerror(err);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
public notify() {
|
|
|
|
if (this.buffer.length) {
|
|
|
|
// parse buffer for response codes
|
|
|
|
const line = this.buffer.replace('\r', '');
|
|
|
|
if (
|
|
|
|
!line
|
|
|
|
.trim()
|
|
|
|
.split(/\n/)
|
|
|
|
.pop()
|
2020-04-21 03:20:06 +00:00
|
|
|
?.match(/^(\d{3})\s/) ?? false
|
2019-09-18 23:24:09 +00:00
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const match = line ? line.match(/(\d+)\s?(.*)/) : null;
|
|
|
|
const data =
|
|
|
|
match !== null
|
|
|
|
? { code: match[1], message: match[2], data: line }
|
|
|
|
: { code: -1, data: line };
|
|
|
|
|
|
|
|
this.stream.emit('response', null, data);
|
|
|
|
this.buffer = '';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected error(err: Error) {
|
|
|
|
this.stream.emit(
|
|
|
|
'response',
|
|
|
|
makeSMTPError('connection encountered an error', SMTPErrorStates.ERROR, err)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected watch(data: string | Buffer) {
|
|
|
|
if (data !== null) {
|
|
|
|
this.buffer += data.toString();
|
|
|
|
this.notify();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
protected timedout(err: Error) {
|
|
|
|
this.stream.end();
|
|
|
|
this.stream.emit(
|
|
|
|
'response',
|
|
|
|
makeSMTPError(
|
|
|
|
'timedout while connecting to smtp server',
|
|
|
|
SMTPErrorStates.TIMEDOUT,
|
|
|
|
err
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected close() {
|
|
|
|
this.stream.emit(
|
|
|
|
'response',
|
|
|
|
makeSMTPError('connection has closed', SMTPErrorStates.CONNECTIONCLOSED)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected end() {
|
|
|
|
this.stream.emit(
|
|
|
|
'response',
|
|
|
|
makeSMTPError('connection has ended', SMTPErrorStates.CONNECTIONENDED)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param [stream] the open socket to stream a response from
|
|
|
|
* @param [timeout] the time to wait (in milliseconds) before closing the socket
|
|
|
|
* @param [onerror] the function to call on error
|
|
|
|
*/
|
|
|
|
export const monitor = (stream: Socket | TLSSocket, timeout: number, onerror: (err: Error) => void) =>
|
|
|
|
new SMTPResponse(stream, timeout, onerror);
|