mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-10-12 02:42:27 +07:00
node_modules: update (#344)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
1 parent
b099186883
commit
6e88c72525
63 files changed
+2863
-1227
No files matched your search
+171
-111
@@ -2,7 +2,7 @@
|
||||
import crypto from 'node:crypto';
|
||||
import fs from 'node:fs';
|
||||
import * as punycode from '../punycode/index.js';
|
||||
import { PassThrough } from 'node:stream';
|
||||
import { PassThrough, finished, pipeline } from 'node:stream';
|
||||
import * as shared from '../shared/index.js';
|
||||
import urlModule from 'node:url';
|
||||
import * as mimeFuncs from '../mime-funcs/index.js';
|
||||
@@ -33,6 +33,14 @@ const PLAIN_ADDRESS = /^[^\s"(),:;<>@[\\\]]+@[^\s"(),:;<>@[\\\]]+$/;
|
||||
// unroutable garbage into mail for a domain the sender never named. None of these
|
||||
// characters are legal in a domain, so keep them away from the mapper.
|
||||
const URL_PARSER_UNSAFE = /[/\\?#%\x00-\x20\x7F]/;
|
||||
// pipeline() needs a callback to not throw, the errors it sees reach the last stream anyway
|
||||
const PIPELINE_NOOP = () => false;
|
||||
// The error a node streaming into an output that was destroyed stops with
|
||||
function abortedError() {
|
||||
const err = new Error('Message stream was closed before the message was generated');
|
||||
err.code = errors.ESTREAM;
|
||||
return err;
|
||||
}
|
||||
/**
|
||||
* Encodes a domain the way browsers, the WHATWG URL Standard and DNS facing resolvers do,
|
||||
* which is with UTS-46 mapping applied before the Punycode step.
|
||||
@@ -52,9 +60,14 @@ const URL_PARSER_UNSAFE = /[/\\?#%\x00-\x20\x7F]/;
|
||||
*/
|
||||
function normalizeDomain(domain, toUnicode) {
|
||||
// domainToASCII and domainToUnicode landed in Node 7, the bundled codec covers Node 6
|
||||
const mapper = toUnicode ? urlModule.domainToUnicode : urlModule.domainToASCII;
|
||||
if (typeof mapper === 'function' && !URL_PARSER_UNSAFE.test(domain)) {
|
||||
const mapped = mapper(domain);
|
||||
if (typeof urlModule.domainToASCII === 'function' &&
|
||||
typeof urlModule.domainToUnicode === 'function' &&
|
||||
!URL_PARSER_UNSAFE.test(domain)) {
|
||||
// The U-label form is decoded from the A-label form rather than from the input:
|
||||
// Deno's domainToASCII returns an empty string for a label it cannot decode, while
|
||||
// its domainToUnicode returns the label with U+FFFD in place of the bad part
|
||||
const ascii = urlModule.domainToASCII(domain);
|
||||
const mapped = ascii && toUnicode ? urlModule.domainToUnicode(ascii) : ascii;
|
||||
if (mapped) {
|
||||
return mapped;
|
||||
}
|
||||
@@ -406,11 +419,7 @@ class MimeNode {
|
||||
if (typeof this.content.pipe === 'function') {
|
||||
// pre-stream handler. might be triggered if a stream is set as content
|
||||
// and 'error' fires before anything is done with this stream
|
||||
this._contentErrorHandler = err => {
|
||||
this.content.removeListener('error', this._contentErrorHandler);
|
||||
this.content = err;
|
||||
};
|
||||
this.content.once('error', this._contentErrorHandler);
|
||||
shared.recordStreamErrors(this.content);
|
||||
}
|
||||
else if (typeof this.content === 'string') {
|
||||
this._isPlainText = mimeFuncs.isPlainText(this.content);
|
||||
@@ -622,40 +631,45 @@ class MimeNode {
|
||||
createReadStream(options) {
|
||||
options = options || {};
|
||||
const stream = new PassThrough(options);
|
||||
let outputStream = stream;
|
||||
let transform;
|
||||
this.stream(stream, options, err => {
|
||||
if (err) {
|
||||
outputStream.emit('error', err);
|
||||
stream.destroy(err);
|
||||
return;
|
||||
}
|
||||
stream.end();
|
||||
});
|
||||
for (let i = 0, len = this._transforms.length; i < len; i++) {
|
||||
transform =
|
||||
typeof this._transforms[i] === 'function' ? this._transforms[i]() : this._transforms[i];
|
||||
outputStream.once('error', err => {
|
||||
transform.emit('error', err);
|
||||
});
|
||||
outputStream = outputStream.pipe(transform);
|
||||
// the content streams of the nodes the message did not get to are not going to be read
|
||||
stream.once('close', () => {
|
||||
if (!stream.writableFinished) {
|
||||
this._destroyContentStreams();
|
||||
}
|
||||
});
|
||||
// The stages are joined with pipeline, which destroys all of them when any one fails
|
||||
// or is destroyed. An error anywhere reaches the returned stream exactly once, and a
|
||||
// consumer that destroys the returned stream stops the tree from reading its sources
|
||||
const stages = [stream];
|
||||
for (const transform of this._transforms) {
|
||||
stages.push(typeof transform === 'function' ? transform() : transform);
|
||||
}
|
||||
// ensure terminating newline after possible user transforms
|
||||
transform = new LastNewline();
|
||||
outputStream.once('error', err => {
|
||||
transform.emit('error', err);
|
||||
});
|
||||
outputStream = outputStream.pipe(transform);
|
||||
// dkim and stuff
|
||||
for (let i = 0, len = this._processFuncs.length; i < len; i++) {
|
||||
transform = this._processFuncs[i];
|
||||
outputStream = transform(outputStream);
|
||||
stages.push(new LastNewline());
|
||||
let outputStream = pipeline(stages, PIPELINE_NOOP);
|
||||
// dkim and stuff. A process function reads its input itself and reports the errors of
|
||||
// that input on its output, so only a consumer abort has to be carried back upstream
|
||||
for (const processFunc of this._processFuncs) {
|
||||
const input = outputStream;
|
||||
outputStream = processFunc(input);
|
||||
if (outputStream !== input) {
|
||||
finished(outputStream, err => {
|
||||
if (err) {
|
||||
input.destroy();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
if (this.newline) {
|
||||
const winbreak = ['win', 'windows', 'dos', '\r\n'].includes(this.newline.toString().toLowerCase());
|
||||
const newlineTransform = winbreak ? new LeWindows() : new LeUnix();
|
||||
const stream = outputStream.pipe(newlineTransform);
|
||||
outputStream.on('error', err => stream.emit('error', err));
|
||||
return stream;
|
||||
outputStream = pipeline(outputStream, winbreak ? new LeWindows() : new LeUnix(), PIPELINE_NOOP);
|
||||
}
|
||||
return outputStream;
|
||||
}
|
||||
@@ -681,8 +695,10 @@ class MimeNode {
|
||||
}
|
||||
stream(outputStream, options, done) {
|
||||
const transferEncoding = this.getTransferEncoding();
|
||||
let contentStream;
|
||||
let localStream;
|
||||
// the streams this node is reading from. A consumer that goes away mid-message destroys
|
||||
// the output, and these are released with it instead of staying paused with a file or a
|
||||
// socket open behind them
|
||||
let activeStreams = [];
|
||||
// protect actual callback against multiple triggering
|
||||
let returned = false;
|
||||
const callback = (err) => {
|
||||
@@ -690,13 +706,39 @@ class MimeNode {
|
||||
return;
|
||||
}
|
||||
returned = true;
|
||||
outputStream.removeListener('close', onOutputClose);
|
||||
done(err);
|
||||
};
|
||||
function onOutputClose() {
|
||||
for (const stream of activeStreams) {
|
||||
stream.destroy();
|
||||
}
|
||||
return callback(abortedError());
|
||||
}
|
||||
// reads the streams into the output. The listener is only attached while the node reads
|
||||
// something, a deeply nested tree would otherwise stack one per level on the output
|
||||
const readInto = (...streams) => {
|
||||
if (!activeStreams.length) {
|
||||
outputStream.once('close', onOutputClose);
|
||||
}
|
||||
activeStreams = streams;
|
||||
};
|
||||
// stops here if the output was destroyed in the meantime
|
||||
const aborted = () => {
|
||||
if (outputStream.destroyed) {
|
||||
callback(abortedError());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
// for multipart nodes, push child nodes
|
||||
// for content nodes end the stream
|
||||
const finalize = () => {
|
||||
let childId = 0;
|
||||
const processChildNode = () => {
|
||||
if (aborted()) {
|
||||
return;
|
||||
}
|
||||
if (childId >= this.childNodes.length) {
|
||||
outputStream.write('\r\n--' + this.boundary + '--\r\n');
|
||||
return callback();
|
||||
@@ -719,89 +761,80 @@ class MimeNode {
|
||||
};
|
||||
// pushes node content
|
||||
const sendContent = () => {
|
||||
if (this.content) {
|
||||
if (Object.prototype.toString.call(this.content) === '[object Error]') {
|
||||
// content is already errored
|
||||
return callback(this.content);
|
||||
}
|
||||
if (typeof this.content.pipe === 'function') {
|
||||
this.content.removeListener('error', this._contentErrorHandler);
|
||||
this._contentErrorHandler = err => callback(err);
|
||||
this.content.once('error', this._contentErrorHandler);
|
||||
}
|
||||
const createStream = () => {
|
||||
if (['quoted-printable', 'base64'].includes(transferEncoding)) {
|
||||
contentStream = new (transferEncoding === 'base64' ? base64 : qp).Encoder(options);
|
||||
contentStream.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
contentStream.once('end', finalize);
|
||||
contentStream.once('error', err => callback(err));
|
||||
localStream = this._getStream(this.content);
|
||||
localStream.pipe(contentStream);
|
||||
}
|
||||
else {
|
||||
// anything that is not QP or Base54 passes as-is
|
||||
localStream = this._getStream(this.content);
|
||||
localStream.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
localStream.once('end', finalize);
|
||||
}
|
||||
localStream.once('error', err => callback(err));
|
||||
};
|
||||
if (this.content._resolve) {
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
let returned = false;
|
||||
const sourceStream = this._getStream(this.content);
|
||||
sourceStream.on('error', err => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
returned = true;
|
||||
callback(err);
|
||||
});
|
||||
sourceStream.on('readable', () => {
|
||||
let chunk;
|
||||
while ((chunk = sourceStream.read()) !== null) {
|
||||
chunks.push(chunk);
|
||||
chunklen += chunk.length;
|
||||
}
|
||||
});
|
||||
sourceStream.on('end', () => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
returned = true;
|
||||
this.content._resolve = false;
|
||||
this.content._resolvedValue = Buffer.concat(chunks, chunklen);
|
||||
setImmediate(createStream);
|
||||
});
|
||||
}
|
||||
else {
|
||||
setImmediate(createStream);
|
||||
}
|
||||
if (aborted()) {
|
||||
return;
|
||||
}
|
||||
return setImmediate(finalize);
|
||||
if (!this.content) {
|
||||
return setImmediate(finalize);
|
||||
}
|
||||
const createStream = () => {
|
||||
if (aborted()) {
|
||||
return;
|
||||
}
|
||||
const contentError = this._takeStreamContent(this.content);
|
||||
if (contentError) {
|
||||
return callback(contentError);
|
||||
}
|
||||
const localStream = this._getStream(this.content);
|
||||
if (['quoted-printable', 'base64'].includes(transferEncoding)) {
|
||||
const contentStream = transferEncoding === 'base64'
|
||||
? new base64.Encoder(options)
|
||||
: // outside of text a lone CR or LF is data, encoding it keeps it from being
|
||||
// turned into a line break by a newline transform or the receiving side
|
||||
new qp.Encoder(Object.assign({}, options, { binary: !/^text\//i.test(this.contentType || '') }));
|
||||
readInto(localStream, contentStream);
|
||||
contentStream.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
// pipeline reports errors and tears both streams down. It calls back once the
|
||||
// encoder took all of its input, the encoded output may still be waiting to
|
||||
// be read, so the node is only done once the encoder's readable side ended
|
||||
pipeline(localStream, contentStream, err => err && callback(err));
|
||||
finished(contentStream, { writable: false }, err => (err ? callback(err) : finalize()));
|
||||
}
|
||||
else {
|
||||
// anything that is not QP or Base54 passes as-is
|
||||
readInto(localStream);
|
||||
localStream.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
finished(localStream, { writable: false }, err => (err ? callback(err) : finalize()));
|
||||
}
|
||||
};
|
||||
if (this.content._resolve) {
|
||||
const sourceStream = this._getStream(this.content);
|
||||
readInto(sourceStream);
|
||||
shared.resolveStream(sourceStream, (err, value) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
this.content._resolve = false;
|
||||
this.content._resolvedValue = value;
|
||||
setImmediate(createStream);
|
||||
});
|
||||
}
|
||||
else {
|
||||
setImmediate(createStream);
|
||||
}
|
||||
};
|
||||
if (this._raw) {
|
||||
setImmediate(() => {
|
||||
if (Object.prototype.toString.call(this._raw) === '[object Error]') {
|
||||
// content is already errored
|
||||
return callback(this._raw);
|
||||
if (aborted()) {
|
||||
return;
|
||||
}
|
||||
// remove default error handler (if set)
|
||||
if (typeof this._raw.pipe === 'function') {
|
||||
this._raw.removeListener('error', this._contentErrorHandler);
|
||||
const rawError = this._takeStreamContent(this._raw);
|
||||
if (rawError) {
|
||||
return callback(rawError);
|
||||
}
|
||||
const raw = this._getStream(this._raw);
|
||||
readInto(raw);
|
||||
raw.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
raw.on('error', err => outputStream.emit('error', err));
|
||||
raw.on('end', finalize);
|
||||
finished(raw, { writable: false }, err => (err ? callback(err) : finalize()));
|
||||
});
|
||||
}
|
||||
else {
|
||||
@@ -917,11 +950,7 @@ class MimeNode {
|
||||
if (this._raw && typeof this._raw.pipe === 'function') {
|
||||
// pre-stream handler. might be triggered if a stream is set as content
|
||||
// and 'error' fires before anything is done with this stream
|
||||
this._contentErrorHandler = err => {
|
||||
this._raw.removeListener('error', this._contentErrorHandler);
|
||||
this._raw = err;
|
||||
};
|
||||
this._raw.once('error', this._contentErrorHandler);
|
||||
shared.recordStreamErrors(this._raw);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -947,6 +976,37 @@ class MimeNode {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Destroys the content streams of this node and of every node below it
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
_destroyContentStreams() {
|
||||
shared.destroyStream(this.content);
|
||||
shared.destroyStream(this._raw);
|
||||
for (const child of this.childNodes) {
|
||||
child._destroyContentStreams();
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Checks that a content value can still be read before the node streams it. A stream is
|
||||
* refused once it errored, ended or was destroyed: piping it would either never finish or
|
||||
* produce an empty body without any error
|
||||
*
|
||||
* @param content Node content or raw value
|
||||
* @returns The error to stop with, or null when the content can be read
|
||||
* @internal
|
||||
*/
|
||||
_takeStreamContent(content) {
|
||||
if (Object.prototype.toString.call(content) === '[object Error]') {
|
||||
return content;
|
||||
}
|
||||
if (!content || typeof content.pipe !== 'function' || content._resolvedValue) {
|
||||
// the value of a resolved stream is read from the buffered copy
|
||||
return null;
|
||||
}
|
||||
return shared.unreadableStreamError(content);
|
||||
}
|
||||
/**
|
||||
* Detects and returns handle to a stream related with the content.
|
||||
*
|
||||
|
||||
Reference in new issue
Block a user