mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-09-17 09:06:48 +07:00
node_modules: update (#322)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
+148
@@ -0,0 +1,148 @@
|
||||
"use strict";
|
||||
// streams through a message body and calculates relaxed body hash
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const node_stream_1 = require("node:stream");
|
||||
const node_crypto_1 = __importDefault(require("node:crypto"));
|
||||
const CHAR_CR = 0x0d;
|
||||
const CHAR_LF = 0x0a;
|
||||
const CHAR_SPACE = 0x20;
|
||||
const CHAR_TAB = 0x09;
|
||||
const CRLF = Buffer.from('\r\n');
|
||||
// a run of empty lines is hashed from this buffer in slices
|
||||
const EMPTY_LINES = Buffer.alloc(4096, CRLF);
|
||||
/**
|
||||
* Passes the message body through unchanged and hashes its relaxed
|
||||
* canonicalization (RFC 6376 section 3.4.4) on the side: whitespace at the end
|
||||
* of a line is dropped, runs of whitespace within a line become a single space,
|
||||
* every line ends with CRLF, empty lines at the end of the body are ignored and
|
||||
* a non-empty body always ends with CRLF. Bytes are canonicalized as they arrive,
|
||||
* so a line of any length costs constant memory.
|
||||
*/
|
||||
class RelaxedBody extends node_stream_1.Transform {
|
||||
constructor(options) {
|
||||
super();
|
||||
options = options || {};
|
||||
this.bodyHash = node_crypto_1.default.createHash(options.hashAlgo || 'sha256');
|
||||
this.byteLength = 0;
|
||||
this.debug = options.debug;
|
||||
this._debugBody = options.debug ? [] : false;
|
||||
this._lineHasContent = false;
|
||||
this._pendingWsp = false;
|
||||
this._pendingCr = false;
|
||||
this._pendingEmptyLines = 0;
|
||||
}
|
||||
/** @internal */
|
||||
_hashCanonical(data) {
|
||||
if (!data.length) {
|
||||
return;
|
||||
}
|
||||
this.bodyHash.update(data);
|
||||
if (this._debugBody) {
|
||||
this._debugBody.push(Buffer.from(data));
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_hashEmptyLines() {
|
||||
while (this._pendingEmptyLines > 0) {
|
||||
const count = Math.min(this._pendingEmptyLines, EMPTY_LINES.length / 2);
|
||||
this._hashCanonical(EMPTY_LINES.subarray(0, count * 2));
|
||||
this._pendingEmptyLines -= count;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Writes a content byte, with the space a pending run of whitespace collapses to,
|
||||
* into the output buffer and returns the new write position. Kept a method rather
|
||||
* than a closure so the write position stays a plain local in the byte loop
|
||||
* @internal
|
||||
*/
|
||||
_emitContent(out, outPos, c) {
|
||||
if (!this._lineHasContent) {
|
||||
if (this._pendingEmptyLines) {
|
||||
// the first content byte of a line is where the empty lines before it
|
||||
// become part of the body, so hash what is in the buffer before them
|
||||
this._hashCanonical(out.subarray(0, outPos));
|
||||
outPos = 0;
|
||||
this._hashEmptyLines();
|
||||
}
|
||||
this._lineHasContent = true;
|
||||
}
|
||||
if (this._pendingWsp) {
|
||||
out[outPos++] = CHAR_SPACE;
|
||||
this._pendingWsp = false;
|
||||
}
|
||||
out[outPos++] = c;
|
||||
return outPos;
|
||||
}
|
||||
updateHash(chunk, final) {
|
||||
// every byte contributes itself at most once, plus a CR for a bare LF
|
||||
// and, once per chunk, a pending space and CR carried over from before
|
||||
const out = Buffer.allocUnsafe(chunk.length * 2 + 2);
|
||||
let outPos = 0;
|
||||
for (let i = 0; i < chunk.length; i++) {
|
||||
const c = chunk[i];
|
||||
if (c === CHAR_LF) {
|
||||
// end of line, a CR right before it and any trailing whitespace are dropped
|
||||
if (this._lineHasContent) {
|
||||
out[outPos++] = CHAR_CR;
|
||||
out[outPos++] = CHAR_LF;
|
||||
this._lineHasContent = false;
|
||||
}
|
||||
else {
|
||||
this._pendingEmptyLines++;
|
||||
}
|
||||
this._pendingWsp = false;
|
||||
this._pendingCr = false;
|
||||
continue;
|
||||
}
|
||||
if (this._pendingCr) {
|
||||
// not followed by LF, so the CR is content
|
||||
outPos = this._emitContent(out, outPos, CHAR_CR);
|
||||
this._pendingCr = false;
|
||||
}
|
||||
if (c === CHAR_CR) {
|
||||
this._pendingCr = true;
|
||||
}
|
||||
else if (c === CHAR_SPACE || c === CHAR_TAB) {
|
||||
this._pendingWsp = true;
|
||||
}
|
||||
else {
|
||||
outPos = this._emitContent(out, outPos, c);
|
||||
}
|
||||
}
|
||||
if (final && this._pendingCr) {
|
||||
// a CR at the very end of the body is content
|
||||
outPos = this._emitContent(out, outPos, CHAR_CR);
|
||||
this._pendingCr = false;
|
||||
}
|
||||
this._hashCanonical(out.subarray(0, outPos));
|
||||
}
|
||||
/** @internal */
|
||||
_transform(chunk, encoding, callback) {
|
||||
if (!chunk || !chunk.length) {
|
||||
return callback();
|
||||
}
|
||||
if (typeof chunk === 'string') {
|
||||
chunk = Buffer.from(chunk, encoding);
|
||||
}
|
||||
this.updateHash(chunk);
|
||||
this.byteLength += chunk.length;
|
||||
this.push(chunk);
|
||||
callback();
|
||||
}
|
||||
/** @internal */
|
||||
_flush(callback) {
|
||||
this.updateHash(Buffer.alloc(0), true);
|
||||
if (this._lineHasContent) {
|
||||
// the body does not end with a line break, add one
|
||||
this._hashCanonical(CRLF);
|
||||
}
|
||||
this.emit('hash', this.bodyHash.digest('base64'), this.debug ? Buffer.concat(this._debugBody) : false);
|
||||
callback();
|
||||
}
|
||||
}
|
||||
exports.default = RelaxedBody;
|
||||
module.exports = exports.default;
|
||||
Object.defineProperty(module.exports, 'default', { value: exports.default, enumerable: false, writable: true, configurable: true });
|
||||
Reference in New Issue
Block a user