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:
+108
@@ -0,0 +1,108 @@
|
||||
import * as punycode from '../punycode/index.js';
|
||||
import * as mimeFuncs from '../mime-funcs/index.js';
|
||||
import crypto from 'node:crypto';
|
||||
/**
|
||||
* Returns DKIM signature header line
|
||||
*
|
||||
* @param headers Parsed headers object from MessageParser
|
||||
* @param bodyHash Base64 encoded hash of the message
|
||||
* @param options DKIM options
|
||||
* @param options.domainName Domain name to be signed for
|
||||
* @param options.keySelector DKIM key selector to use
|
||||
* @param options.privateKey DKIM private key to use
|
||||
* @return Complete header line
|
||||
*/
|
||||
function sign(headers, hashAlgo, bodyHash, options) {
|
||||
options = options || {};
|
||||
// all listed fields from RFC4871 #5.5
|
||||
const defaultFieldNames = 'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
|
||||
'Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:' +
|
||||
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
||||
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
||||
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
||||
'List-Owner:List-Archive';
|
||||
const fieldNames = options.headerFieldNames || defaultFieldNames;
|
||||
const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
||||
const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
||||
canonicalizedHeaderData.headers += 'dkim-signature:' + relaxedHeaderLine(dkimHeader);
|
||||
const signer = crypto.createSign(('rsa-' + hashAlgo).toUpperCase());
|
||||
// the header lines are 'binary' strings, so this reproduces the original header bytes
|
||||
signer.update(canonicalizedHeaderData.headers, 'latin1');
|
||||
let signature;
|
||||
try {
|
||||
signature = signer.sign(options.privateKey, 'base64');
|
||||
}
|
||||
catch (_E) {
|
||||
return false;
|
||||
}
|
||||
return dkimHeader + signature.replace(/(^.{73}|.{75}(?!\r?\n|\r))/g, '$&\r\n ').trim();
|
||||
}
|
||||
sign.relaxedHeaders = relaxedHeaders;
|
||||
export default sign;
|
||||
function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyHash) {
|
||||
// the caller supplied tag values are interpolated straight into the tag list, and none of
|
||||
// them has any way to carry a control char, DEL, or one of the delimiters that would close
|
||||
// the value and open a tag of its own
|
||||
const cleanTagValue = (value) => (value || '').toString().replace(/[\x00-\x1f\x7f;=]/g, '');
|
||||
const dkim = [
|
||||
'v=1',
|
||||
'a=rsa-' + hashAlgo,
|
||||
'c=relaxed/relaxed',
|
||||
'd=' + punycode.toASCII(cleanTagValue(domainName)),
|
||||
'q=dns/txt',
|
||||
's=' + cleanTagValue(keySelector),
|
||||
'bh=' + bodyHash,
|
||||
'h=' + cleanTagValue(fieldNames)
|
||||
].join('; ');
|
||||
return mimeFuncs.foldLines('DKIM-Signature: ' + dkim, 76) + ';\r\n b=';
|
||||
}
|
||||
function relaxedHeaders(headers, fieldNames, skipFields) {
|
||||
const includedFields = new Set();
|
||||
const skip = new Set();
|
||||
const headerFields = new Map();
|
||||
(skipFields || '')
|
||||
.toLowerCase()
|
||||
.split(':')
|
||||
.forEach(field => {
|
||||
skip.add(field.trim());
|
||||
});
|
||||
(fieldNames || '')
|
||||
.toLowerCase()
|
||||
.split(':')
|
||||
.filter(field => !skip.has(field.trim()))
|
||||
.forEach(field => {
|
||||
includedFields.add(field.trim());
|
||||
});
|
||||
for (let i = headers.length - 1; i >= 0; i--) {
|
||||
const line = headers[i];
|
||||
// only include the first value from bottom to top
|
||||
if (includedFields.has(line.key) && !headerFields.has(line.key)) {
|
||||
headerFields.set(line.key, relaxedHeaderLine(line.line));
|
||||
}
|
||||
}
|
||||
const headersList = [];
|
||||
const fields = [];
|
||||
includedFields.forEach(field => {
|
||||
if (headerFields.has(field)) {
|
||||
fields.push(field);
|
||||
headersList.push(field + ':' + headerFields.get(field));
|
||||
}
|
||||
});
|
||||
return {
|
||||
headers: headersList.join('\r\n') + '\r\n',
|
||||
fieldNames: fields.join(':')
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Relaxed canonicalization of a header field value (RFC 6376 section 3.4.2): unfold, turn
|
||||
* every run of SP and HTAB into a single SP and drop the whitespace next to the colon and
|
||||
* at the end. Only SP and HTAB count as whitespace, so bytes that decode to other space
|
||||
* characters, such as a non-breaking space in a UTF-8 header, stay as they are
|
||||
*/
|
||||
function relaxedHeaderLine(line) {
|
||||
return line
|
||||
.substr(line.indexOf(':') + 1)
|
||||
.replace(/\r?\n/g, '')
|
||||
.replace(/[ \t]+/g, ' ')
|
||||
.replace(/^ | $/g, '');
|
||||
}
|
||||
Reference in New Issue
Block a user