mirror of
https://github.com/dawidd6/action-send-mail.git
synced 2026-05-04 16:44:22 +07:00
node_modules: update (#290)
Co-authored-by: dawidd6 <9713907+dawidd6@users.noreply.github.com>
This commit is contained in:
39
node_modules/nodemailer/CHANGELOG.md
generated
vendored
39
node_modules/nodemailer/CHANGELOG.md
generated
vendored
@@ -1,5 +1,44 @@
|
||||
# CHANGELOG
|
||||
|
||||
## [8.0.7](https://github.com/nodemailer/nodemailer/compare/v8.0.6...v8.0.7) (2026-04-27)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* keep domain as UTF-8 when local part is non-ASCII ([#1814](https://github.com/nodemailer/nodemailer/issues/1814)) ([66d4ecb](https://github.com/nodemailer/nodemailer/commit/66d4ecb5aa431f3614a26b3c08b9c63cdf32a9ea))
|
||||
|
||||
## [8.0.6](https://github.com/nodemailer/nodemailer/compare/v8.0.5...v8.0.6) (2026-04-24)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* restore base64 wrap() trim behavior to prevent trailing CRLF ([#1810](https://github.com/nodemailer/nodemailer/issues/1810)) ([#1811](https://github.com/nodemailer/nodemailer/issues/1811)) ([b1ae6c1](https://github.com/nodemailer/nodemailer/commit/b1ae6c1c2927240737d9f68f316f0c84042b8adb))
|
||||
|
||||
## [8.0.5](https://github.com/nodemailer/nodemailer/compare/v8.0.4...v8.0.5) (2026-04-07)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* decode SMTP server responses as UTF-8 at line boundary ([95876b1](https://github.com/nodemailer/nodemailer/commit/95876b103e587e49583e43f88cb2c3a61556f3ac))
|
||||
* sanitize CRLF in transport name option to prevent SMTP command injection (GHSA-vvjj-xcjg-gr5g) ([0a43876](https://github.com/nodemailer/nodemailer/commit/0a43876801a420ca528f492eaa01bfc421cc306e))
|
||||
|
||||
## [8.0.4](https://github.com/nodemailer/nodemailer/compare/v8.0.3...v8.0.4) (2026-03-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* sanitize envelope size to prevent SMTP command injection ([2d7b971](https://github.com/nodemailer/nodemailer/commit/2d7b9710e63555a1eb13d721296c51186d4b5651))
|
||||
|
||||
## [8.0.3](https://github.com/nodemailer/nodemailer/compare/v8.0.2...v8.0.3) (2026-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* clean up addressparser and fix group name fallback producing undefined ([9d55877](https://github.com/nodemailer/nodemailer/commit/9d55877f8ed15a6aefd7ba76cbb6b6a6cdbcc4fd))
|
||||
* fix cookie bugs, remove dead code, and improve hot-path efficiency ([e8c8b92](https://github.com/nodemailer/nodemailer/commit/e8c8b92f46f2a82d06d49cc9a6ffc26067f68524))
|
||||
* refactor smtp-connection for clarity and add Node.js 6 syntax compat test ([c5b48ea](https://github.com/nodemailer/nodemailer/commit/c5b48ea61c28eabf347972f4198a12cdab226ff7))
|
||||
* remove familySupportCache that broke DNS resolution tests ([c803d90](https://github.com/nodemailer/nodemailer/commit/c803d901f195a21edbb2c276b2e116564467aaaa))
|
||||
|
||||
## [8.0.2](https://github.com/nodemailer/nodemailer/compare/v8.0.1...v8.0.2) (2026-03-09)
|
||||
|
||||
|
||||
|
||||
55
node_modules/nodemailer/CLAUDE.md
generated
vendored
Normal file
55
node_modules/nodemailer/CLAUDE.md
generated
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
# Nodemailer
|
||||
|
||||
E-mail sending library for Node.js. Zero runtime dependencies. Entry point is `lib/nodemailer.js`, which exposes `createTransport(transporter, defaults)` and routes to one of the bundled transports based on the options object.
|
||||
|
||||
## Layout
|
||||
|
||||
- `lib/nodemailer.js` — public entry, transport dispatch (`createTransport`).
|
||||
- `lib/mailer/` — `Mail` class: the user-facing transport wrapper that normalizes messages, runs the DKIM signer, and hands off to the underlying transport's `.send()`.
|
||||
- `lib/mail-composer/` + `lib/mime-node/` — message → MIME tree → raw RFC822 stream.
|
||||
- `lib/smtp-connection/` — low-level SMTP/LMTP/ESMTP client. Hot path; security-sensitive. Used by `smtp-transport` and `smtp-pool`.
|
||||
- `lib/smtp-transport/` — single-shot SMTP transport.
|
||||
- `lib/smtp-pool/` — pooled SMTP transport with rate limiting.
|
||||
- `lib/sendmail-transport/`, `lib/ses-transport/`, `lib/stream-transport/`, `lib/json-transport/` — alternate transports.
|
||||
- `lib/dkim/`, `lib/addressparser/`, `lib/mime-funcs/`, `lib/base64/`, `lib/qp/`, `lib/punycode/`, `lib/well-known/`, `lib/xoauth2/`, `lib/fetch/`, `lib/shared/` — supporting modules.
|
||||
- `test/` — mirrors `lib/` structure. Most suites spin up real `smtp-server` instances on ephemeral ports; raw `net` servers are used when byte-exact reply control is needed (e.g. injecting non-ASCII or invalid UTF-8).
|
||||
|
||||
Each transport must implement `name`, `version`, and `send(mail, callback)`. `Mail` discovers them via duck typing.
|
||||
|
||||
## Engine target
|
||||
|
||||
`engines.node = ">=6.0.0"`. The library is shipped as ES2017 script-mode CommonJS — no `import`, no top-level `await`, no optional chaining, no nullish coalescing, no class fields. ESLint enforces `ecmaVersion: 2017` and `sourceType: 'script'`. There is a Node 6 syntax-compat check (`npm run test:syntax`, runs `test/syntax-compat.js` inside `node:6-alpine`) that must keep passing — do not introduce syntax that breaks it. `'use strict';` directive at the top of every file.
|
||||
|
||||
## Conventions
|
||||
|
||||
- CommonJS only: `const x = require('...')`, `module.exports = ...`.
|
||||
- Callback-first style throughout the public API. Many internals are still callback-based — match the style of the file you are editing rather than introducing promises mid-module.
|
||||
- Prettier handles formatting; ESLint handles correctness. Run `npm run format` and `npm run lint` before sending changes. The lint config disables Prettier-overlapping rules.
|
||||
- Snake_case is not used; camelCase for variables and methods, PascalCase for classes.
|
||||
- Prefer small, surgical diffs. The codebase is mature and load-bearing — avoid drive-by refactors, comment churn, or "improvements" outside the scope of the change.
|
||||
- Every change to security-sensitive code (anything in `lib/smtp-connection/`, address parsing, header generation, DKIM) needs tests that exercise the failure mode, not just the happy path.
|
||||
|
||||
## Testing
|
||||
|
||||
- `npm test` — full suite via `node --test` (~150s, 480+ tests, runs serially).
|
||||
- `npm run test:coverage` — same suite under `c8`.
|
||||
- `npm run test:syntax` — Node 6 syntax compatibility check in Docker.
|
||||
- `npm run lint` / `npm run lint:fix`.
|
||||
- `npm run format` / `npm run format:check`.
|
||||
|
||||
Always run `npm test` and `npm run lint` before considering a change done. Tests are required to pass on every commit because release-please cuts releases directly from `master`.
|
||||
|
||||
## Releases
|
||||
|
||||
Releases, version numbers, the `version` field in `package.json`, git tags, `CHANGELOG.md` entries, and npm publication are all managed automatically by the release-please GitHub Action (`.github/workflows/release.yaml`, configured by `.release-please-config.json`). **Never edit any of these manually and never propose manual edits to them.**
|
||||
|
||||
Release-please derives the next version and changelog from Conventional Commit messages on `master`, opens a release PR (`chore: release X.Y.Z [skip-ci]`), and publishes to npm with provenance when that PR is merged. The only thing that should land on `master` between releases is normal commits with Conventional Commit prefixes — release-please takes care of the rest.
|
||||
|
||||
Conventional Commit prefixes used in this repo: `fix:`, `feat:`, `chore:`, `docs:`, `refactor:`, `test:`. Use `fix:` for anything users would benefit from seeing in the changelog, including security fixes (reference the GHSA in the body).
|
||||
|
||||
## Security
|
||||
|
||||
This is a widely-deployed library — security-sensitive changes get extra scrutiny:
|
||||
- SMTP command injection: any user-controllable value that flows into a written SMTP command (envelope addresses, sizes, the `name`/EHLO option, headers) must be CRLF-stripped or rejected at the boundary. Sanitize at the assignment, not at every call site.
|
||||
- Server reply parsing in `lib/smtp-connection/index.js` uses a `'binary'` byte-container intermediate to reassemble multi-byte UTF-8 across socket chunks; the actual decode happens at line boundaries via `decodeServerResponse`. Don't change the chunk-buffering encoding without understanding why.
|
||||
- Reference the GHSA ID in commit messages for advisories.
|
||||
151
node_modules/nodemailer/lib/addressparser/index.js
generated
vendored
151
node_modules/nodemailer/lib/addressparser/index.js
generated
vendored
@@ -10,23 +10,20 @@
|
||||
function _handleAddress(tokens, depth) {
|
||||
let isGroup = false;
|
||||
let state = 'text';
|
||||
let address;
|
||||
let addresses = [];
|
||||
let data = {
|
||||
const addresses = [];
|
||||
const data = {
|
||||
address: [],
|
||||
comment: [],
|
||||
group: [],
|
||||
text: [],
|
||||
textWasQuoted: [] // Track which text tokens came from inside quotes
|
||||
textWasQuoted: []
|
||||
};
|
||||
let i;
|
||||
let len;
|
||||
let insideQuotes = false; // Track if we're currently inside a quoted string
|
||||
let insideQuotes = false;
|
||||
|
||||
// Filter out <addresses>, (comments) and regular text
|
||||
for (i = 0, len = tokens.length; i < len; i++) {
|
||||
let token = tokens[i];
|
||||
let prevToken = i ? tokens[i - 1] : null;
|
||||
for (let i = 0, len = tokens.length; i < len; i++) {
|
||||
const token = tokens[i];
|
||||
const prevToken = i ? tokens[i - 1] : null;
|
||||
if (token.type === 'operator') {
|
||||
switch (token.value) {
|
||||
case '<':
|
||||
@@ -43,7 +40,6 @@ function _handleAddress(tokens, depth) {
|
||||
insideQuotes = false;
|
||||
break;
|
||||
case '"':
|
||||
// Track quote state for text tokens
|
||||
insideQuotes = !insideQuotes;
|
||||
state = 'text';
|
||||
break;
|
||||
@@ -54,14 +50,12 @@ function _handleAddress(tokens, depth) {
|
||||
}
|
||||
} else if (token.value) {
|
||||
if (state === 'address') {
|
||||
// handle use case where unquoted name includes a "<"
|
||||
// Apple Mail truncates everything between an unexpected < and an address
|
||||
// and so will we
|
||||
// Handle unquoted name that includes a "<".
|
||||
// Apple Mail truncates everything between an unexpected < and an address.
|
||||
token.value = token.value.replace(/^[^<]*<\s*/, '');
|
||||
}
|
||||
|
||||
if (prevToken && prevToken.noBreak && data[state].length) {
|
||||
// join values
|
||||
data[state][data[state].length - 1] += token.value;
|
||||
if (state === 'text' && insideQuotes) {
|
||||
data.textWasQuoted[data.textWasQuoted.length - 1] = true;
|
||||
@@ -88,11 +82,9 @@ function _handleAddress(tokens, depth) {
|
||||
// Parse group members, but flatten any nested groups (RFC 5322 doesn't allow nesting)
|
||||
let groupMembers = [];
|
||||
if (data.group.length) {
|
||||
let parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
|
||||
// Flatten: if any member is itself a group, extract its members into the sequence
|
||||
const parsedGroup = addressparser(data.group.join(','), { _depth: depth + 1 });
|
||||
parsedGroup.forEach(member => {
|
||||
if (member.group) {
|
||||
// Nested group detected - flatten it by adding its members directly
|
||||
groupMembers = groupMembers.concat(member.group);
|
||||
} else {
|
||||
groupMembers.push(member);
|
||||
@@ -101,40 +93,40 @@ function _handleAddress(tokens, depth) {
|
||||
}
|
||||
|
||||
addresses.push({
|
||||
name: data.text || (address && address.name),
|
||||
name: data.text || '',
|
||||
group: groupMembers
|
||||
});
|
||||
} else {
|
||||
// If no address was found, try to detect one from regular text
|
||||
if (!data.address.length && data.text.length) {
|
||||
for (i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security fix: Do not extract email addresses from quoted strings
|
||||
// RFC 5321 allows @ inside quoted local-parts like "user@domain"@example.com
|
||||
// Extracting emails from quoted text leads to misrouting vulnerabilities
|
||||
if (!data.textWasQuoted[i] && data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
|
||||
for (let i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security: Do not extract email addresses from quoted strings.
|
||||
// RFC 5321 allows @ inside quoted local-parts like "user@domain"@example.com.
|
||||
// Extracting emails from quoted text leads to misrouting vulnerabilities.
|
||||
if (!data.textWasQuoted[i] && /^[^@\s]+@[^@\s]+$/.test(data.text[i])) {
|
||||
data.address = data.text.splice(i, 1);
|
||||
data.textWasQuoted.splice(i, 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let _regexHandler = function (address) {
|
||||
if (!data.address.length) {
|
||||
data.address = [address.trim()];
|
||||
return ' ';
|
||||
} else {
|
||||
return address;
|
||||
}
|
||||
};
|
||||
|
||||
// still no address
|
||||
// Try a looser regex match if strict match found nothing
|
||||
if (!data.address.length) {
|
||||
for (i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security fix: Do not extract email addresses from quoted strings
|
||||
let extracted = false;
|
||||
for (let i = data.text.length - 1; i >= 0; i--) {
|
||||
// Security: Do not extract email addresses from quoted strings
|
||||
if (!data.textWasQuoted[i]) {
|
||||
// fixed the regex to parse email address correctly when email address has more than one @
|
||||
data.text[i] = data.text[i].replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, _regexHandler).trim();
|
||||
if (data.address.length) {
|
||||
data.text[i] = data.text[i]
|
||||
.replace(/\s*\b[^@\s]+@[^\s]+\b\s*/, match => {
|
||||
if (!extracted) {
|
||||
data.address = [match.trim()];
|
||||
extracted = true;
|
||||
return ' ';
|
||||
}
|
||||
return match;
|
||||
})
|
||||
.trim();
|
||||
if (extracted) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -142,13 +134,13 @@ function _handleAddress(tokens, depth) {
|
||||
}
|
||||
}
|
||||
|
||||
// If there's still is no text but a comment exixts, replace the two
|
||||
// If there's still no text but a comment exists, replace the two
|
||||
if (!data.text.length && data.comment.length) {
|
||||
data.text = data.comment;
|
||||
data.comment = [];
|
||||
}
|
||||
|
||||
// Keep only the first address occurence, push others to regular text
|
||||
// Keep only the first address occurrence, push others to regular text
|
||||
if (data.address.length > 1) {
|
||||
data.text = data.text.concat(data.address.splice(1));
|
||||
}
|
||||
@@ -157,24 +149,20 @@ function _handleAddress(tokens, depth) {
|
||||
data.text = data.text.join(' ');
|
||||
data.address = data.address.join(' ');
|
||||
|
||||
if (!data.address && isGroup) {
|
||||
return [];
|
||||
} else {
|
||||
address = {
|
||||
address: data.address || data.text || '',
|
||||
name: data.text || data.address || ''
|
||||
};
|
||||
const address = {
|
||||
address: data.address || data.text || '',
|
||||
name: data.text || data.address || ''
|
||||
};
|
||||
|
||||
if (address.address === address.name) {
|
||||
if ((address.address || '').match(/@/)) {
|
||||
address.name = '';
|
||||
} else {
|
||||
address.address = '';
|
||||
}
|
||||
if (address.address === address.name) {
|
||||
if (/@/.test(address.address || '')) {
|
||||
address.name = '';
|
||||
} else {
|
||||
address.address = '';
|
||||
}
|
||||
|
||||
addresses.push(address);
|
||||
}
|
||||
|
||||
addresses.push(address);
|
||||
}
|
||||
|
||||
return addresses;
|
||||
@@ -220,11 +208,11 @@ class Tokenizer {
|
||||
* @return {Array} An array of operator|text tokens
|
||||
*/
|
||||
tokenize() {
|
||||
let list = [];
|
||||
const list = [];
|
||||
|
||||
for (let i = 0, len = this.str.length; i < len; i++) {
|
||||
let chr = this.str.charAt(i);
|
||||
let nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
|
||||
const chr = this.str.charAt(i);
|
||||
const nextChr = i < len - 1 ? this.str.charAt(i + 1) : null;
|
||||
this.checkChar(chr, nextChr);
|
||||
}
|
||||
|
||||
@@ -325,17 +313,17 @@ const MAX_NESTED_GROUP_DEPTH = 50;
|
||||
*/
|
||||
function addressparser(str, options) {
|
||||
options = options || {};
|
||||
let depth = options._depth || 0;
|
||||
const depth = options._depth || 0;
|
||||
|
||||
// Prevent stack overflow from deeply nested groups (DoS protection)
|
||||
if (depth > MAX_NESTED_GROUP_DEPTH) {
|
||||
return [];
|
||||
}
|
||||
|
||||
let tokenizer = new Tokenizer(str);
|
||||
let tokens = tokenizer.tokenize();
|
||||
const tokenizer = new Tokenizer(str);
|
||||
const tokens = tokenizer.tokenize();
|
||||
|
||||
let addresses = [];
|
||||
const addresses = [];
|
||||
let address = [];
|
||||
let parsedAddresses = [];
|
||||
|
||||
@@ -354,44 +342,41 @@ function addressparser(str, options) {
|
||||
addresses.push(address);
|
||||
}
|
||||
|
||||
addresses.forEach(address => {
|
||||
address = _handleAddress(address, depth);
|
||||
if (address.length) {
|
||||
parsedAddresses = parsedAddresses.concat(address);
|
||||
addresses.forEach(addr => {
|
||||
const handled = _handleAddress(addr, depth);
|
||||
if (handled.length) {
|
||||
parsedAddresses = parsedAddresses.concat(handled);
|
||||
}
|
||||
});
|
||||
|
||||
// Merge fragments from unquoted display names containing commas/semicolons.
|
||||
// When "Joe Foo, PhD <joe@example.com>" is split on the comma, it produces
|
||||
// Merge fragments produced when unquoted display names contain commas.
|
||||
// "Joe Foo, PhD <joe@example.com>" is split on the comma into
|
||||
// [{name:"Joe Foo", address:""}, {name:"PhD", address:"joe@example.com"}].
|
||||
// Detect this pattern and recombine: a name-only entry followed by an entry
|
||||
// that has both a name and an address (from angle-bracket notation).
|
||||
// Recombine: a name-only entry followed by an entry with both name and address.
|
||||
for (let i = parsedAddresses.length - 2; i >= 0; i--) {
|
||||
let current = parsedAddresses[i];
|
||||
let next = parsedAddresses[i + 1];
|
||||
if (current.address === '' && current.name && !current.group && next.address && next.name && !next.group) {
|
||||
const current = parsedAddresses[i];
|
||||
const next = parsedAddresses[i + 1];
|
||||
if (current.address === '' && current.name && !current.group && next.address && next.name) {
|
||||
next.name = current.name + ', ' + next.name;
|
||||
parsedAddresses.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.flatten) {
|
||||
let addresses = [];
|
||||
let walkAddressList = list => {
|
||||
list.forEach(address => {
|
||||
if (address.group) {
|
||||
return walkAddressList(address.group);
|
||||
} else {
|
||||
addresses.push(address);
|
||||
const flatAddresses = [];
|
||||
const walkAddressList = list => {
|
||||
list.forEach(entry => {
|
||||
if (entry.group) {
|
||||
return walkAddressList(entry.group);
|
||||
}
|
||||
flatAddresses.push(entry);
|
||||
});
|
||||
};
|
||||
walkAddressList(parsedAddresses);
|
||||
return addresses;
|
||||
return flatAddresses;
|
||||
}
|
||||
|
||||
return parsedAddresses;
|
||||
}
|
||||
|
||||
// expose to the world
|
||||
module.exports = addressparser;
|
||||
|
||||
23
node_modules/nodemailer/lib/base64/index.js
generated
vendored
23
node_modules/nodemailer/lib/base64/index.js
generated
vendored
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const Transform = require('stream').Transform;
|
||||
const { Transform } = require('stream');
|
||||
|
||||
/**
|
||||
* Encodes a Buffer into a base64 encoded string
|
||||
@@ -31,16 +31,17 @@ function wrap(str, lineLength) {
|
||||
return str;
|
||||
}
|
||||
|
||||
let result = [];
|
||||
const result = [];
|
||||
let pos = 0;
|
||||
let chunkLength = lineLength * 1024;
|
||||
const chunkLength = lineLength * 1024;
|
||||
const wrapRegex = new RegExp('.{' + lineLength + '}', 'g');
|
||||
while (pos < str.length) {
|
||||
let wrappedLines = str.substr(pos, chunkLength).replace(new RegExp('.{' + lineLength + '}', 'g'), '$&\r\n');
|
||||
const wrappedLines = str.substr(pos, chunkLength).replace(wrapRegex, '$&\r\n').trim();
|
||||
result.push(wrappedLines);
|
||||
pos += chunkLength;
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
return result.join('\r\n').trim();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -94,20 +95,17 @@ class Encoder extends Transform {
|
||||
if (this.options.lineLength) {
|
||||
b64 = wrap(b64, this.options.lineLength);
|
||||
|
||||
let lastLF = b64.lastIndexOf('\n');
|
||||
// remove last line as it is still most probably incomplete
|
||||
const lastLF = b64.lastIndexOf('\n');
|
||||
if (lastLF < 0) {
|
||||
this._curLine = b64;
|
||||
b64 = '';
|
||||
} else if (lastLF === b64.length - 1) {
|
||||
this._curLine = '';
|
||||
} else {
|
||||
this._curLine = b64.substring(lastLF + 1);
|
||||
b64 = b64.substring(0, lastLF + 1);
|
||||
|
||||
if (b64 && !b64.endsWith('\r\n')) {
|
||||
b64 += '\r\n';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._curLine = '';
|
||||
}
|
||||
|
||||
if (b64) {
|
||||
@@ -124,6 +122,7 @@ class Encoder extends Transform {
|
||||
}
|
||||
|
||||
if (this._curLine) {
|
||||
this._curLine = wrap(this._curLine, this.options.lineLength);
|
||||
this.outputBytes += this._curLine.length;
|
||||
this.push(Buffer.from(this._curLine, 'ascii'));
|
||||
this._curLine = '';
|
||||
|
||||
24
node_modules/nodemailer/lib/dkim/index.js
generated
vendored
24
node_modules/nodemailer/lib/dkim/index.js
generated
vendored
@@ -6,7 +6,7 @@
|
||||
const MessageParser = require('./message-parser');
|
||||
const RelaxedBody = require('./relaxed-body');
|
||||
const sign = require('./sign');
|
||||
const PassThrough = require('stream').PassThrough;
|
||||
const { PassThrough } = require('stream');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const crypto = require('crypto');
|
||||
@@ -96,7 +96,7 @@ class DKIMSigner {
|
||||
}
|
||||
return this.createReadCache();
|
||||
}
|
||||
let chunk = this.chunks[this.readPos++];
|
||||
const chunk = this.chunks[this.readPos++];
|
||||
if (this.output.write(chunk) === false) {
|
||||
return this.output.once('drain', () => {
|
||||
this.sendNextChunk();
|
||||
@@ -107,13 +107,13 @@ class DKIMSigner {
|
||||
|
||||
sendSignedOutput() {
|
||||
let keyPos = 0;
|
||||
let signNextKey = () => {
|
||||
const signNextKey = () => {
|
||||
if (keyPos >= this.keys.length) {
|
||||
this.output.write(this.parser.rawHeaders);
|
||||
return setImmediate(() => this.sendNextChunk());
|
||||
}
|
||||
let key = this.keys[keyPos++];
|
||||
let dkimField = sign(this.headers, this.hashAlgo, this.bodyHash, {
|
||||
const key = this.keys[keyPos++];
|
||||
const dkimField = sign(this.headers, this.hashAlgo, this.bodyHash, {
|
||||
domainName: key.domainName,
|
||||
keySelector: key.keySelector,
|
||||
privateKey: key.privateKey,
|
||||
@@ -211,7 +211,7 @@ class DKIM {
|
||||
}
|
||||
|
||||
sign(input, extraOptions) {
|
||||
let output = new PassThrough();
|
||||
const output = new PassThrough();
|
||||
let inputStream = input;
|
||||
let writeValue = false;
|
||||
|
||||
@@ -225,18 +225,10 @@ class DKIM {
|
||||
|
||||
let options = this.options;
|
||||
if (extraOptions && Object.keys(extraOptions).length) {
|
||||
options = {};
|
||||
Object.keys(this.options || {}).forEach(key => {
|
||||
options[key] = this.options[key];
|
||||
});
|
||||
Object.keys(extraOptions || {}).forEach(key => {
|
||||
if (!(key in options)) {
|
||||
options[key] = extraOptions[key];
|
||||
}
|
||||
});
|
||||
options = Object.assign({}, extraOptions, this.options);
|
||||
}
|
||||
|
||||
let signer = new DKIMSigner(options, this.keys, inputStream, output);
|
||||
const signer = new DKIMSigner(options, this.keys, inputStream, output);
|
||||
setImmediate(() => {
|
||||
signer.signStream();
|
||||
if (writeValue) {
|
||||
|
||||
25
node_modules/nodemailer/lib/dkim/message-parser.js
generated
vendored
25
node_modules/nodemailer/lib/dkim/message-parser.js
generated
vendored
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const Transform = require('stream').Transform;
|
||||
const { Transform } = require('stream');
|
||||
|
||||
/**
|
||||
* MessageParser instance is a transform stream that separates message headers
|
||||
@@ -24,8 +24,8 @@ class MessageParser extends Transform {
|
||||
* @param {Buffer} data Next data chunk from the stream
|
||||
*/
|
||||
updateLastBytes(data) {
|
||||
let lblen = this.lastBytes.length;
|
||||
let nblen = Math.min(data.length, lblen);
|
||||
const lblen = this.lastBytes.length;
|
||||
const nblen = Math.min(data.length, lblen);
|
||||
|
||||
// shift existing bytes
|
||||
for (let i = 0, len = lblen - nblen; i < len; i++) {
|
||||
@@ -50,9 +50,8 @@ class MessageParser extends Transform {
|
||||
return true;
|
||||
}
|
||||
|
||||
let lblen = this.lastBytes.length;
|
||||
const lblen = this.lastBytes.length;
|
||||
let headerPos = 0;
|
||||
this.curLinePos = 0;
|
||||
for (let i = 0, len = this.lastBytes.length + data.length; i < len; i++) {
|
||||
let chr;
|
||||
if (i < lblen) {
|
||||
@@ -61,8 +60,8 @@ class MessageParser extends Transform {
|
||||
chr = data[i - lblen];
|
||||
}
|
||||
if (chr === 0x0a && i) {
|
||||
let pr1 = i - 1 < lblen ? this.lastBytes[i - 1] : data[i - 1 - lblen];
|
||||
let pr2 = i > 1 ? (i - 2 < lblen ? this.lastBytes[i - 2] : data[i - 2 - lblen]) : false;
|
||||
const pr1 = i - 1 < lblen ? this.lastBytes[i - 1] : data[i - 1 - lblen];
|
||||
const pr2 = i > 1 ? (i - 2 < lblen ? this.lastBytes[i - 2] : data[i - 2 - lblen]) : false;
|
||||
if (pr1 === 0x0a) {
|
||||
this.headersParsed = true;
|
||||
headerPos = i - lblen + 1;
|
||||
@@ -83,17 +82,17 @@ class MessageParser extends Transform {
|
||||
this.headerChunks = null;
|
||||
this.emit('headers', this.parseHeaders());
|
||||
if (data.length - 1 > headerPos) {
|
||||
let chunk = data.slice(headerPos);
|
||||
const chunk = data.slice(headerPos);
|
||||
this.bodySize += chunk.length;
|
||||
// this would be the first chunk of data sent downstream
|
||||
setImmediate(() => this.push(chunk));
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
this.headerBytes += data.length;
|
||||
this.headerChunks.push(data);
|
||||
}
|
||||
|
||||
this.headerBytes += data.length;
|
||||
this.headerChunks.push(data);
|
||||
|
||||
// store last 4 bytes to catch header break
|
||||
this.updateLastBytes(data);
|
||||
|
||||
@@ -127,7 +126,7 @@ class MessageParser extends Transform {
|
||||
|
||||
_flush(callback) {
|
||||
if (this.headerChunks) {
|
||||
let chunk = Buffer.concat(this.headerChunks, this.headerBytes);
|
||||
const chunk = Buffer.concat(this.headerChunks, this.headerBytes);
|
||||
this.bodySize += chunk.length;
|
||||
this.push(chunk);
|
||||
this.headerChunks = null;
|
||||
@@ -136,7 +135,7 @@ class MessageParser extends Transform {
|
||||
}
|
||||
|
||||
parseHeaders() {
|
||||
let lines = (this.rawHeaders || '').toString().split(/\r?\n/);
|
||||
const lines = (this.rawHeaders || '').toString().split(/\r?\n/);
|
||||
for (let i = lines.length - 1; i > 0; i--) {
|
||||
if (/^\s/.test(lines[i])) {
|
||||
lines[i - 1] += '\n' + lines[i];
|
||||
|
||||
4
node_modules/nodemailer/lib/dkim/relaxed-body.js
generated
vendored
4
node_modules/nodemailer/lib/dkim/relaxed-body.js
generated
vendored
@@ -2,7 +2,7 @@
|
||||
|
||||
// streams through a message body and calculates relaxed body hash
|
||||
|
||||
const Transform = require('stream').Transform;
|
||||
const { Transform } = require('stream');
|
||||
const crypto = require('crypto');
|
||||
|
||||
class RelaxedBody extends Transform {
|
||||
@@ -29,7 +29,7 @@ class RelaxedBody extends Transform {
|
||||
// If we get another chunk that does not match this description then we can restore the previously processed data
|
||||
let state = 'file';
|
||||
for (let i = chunk.length - 1; i >= 0; i--) {
|
||||
let c = chunk[i];
|
||||
const c = chunk[i];
|
||||
|
||||
if (state === 'file' && (c === 0x0a || c === 0x0d)) {
|
||||
// do nothing, found \n or \r at the end of chunk, stil end of file
|
||||
|
||||
27
node_modules/nodemailer/lib/dkim/sign.js
generated
vendored
27
node_modules/nodemailer/lib/dkim/sign.js
generated
vendored
@@ -20,7 +20,7 @@ module.exports = (headers, hashAlgo, bodyHash, options) => {
|
||||
options = options || {};
|
||||
|
||||
// all listed fields from RFC4871 #5.5
|
||||
let defaultFieldNames =
|
||||
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:' +
|
||||
@@ -28,17 +28,16 @@ module.exports = (headers, hashAlgo, bodyHash, options) => {
|
||||
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
||||
'List-Owner:List-Archive';
|
||||
|
||||
let fieldNames = options.headerFieldNames || defaultFieldNames;
|
||||
const fieldNames = options.headerFieldNames || defaultFieldNames;
|
||||
|
||||
let canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
||||
let dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
||||
|
||||
let signer, signature;
|
||||
const canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
|
||||
const dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
|
||||
|
||||
canonicalizedHeaderData.headers += 'dkim-signature:' + relaxedHeaderLine(dkimHeader);
|
||||
|
||||
signer = crypto.createSign(('rsa-' + hashAlgo).toUpperCase());
|
||||
const signer = crypto.createSign(('rsa-' + hashAlgo).toUpperCase());
|
||||
signer.update(canonicalizedHeaderData.headers);
|
||||
let signature;
|
||||
try {
|
||||
signature = signer.sign(options.privateKey, 'base64');
|
||||
} catch (_E) {
|
||||
@@ -51,7 +50,7 @@ module.exports = (headers, hashAlgo, bodyHash, options) => {
|
||||
module.exports.relaxedHeaders = relaxedHeaders;
|
||||
|
||||
function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyHash) {
|
||||
let dkim = [
|
||||
const dkim = [
|
||||
'v=1',
|
||||
'a=rsa-' + hashAlgo,
|
||||
'c=relaxed/relaxed',
|
||||
@@ -66,9 +65,9 @@ function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyH
|
||||
}
|
||||
|
||||
function relaxedHeaders(headers, fieldNames, skipFields) {
|
||||
let includedFields = new Set();
|
||||
let skip = new Set();
|
||||
let headerFields = new Map();
|
||||
const includedFields = new Set();
|
||||
const skip = new Set();
|
||||
const headerFields = new Map();
|
||||
|
||||
(skipFields || '')
|
||||
.toLowerCase()
|
||||
@@ -86,15 +85,15 @@ function relaxedHeaders(headers, fieldNames, skipFields) {
|
||||
});
|
||||
|
||||
for (let i = headers.length - 1; i >= 0; i--) {
|
||||
let line = headers[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));
|
||||
}
|
||||
}
|
||||
|
||||
let headersList = [];
|
||||
let fields = [];
|
||||
const headersList = [];
|
||||
const fields = [];
|
||||
includedFields.forEach(field => {
|
||||
if (headerFields.has(field)) {
|
||||
fields.push(field);
|
||||
|
||||
11
node_modules/nodemailer/lib/errors.js
generated
vendored
11
node_modules/nodemailer/lib/errors.js
generated
vendored
@@ -52,10 +52,7 @@ const ERROR_CODES = {
|
||||
};
|
||||
|
||||
// Export error codes as string constants and the full definitions object
|
||||
module.exports = Object.keys(ERROR_CODES).reduce(
|
||||
(exports, code) => {
|
||||
exports[code] = code;
|
||||
return exports;
|
||||
},
|
||||
{ ERROR_CODES }
|
||||
);
|
||||
module.exports = { ERROR_CODES };
|
||||
for (const code of Object.keys(ERROR_CODES)) {
|
||||
module.exports[code] = code;
|
||||
}
|
||||
|
||||
31
node_modules/nodemailer/lib/fetch/cookies.js
generated
vendored
31
node_modules/nodemailer/lib/fetch/cookies.js
generated
vendored
@@ -25,8 +25,8 @@ class Cookies {
|
||||
* @param {String} url Current URL
|
||||
*/
|
||||
set(cookieStr, url) {
|
||||
let urlparts = urllib.parse(url || '');
|
||||
let cookie = this.parse(cookieStr);
|
||||
const urlparts = urllib.parse(url || '');
|
||||
const cookie = this.parse(cookieStr);
|
||||
let domain;
|
||||
|
||||
if (cookie.domain) {
|
||||
@@ -76,15 +76,13 @@ class Cookies {
|
||||
* @returns {Array} An array of cookie objects
|
||||
*/
|
||||
list(url) {
|
||||
let result = [];
|
||||
let i;
|
||||
let cookie;
|
||||
const result = [];
|
||||
|
||||
for (i = this.cookies.length - 1; i >= 0; i--) {
|
||||
cookie = this.cookies[i];
|
||||
for (let i = this.cookies.length - 1; i >= 0; i--) {
|
||||
const cookie = this.cookies[i];
|
||||
|
||||
if (this.isExpired(cookie)) {
|
||||
this.cookies.splice(i, i);
|
||||
this.cookies.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -103,14 +101,14 @@ class Cookies {
|
||||
* @returns {Object} Cookie object
|
||||
*/
|
||||
parse(cookieStr) {
|
||||
let cookie = {};
|
||||
const cookie = {};
|
||||
|
||||
(cookieStr || '')
|
||||
.toString()
|
||||
.split(';')
|
||||
.forEach(cookiePart => {
|
||||
let valueParts = cookiePart.split('=');
|
||||
let key = valueParts.shift().trim().toLowerCase();
|
||||
const valueParts = cookiePart.split('=');
|
||||
const key = valueParts.shift().trim().toLowerCase();
|
||||
let value = valueParts.join('=').trim();
|
||||
let domain;
|
||||
|
||||
@@ -171,7 +169,7 @@ class Cookies {
|
||||
* @returns {Boolean} true if cookie is valid for specifiec URL
|
||||
*/
|
||||
match(cookie, url) {
|
||||
let urlparts = urllib.parse(url || '');
|
||||
const urlparts = urllib.parse(url || '');
|
||||
|
||||
// check if hostname matches
|
||||
// .foo.com also matches subdomains, foo.com does not
|
||||
@@ -183,7 +181,7 @@ class Cookies {
|
||||
}
|
||||
|
||||
// check if path matches
|
||||
let path = this.getPath(urlparts.pathname);
|
||||
const path = this.getPath(urlparts.pathname);
|
||||
if (path.substr(0, cookie.path.length) !== cookie.path) {
|
||||
return false;
|
||||
}
|
||||
@@ -202,16 +200,13 @@ class Cookies {
|
||||
* @param {Object} cookie Cookie value to be stored
|
||||
*/
|
||||
add(cookie) {
|
||||
let i;
|
||||
let len;
|
||||
|
||||
// nothing to do here
|
||||
if (!cookie || !cookie.name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// overwrite if has same params
|
||||
for (i = 0, len = this.cookies.length; i < len; i++) {
|
||||
for (let i = 0, len = this.cookies.length; i < len; i++) {
|
||||
if (this.compare(this.cookies[i], cookie)) {
|
||||
// check if the cookie needs to be removed instead
|
||||
if (this.isExpired(cookie)) {
|
||||
@@ -240,7 +235,7 @@ class Cookies {
|
||||
* @returns {Boolean} True, if the cookies are the same
|
||||
*/
|
||||
compare(a, b) {
|
||||
return a.name === b.name && a.path === b.path && a.domain === b.domain && a.secure === b.secure && a.httponly === a.httponly;
|
||||
return a.name === b.name && a.path === b.path && a.domain === b.domain && a.secure === b.secure && a.httponly === b.httponly;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
27
node_modules/nodemailer/lib/fetch/index.js
generated
vendored
27
node_modules/nodemailer/lib/fetch/index.js
generated
vendored
@@ -4,7 +4,7 @@ const http = require('http');
|
||||
const https = require('https');
|
||||
const urllib = require('url');
|
||||
const zlib = require('zlib');
|
||||
const PassThrough = require('stream').PassThrough;
|
||||
const { PassThrough } = require('stream');
|
||||
const Cookies = require('./cookies');
|
||||
const packageData = require('../../package.json');
|
||||
const net = require('net');
|
||||
@@ -33,16 +33,16 @@ function nmfetch(url, options) {
|
||||
options.cookie = false;
|
||||
}
|
||||
|
||||
let fetchRes = options.fetchRes;
|
||||
let parsed = urllib.parse(url);
|
||||
const fetchRes = options.fetchRes;
|
||||
const parsed = urllib.parse(url);
|
||||
let method = (options.method || '').toString().trim().toUpperCase() || 'GET';
|
||||
let finished = false;
|
||||
let cookies;
|
||||
let body;
|
||||
|
||||
let handler = parsed.protocol === 'https:' ? https : http;
|
||||
const handler = parsed.protocol === 'https:' ? https : http;
|
||||
|
||||
let headers = {
|
||||
const headers = {
|
||||
'accept-encoding': 'gzip,deflate',
|
||||
'user-agent': 'nodemailer/' + packageData.version
|
||||
};
|
||||
@@ -90,7 +90,7 @@ function nmfetch(url, options) {
|
||||
body = Buffer.from(
|
||||
Object.keys(options.body)
|
||||
.map(key => {
|
||||
let value = options.body[key].toString().trim();
|
||||
const value = options.body[key].toString().trim();
|
||||
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
||||
})
|
||||
.join('&')
|
||||
@@ -117,7 +117,7 @@ function nmfetch(url, options) {
|
||||
}
|
||||
|
||||
let req;
|
||||
let reqOptions = {
|
||||
const reqOptions = {
|
||||
method,
|
||||
host: parsed.hostname,
|
||||
path: parsed.path,
|
||||
@@ -128,9 +128,7 @@ function nmfetch(url, options) {
|
||||
};
|
||||
|
||||
if (options.tls) {
|
||||
Object.keys(options.tls).forEach(key => {
|
||||
reqOptions[key] = options.tls[key];
|
||||
});
|
||||
Object.assign(reqOptions, options.tls);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -162,7 +160,7 @@ function nmfetch(url, options) {
|
||||
}
|
||||
finished = true;
|
||||
req.abort();
|
||||
let err = new Error('Request Timeout');
|
||||
const err = new Error('Request Timeout');
|
||||
err.code = errors.EFETCH;
|
||||
err.sourceUrl = url;
|
||||
fetchRes.emit('error', err);
|
||||
@@ -204,7 +202,7 @@ function nmfetch(url, options) {
|
||||
options.redirects++;
|
||||
if (options.redirects > options.maxRedirects) {
|
||||
finished = true;
|
||||
let err = new Error('Maximum redirect count exceeded');
|
||||
const err = new Error('Maximum redirect count exceeded');
|
||||
err.code = errors.EFETCH;
|
||||
err.sourceUrl = url;
|
||||
fetchRes.emit('error', err);
|
||||
@@ -222,7 +220,7 @@ function nmfetch(url, options) {
|
||||
|
||||
if (res.statusCode >= 300 && !options.allowErrorResponse) {
|
||||
finished = true;
|
||||
let err = new Error('Invalid status code ' + res.statusCode);
|
||||
const err = new Error('Invalid status code ' + res.statusCode);
|
||||
err.code = errors.EFETCH;
|
||||
err.sourceUrl = url;
|
||||
fetchRes.emit('error', err);
|
||||
@@ -263,9 +261,8 @@ function nmfetch(url, options) {
|
||||
try {
|
||||
if (typeof body.pipe === 'function') {
|
||||
return body.pipe(req);
|
||||
} else {
|
||||
req.write(body);
|
||||
}
|
||||
req.write(body);
|
||||
} catch (err) {
|
||||
finished = true;
|
||||
err.code = errors.EFETCH;
|
||||
|
||||
8
node_modules/nodemailer/lib/json-transport/index.js
generated
vendored
8
node_modules/nodemailer/lib/json-transport/index.js
generated
vendored
@@ -13,7 +13,7 @@ class JSONTransport {
|
||||
constructor(options) {
|
||||
options = options || {};
|
||||
|
||||
this.options = options || {};
|
||||
this.options = options;
|
||||
|
||||
this.name = 'JSONTransport';
|
||||
this.version = packageData.version;
|
||||
@@ -33,10 +33,10 @@ class JSONTransport {
|
||||
// Sendmail strips this header line by itself
|
||||
mail.message.keepBcc = true;
|
||||
|
||||
let envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
let messageId = mail.message.messageId();
|
||||
const envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
const messageId = mail.message.messageId();
|
||||
|
||||
let recipients = [].concat(envelope.to || []);
|
||||
const recipients = [].concat(envelope.to || []);
|
||||
if (recipients.length > 3) {
|
||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
||||
}
|
||||
|
||||
202
node_modules/nodemailer/lib/mail-composer/index.js
generated
vendored
202
node_modules/nodemailer/lib/mail-composer/index.js
generated
vendored
@@ -4,7 +4,7 @@
|
||||
|
||||
const MimeNode = require('../mime-node');
|
||||
const mimeFuncs = require('../mime-funcs');
|
||||
const parseDataURI = require('../shared').parseDataURI;
|
||||
const { parseDataURI } = require('../shared');
|
||||
|
||||
/**
|
||||
* Creates the object for composing a MimeNode instance out from the mail options
|
||||
@@ -59,7 +59,7 @@ class MailComposer {
|
||||
|
||||
// Add headers to the root node, always overrides custom headers
|
||||
['from', 'sender', 'to', 'cc', 'bcc', 'reply-to', 'in-reply-to', 'references', 'subject', 'message-id', 'date'].forEach(header => {
|
||||
let key = header.replace(/-(\w)/g, (o, c) => c.toUpperCase());
|
||||
const key = header.replace(/-(\w)/g, (o, c) => c.toUpperCase());
|
||||
if (this.mail[key]) {
|
||||
this.message.setHeader(header, this.mail[key]);
|
||||
}
|
||||
@@ -84,20 +84,18 @@ class MailComposer {
|
||||
*/
|
||||
getAttachments(findRelated) {
|
||||
let icalEvent, eventObject;
|
||||
let attachments = [].concat(this.mail.attachments || []).map((attachment, i) => {
|
||||
let data;
|
||||
|
||||
const attachments = [].concat(this.mail.attachments || []).map((attachment, i) => {
|
||||
if (/^data:/i.test(attachment.path || attachment.href)) {
|
||||
attachment = this._processDataUrl(attachment);
|
||||
}
|
||||
|
||||
let contentType =
|
||||
const contentType =
|
||||
attachment.contentType || mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
|
||||
|
||||
let isImage = /^image\//i.test(contentType);
|
||||
let isMessageNode = /^message\//i.test(contentType);
|
||||
const isImage = /^image\//i.test(contentType);
|
||||
const isMessageNode = /^message\//i.test(contentType);
|
||||
|
||||
let contentDisposition =
|
||||
const contentDisposition =
|
||||
attachment.contentDisposition || (isMessageNode || (isImage && attachment.cid) ? 'inline' : 'attachment');
|
||||
|
||||
let contentTransferEncoding;
|
||||
@@ -111,7 +109,7 @@ class MailComposer {
|
||||
contentTransferEncoding = 'base64'; // the default
|
||||
}
|
||||
|
||||
data = {
|
||||
const data = {
|
||||
contentType,
|
||||
contentDisposition,
|
||||
contentTransferEncoding
|
||||
@@ -173,10 +171,7 @@ class MailComposer {
|
||||
};
|
||||
}
|
||||
|
||||
eventObject = {};
|
||||
Object.keys(icalEvent).forEach(key => {
|
||||
eventObject[key] = icalEvent[key];
|
||||
});
|
||||
eventObject = Object.assign({}, icalEvent);
|
||||
|
||||
eventObject.contentType = 'application/ics';
|
||||
if (!eventObject.headers) {
|
||||
@@ -192,12 +187,12 @@ class MailComposer {
|
||||
attached: attachments.concat(eventObject || []),
|
||||
related: []
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
attached: attachments.filter(attachment => !attachment.cid).concat(eventObject || []),
|
||||
related: attachments.filter(attachment => !!attachment.cid)
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
attached: attachments.filter(attachment => !attachment.cid).concat(eventObject || []),
|
||||
related: attachments.filter(attachment => !!attachment.cid)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,13 +201,8 @@ class MailComposer {
|
||||
* @returns {Array} An array of alternative elements. Includes the `text` and `html` values as well
|
||||
*/
|
||||
getAlternatives() {
|
||||
let alternatives = [],
|
||||
text,
|
||||
html,
|
||||
watchHtml,
|
||||
amp,
|
||||
icalEvent,
|
||||
eventObject;
|
||||
const alternatives = [];
|
||||
let text, html, watchHtml, amp, icalEvent, eventObject;
|
||||
|
||||
if (this.mail.text) {
|
||||
if (
|
||||
@@ -269,10 +259,7 @@ class MailComposer {
|
||||
};
|
||||
}
|
||||
|
||||
eventObject = {};
|
||||
Object.keys(icalEvent).forEach(key => {
|
||||
eventObject[key] = icalEvent[key];
|
||||
});
|
||||
eventObject = Object.assign({}, icalEvent);
|
||||
|
||||
if (eventObject.content && typeof eventObject.content === 'object') {
|
||||
// we are going to have the same attachment twice, so mark this to be
|
||||
@@ -310,13 +297,11 @@ class MailComposer {
|
||||
.concat(eventObject || [])
|
||||
.concat(this.mail.alternatives || [])
|
||||
.forEach(alternative => {
|
||||
let data;
|
||||
|
||||
if (/^data:/i.test(alternative.path || alternative.href)) {
|
||||
alternative = this._processDataUrl(alternative);
|
||||
}
|
||||
|
||||
data = {
|
||||
const data = {
|
||||
contentType:
|
||||
alternative.contentType ||
|
||||
mimeFuncs.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt'),
|
||||
@@ -368,26 +353,22 @@ class MailComposer {
|
||||
* @returns {Object} MimeNode node element
|
||||
*/
|
||||
_createMixed(parentNode) {
|
||||
let node;
|
||||
|
||||
if (!parentNode) {
|
||||
node = new MimeNode('multipart/mixed', {
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
} else {
|
||||
node = parentNode.createChild('multipart/mixed', {
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
}
|
||||
const node = parentNode
|
||||
? parentNode.createChild('multipart/mixed', {
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
})
|
||||
: new MimeNode('multipart/mixed', {
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
|
||||
if (this._useAlternative) {
|
||||
this._createAlternative(node);
|
||||
@@ -416,26 +397,22 @@ class MailComposer {
|
||||
* @returns {Object} MimeNode node element
|
||||
*/
|
||||
_createAlternative(parentNode) {
|
||||
let node;
|
||||
|
||||
if (!parentNode) {
|
||||
node = new MimeNode('multipart/alternative', {
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
} else {
|
||||
node = parentNode.createChild('multipart/alternative', {
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
}
|
||||
const node = parentNode
|
||||
? parentNode.createChild('multipart/alternative', {
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
})
|
||||
: new MimeNode('multipart/alternative', {
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
|
||||
this._alternatives.forEach(alternative => {
|
||||
if (this._useRelated && this._htmlNode === alternative) {
|
||||
@@ -455,26 +432,22 @@ class MailComposer {
|
||||
* @returns {Object} MimeNode node element
|
||||
*/
|
||||
_createRelated(parentNode) {
|
||||
let node;
|
||||
|
||||
if (!parentNode) {
|
||||
node = new MimeNode('multipart/related; type="text/html"', {
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
} else {
|
||||
node = parentNode.createChild('multipart/related; type="text/html"', {
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
}
|
||||
const node = parentNode
|
||||
? parentNode.createChild('multipart/related; type="text/html"', {
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
})
|
||||
: new MimeNode('multipart/related; type="text/html"', {
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
|
||||
this._createContentNode(node, this._htmlNode);
|
||||
|
||||
@@ -494,33 +467,30 @@ class MailComposer {
|
||||
element = element || {};
|
||||
element.content = element.content || '';
|
||||
|
||||
let node;
|
||||
let encoding = (element.encoding || 'utf8')
|
||||
const encoding = (element.encoding || 'utf8')
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/[-_\s]/g, '');
|
||||
|
||||
if (!parentNode) {
|
||||
node = new MimeNode(element.contentType, {
|
||||
filename: element.filename,
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
} else {
|
||||
node = parentNode.createChild(element.contentType, {
|
||||
filename: element.filename,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
}
|
||||
const node = parentNode
|
||||
? parentNode.createChild(element.contentType, {
|
||||
filename: element.filename,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
})
|
||||
: new MimeNode(element.contentType, {
|
||||
filename: element.filename,
|
||||
baseBoundary: this.mail.baseBoundary,
|
||||
textEncoding: this.mail.textEncoding,
|
||||
boundaryPrefix: this.mail.boundaryPrefix,
|
||||
disableUrlAccess: this.mail.disableUrlAccess,
|
||||
disableFileAccess: this.mail.disableFileAccess,
|
||||
normalizeHeaderKey: this.mail.normalizeHeaderKey,
|
||||
newline: this.mail.newline
|
||||
});
|
||||
|
||||
// add custom headers
|
||||
if (element.headers) {
|
||||
|
||||
52
node_modules/nodemailer/lib/mailer/index.js
generated
vendored
52
node_modules/nodemailer/lib/mailer/index.js
generated
vendored
@@ -106,17 +106,17 @@ class Mail extends EventEmitter {
|
||||
this.getSocket = false;
|
||||
}
|
||||
return this.transporter[method](...args);
|
||||
} else {
|
||||
this.logger.warn(
|
||||
{
|
||||
tnx: 'transport',
|
||||
methodName: method
|
||||
},
|
||||
'Non existing method %s called for transport',
|
||||
method
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.warn(
|
||||
{
|
||||
tnx: 'transport',
|
||||
methodName: method
|
||||
},
|
||||
'Non existing method %s called for transport',
|
||||
method
|
||||
);
|
||||
return false;
|
||||
};
|
||||
});
|
||||
|
||||
@@ -157,7 +157,7 @@ class Mail extends EventEmitter {
|
||||
this.getSocket = false;
|
||||
}
|
||||
|
||||
let mail = new MailMessage(this, data);
|
||||
const mail = new MailMessage(this, data);
|
||||
|
||||
this.logger.debug(
|
||||
{
|
||||
@@ -207,7 +207,7 @@ class Mail extends EventEmitter {
|
||||
|
||||
if (mail.data.dkim || this.dkim) {
|
||||
mail.message.processFunc(input => {
|
||||
let dkim = mail.data.dkim ? new DKIM(mail.data.dkim) : this.dkim;
|
||||
const dkim = mail.data.dkim ? new DKIM(mail.data.dkim) : this.dkim;
|
||||
this.logger.debug(
|
||||
{
|
||||
tnx: 'DKIM',
|
||||
@@ -259,8 +259,8 @@ class Mail extends EventEmitter {
|
||||
return callback();
|
||||
}
|
||||
|
||||
let userPlugins = this._userPlugins[step] || [];
|
||||
let defaultPlugins = this._defaultPlugins[step] || [];
|
||||
const userPlugins = this._userPlugins[step] || [];
|
||||
const defaultPlugins = this._defaultPlugins[step] || [];
|
||||
|
||||
if (userPlugins.length) {
|
||||
this.logger.debug(
|
||||
@@ -281,7 +281,7 @@ class Mail extends EventEmitter {
|
||||
|
||||
let pos = 0;
|
||||
let block = 'default';
|
||||
let processPlugins = () => {
|
||||
const processPlugins = () => {
|
||||
let curplugins = block === 'default' ? defaultPlugins : userPlugins;
|
||||
if (pos >= curplugins.length) {
|
||||
if (block === 'default' && userPlugins.length) {
|
||||
@@ -292,7 +292,7 @@ class Mail extends EventEmitter {
|
||||
return callback();
|
||||
}
|
||||
}
|
||||
let plugin = curplugins[pos++];
|
||||
const plugin = curplugins[pos++];
|
||||
plugin(mail, err => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
@@ -310,11 +310,11 @@ class Mail extends EventEmitter {
|
||||
* @param {String} proxyUrl Proxy configuration url
|
||||
*/
|
||||
setupProxy(proxyUrl) {
|
||||
let proxy = urllib.parse(proxyUrl);
|
||||
const proxy = urllib.parse(proxyUrl);
|
||||
|
||||
// setup socket handler for the mailer object
|
||||
this.getSocket = (options, callback) => {
|
||||
let protocol = proxy.protocol.replace(/:$/, '').toLowerCase();
|
||||
const protocol = proxy.protocol.replace(/:$/, '').toLowerCase();
|
||||
|
||||
if (this.meta.has('proxy_handler_' + protocol)) {
|
||||
return this.meta.get('proxy_handler_' + protocol)(proxy, options, callback);
|
||||
@@ -342,11 +342,11 @@ class Mail extends EventEmitter {
|
||||
err.code = errors.EPROXY;
|
||||
return callback(err);
|
||||
}
|
||||
let connect = ipaddress => {
|
||||
let proxyV2 = !!this.meta.get('proxy_socks_module').SocksClient;
|
||||
let socksClient = proxyV2 ? this.meta.get('proxy_socks_module').SocksClient : this.meta.get('proxy_socks_module');
|
||||
let proxyType = Number(proxy.protocol.replace(/\D/g, '')) || 5;
|
||||
let connectionOpts = {
|
||||
const connect = ipaddress => {
|
||||
const proxyV2 = !!this.meta.get('proxy_socks_module').SocksClient;
|
||||
const socksClient = proxyV2 ? this.meta.get('proxy_socks_module').SocksClient : this.meta.get('proxy_socks_module');
|
||||
const proxyType = Number(proxy.protocol.replace(/\D/g, '')) || 5;
|
||||
const connectionOpts = {
|
||||
proxy: {
|
||||
ipaddress,
|
||||
port: Number(proxy.port),
|
||||
@@ -360,8 +360,8 @@ class Mail extends EventEmitter {
|
||||
};
|
||||
|
||||
if (proxy.auth) {
|
||||
let username = decodeURIComponent(proxy.auth.split(':').shift());
|
||||
let password = decodeURIComponent(proxy.auth.split(':').pop());
|
||||
const username = decodeURIComponent(proxy.auth.split(':').shift());
|
||||
const password = decodeURIComponent(proxy.auth.split(':').pop());
|
||||
if (proxyV2) {
|
||||
connectionOpts.proxy.userId = username;
|
||||
connectionOpts.proxy.password = password;
|
||||
@@ -415,7 +415,7 @@ class Mail extends EventEmitter {
|
||||
html = (html || '')
|
||||
.toString()
|
||||
.replace(/(<img\b[^<>]{0,1024} src\s{0,20}=[\s"']{0,20})(data:([^;]+);[^"'>\s]+)/gi, (match, prefix, dataUri, mimeType) => {
|
||||
let cid = crypto.randomBytes(10).toString('hex') + '@localhost';
|
||||
const cid = crypto.randomBytes(10).toString('hex') + '@localhost';
|
||||
if (!mail.data.attachments) {
|
||||
mail.data.attachments = [];
|
||||
}
|
||||
|
||||
38
node_modules/nodemailer/lib/mailer/mail-message.js
generated
vendored
38
node_modules/nodemailer/lib/mailer/mail-message.js
generated
vendored
@@ -11,12 +11,10 @@ class MailMessage {
|
||||
this.message = null;
|
||||
|
||||
data = data || {};
|
||||
let options = mailer.options || {};
|
||||
let defaults = mailer._defaults || {};
|
||||
const options = mailer.options || {};
|
||||
const defaults = mailer._defaults || {};
|
||||
|
||||
Object.keys(data).forEach(key => {
|
||||
this.data[key] = data[key];
|
||||
});
|
||||
Object.assign(this.data, data);
|
||||
|
||||
this.data.headers = this.data.headers || {};
|
||||
|
||||
@@ -47,7 +45,7 @@ class MailMessage {
|
||||
}
|
||||
|
||||
resolveAll(callback) {
|
||||
let keys = [
|
||||
const keys = [
|
||||
[this.data, 'html'],
|
||||
[this.data, 'text'],
|
||||
[this.data, 'watchHtml'],
|
||||
@@ -79,9 +77,9 @@ class MailMessage {
|
||||
});
|
||||
}
|
||||
|
||||
let mimeNode = new MimeNode();
|
||||
const mimeNode = new MimeNode();
|
||||
|
||||
let addressKeys = ['from', 'to', 'cc', 'bcc', 'sender', 'replyTo'];
|
||||
const addressKeys = ['from', 'to', 'cc', 'bcc', 'sender', 'replyTo'];
|
||||
|
||||
addressKeys.forEach(address => {
|
||||
let value;
|
||||
@@ -97,7 +95,7 @@ class MailMessage {
|
||||
}
|
||||
});
|
||||
|
||||
let singleKeys = ['from', 'sender'];
|
||||
const singleKeys = ['from', 'sender'];
|
||||
singleKeys.forEach(address => {
|
||||
if (this.data[address]) {
|
||||
this.data[address] = this.data[address].shift();
|
||||
@@ -105,11 +103,11 @@ class MailMessage {
|
||||
});
|
||||
|
||||
let pos = 0;
|
||||
let resolveNext = () => {
|
||||
const resolveNext = () => {
|
||||
if (pos >= keys.length) {
|
||||
return callback(null, this.data);
|
||||
}
|
||||
let args = keys[pos++];
|
||||
const args = keys[pos++];
|
||||
if (!args[0] || !args[0][args[1]]) {
|
||||
return resolveNext();
|
||||
}
|
||||
@@ -118,7 +116,7 @@ class MailMessage {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let node = {
|
||||
const node = {
|
||||
content: value
|
||||
};
|
||||
if (args[0][args[1]] && typeof args[0][args[1]] === 'object' && !Buffer.isBuffer(args[0][args[1]])) {
|
||||
@@ -138,8 +136,8 @@ class MailMessage {
|
||||
}
|
||||
|
||||
normalize(callback) {
|
||||
let envelope = this.data.envelope || this.message.getEnvelope();
|
||||
let messageId = this.message.messageId();
|
||||
const envelope = this.data.envelope || this.message.getEnvelope();
|
||||
const messageId = this.message.messageId();
|
||||
|
||||
this.resolveAll((err, data) => {
|
||||
if (err) {
|
||||
@@ -195,7 +193,7 @@ class MailMessage {
|
||||
});
|
||||
|
||||
if (data.list && typeof data.list === 'object') {
|
||||
let listHeaders = this._getListHeaders(data.list);
|
||||
const listHeaders = this._getListHeaders(data.list);
|
||||
listHeaders.forEach(entry => {
|
||||
data.normalizedHeaders[entry.key] = entry.value.map(val => (val && val.value) || val).join(', ');
|
||||
});
|
||||
@@ -245,13 +243,11 @@ class MailMessage {
|
||||
return;
|
||||
}
|
||||
// add optional List-* headers
|
||||
if (this.data.list && typeof this.data.list === 'object') {
|
||||
this._getListHeaders(this.data.list).forEach(listHeader => {
|
||||
listHeader.value.forEach(value => {
|
||||
this.message.addHeader(listHeader.key, value);
|
||||
});
|
||||
this._getListHeaders(this.data.list).forEach(listHeader => {
|
||||
listHeader.value.forEach(value => {
|
||||
this.message.addHeader(listHeader.key, value);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
_getListHeaders(listData) {
|
||||
|
||||
65
node_modules/nodemailer/lib/mime-funcs/index.js
generated
vendored
65
node_modules/nodemailer/lib/mime-funcs/index.js
generated
vendored
@@ -15,11 +15,7 @@ module.exports = {
|
||||
*/
|
||||
isPlainText(value, isParam) {
|
||||
const re = isParam ? /[\x00-\x08\x0b\x0c\x0e-\x1f"\u0080-\uFFFF]/ : /[\x00-\x08\x0b\x0c\x0e-\x1f\u0080-\uFFFF]/;
|
||||
if (typeof value !== 'string' || re.test(value)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return typeof value === 'string' && !re.test(value);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -54,7 +50,7 @@ module.exports = {
|
||||
maxLength = maxLength || 0;
|
||||
|
||||
let encodedStr;
|
||||
let toCharset = 'UTF-8';
|
||||
const toCharset = 'UTF-8';
|
||||
|
||||
if (maxLength && maxLength > 7 + toCharset.length) {
|
||||
maxLength -= 7 + toCharset.length;
|
||||
@@ -63,12 +59,11 @@ module.exports = {
|
||||
if (mimeWordEncoding === 'Q') {
|
||||
// https://tools.ietf.org/html/rfc2047#section-5 rule (3)
|
||||
encodedStr = qp.encode(data).replace(/[^a-z0-9!*+\-/=]/gi, chr => {
|
||||
let ord = chr.charCodeAt(0).toString(16).toUpperCase();
|
||||
const ord = chr.charCodeAt(0).toString(16).toUpperCase();
|
||||
if (chr === ' ') {
|
||||
return '_';
|
||||
} else {
|
||||
return '=' + (ord.length === 1 ? '0' + ord : ord);
|
||||
}
|
||||
return '=' + (ord.length === 1 ? '0' + ord : ord);
|
||||
});
|
||||
} else if (mimeWordEncoding === 'B') {
|
||||
encodedStr = typeof data === 'string' ? data : base64.encode(data);
|
||||
@@ -80,7 +75,7 @@ module.exports = {
|
||||
encodedStr = this.splitMimeEncodedString(encodedStr, maxLength).join('?= =?' + toCharset + '?' + mimeWordEncoding + '?');
|
||||
} else {
|
||||
// RFC2047 6.3 (2) states that encoded-word must include an integral number of characters, so no chopping unicode sequences
|
||||
let parts = [];
|
||||
const parts = [];
|
||||
let lpart = '';
|
||||
for (let i = 0, len = encodedStr.length; i < len; i++) {
|
||||
let chr = encodedStr.charAt(i);
|
||||
@@ -129,42 +124,38 @@ module.exports = {
|
||||
encodeWords(value, mimeWordEncoding, maxLength, encodeAll) {
|
||||
maxLength = maxLength || 0;
|
||||
|
||||
let encodedValue;
|
||||
|
||||
// find first word with a non-printable ascii or special symbol in it
|
||||
let firstMatch = value.match(/(?:^|\s)([^\s]*["\u0080-\uFFFF])/);
|
||||
const firstMatch = value.match(/(?:^|\s)([^\s]*["\u0080-\uFFFF])/);
|
||||
if (!firstMatch) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (encodeAll) {
|
||||
// if it is requested to encode everything or the string contains something that resebles encoded word, then encode everything
|
||||
|
||||
return this.encodeWord(value, mimeWordEncoding, maxLength);
|
||||
}
|
||||
|
||||
// find the last word with a non-printable ascii in it
|
||||
let lastMatch = value.match(/(["\u0080-\uFFFF][^\s]*)[^"\u0080-\uFFFF]*$/);
|
||||
const lastMatch = value.match(/(["\u0080-\uFFFF][^\s]*)[^"\u0080-\uFFFF]*$/);
|
||||
if (!lastMatch) {
|
||||
// should not happen
|
||||
return value;
|
||||
}
|
||||
|
||||
let startIndex =
|
||||
const startIndex =
|
||||
firstMatch.index +
|
||||
(
|
||||
firstMatch[0].match(/[^\s]/) || {
|
||||
index: 0
|
||||
}
|
||||
).index;
|
||||
let endIndex = lastMatch.index + (lastMatch[1] || '').length;
|
||||
const endIndex = lastMatch.index + (lastMatch[1] || '').length;
|
||||
|
||||
encodedValue =
|
||||
return (
|
||||
(startIndex ? value.substr(0, startIndex) : '') +
|
||||
this.encodeWord(value.substring(startIndex, endIndex), mimeWordEncoding || 'Q', maxLength) +
|
||||
(endIndex < value.length ? value.substr(endIndex) : '');
|
||||
|
||||
return encodedValue;
|
||||
(endIndex < value.length ? value.substr(endIndex) : '')
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -175,12 +166,12 @@ module.exports = {
|
||||
* @return {String} joined header value
|
||||
*/
|
||||
buildHeaderValue(structured) {
|
||||
let paramsArray = [];
|
||||
const paramsArray = [];
|
||||
|
||||
Object.keys(structured.params || {}).forEach(param => {
|
||||
// filename might include unicode characters so it is a special case
|
||||
// other values probably do not
|
||||
let value = structured.params[param];
|
||||
const value = structured.params[param];
|
||||
if (!this.isPlainText(value, true) || value.length >= 75) {
|
||||
this.buildHeaderParam(param, value, 50).forEach(encodedParam => {
|
||||
if (!/[\s"\\;:/=(),<>@[\]?]|^[-']|'$/.test(encodedParam.value) || encodedParam.key.substr(-1) === '*') {
|
||||
@@ -215,9 +206,8 @@ module.exports = {
|
||||
* @return {Array} A list of encoded keys and headers
|
||||
*/
|
||||
buildHeaderParam(key, data, maxLength) {
|
||||
let list = [];
|
||||
const list = [];
|
||||
let encodedStr = typeof data === 'string' ? data : (data || '').toString();
|
||||
let encodedStrArr;
|
||||
let chr, ord;
|
||||
let line;
|
||||
let startPos = 0;
|
||||
@@ -252,7 +242,7 @@ module.exports = {
|
||||
} else {
|
||||
if (/[\uD800-\uDBFF]/.test(encodedStr)) {
|
||||
// string containts surrogate pairs, so normalize it to an array of bytes
|
||||
encodedStrArr = [];
|
||||
const encodedStrArr = [];
|
||||
for (i = 0, len = encodedStr.length; i < len; i++) {
|
||||
chr = encodedStr.charAt(i);
|
||||
ord = chr.charCodeAt(0);
|
||||
@@ -356,7 +346,7 @@ module.exports = {
|
||||
* @return {Object} Header value as a parsed structure
|
||||
*/
|
||||
parseHeaderValue(str) {
|
||||
let response = {
|
||||
const response = {
|
||||
value: false,
|
||||
params: {}
|
||||
};
|
||||
@@ -458,12 +448,11 @@ module.exports = {
|
||||
value
|
||||
// fix invalidly encoded chars
|
||||
.replace(/[=?_\s]/g, s => {
|
||||
let c = s.charCodeAt(0).toString(16);
|
||||
const c = s.charCodeAt(0).toString(16);
|
||||
if (s === ' ') {
|
||||
return '_';
|
||||
} else {
|
||||
return '%' + (c.length < 2 ? '0' : '') + c;
|
||||
}
|
||||
return '%' + (c.length < 2 ? '0' : '') + c;
|
||||
})
|
||||
// change from urlencoding to percent encoding
|
||||
.replace(/%/g, '=') +
|
||||
@@ -508,11 +497,10 @@ module.exports = {
|
||||
str = (str || '').toString();
|
||||
lineLength = lineLength || 76;
|
||||
|
||||
let pos = 0,
|
||||
len = str.length,
|
||||
result = '',
|
||||
line,
|
||||
match;
|
||||
let pos = 0;
|
||||
const len = str.length;
|
||||
let result = '';
|
||||
let line, match;
|
||||
|
||||
while (pos < len) {
|
||||
line = str.substr(pos, lineLength);
|
||||
@@ -549,11 +537,8 @@ module.exports = {
|
||||
* @return {Array} Split string
|
||||
*/
|
||||
splitMimeEncodedString: (str, maxlen) => {
|
||||
let curLine,
|
||||
match,
|
||||
chr,
|
||||
done,
|
||||
lines = [];
|
||||
const lines = [];
|
||||
let curLine, match, chr, done;
|
||||
|
||||
// require at least 12 symbols to fit possible 4 octet UTF-8 sequences
|
||||
maxlen = Math.max(maxlen || 0, 12);
|
||||
|
||||
18
node_modules/nodemailer/lib/mime-funcs/mime-types.js
generated
vendored
18
node_modules/nodemailer/lib/mime-funcs/mime-types.js
generated
vendored
@@ -2073,13 +2073,9 @@ module.exports = {
|
||||
return defaultMimeType;
|
||||
}
|
||||
|
||||
let parsed = path.parse(filename);
|
||||
let extension = (parsed.ext.substr(1) || parsed.name || '').split('?').shift().trim().toLowerCase();
|
||||
let value = defaultMimeType;
|
||||
|
||||
if (extensions.has(extension)) {
|
||||
value = extensions.get(extension);
|
||||
}
|
||||
const parsed = path.parse(filename);
|
||||
const extension = (parsed.ext.substr(1) || parsed.name || '').split('?').shift().trim().toLowerCase();
|
||||
const value = extensions.has(extension) ? extensions.get(extension) : defaultMimeType;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
return value[0];
|
||||
@@ -2091,12 +2087,12 @@ module.exports = {
|
||||
if (!mimeType) {
|
||||
return defaultExtension;
|
||||
}
|
||||
let parts = (mimeType || '').toLowerCase().trim().split('/');
|
||||
let rootType = parts.shift().trim();
|
||||
let subType = parts.join('/').trim();
|
||||
const parts = (mimeType || '').toLowerCase().trim().split('/');
|
||||
const rootType = parts.shift().trim();
|
||||
const subType = parts.join('/').trim();
|
||||
|
||||
if (mimeTypes.has(rootType + '/' + subType)) {
|
||||
let value = mimeTypes.get(rootType + '/' + subType);
|
||||
const value = mimeTypes.get(rootType + '/' + subType);
|
||||
if (Array.isArray(value)) {
|
||||
return value[0];
|
||||
}
|
||||
|
||||
171
node_modules/nodemailer/lib/mime-node/index.js
generated
vendored
171
node_modules/nodemailer/lib/mime-node/index.js
generated
vendored
@@ -5,7 +5,7 @@
|
||||
const crypto = require('crypto');
|
||||
const fs = require('fs');
|
||||
const punycode = require('../punycode');
|
||||
const PassThrough = require('stream').PassThrough;
|
||||
const { PassThrough } = require('stream');
|
||||
const shared = require('../shared');
|
||||
|
||||
const mimeFuncs = require('../mime-funcs');
|
||||
@@ -19,6 +19,8 @@ const LastNewline = require('./last-newline');
|
||||
const LeWindows = require('./le-windows');
|
||||
const LeUnix = require('./le-unix');
|
||||
|
||||
const FORMATTED_HEADERS = ['From', 'Sender', 'To', 'Cc', 'Bcc', 'Reply-To', 'Date', 'References'];
|
||||
|
||||
/**
|
||||
* Creates a new mime tree node. Assumes 'multipart/*' as the content type
|
||||
* if it is a branch, anything else counts as leaf. If rootNode is missing from
|
||||
@@ -54,7 +56,7 @@ class MimeNode {
|
||||
/**
|
||||
* If date headers is missing and current node is the root, this value is used instead
|
||||
*/
|
||||
this.date = new Date();
|
||||
this.date = options.parentNode ? null : new Date();
|
||||
|
||||
/**
|
||||
* Root node for current mime tree
|
||||
@@ -175,7 +177,7 @@ class MimeNode {
|
||||
options = contentType;
|
||||
contentType = undefined;
|
||||
}
|
||||
let node = new MimeNode(contentType, options);
|
||||
const node = new MimeNode(contentType, options);
|
||||
this.appendChild(node);
|
||||
return node;
|
||||
}
|
||||
@@ -256,8 +258,7 @@ class MimeNode {
|
||||
* @return {Object} current node
|
||||
*/
|
||||
setHeader(key, value) {
|
||||
let added = false,
|
||||
headerValue;
|
||||
let added = false;
|
||||
|
||||
// Allow setting multiple headers at once
|
||||
if (!value && key && typeof key === 'object') {
|
||||
@@ -280,7 +281,7 @@ class MimeNode {
|
||||
|
||||
key = this._normalizeHeaderKey(key);
|
||||
|
||||
headerValue = {
|
||||
const headerValue = {
|
||||
key,
|
||||
value
|
||||
};
|
||||
@@ -404,8 +405,8 @@ class MimeNode {
|
||||
});
|
||||
}
|
||||
|
||||
let stream = this.createReadStream();
|
||||
let buf = [];
|
||||
const stream = this.createReadStream();
|
||||
const buf = [];
|
||||
let buflen = 0;
|
||||
let returned = false;
|
||||
|
||||
@@ -445,7 +446,7 @@ class MimeNode {
|
||||
|
||||
getTransferEncoding() {
|
||||
let transferEncoding = false;
|
||||
let contentType = (this.getHeader('Content-Type') || '').toString().toLowerCase().trim();
|
||||
const contentType = (this.getHeader('Content-Type') || '').toString().toLowerCase().trim();
|
||||
|
||||
if (this.content) {
|
||||
transferEncoding = (this.getHeader('Content-Transfer-Encoding') || '').toString().toLowerCase().trim();
|
||||
@@ -475,8 +476,8 @@ class MimeNode {
|
||||
* @returns {String} Headers
|
||||
*/
|
||||
buildHeaders() {
|
||||
let transferEncoding = this.getTransferEncoding();
|
||||
let headers = [];
|
||||
const transferEncoding = this.getTransferEncoding();
|
||||
const headers = [];
|
||||
|
||||
if (transferEncoding) {
|
||||
this.setHeader('Content-Transfer-Encoding', transferEncoding);
|
||||
@@ -501,7 +502,7 @@ class MimeNode {
|
||||
|
||||
// Ensure that Content-Type is the last header for the root node
|
||||
for (let i = this._headers.length - 2; i >= 0; i--) {
|
||||
let header = this._headers[i];
|
||||
const header = this._headers[i];
|
||||
if (header.key === 'Content-Type') {
|
||||
this._headers.splice(i, 1);
|
||||
this._headers.push(header);
|
||||
@@ -514,8 +515,8 @@ class MimeNode {
|
||||
let value = header.value;
|
||||
let structured;
|
||||
let param;
|
||||
let options = {};
|
||||
let formattedHeaders = ['From', 'Sender', 'To', 'Cc', 'Bcc', 'Reply-To', 'Date', 'References'];
|
||||
const options = {};
|
||||
const formattedHeaders = FORMATTED_HEADERS;
|
||||
|
||||
if (value && typeof value === 'object' && !formattedHeaders.includes(key)) {
|
||||
Object.keys(value).forEach(key => {
|
||||
@@ -593,7 +594,7 @@ class MimeNode {
|
||||
}
|
||||
|
||||
if (typeof this.normalizeHeaderKey === 'function') {
|
||||
let normalized = this.normalizeHeaderKey(key, value);
|
||||
const normalized = this.normalizeHeaderKey(key, value);
|
||||
if (normalized && typeof normalized === 'string' && normalized.length) {
|
||||
key = normalized;
|
||||
}
|
||||
@@ -614,7 +615,7 @@ class MimeNode {
|
||||
createReadStream(options) {
|
||||
options = options || {};
|
||||
|
||||
let stream = new PassThrough(options);
|
||||
const stream = new PassThrough(options);
|
||||
let outputStream = stream;
|
||||
let transform;
|
||||
|
||||
@@ -682,13 +683,13 @@ class MimeNode {
|
||||
}
|
||||
|
||||
stream(outputStream, options, done) {
|
||||
let transferEncoding = this.getTransferEncoding();
|
||||
const transferEncoding = this.getTransferEncoding();
|
||||
let contentStream;
|
||||
let localStream;
|
||||
|
||||
// protect actual callback against multiple triggering
|
||||
let returned = false;
|
||||
let callback = err => {
|
||||
const callback = err => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
@@ -698,14 +699,14 @@ class MimeNode {
|
||||
|
||||
// for multipart nodes, push child nodes
|
||||
// for content nodes end the stream
|
||||
let finalize = () => {
|
||||
const finalize = () => {
|
||||
let childId = 0;
|
||||
let processChildNode = () => {
|
||||
const processChildNode = () => {
|
||||
if (childId >= this.childNodes.length) {
|
||||
outputStream.write('\r\n--' + this.boundary + '--\r\n');
|
||||
return callback();
|
||||
}
|
||||
let child = this.childNodes[childId++];
|
||||
const child = this.childNodes[childId++];
|
||||
outputStream.write((childId > 1 ? '\r\n' : '') + '--' + this.boundary + '\r\n');
|
||||
child.stream(outputStream, options, err => {
|
||||
if (err) {
|
||||
@@ -723,7 +724,7 @@ class MimeNode {
|
||||
};
|
||||
|
||||
// pushes node content
|
||||
let sendContent = () => {
|
||||
const sendContent = () => {
|
||||
if (this.content) {
|
||||
if (Object.prototype.toString.call(this.content) === '[object Error]') {
|
||||
// content is already errored
|
||||
@@ -736,7 +737,7 @@ class MimeNode {
|
||||
this.content.once('error', this._contentErrorHandler);
|
||||
}
|
||||
|
||||
let createStream = () => {
|
||||
const createStream = () => {
|
||||
if (['quoted-printable', 'base64'].includes(transferEncoding)) {
|
||||
contentStream = new (transferEncoding === 'base64' ? base64 : qp).Encoder(options);
|
||||
|
||||
@@ -761,10 +762,10 @@ class MimeNode {
|
||||
};
|
||||
|
||||
if (this.content._resolve) {
|
||||
let chunks = [];
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
let returned = false;
|
||||
let sourceStream = this._getStream(this.content);
|
||||
const sourceStream = this._getStream(this.content);
|
||||
sourceStream.on('error', err => {
|
||||
if (returned) {
|
||||
return;
|
||||
@@ -792,9 +793,8 @@ class MimeNode {
|
||||
setImmediate(createStream);
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
return setImmediate(finalize);
|
||||
}
|
||||
return setImmediate(finalize);
|
||||
};
|
||||
|
||||
if (this._raw) {
|
||||
@@ -809,7 +809,7 @@ class MimeNode {
|
||||
this._raw.removeListener('error', this._contentErrorHandler);
|
||||
}
|
||||
|
||||
let raw = this._getStream(this._raw);
|
||||
const raw = this._getStream(this._raw);
|
||||
raw.pipe(outputStream, {
|
||||
end: false
|
||||
});
|
||||
@@ -851,7 +851,7 @@ class MimeNode {
|
||||
|
||||
this._envelope.to = this._envelope.to.map(to => to.address).filter(address => address);
|
||||
|
||||
let standardFields = ['to', 'cc', 'bcc', 'from'];
|
||||
const standardFields = ['to', 'cc', 'bcc', 'from'];
|
||||
Object.keys(envelope).forEach(key => {
|
||||
if (!standardFields.includes(key)) {
|
||||
this._envelope[key] = envelope[key];
|
||||
@@ -867,10 +867,10 @@ class MimeNode {
|
||||
* @return {Object} Address object
|
||||
*/
|
||||
getAddresses() {
|
||||
let addresses = {};
|
||||
const addresses = {};
|
||||
|
||||
this._headers.forEach(header => {
|
||||
let key = header.key.toLowerCase();
|
||||
const key = header.key.toLowerCase();
|
||||
if (['from', 'sender', 'reply-to', 'to', 'cc', 'bcc'].includes(key)) {
|
||||
if (!Array.isArray(addresses[key])) {
|
||||
addresses[key] = [];
|
||||
@@ -893,12 +893,12 @@ class MimeNode {
|
||||
return this._envelope;
|
||||
}
|
||||
|
||||
let envelope = {
|
||||
const envelope = {
|
||||
from: false,
|
||||
to: []
|
||||
};
|
||||
this._headers.forEach(header => {
|
||||
let list = [];
|
||||
const list = [];
|
||||
if (header.key === 'From' || (!envelope.from && ['Reply-To', 'Sender'].includes(header.key))) {
|
||||
this._convertAddresses(this._parseAddresses(header.value), list);
|
||||
if (list.length && list[0]) {
|
||||
@@ -974,14 +974,18 @@ class MimeNode {
|
||||
});
|
||||
|
||||
return contentStream;
|
||||
} else if (typeof content.pipe === 'function') {
|
||||
}
|
||||
|
||||
if (typeof content.pipe === 'function') {
|
||||
// assume as stream
|
||||
return content;
|
||||
} else if (content && typeof content.path === 'string' && !content.href) {
|
||||
}
|
||||
|
||||
if (content && typeof content.path === 'string' && !content.href) {
|
||||
if (this.disableFileAccess) {
|
||||
contentStream = new PassThrough();
|
||||
setImmediate(() => {
|
||||
let err = new Error('File access rejected for ' + content.path);
|
||||
const err = new Error('File access rejected for ' + content.path);
|
||||
err.code = errors.EFILEACCESS;
|
||||
contentStream.emit('error', err);
|
||||
});
|
||||
@@ -989,11 +993,13 @@ class MimeNode {
|
||||
}
|
||||
// read file
|
||||
return fs.createReadStream(content.path);
|
||||
} else if (content && typeof content.href === 'string') {
|
||||
}
|
||||
|
||||
if (content && typeof content.href === 'string') {
|
||||
if (this.disableUrlAccess) {
|
||||
contentStream = new PassThrough();
|
||||
setImmediate(() => {
|
||||
let err = new Error('Url access rejected for ' + content.href);
|
||||
const err = new Error('Url access rejected for ' + content.href);
|
||||
err.code = errors.EURLACCESS;
|
||||
contentStream.emit('error', err);
|
||||
});
|
||||
@@ -1001,19 +1007,19 @@ class MimeNode {
|
||||
}
|
||||
// fetch URL
|
||||
return nmfetch(content.href, { headers: content.httpHeaders });
|
||||
} else {
|
||||
// pass string or buffer content as a stream
|
||||
contentStream = new PassThrough();
|
||||
|
||||
setImmediate(() => {
|
||||
try {
|
||||
contentStream.end(content || '');
|
||||
} catch (_err) {
|
||||
contentStream.emit('error', _err);
|
||||
}
|
||||
});
|
||||
return contentStream;
|
||||
}
|
||||
|
||||
// pass string or buffer content as a stream
|
||||
contentStream = new PassThrough();
|
||||
|
||||
setImmediate(() => {
|
||||
try {
|
||||
contentStream.end(content || '');
|
||||
} catch (_err) {
|
||||
contentStream.emit('error', _err);
|
||||
}
|
||||
});
|
||||
return contentStream;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1172,7 +1178,7 @@ class MimeNode {
|
||||
* @return {String} address string
|
||||
*/
|
||||
_convertAddresses(addresses, uniqueList) {
|
||||
let values = [];
|
||||
const values = [];
|
||||
|
||||
uniqueList = uniqueList || [];
|
||||
|
||||
@@ -1182,17 +1188,15 @@ class MimeNode {
|
||||
|
||||
if (!address.name) {
|
||||
values.push(address.address.indexOf(' ') >= 0 ? `<${address.address}>` : `${address.address}`);
|
||||
} else if (address.name) {
|
||||
} else {
|
||||
values.push(`${this._encodeAddressName(address.name)} <${address.address}>`);
|
||||
}
|
||||
|
||||
if (address.address) {
|
||||
if (!uniqueList.filter(a => a.address === address.address).length) {
|
||||
uniqueList.push(address);
|
||||
}
|
||||
if (!uniqueList.some(a => a.address === address.address)) {
|
||||
uniqueList.push(address);
|
||||
}
|
||||
} else if (address.group) {
|
||||
let groupListAddresses = (address.group.length ? this._convertAddresses(address.group, uniqueList) : '').trim();
|
||||
const groupListAddresses = (address.group.length ? this._convertAddresses(address.group, uniqueList) : '').trim();
|
||||
values.push(`${this._encodeAddressName(address.name)}:${groupListAddresses};`);
|
||||
}
|
||||
});
|
||||
@@ -1212,26 +1216,30 @@ class MimeNode {
|
||||
.replace(/[\x00-\x1F<>]+/g, ' ') // remove unallowed characters
|
||||
.trim();
|
||||
|
||||
let lastAt = address.lastIndexOf('@');
|
||||
const lastAt = address.lastIndexOf('@');
|
||||
if (lastAt < 0) {
|
||||
// Bare username
|
||||
return address;
|
||||
}
|
||||
|
||||
let user = address.substr(0, lastAt);
|
||||
let domain = address.substr(lastAt + 1);
|
||||
const domain = address.substr(lastAt + 1);
|
||||
|
||||
// Usernames are not touched and are kept as is even if these include unicode
|
||||
// Domains are punycoded by default
|
||||
// 'jõgeva.ee' will be converted to 'xn--jgeva-dua.ee'
|
||||
// non-unicode domains are left as is
|
||||
// Usernames are not touched and are kept as is even if these include unicode.
|
||||
// Domains are punycoded when the local part is ASCII ('safe@jõgeva.ee' -> 'safe@xn--jgeva-dua.ee').
|
||||
// When the local part contains non-ASCII bytes the address already requires SMTPUTF8,
|
||||
// so the domain is kept (or decoded back) as UTF-8 for symmetry on both sides of '@'.
|
||||
|
||||
let encodedDomain;
|
||||
let encodedDomain = domain;
|
||||
|
||||
try {
|
||||
encodedDomain = punycode.toASCII(domain.toLowerCase());
|
||||
if (/[\x80-\uFFFF]/.test(user)) {
|
||||
encodedDomain = punycode.toUnicode(domain.toLowerCase());
|
||||
} else {
|
||||
encodedDomain = punycode.toASCII(domain.toLowerCase());
|
||||
}
|
||||
} catch (_err) {
|
||||
// keep as is?
|
||||
// keep domain as supplied
|
||||
}
|
||||
|
||||
if (user.indexOf(' ') >= 0) {
|
||||
@@ -1285,20 +1293,25 @@ class MimeNode {
|
||||
_getTextEncoding(value) {
|
||||
value = (value || '').toString();
|
||||
|
||||
let encoding = this.textEncoding;
|
||||
let latinLen;
|
||||
let nonLatinLen;
|
||||
|
||||
if (!encoding) {
|
||||
// count latin alphabet symbols and 8-bit range symbols + control symbols
|
||||
// if there are more latin characters, then use quoted-printable
|
||||
// encoding, otherwise use base64
|
||||
nonLatinLen = (value.match(/[\x00-\x08\x0B\x0C\x0E-\x1F\u0080-\uFFFF]/g) || []).length;
|
||||
latinLen = (value.match(/[a-z]/gi) || []).length;
|
||||
// if there are more latin symbols than binary/unicode, then prefer Q, otherwise B
|
||||
encoding = nonLatinLen < latinLen ? 'Q' : 'B';
|
||||
if (this.textEncoding) {
|
||||
return this.textEncoding;
|
||||
}
|
||||
return encoding;
|
||||
|
||||
// count latin alphabet symbols and 8-bit range symbols + control symbols
|
||||
// if there are more latin characters, then use quoted-printable
|
||||
// encoding, otherwise use base64
|
||||
let nonLatinLen = 0;
|
||||
let latinLen = 0;
|
||||
for (let i = 0, len = value.length; i < len; i++) {
|
||||
const code = value.charCodeAt(i);
|
||||
if ((code >= 0x00 && code <= 0x08) || code === 0x0b || code === 0x0c || (code >= 0x0e && code <= 0x1f) || code >= 0x80) {
|
||||
nonLatinLen++;
|
||||
} else if ((code >= 0x41 && code <= 0x5a) || (code >= 0x61 && code <= 0x7a)) {
|
||||
latinLen++;
|
||||
}
|
||||
}
|
||||
// if there are more latin symbols than binary/unicode, then prefer Q, otherwise B
|
||||
return nonLatinLen < latinLen ? 'Q' : 'B';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
2
node_modules/nodemailer/lib/mime-node/last-newline.js
generated
vendored
2
node_modules/nodemailer/lib/mime-node/last-newline.js
generated
vendored
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const Transform = require('stream').Transform;
|
||||
const { Transform } = require('stream');
|
||||
|
||||
class LastNewline extends Transform {
|
||||
constructor() {
|
||||
|
||||
11
node_modules/nodemailer/lib/mime-node/le-unix.js
generated
vendored
11
node_modules/nodemailer/lib/mime-node/le-unix.js
generated
vendored
@@ -1,18 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
const stream = require('stream');
|
||||
const Transform = stream.Transform;
|
||||
const { Transform } = require('stream');
|
||||
|
||||
/**
|
||||
* Ensures that only <LF> is used for linebreaks
|
||||
*
|
||||
* @param {Object} options Stream options
|
||||
*/
|
||||
class LeWindows extends Transform {
|
||||
class LeUnix extends Transform {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
// init Transform
|
||||
this.options = options || {};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -24,7 +21,7 @@ class LeWindows extends Transform {
|
||||
|
||||
for (let i = 0, len = chunk.length; i < len; i++) {
|
||||
if (chunk[i] === 0x0d) {
|
||||
// \n
|
||||
// \r
|
||||
buf = chunk.slice(lastPos, i);
|
||||
lastPos = i + 1;
|
||||
this.push(buf);
|
||||
@@ -40,4 +37,4 @@ class LeWindows extends Transform {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LeWindows;
|
||||
module.exports = LeUnix;
|
||||
|
||||
5
node_modules/nodemailer/lib/mime-node/le-windows.js
generated
vendored
5
node_modules/nodemailer/lib/mime-node/le-windows.js
generated
vendored
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const stream = require('stream');
|
||||
const Transform = stream.Transform;
|
||||
const { Transform } = require('stream');
|
||||
|
||||
/**
|
||||
* Ensures that only <CR><LF> sequences are used for linebreaks
|
||||
@@ -11,8 +10,6 @@ const Transform = stream.Transform;
|
||||
class LeWindows extends Transform {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
// init Transform
|
||||
this.options = options || {};
|
||||
this.lastByte = false;
|
||||
}
|
||||
|
||||
|
||||
29
node_modules/nodemailer/lib/nodemailer.js
generated
vendored
29
node_modules/nodemailer/lib/nodemailer.js
generated
vendored
@@ -20,9 +20,7 @@ const ETHEREAL_CACHE = ['true', 'yes', 'y', '1'].includes((process.env.ETHEREAL_
|
||||
let testAccount = false;
|
||||
|
||||
module.exports.createTransport = function (transporter, defaults) {
|
||||
let urlConfig;
|
||||
let options;
|
||||
let mailer;
|
||||
|
||||
if (
|
||||
// provided transporter is a configuration object, not transporter plugin
|
||||
@@ -30,7 +28,8 @@ module.exports.createTransport = function (transporter, defaults) {
|
||||
// provided transporter looks like a connection url
|
||||
(typeof transporter === 'string' && /^(smtps?|direct):/i.test(transporter))
|
||||
) {
|
||||
if ((urlConfig = typeof transporter === 'string' ? transporter : transporter.url)) {
|
||||
const urlConfig = typeof transporter === 'string' ? transporter : transporter.url;
|
||||
if (urlConfig) {
|
||||
// parse a configuration URL into configuration options
|
||||
options = shared.parseConnectionUrl(urlConfig);
|
||||
} else {
|
||||
@@ -47,7 +46,7 @@ module.exports.createTransport = function (transporter, defaults) {
|
||||
transporter = new JSONTransport(options);
|
||||
} else if (options.SES) {
|
||||
if (options.SES.ses && options.SES.aws) {
|
||||
let error = new Error(
|
||||
const error = new Error(
|
||||
'Using legacy SES configuration, expecting @aws-sdk/client-sesv2, see https://nodemailer.com/transports/ses/'
|
||||
);
|
||||
error.code = errors.ECONFIG;
|
||||
@@ -59,9 +58,7 @@ module.exports.createTransport = function (transporter, defaults) {
|
||||
}
|
||||
}
|
||||
|
||||
mailer = new Mailer(transporter, options, defaults);
|
||||
|
||||
return mailer;
|
||||
return new Mailer(transporter, options, defaults);
|
||||
};
|
||||
|
||||
module.exports.createTestAccount = function (apiUrl, callback) {
|
||||
@@ -85,11 +82,11 @@ module.exports.createTestAccount = function (apiUrl, callback) {
|
||||
|
||||
apiUrl = apiUrl || ETHEREAL_API;
|
||||
|
||||
let chunks = [];
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
|
||||
let requestHeaders = {};
|
||||
let requestBody = {
|
||||
const requestHeaders = {};
|
||||
const requestBody = {
|
||||
requestor: packageData.name,
|
||||
version: packageData.version
|
||||
};
|
||||
@@ -98,7 +95,7 @@ module.exports.createTestAccount = function (apiUrl, callback) {
|
||||
requestHeaders.Authorization = 'Bearer ' + ETHEREAL_API_KEY;
|
||||
}
|
||||
|
||||
let req = nmfetch(apiUrl + '/user', {
|
||||
const req = nmfetch(apiUrl + '/user', {
|
||||
contentType: 'application/json',
|
||||
method: 'POST',
|
||||
headers: requestHeaders,
|
||||
@@ -116,16 +113,12 @@ module.exports.createTestAccount = function (apiUrl, callback) {
|
||||
req.once('error', err => callback(err));
|
||||
|
||||
req.once('end', () => {
|
||||
let res = Buffer.concat(chunks, chunklen);
|
||||
const res = Buffer.concat(chunks, chunklen);
|
||||
let data;
|
||||
let err;
|
||||
try {
|
||||
data = JSON.parse(res.toString());
|
||||
} catch (E) {
|
||||
err = E;
|
||||
}
|
||||
if (err) {
|
||||
return callback(err);
|
||||
return callback(E);
|
||||
}
|
||||
if (data.status !== 'success' || data.error) {
|
||||
return callback(new Error(data.error || 'Request failed'));
|
||||
@@ -143,7 +136,7 @@ module.exports.getTestMessageUrl = function (info) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let infoProps = new Map();
|
||||
const infoProps = new Map();
|
||||
info.response.replace(/\[([^\]]+)\]$/, (m, props) => {
|
||||
props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
|
||||
infoProps.set(key, value);
|
||||
|
||||
45
node_modules/nodemailer/lib/qp/index.js
generated
vendored
45
node_modules/nodemailer/lib/qp/index.js
generated
vendored
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const Transform = require('stream').Transform;
|
||||
const { Transform } = require('stream');
|
||||
|
||||
/**
|
||||
* Encodes a Buffer into a Quoted-Printable encoded string
|
||||
@@ -8,20 +8,21 @@ const Transform = require('stream').Transform;
|
||||
* @param {Buffer} buffer Buffer to convert
|
||||
* @returns {String} Quoted-Printable encoded string
|
||||
*/
|
||||
// usable characters that do not need encoding
|
||||
// https://tools.ietf.org/html/rfc2045#section-6.7
|
||||
const QP_RANGES = [
|
||||
[0x09], // <TAB>
|
||||
[0x0a], // <LF>
|
||||
[0x0d], // <CR>
|
||||
[0x20, 0x3c], // <SP>!"#$%&'()*+,-./0123456789:;
|
||||
[0x3e, 0x7e] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
|
||||
];
|
||||
|
||||
function encode(buffer) {
|
||||
if (typeof buffer === 'string') {
|
||||
buffer = Buffer.from(buffer, 'utf-8');
|
||||
}
|
||||
|
||||
// usable characters that do not need encoding
|
||||
let ranges = [
|
||||
// https://tools.ietf.org/html/rfc2045#section-6.7
|
||||
[0x09], // <TAB>
|
||||
[0x0a], // <LF>
|
||||
[0x0d], // <CR>
|
||||
[0x20, 0x3c], // <SP>!"#$%&'()*+,-./0123456789:;
|
||||
[0x3e, 0x7e] // >?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}
|
||||
];
|
||||
let result = '';
|
||||
let ord;
|
||||
|
||||
@@ -29,7 +30,7 @@ function encode(buffer) {
|
||||
ord = buffer[i];
|
||||
// if the char is in allowed range, then keep as is, unless it is a WS in the end of a line
|
||||
if (
|
||||
checkRanges(ord, ranges) &&
|
||||
checkRanges(ord, QP_RANGES) &&
|
||||
!((ord === 0x20 || ord === 0x09) && (i === len - 1 || buffer[i + 1] === 0x0a || buffer[i + 1] === 0x0d))
|
||||
) {
|
||||
result += String.fromCharCode(ord);
|
||||
@@ -57,9 +58,9 @@ function wrap(str, lineLength) {
|
||||
}
|
||||
|
||||
let pos = 0;
|
||||
let len = str.length;
|
||||
const len = str.length;
|
||||
let match, code, line;
|
||||
let lineMargin = Math.floor(lineLength / 3);
|
||||
const lineMargin = Math.floor(lineLength / 3);
|
||||
let result = '';
|
||||
|
||||
// insert soft linebreaks where needed
|
||||
@@ -73,17 +74,20 @@ function wrap(str, lineLength) {
|
||||
}
|
||||
|
||||
if (line.substr(-1) === '\n') {
|
||||
// nothing to change here
|
||||
result += line;
|
||||
pos += line.length;
|
||||
continue;
|
||||
} else if ((match = line.substr(-lineMargin).match(/\n.*?$/))) {
|
||||
}
|
||||
|
||||
if ((match = line.substr(-lineMargin).match(/\n.*?$/))) {
|
||||
// truncate to nearest line break
|
||||
line = line.substr(0, line.length - (match[0].length - 1));
|
||||
result += line;
|
||||
pos += line.length;
|
||||
continue;
|
||||
} else if (line.length > lineLength - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) {
|
||||
}
|
||||
|
||||
if (line.length > lineLength - lineMargin && (match = line.substr(-lineMargin).match(/[ \t.,!?][^ \t.,!?]*$/))) {
|
||||
// truncate to nearest space
|
||||
line = line.substr(0, line.length - (match[0].length - 1));
|
||||
} else if (line.match(/[=][\da-f]{0,2}$/i)) {
|
||||
@@ -139,13 +143,14 @@ function wrap(str, lineLength) {
|
||||
*/
|
||||
function checkRanges(nr, ranges) {
|
||||
for (let i = ranges.length - 1; i >= 0; i--) {
|
||||
if (!ranges[i].length) {
|
||||
const range = ranges[i];
|
||||
if (!range.length) {
|
||||
continue;
|
||||
}
|
||||
if (ranges[i].length === 1 && nr === ranges[i][0]) {
|
||||
if (range.length === 1 && nr === range[0]) {
|
||||
return true;
|
||||
}
|
||||
if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) {
|
||||
if (range.length === 2 && nr >= range[0] && nr <= range[1]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -163,7 +168,6 @@ class Encoder extends Transform {
|
||||
constructor(options) {
|
||||
super();
|
||||
|
||||
// init Transform
|
||||
this.options = options || {};
|
||||
|
||||
if (this.options.lineLength !== false) {
|
||||
@@ -219,7 +223,6 @@ class Encoder extends Transform {
|
||||
}
|
||||
}
|
||||
|
||||
// expose to the world
|
||||
module.exports = {
|
||||
encode,
|
||||
wrap,
|
||||
|
||||
71
node_modules/nodemailer/lib/sendmail-transport/index.js
generated
vendored
71
node_modules/nodemailer/lib/sendmail-transport/index.js
generated
vendored
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const spawn = require('child_process').spawn;
|
||||
const { spawn } = require('child_process');
|
||||
const packageData = require('../../package.json');
|
||||
const shared = require('../shared');
|
||||
const errors = require('../errors');
|
||||
@@ -24,30 +24,26 @@ class SendmailTransport {
|
||||
// use a reference to spawn for mocking purposes
|
||||
this._spawn = spawn;
|
||||
|
||||
this.options = options || {};
|
||||
this.options = options;
|
||||
|
||||
this.name = 'Sendmail';
|
||||
this.version = packageData.version;
|
||||
|
||||
this.path = 'sendmail';
|
||||
this.args = false;
|
||||
this.winbreak = false;
|
||||
|
||||
this.logger = shared.getLogger(this.options, {
|
||||
component: this.options.component || 'sendmail'
|
||||
});
|
||||
|
||||
if (options) {
|
||||
if (typeof options === 'string') {
|
||||
this.path = options;
|
||||
} else if (typeof options === 'object') {
|
||||
if (options.path) {
|
||||
this.path = options.path;
|
||||
}
|
||||
if (Array.isArray(options.args)) {
|
||||
this.args = options.args;
|
||||
}
|
||||
this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
|
||||
if (typeof options === 'string') {
|
||||
this.path = options;
|
||||
} else if (typeof options === 'object') {
|
||||
if (options.path) {
|
||||
this.path = options.path;
|
||||
}
|
||||
if (Array.isArray(options.args)) {
|
||||
this.args = options.args;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -62,10 +58,8 @@ class SendmailTransport {
|
||||
// Sendmail strips this header line by itself
|
||||
mail.message.keepBcc = true;
|
||||
|
||||
let envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
let messageId = mail.message.messageId();
|
||||
let args;
|
||||
let sendmail;
|
||||
const envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
const messageId = mail.message.messageId();
|
||||
let returned;
|
||||
|
||||
const hasInvalidAddresses = []
|
||||
@@ -73,19 +67,17 @@ class SendmailTransport {
|
||||
.concat(envelope.to || [])
|
||||
.some(addr => /^-/.test(addr));
|
||||
if (hasInvalidAddresses) {
|
||||
let err = new Error('Can not send mail. Invalid envelope addresses.');
|
||||
const err = new Error('Can not send mail. Invalid envelope addresses.');
|
||||
err.code = errors.ESENDMAIL;
|
||||
return done(err);
|
||||
}
|
||||
|
||||
if (this.args) {
|
||||
// force -i to keep single dots
|
||||
args = ['-i'].concat(this.args).concat(envelope.to);
|
||||
} else {
|
||||
args = ['-i'].concat(envelope.from ? ['-f', envelope.from] : []).concat(envelope.to);
|
||||
}
|
||||
// force -i to keep single dots
|
||||
const args = this.args
|
||||
? ['-i'].concat(this.args).concat(envelope.to)
|
||||
: ['-i'].concat(envelope.from ? ['-f', envelope.from] : []).concat(envelope.to);
|
||||
|
||||
let callback = err => {
|
||||
const callback = err => {
|
||||
if (returned) {
|
||||
// ignore any additional responses, already done
|
||||
return;
|
||||
@@ -94,16 +86,16 @@ class SendmailTransport {
|
||||
if (typeof done === 'function') {
|
||||
if (err) {
|
||||
return done(err);
|
||||
} else {
|
||||
return done(null, {
|
||||
envelope: mail.data.envelope || mail.message.getEnvelope(),
|
||||
messageId,
|
||||
response: 'Messages queued for delivery'
|
||||
});
|
||||
}
|
||||
return done(null, {
|
||||
envelope,
|
||||
messageId,
|
||||
response: 'Messages queued for delivery'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let sendmail;
|
||||
try {
|
||||
sendmail = this._spawn(this.path, args);
|
||||
} catch (E) {
|
||||
@@ -138,12 +130,9 @@ class SendmailTransport {
|
||||
if (!code) {
|
||||
return callback();
|
||||
}
|
||||
let err;
|
||||
if (code === 127) {
|
||||
err = new Error('Sendmail command not found, process exited with code ' + code);
|
||||
} else {
|
||||
err = new Error('Sendmail exited with code ' + code);
|
||||
}
|
||||
const err = new Error(
|
||||
code === 127 ? 'Sendmail command not found, process exited with code ' + code : 'Sendmail exited with code ' + code
|
||||
);
|
||||
err.code = errors.ESENDMAIL;
|
||||
|
||||
this.logger.error(
|
||||
@@ -174,7 +163,7 @@ class SendmailTransport {
|
||||
callback(err);
|
||||
});
|
||||
|
||||
let recipients = [].concat(envelope.to || []);
|
||||
const recipients = [].concat(envelope.to || []);
|
||||
if (recipients.length > 3) {
|
||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
||||
}
|
||||
@@ -188,7 +177,7 @@ class SendmailTransport {
|
||||
recipients.join(', ')
|
||||
);
|
||||
|
||||
let sourceStream = mail.message.createReadStream();
|
||||
const sourceStream = mail.message.createReadStream();
|
||||
sourceStream.once('error', err => {
|
||||
this.logger.error(
|
||||
{
|
||||
@@ -206,7 +195,7 @@ class SendmailTransport {
|
||||
|
||||
sourceStream.pipe(sendmail.stdin);
|
||||
} else {
|
||||
let err = new Error('sendmail was not found');
|
||||
const err = new Error('sendmail was not found');
|
||||
err.code = errors.ESENDMAIL;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
57
node_modules/nodemailer/lib/ses-transport/index.js
generated
vendored
57
node_modules/nodemailer/lib/ses-transport/index.js
generated
vendored
@@ -17,7 +17,7 @@ class SESTransport extends EventEmitter {
|
||||
super();
|
||||
options = options || {};
|
||||
|
||||
this.options = options || {};
|
||||
this.options = options;
|
||||
this.ses = this.options.SES;
|
||||
|
||||
this.name = 'SESTransport';
|
||||
@@ -46,21 +46,16 @@ class SESTransport extends EventEmitter {
|
||||
* @param {Function} callback Callback function to run when the sending is completed
|
||||
*/
|
||||
send(mail, callback) {
|
||||
let statObject = {
|
||||
ts: Date.now(),
|
||||
pending: true
|
||||
};
|
||||
|
||||
let fromHeader = mail.message._headers.find(header => /^from$/i.test(header.key));
|
||||
if (fromHeader) {
|
||||
let mimeNode = new MimeNode('text/plain');
|
||||
const mimeNode = new MimeNode('text/plain');
|
||||
fromHeader = mimeNode._convertAddresses(mimeNode._parseAddresses(fromHeader.value));
|
||||
}
|
||||
|
||||
let envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
let messageId = mail.message.messageId();
|
||||
const envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
const messageId = mail.message.messageId();
|
||||
|
||||
let recipients = [].concat(envelope.to || []);
|
||||
const recipients = [].concat(envelope.to || []);
|
||||
if (recipients.length > 3) {
|
||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
||||
}
|
||||
@@ -74,7 +69,7 @@ class SESTransport extends EventEmitter {
|
||||
recipients.join(', ')
|
||||
);
|
||||
|
||||
let getRawMessage = next => {
|
||||
const getRawMessage = next => {
|
||||
// do not use Message-ID and Date in DKIM signature
|
||||
if (!mail.data._dkim) {
|
||||
mail.data._dkim = {};
|
||||
@@ -85,9 +80,9 @@ class SESTransport extends EventEmitter {
|
||||
mail.data._dkim.skipFields = 'date:message-id';
|
||||
}
|
||||
|
||||
let sourceStream = mail.message.createReadStream();
|
||||
let stream = sourceStream.pipe(new LeWindows());
|
||||
let chunks = [];
|
||||
const sourceStream = mail.message.createReadStream();
|
||||
const stream = sourceStream.pipe(new LeWindows());
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
|
||||
stream.on('readable', () => {
|
||||
@@ -100,9 +95,7 @@ class SESTransport extends EventEmitter {
|
||||
|
||||
sourceStream.once('error', err => stream.emit('error', err));
|
||||
|
||||
stream.once('error', err => {
|
||||
next(err);
|
||||
});
|
||||
stream.once('error', err => next(err));
|
||||
|
||||
stream.once('end', () => next(null, Buffer.concat(chunks, chunklen)));
|
||||
};
|
||||
@@ -120,26 +113,24 @@ class SESTransport extends EventEmitter {
|
||||
messageId,
|
||||
err.message
|
||||
);
|
||||
statObject.pending = false;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let sesMessage = {
|
||||
Content: {
|
||||
Raw: {
|
||||
// required
|
||||
Data: raw // required
|
||||
const sesMessage = Object.assign(
|
||||
{
|
||||
Content: {
|
||||
Raw: {
|
||||
// required
|
||||
Data: raw // required
|
||||
}
|
||||
},
|
||||
FromEmailAddress: fromHeader || envelope.from,
|
||||
Destination: {
|
||||
ToAddresses: envelope.to
|
||||
}
|
||||
},
|
||||
FromEmailAddress: fromHeader ? fromHeader : envelope.from,
|
||||
Destination: {
|
||||
ToAddresses: envelope.to
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(mail.data.ses || {}).forEach(key => {
|
||||
sesMessage[key] = mail.data.ses[key];
|
||||
});
|
||||
mail.data.ses || {}
|
||||
);
|
||||
|
||||
this.getRegion((err, region) => {
|
||||
if (err || !region) {
|
||||
@@ -155,7 +146,6 @@ class SESTransport extends EventEmitter {
|
||||
region = 'email';
|
||||
}
|
||||
|
||||
statObject.pending = true;
|
||||
callback(null, {
|
||||
envelope: {
|
||||
from: envelope.from,
|
||||
@@ -176,7 +166,6 @@ class SESTransport extends EventEmitter {
|
||||
messageId,
|
||||
err.message
|
||||
);
|
||||
statObject.pending = false;
|
||||
callback(err);
|
||||
});
|
||||
});
|
||||
|
||||
236
node_modules/nodemailer/lib/shared/index.js
generated
vendored
236
node_modules/nodemailer/lib/shared/index.js
generated
vendored
@@ -30,34 +30,28 @@ try {
|
||||
module.exports.networkInterfaces = networkInterfaces;
|
||||
|
||||
const isFamilySupported = (family, allowInternal) => {
|
||||
let networkInterfaces = module.exports.networkInterfaces;
|
||||
if (!networkInterfaces) {
|
||||
const ifaces = module.exports.networkInterfaces;
|
||||
if (!ifaces) {
|
||||
// hope for the best
|
||||
return true;
|
||||
}
|
||||
|
||||
const familySupported =
|
||||
// crux that replaces Object.values(networkInterfaces) as Object.values is not supported in nodejs v6
|
||||
Object.keys(networkInterfaces)
|
||||
.map(key => networkInterfaces[key])
|
||||
// crux that replaces .flat() as it is not supported in older Node versions (v10 and older)
|
||||
.reduce((acc, val) => acc.concat(val), [])
|
||||
.filter(i => !i.internal || allowInternal)
|
||||
.filter(i => i.family === 'IPv' + family || i.family === family).length > 0;
|
||||
|
||||
return familySupported;
|
||||
return Object.keys(ifaces)
|
||||
.map(key => ifaces[key])
|
||||
.reduce((acc, val) => acc.concat(val), [])
|
||||
.filter(i => !i.internal || allowInternal)
|
||||
.some(i => i.family === 'IPv' + family || i.family === family);
|
||||
};
|
||||
|
||||
const resolver = (family, hostname, options, callback) => {
|
||||
const resolve = (family, hostname, options, callback) => {
|
||||
options = options || {};
|
||||
const familySupported = isFamilySupported(family, options.allowInternalNetworkInterfaces);
|
||||
|
||||
if (!familySupported) {
|
||||
if (!isFamilySupported(family, options.allowInternalNetworkInterfaces)) {
|
||||
return callback(null, []);
|
||||
}
|
||||
|
||||
const resolver = dns.Resolver ? new dns.Resolver(options) : dns;
|
||||
resolver['resolve' + family](hostname, (err, addresses) => {
|
||||
const dnsResolver = dns.Resolver ? new dns.Resolver(options) : dns;
|
||||
dnsResolver['resolve' + family](hostname, (err, addresses) => {
|
||||
if (err) {
|
||||
switch (err.code) {
|
||||
case dns.NODATA:
|
||||
@@ -82,15 +76,10 @@ const formatDNSValue = (value, extra) => {
|
||||
return Object.assign({}, extra || {});
|
||||
}
|
||||
|
||||
let addresses = value.addresses || [];
|
||||
const addresses = value.addresses || [];
|
||||
|
||||
// Select a random address from available addresses, or null if none
|
||||
let host = null;
|
||||
if (addresses.length === 1) {
|
||||
host = addresses[0];
|
||||
} else if (addresses.length > 1) {
|
||||
host = addresses[Math.floor(Math.random() * addresses.length)];
|
||||
}
|
||||
const host = addresses.length > 0 ? addresses[Math.floor(Math.random() * addresses.length)] : null;
|
||||
|
||||
return Object.assign(
|
||||
{
|
||||
@@ -112,7 +101,7 @@ module.exports.resolveHostname = (options, callback) => {
|
||||
|
||||
if (!options.host || net.isIP(options.host)) {
|
||||
// nothing to do here
|
||||
let value = {
|
||||
const value = {
|
||||
addresses: [options.host],
|
||||
servername: options.servername || false
|
||||
};
|
||||
@@ -164,14 +153,14 @@ module.exports.resolveHostname = (options, callback) => {
|
||||
let ipv4Error = null;
|
||||
let ipv6Error = null;
|
||||
|
||||
resolver(4, options.host, options, (err, addresses) => {
|
||||
resolve(4, options.host, options, (err, addresses) => {
|
||||
if (err) {
|
||||
ipv4Error = err;
|
||||
} else {
|
||||
ipv4Addresses = addresses || [];
|
||||
}
|
||||
|
||||
resolver(6, options.host, options, (err, addresses) => {
|
||||
resolve(6, options.host, options, (err, addresses) => {
|
||||
if (err) {
|
||||
ipv6Error = err;
|
||||
} else {
|
||||
@@ -179,10 +168,10 @@ module.exports.resolveHostname = (options, callback) => {
|
||||
}
|
||||
|
||||
// Combine addresses: IPv4 first, then IPv6
|
||||
let allAddresses = ipv4Addresses.concat(ipv6Addresses);
|
||||
const allAddresses = ipv4Addresses.concat(ipv6Addresses);
|
||||
|
||||
if (allAddresses.length) {
|
||||
let value = {
|
||||
const value = {
|
||||
addresses: allAddresses,
|
||||
servername: options.servername || options.host
|
||||
};
|
||||
@@ -240,7 +229,7 @@ module.exports.resolveHostname = (options, callback) => {
|
||||
}
|
||||
|
||||
// Get all supported addresses from dns.lookup
|
||||
let supportedAddresses = addresses
|
||||
const supportedAddresses = addresses
|
||||
? addresses.filter(addr => isFamilySupported(addr.family)).map(addr => addr.address)
|
||||
: [];
|
||||
|
||||
@@ -259,7 +248,7 @@ module.exports.resolveHostname = (options, callback) => {
|
||||
);
|
||||
}
|
||||
|
||||
let value = {
|
||||
const value = {
|
||||
addresses: supportedAddresses.length ? supportedAddresses : [options.host],
|
||||
servername: options.servername || options.host
|
||||
};
|
||||
@@ -304,95 +293,78 @@ module.exports.resolveHostname = (options, callback) => {
|
||||
*/
|
||||
module.exports.parseConnectionUrl = str => {
|
||||
str = str || '';
|
||||
let options = {};
|
||||
const options = {};
|
||||
const url = urllib.parse(str, true);
|
||||
|
||||
[urllib.parse(str, true)].forEach(url => {
|
||||
let auth;
|
||||
switch (url.protocol) {
|
||||
case 'smtp:':
|
||||
options.secure = false;
|
||||
break;
|
||||
case 'smtps:':
|
||||
options.secure = true;
|
||||
break;
|
||||
case 'direct:':
|
||||
options.direct = true;
|
||||
break;
|
||||
}
|
||||
|
||||
switch (url.protocol) {
|
||||
case 'smtp:':
|
||||
options.secure = false;
|
||||
if (!isNaN(url.port) && Number(url.port)) {
|
||||
options.port = Number(url.port);
|
||||
}
|
||||
|
||||
if (url.hostname) {
|
||||
options.host = url.hostname;
|
||||
}
|
||||
|
||||
if (url.auth) {
|
||||
const auth = url.auth.split(':');
|
||||
options.auth = {
|
||||
user: auth.shift(),
|
||||
pass: auth.join(':')
|
||||
};
|
||||
}
|
||||
|
||||
Object.keys(url.query || {}).forEach(key => {
|
||||
let obj = options;
|
||||
let lKey = key;
|
||||
let value = url.query[key];
|
||||
|
||||
if (!isNaN(value)) {
|
||||
value = Number(value);
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case 'true':
|
||||
value = true;
|
||||
break;
|
||||
case 'smtps:':
|
||||
options.secure = true;
|
||||
break;
|
||||
case 'direct:':
|
||||
options.direct = true;
|
||||
case 'false':
|
||||
value = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!isNaN(url.port) && Number(url.port)) {
|
||||
options.port = Number(url.port);
|
||||
// tls is nested object
|
||||
if (key.indexOf('tls.') === 0) {
|
||||
lKey = key.substr(4);
|
||||
if (!options.tls) {
|
||||
options.tls = {};
|
||||
}
|
||||
obj = options.tls;
|
||||
} else if (key.indexOf('.') >= 0) {
|
||||
// ignore nested properties besides tls
|
||||
return;
|
||||
}
|
||||
|
||||
if (url.hostname) {
|
||||
options.host = url.hostname;
|
||||
if (!(lKey in obj)) {
|
||||
obj[lKey] = value;
|
||||
}
|
||||
|
||||
if (url.auth) {
|
||||
auth = url.auth.split(':');
|
||||
|
||||
if (!options.auth) {
|
||||
options.auth = {};
|
||||
}
|
||||
|
||||
options.auth.user = auth.shift();
|
||||
options.auth.pass = auth.join(':');
|
||||
}
|
||||
|
||||
Object.keys(url.query || {}).forEach(key => {
|
||||
let obj = options;
|
||||
let lKey = key;
|
||||
let value = url.query[key];
|
||||
|
||||
if (!isNaN(value)) {
|
||||
value = Number(value);
|
||||
}
|
||||
|
||||
switch (value) {
|
||||
case 'true':
|
||||
value = true;
|
||||
break;
|
||||
case 'false':
|
||||
value = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// tls is nested object
|
||||
if (key.indexOf('tls.') === 0) {
|
||||
lKey = key.substr(4);
|
||||
if (!options.tls) {
|
||||
options.tls = {};
|
||||
}
|
||||
obj = options.tls;
|
||||
} else if (key.indexOf('.') >= 0) {
|
||||
// ignore nested properties besides tls
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(lKey in obj)) {
|
||||
obj[lKey] = value;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
module.exports._logFunc = (logger, level, defaults, data, message, ...args) => {
|
||||
let entry = {};
|
||||
|
||||
Object.keys(defaults || {}).forEach(key => {
|
||||
if (key !== 'level') {
|
||||
entry[key] = defaults[key];
|
||||
}
|
||||
});
|
||||
|
||||
Object.keys(data || {}).forEach(key => {
|
||||
if (key !== 'level') {
|
||||
entry[key] = data[key];
|
||||
}
|
||||
});
|
||||
const entry = Object.assign({}, defaults || {}, data || {});
|
||||
delete entry.level;
|
||||
|
||||
logger[level](entry, message, ...args);
|
||||
};
|
||||
@@ -407,8 +379,8 @@ module.exports._logFunc = (logger, level, defaults, data, message, ...args) => {
|
||||
module.exports.getLogger = (options, defaults) => {
|
||||
options = options || {};
|
||||
|
||||
let response = {};
|
||||
let levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
||||
const response = {};
|
||||
const levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
||||
|
||||
if (!options.logger) {
|
||||
// use vanity logger
|
||||
@@ -418,12 +390,7 @@ module.exports.getLogger = (options, defaults) => {
|
||||
return response;
|
||||
}
|
||||
|
||||
let logger = options.logger;
|
||||
|
||||
if (options.logger === true) {
|
||||
// create console logger
|
||||
logger = createDefaultLogger(levels);
|
||||
}
|
||||
const logger = options.logger === true ? createDefaultLogger(levels) : options.logger;
|
||||
|
||||
levels.forEach(level => {
|
||||
response[level] = (data, message, ...args) => {
|
||||
@@ -443,8 +410,8 @@ module.exports.getLogger = (options, defaults) => {
|
||||
*/
|
||||
module.exports.callbackPromise = (resolve, reject) =>
|
||||
function () {
|
||||
let args = Array.from(arguments);
|
||||
let err = args.shift();
|
||||
const args = Array.from(arguments);
|
||||
const err = args.shift();
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -546,8 +513,7 @@ module.exports.resolveContent = (data, key, callback) => {
|
||||
}
|
||||
|
||||
let content = (data && data[key] && data[key].content) || data[key];
|
||||
let contentStream;
|
||||
let encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
|
||||
const encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
|
||||
.toString()
|
||||
.toLowerCase()
|
||||
.replace(/[-_\s]/g, '');
|
||||
@@ -572,10 +538,9 @@ module.exports.resolveContent = (data, key, callback) => {
|
||||
callback(null, value);
|
||||
});
|
||||
} else if (/^https?:\/\//i.test(content.path || content.href)) {
|
||||
contentStream = nmfetch(content.path || content.href);
|
||||
return resolveStream(contentStream, callback);
|
||||
return resolveStream(nmfetch(content.path || content.href), callback);
|
||||
} else if (/^data:/i.test(content.path || content.href)) {
|
||||
let parsedDataUri = module.exports.parseDataURI(content.path || content.href);
|
||||
const parsedDataUri = module.exports.parseDataURI(content.path || content.href);
|
||||
|
||||
if (!parsedDataUri || !parsedDataUri.data) {
|
||||
return callback(null, Buffer.from(0));
|
||||
@@ -600,21 +565,15 @@ module.exports.resolveContent = (data, key, callback) => {
|
||||
* Copies properties from source objects to target objects
|
||||
*/
|
||||
module.exports.assign = function (/* target, ... sources */) {
|
||||
let args = Array.from(arguments);
|
||||
let target = args.shift() || {};
|
||||
const args = Array.from(arguments);
|
||||
const target = args.shift() || {};
|
||||
|
||||
args.forEach(source => {
|
||||
Object.keys(source || {}).forEach(key => {
|
||||
if (['tls', 'auth'].includes(key) && source[key] && typeof source[key] === 'object') {
|
||||
// tls and auth are special keys that need to be enumerated separately
|
||||
// other objects are passed as is
|
||||
if (!target[key]) {
|
||||
// ensure that target has this key
|
||||
target[key] = {};
|
||||
}
|
||||
Object.keys(source[key]).forEach(subKey => {
|
||||
target[key][subKey] = source[key][subKey];
|
||||
});
|
||||
target[key] = Object.assign(target[key] || {}, source[key]);
|
||||
} else {
|
||||
target[key] = source[key];
|
||||
}
|
||||
@@ -631,10 +590,10 @@ module.exports.encodeXText = str => {
|
||||
if (!/[^\x21-\x2A\x2C-\x3C\x3E-\x7E]/.test(str)) {
|
||||
return str;
|
||||
}
|
||||
let buf = Buffer.from(str);
|
||||
const buf = Buffer.from(str);
|
||||
let result = '';
|
||||
for (let i = 0, len = buf.length; i < len; i++) {
|
||||
let c = buf[i];
|
||||
const c = buf[i];
|
||||
if (c < 0x21 || c > 0x7e || c === 0x2b || c === 0x3d) {
|
||||
result += '+' + (c < 0x10 ? '0' : '') + c.toString(16).toUpperCase();
|
||||
} else {
|
||||
@@ -652,7 +611,7 @@ module.exports.encodeXText = str => {
|
||||
*/
|
||||
function resolveStream(stream, callback) {
|
||||
let responded = false;
|
||||
let chunks = [];
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
|
||||
stream.on('error', err => {
|
||||
@@ -695,13 +654,8 @@ function resolveStream(stream, callback) {
|
||||
* @returns {Object} Bunyan logger instance
|
||||
*/
|
||||
function createDefaultLogger(levels) {
|
||||
let levelMaxLen = 0;
|
||||
let levelNames = new Map();
|
||||
levels.forEach(level => {
|
||||
if (level.length > levelMaxLen) {
|
||||
levelMaxLen = level.length;
|
||||
}
|
||||
});
|
||||
const levelMaxLen = levels.reduce((max, level) => Math.max(max, level.length), 0);
|
||||
const levelNames = new Map();
|
||||
|
||||
levels.forEach(level => {
|
||||
let levelName = level.toUpperCase();
|
||||
@@ -711,7 +665,7 @@ function createDefaultLogger(levels) {
|
||||
levelNames.set(level, levelName);
|
||||
});
|
||||
|
||||
let print = (level, entry, message, ...args) => {
|
||||
const print = (level, entry, message, ...args) => {
|
||||
let prefix = '';
|
||||
if (entry) {
|
||||
if (entry.tnx === 'server') {
|
||||
@@ -735,7 +689,7 @@ function createDefaultLogger(levels) {
|
||||
});
|
||||
};
|
||||
|
||||
let logger = {};
|
||||
const logger = {};
|
||||
levels.forEach(level => {
|
||||
logger[level] = print.bind(null, level);
|
||||
});
|
||||
|
||||
9
node_modules/nodemailer/lib/smtp-connection/data-stream.js
generated
vendored
9
node_modules/nodemailer/lib/smtp-connection/data-stream.js
generated
vendored
@@ -1,7 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const stream = require('stream');
|
||||
const Transform = stream.Transform;
|
||||
const { Transform } = require('stream');
|
||||
|
||||
/**
|
||||
* Escapes dots in the beginning of lines. Ends the stream with <CR><LF>.<CR><LF>
|
||||
@@ -12,9 +11,7 @@ const Transform = stream.Transform;
|
||||
class DataStream extends Transform {
|
||||
constructor(options) {
|
||||
super(options);
|
||||
// init Transform
|
||||
this.options = options || {};
|
||||
this._curLine = '';
|
||||
|
||||
this.inByteCount = 0;
|
||||
this.outByteCount = 0;
|
||||
@@ -25,7 +22,7 @@ class DataStream extends Transform {
|
||||
* Escapes dots
|
||||
*/
|
||||
_transform(chunk, encoding, done) {
|
||||
let chunks = [];
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
let i,
|
||||
len,
|
||||
@@ -53,7 +50,7 @@ class DataStream extends Transform {
|
||||
lastPos = i + 1;
|
||||
}
|
||||
} else if (chunk[i] === 0x0a) {
|
||||
// .
|
||||
// \n
|
||||
if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
|
||||
if (i > lastPos) {
|
||||
buf = chunk.slice(lastPos, i);
|
||||
|
||||
24
node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js
generated
vendored
24
node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js
generated
vendored
@@ -22,18 +22,14 @@ const errors = require('../errors');
|
||||
* @param {Function} callback Callback to run with the rocket object once connection is established
|
||||
*/
|
||||
function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
||||
let proxy = urllib.parse(proxyUrl);
|
||||
const proxy = urllib.parse(proxyUrl);
|
||||
|
||||
// create a socket connection to the proxy server
|
||||
let options;
|
||||
let connect;
|
||||
let socket;
|
||||
|
||||
options = {
|
||||
const options = {
|
||||
host: proxy.hostname,
|
||||
port: Number(proxy.port) ? Number(proxy.port) : proxy.protocol === 'https:' ? 443 : 80
|
||||
};
|
||||
|
||||
let connect;
|
||||
if (proxy.protocol === 'https:') {
|
||||
// we can use untrusted proxies as long as we verify actual SMTP certificates
|
||||
options.rejectUnauthorized = false;
|
||||
@@ -42,10 +38,12 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
||||
connect = net.connect.bind(net);
|
||||
}
|
||||
|
||||
let socket;
|
||||
|
||||
// Error harness for initial connection. Once connection is established, the responsibility
|
||||
// to handle errors is passed to whoever uses this socket
|
||||
let finished = false;
|
||||
let tempSocketErr = err => {
|
||||
const tempSocketErr = err => {
|
||||
if (finished) {
|
||||
return;
|
||||
}
|
||||
@@ -58,8 +56,8 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
||||
callback(err);
|
||||
};
|
||||
|
||||
let timeoutErr = () => {
|
||||
let err = new Error('Proxy socket timed out');
|
||||
const timeoutErr = () => {
|
||||
const err = new Error('Proxy socket timed out');
|
||||
err.code = 'ETIMEDOUT';
|
||||
tempSocketErr(err);
|
||||
};
|
||||
@@ -69,7 +67,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
||||
return;
|
||||
}
|
||||
|
||||
let reqHeaders = {
|
||||
const reqHeaders = {
|
||||
Host: destinationHost + ':' + destinationPort,
|
||||
Connection: 'close'
|
||||
};
|
||||
@@ -93,7 +91,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
||||
);
|
||||
|
||||
let headers = '';
|
||||
let onSocketData = chunk => {
|
||||
const onSocketData = chunk => {
|
||||
let match;
|
||||
let remainder;
|
||||
|
||||
@@ -122,7 +120,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, callback) {
|
||||
} catch (_E) {
|
||||
// ignore
|
||||
}
|
||||
let err = new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''));
|
||||
const err = new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || ''));
|
||||
err.code = errors.EPROXY;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
332
node_modules/nodemailer/lib/smtp-connection/index.js
generated
vendored
332
node_modules/nodemailer/lib/smtp-connection/index.js
generated
vendored
@@ -1,13 +1,13 @@
|
||||
'use strict';
|
||||
|
||||
const packageInfo = require('../../package.json');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const { EventEmitter } = require('events');
|
||||
const net = require('net');
|
||||
const tls = require('tls');
|
||||
const os = require('os');
|
||||
const crypto = require('crypto');
|
||||
const DataStream = require('./data-stream');
|
||||
const PassThrough = require('stream').PassThrough;
|
||||
const { PassThrough } = require('stream');
|
||||
const shared = require('../shared');
|
||||
|
||||
// default timeout values in ms
|
||||
@@ -17,6 +17,28 @@ const GREETING_TIMEOUT = 30 * 1000; // how much to wait after connection is esta
|
||||
const DNS_TIMEOUT = 30 * 1000; // how much to wait for resolveHostname
|
||||
const TEARDOWN_NOOP = () => {}; // reusable no-op handler for absorbing errors during socket teardown
|
||||
|
||||
/**
|
||||
* Re-interpret a server response stored in fake 8-bit byte-container form
|
||||
* (the result of chunk.toString('binary') in _onData) as UTF-8.
|
||||
*
|
||||
* Server reply text has no formally defined charset (RFC 5321 §4.2.1), but
|
||||
* modern MTAs commonly use UTF-8. The byte-container plumbing in _onData is
|
||||
* required to reassemble multi-byte sequences split across socket chunks;
|
||||
* this helper performs the actual decode at the line boundary, falling back
|
||||
* to the byte-container form when the bytes are not valid UTF-8 so that
|
||||
* legacy 8-bit replies are still recoverable byte-for-byte.
|
||||
*/
|
||||
function decodeServerResponse(str) {
|
||||
if (!str) {
|
||||
return str;
|
||||
}
|
||||
const utf8 = Buffer.from(str, 'binary').toString('utf8');
|
||||
// The input is a byte container (each char is in U+0000..U+00FF) so it can never
|
||||
// already contain U+FFFD; any \uFFFD in the result was inserted by Node's UTF-8
|
||||
// decoder for invalid bytes, which means we should return the original bytes intact.
|
||||
return utf8.includes('\uFFFD') ? str : utf8;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a SMTP connection object
|
||||
*
|
||||
@@ -68,7 +90,7 @@ class SMTPConnection extends EventEmitter {
|
||||
this.secureConnection = true;
|
||||
}
|
||||
|
||||
this.name = this.options.name || this._getHostname();
|
||||
this.name = (this.options.name || this._getHostname()).toString().replace(/[\r\n]+/g, '');
|
||||
|
||||
this.logger = shared.getLogger(this.options, {
|
||||
component: this.options.component || 'smtp-connection',
|
||||
@@ -76,13 +98,12 @@ class SMTPConnection extends EventEmitter {
|
||||
});
|
||||
|
||||
this.customAuth = new Map();
|
||||
Object.keys(this.options.customAuth || {}).forEach(key => {
|
||||
let mapKey = (key || '').toString().trim().toUpperCase();
|
||||
if (!mapKey) {
|
||||
return;
|
||||
for (const key of Object.keys(this.options.customAuth || {})) {
|
||||
const mapKey = (key || '').toString().trim().toUpperCase();
|
||||
if (mapKey) {
|
||||
this.customAuth.set(mapKey, this.options.customAuth[key]);
|
||||
}
|
||||
this.customAuth.set(mapKey, this.options.customAuth[key]);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Expose version nr, just for the reference
|
||||
@@ -266,27 +287,7 @@ class SMTPConnection extends EventEmitter {
|
||||
} else if (this.options.socket) {
|
||||
// socket object is set up but not yet connected
|
||||
this._socket = this.options.socket;
|
||||
return shared.resolveHostname(opts, (err, resolved) => {
|
||||
if (err) {
|
||||
return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
|
||||
}
|
||||
this.logger.debug(
|
||||
{
|
||||
tnx: 'dns',
|
||||
source: opts.host,
|
||||
resolved: resolved.host,
|
||||
cached: !!resolved.cached
|
||||
},
|
||||
'Resolved %s as %s [cache %s]',
|
||||
opts.host,
|
||||
resolved.host,
|
||||
resolved.cached ? 'hit' : 'miss'
|
||||
);
|
||||
Object.keys(resolved).forEach(key => {
|
||||
if (key.charAt(0) !== '_' && resolved[key]) {
|
||||
opts[key] = resolved[key];
|
||||
}
|
||||
});
|
||||
return this._resolveAndConnect(opts, _resolved => {
|
||||
try {
|
||||
this._socket.connect(this.port, this.host, () => {
|
||||
this._socket.setKeepAlive(true);
|
||||
@@ -297,80 +298,59 @@ class SMTPConnection extends EventEmitter {
|
||||
return setImmediate(() => this._onError(E, 'ECONNECTION', false, 'CONN'));
|
||||
}
|
||||
});
|
||||
} else if (this.secureConnection) {
|
||||
// connect using tls
|
||||
if (this.options.tls) {
|
||||
Object.keys(this.options.tls).forEach(key => {
|
||||
opts[key] = this.options.tls[key];
|
||||
});
|
||||
}
|
||||
|
||||
// ensure servername for SNI
|
||||
if (this.servername && !opts.servername) {
|
||||
opts.servername = this.servername;
|
||||
}
|
||||
|
||||
return shared.resolveHostname(opts, (err, resolved) => {
|
||||
if (err) {
|
||||
return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
|
||||
}
|
||||
this.logger.debug(
|
||||
{
|
||||
tnx: 'dns',
|
||||
source: opts.host,
|
||||
resolved: resolved.host,
|
||||
cached: !!resolved.cached
|
||||
},
|
||||
'Resolved %s as %s [cache %s]',
|
||||
opts.host,
|
||||
resolved.host,
|
||||
resolved.cached ? 'hit' : 'miss'
|
||||
);
|
||||
Object.keys(resolved).forEach(key => {
|
||||
if (key.charAt(0) !== '_' && resolved[key]) {
|
||||
opts[key] = resolved[key];
|
||||
}
|
||||
});
|
||||
|
||||
// Store fallback addresses for retry on connection failure
|
||||
this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host);
|
||||
this._connectOpts = Object.assign({}, opts);
|
||||
|
||||
this._connectToHost(opts, true);
|
||||
});
|
||||
} else {
|
||||
// connect using plaintext
|
||||
return shared.resolveHostname(opts, (err, resolved) => {
|
||||
if (err) {
|
||||
return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
|
||||
}
|
||||
this.logger.debug(
|
||||
{
|
||||
tnx: 'dns',
|
||||
source: opts.host,
|
||||
resolved: resolved.host,
|
||||
cached: !!resolved.cached
|
||||
},
|
||||
'Resolved %s as %s [cache %s]',
|
||||
opts.host,
|
||||
resolved.host,
|
||||
resolved.cached ? 'hit' : 'miss'
|
||||
);
|
||||
Object.keys(resolved).forEach(key => {
|
||||
if (key.charAt(0) !== '_' && resolved[key]) {
|
||||
opts[key] = resolved[key];
|
||||
}
|
||||
});
|
||||
if (this.secureConnection) {
|
||||
Object.assign(opts, this.options.tls || {});
|
||||
|
||||
// ensure servername for SNI
|
||||
if (this.servername && !opts.servername) {
|
||||
opts.servername = this.servername;
|
||||
}
|
||||
}
|
||||
|
||||
return this._resolveAndConnect(opts, resolved => {
|
||||
// Store fallback addresses for retry on connection failure
|
||||
this._fallbackAddresses = (resolved._addresses || []).filter(addr => addr !== opts.host);
|
||||
this._connectOpts = Object.assign({}, opts);
|
||||
|
||||
this._connectToHost(opts, false);
|
||||
this._connectToHost(opts, this.secureConnection);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the hostname and applies resolved values to opts,
|
||||
* then calls the provided callback with the resolved data
|
||||
*
|
||||
* @param {Object} opts Connection options (modified in place)
|
||||
* @param {Function} callback Called with resolved data on success
|
||||
*/
|
||||
_resolveAndConnect(opts, callback) {
|
||||
return shared.resolveHostname(opts, (err, resolved) => {
|
||||
if (err) {
|
||||
return setImmediate(() => this._onError(err, 'EDNS', false, 'CONN'));
|
||||
}
|
||||
this.logger.debug(
|
||||
{
|
||||
tnx: 'dns',
|
||||
source: opts.host,
|
||||
resolved: resolved.host,
|
||||
cached: !!resolved.cached
|
||||
},
|
||||
'Resolved %s as %s [cache %s]',
|
||||
opts.host,
|
||||
resolved.host,
|
||||
resolved.cached ? 'hit' : 'miss'
|
||||
);
|
||||
for (const key of Object.keys(resolved)) {
|
||||
if (key.charAt(0) !== '_' && resolved[key]) {
|
||||
opts[key] = resolved[key];
|
||||
}
|
||||
}
|
||||
callback(resolved);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to connect to the specified host address
|
||||
*
|
||||
@@ -381,7 +361,7 @@ class SMTPConnection extends EventEmitter {
|
||||
this._connectionAttemptId++;
|
||||
const currentAttemptId = this._connectionAttemptId;
|
||||
|
||||
let connectFn = secure ? tls.connect : net.connect;
|
||||
const connectFn = secure ? tls.connect : net.connect;
|
||||
try {
|
||||
this._socket = connectFn(opts, () => {
|
||||
// Ignore callback if this is a stale connection attempt
|
||||
@@ -418,7 +398,7 @@ class SMTPConnection extends EventEmitter {
|
||||
clearTimeout(this._connectionTimeout);
|
||||
|
||||
// Check if we have fallback addresses to try
|
||||
let canFallback = this._fallbackAddresses && this._fallbackAddresses.length && this.stage === 'init' && !this._destroyed;
|
||||
const canFallback = this._fallbackAddresses && this._fallbackAddresses.length && this.stage === 'init' && !this._destroyed;
|
||||
|
||||
if (!canFallback) {
|
||||
// No more fallback addresses, report the error
|
||||
@@ -426,7 +406,7 @@ class SMTPConnection extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextHost = this._fallbackAddresses.shift();
|
||||
const nextHost = this._fallbackAddresses.shift();
|
||||
|
||||
this.logger.info(
|
||||
{
|
||||
@@ -478,12 +458,7 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
this._closing = true;
|
||||
|
||||
let closeMethod = 'end';
|
||||
|
||||
if (this.stage === 'init') {
|
||||
// Close the socket immediately when connection timed out
|
||||
closeMethod = 'destroy';
|
||||
}
|
||||
const closeMethod = this.stage === 'init' ? 'destroy' : 'end';
|
||||
|
||||
this.logger.debug(
|
||||
{
|
||||
@@ -493,7 +468,7 @@ class SMTPConnection extends EventEmitter {
|
||||
closeMethod
|
||||
);
|
||||
|
||||
let socket = (this._socket && this._socket.socket) || this._socket;
|
||||
const socket = (this._socket && this._socket.socket) || this._socket;
|
||||
|
||||
if (socket && !socket.destroyed) {
|
||||
try {
|
||||
@@ -551,11 +526,11 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
|
||||
if (this.customAuth.has(this._authMethod)) {
|
||||
let handler = this.customAuth.get(this._authMethod);
|
||||
const handler = this.customAuth.get(this._authMethod);
|
||||
let lastResponse;
|
||||
let returned = false;
|
||||
|
||||
let resolve = () => {
|
||||
const resolve = () => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
@@ -574,7 +549,7 @@ class SMTPConnection extends EventEmitter {
|
||||
callback(null, true);
|
||||
};
|
||||
|
||||
let reject = err => {
|
||||
const reject = err => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
@@ -582,7 +557,7 @@ class SMTPConnection extends EventEmitter {
|
||||
callback(this._formatError(err, 'EAUTH', lastResponse, 'AUTH ' + this._authMethod));
|
||||
};
|
||||
|
||||
let handlerResponse = handler({
|
||||
const handlerResponse = handler({
|
||||
auth: this._auth,
|
||||
method: this._authMethod,
|
||||
|
||||
@@ -709,7 +684,7 @@ class SMTPConnection extends EventEmitter {
|
||||
|
||||
// ensure that callback is only called once
|
||||
let returned = false;
|
||||
let callback = function () {
|
||||
const callback = function () {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
@@ -722,11 +697,11 @@ class SMTPConnection extends EventEmitter {
|
||||
message.on('error', err => callback(this._formatError(err, 'ESTREAM', false, 'API')));
|
||||
}
|
||||
|
||||
let startTime = Date.now();
|
||||
const startTime = Date.now();
|
||||
this._setEnvelope(envelope, (err, info) => {
|
||||
if (err) {
|
||||
// create passthrough stream to consume to prevent OOM
|
||||
let stream = new PassThrough();
|
||||
const stream = new PassThrough();
|
||||
if (typeof message.pipe === 'function') {
|
||||
message.pipe(stream);
|
||||
} else {
|
||||
@@ -736,8 +711,8 @@ class SMTPConnection extends EventEmitter {
|
||||
|
||||
return callback(err);
|
||||
}
|
||||
let envelopeTime = Date.now();
|
||||
let stream = this._createSendStream((err, str) => {
|
||||
const envelopeTime = Date.now();
|
||||
const stream = this._createSendStream((err, str) => {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
@@ -921,7 +896,7 @@ class SMTPConnection extends EventEmitter {
|
||||
err.message += ': ' + response;
|
||||
}
|
||||
|
||||
let responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
|
||||
const responseCode = (typeof response === 'string' && Number((response.match(/^\d+/) || [])[0])) || false;
|
||||
if (responseCode) {
|
||||
err.responseCode = responseCode;
|
||||
}
|
||||
@@ -942,15 +917,15 @@ class SMTPConnection extends EventEmitter {
|
||||
let serverResponse = false;
|
||||
|
||||
if (this._remainder && this._remainder.trim()) {
|
||||
this.lastServerResponse = serverResponse = decodeServerResponse(this._remainder.trim());
|
||||
if (this.options.debug || this.options.transactionLog) {
|
||||
this.logger.debug(
|
||||
{
|
||||
tnx: 'server'
|
||||
},
|
||||
this._remainder.replace(/\r?\n$/, '')
|
||||
serverResponse
|
||||
);
|
||||
}
|
||||
this.lastServerResponse = serverResponse = this._remainder.trim();
|
||||
}
|
||||
|
||||
this.logger.info(
|
||||
@@ -1016,15 +991,14 @@ class SMTPConnection extends EventEmitter {
|
||||
this._socket.removeListener('data', this._onSocketData); // incoming data is going to be gibberish from this point onwards
|
||||
this._socket.removeListener('timeout', this._onSocketTimeout); // timeout will be re-set for the new socket object
|
||||
|
||||
let socketPlain = this._socket;
|
||||
let opts = {
|
||||
socket: this._socket,
|
||||
host: this.host
|
||||
};
|
||||
|
||||
Object.keys(this.options.tls || {}).forEach(key => {
|
||||
opts[key] = this.options.tls[key];
|
||||
});
|
||||
const socketPlain = this._socket;
|
||||
const opts = Object.assign(
|
||||
{
|
||||
socket: this._socket,
|
||||
host: this.host
|
||||
},
|
||||
this.options.tls || {}
|
||||
);
|
||||
|
||||
// ensure servername for SNI
|
||||
if (this.servername && !opts.servername) {
|
||||
@@ -1071,7 +1045,7 @@ class SMTPConnection extends EventEmitter {
|
||||
return false;
|
||||
}
|
||||
|
||||
let str = (this.lastServerResponse = (this._responseQueue.shift() || '').toString());
|
||||
let str = (this.lastServerResponse = decodeServerResponse((this._responseQueue.shift() || '').toString()));
|
||||
|
||||
if (/^\d+-/.test(str.split('\n').pop())) {
|
||||
// keep waiting for the final part of multiline response
|
||||
@@ -1092,7 +1066,7 @@ class SMTPConnection extends EventEmitter {
|
||||
setImmediate(() => this._processResponse());
|
||||
}
|
||||
|
||||
let action = this._responseActions.shift();
|
||||
const action = this._responseActions.shift();
|
||||
|
||||
if (typeof action === 'function') {
|
||||
action.call(this, str);
|
||||
@@ -1140,7 +1114,7 @@ class SMTPConnection extends EventEmitter {
|
||||
* {from:{address:'...',name:'...'}, to:[address:'...',name:'...']}
|
||||
*/
|
||||
_setEnvelope(envelope, callback) {
|
||||
let args = [];
|
||||
const args = [];
|
||||
let useSmtpUtf8 = false;
|
||||
|
||||
this._envelope = envelope || {};
|
||||
@@ -1175,7 +1149,7 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
|
||||
// clone the recipients array for latter manipulation
|
||||
this._envelope.rcptQueue = JSON.parse(JSON.stringify(this._envelope.to || []));
|
||||
this._envelope.rcptQueue = [].concat(this._envelope.to || []);
|
||||
this._envelope.rejected = [];
|
||||
this._envelope.rejectedErrors = [];
|
||||
this._envelope.accepted = [];
|
||||
@@ -1207,7 +1181,10 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
|
||||
if (this._envelope.size && this._supportedExtensions.includes('SIZE')) {
|
||||
args.push('SIZE=' + this._envelope.size);
|
||||
const sizeValue = Number(this._envelope.size) || 0;
|
||||
if (sizeValue > 0) {
|
||||
args.push('SIZE=' + sizeValue);
|
||||
}
|
||||
}
|
||||
|
||||
// If the server supports DSN and the envelope includes an DSN prop
|
||||
@@ -1260,7 +1237,7 @@ class SMTPConnection extends EventEmitter {
|
||||
throw new Error('ret: ' + JSON.stringify(ret));
|
||||
}
|
||||
|
||||
let envid = (params.envid || params.id || '').toString() || null;
|
||||
const envid = (params.envid || params.id || '').toString() || null;
|
||||
|
||||
let notify = params.notify || null;
|
||||
if (notify) {
|
||||
@@ -1268,8 +1245,8 @@ class SMTPConnection extends EventEmitter {
|
||||
notify = notify.split(',');
|
||||
}
|
||||
notify = notify.map(n => n.trim().toUpperCase());
|
||||
let validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
|
||||
let invalidNotify = notify.filter(n => !validNotify.includes(n));
|
||||
const validNotify = ['NEVER', 'SUCCESS', 'FAILURE', 'DELAY'];
|
||||
const invalidNotify = notify.filter(n => !validNotify.includes(n));
|
||||
if (invalidNotify.length || (notify.length > 1 && notify.includes('NEVER'))) {
|
||||
throw new Error('notify: ' + JSON.stringify(notify.join(',')));
|
||||
}
|
||||
@@ -1290,7 +1267,7 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
|
||||
_getDsnRcptToArgs() {
|
||||
let args = [];
|
||||
const args = [];
|
||||
// If the server supports DSN and the envelope includes an DSN prop
|
||||
// then append DSN params to the RCPT TO command
|
||||
if (this._envelope.dsn && this._supportedExtensions.includes('DSN')) {
|
||||
@@ -1305,12 +1282,11 @@ class SMTPConnection extends EventEmitter {
|
||||
}
|
||||
|
||||
_createSendStream(callback) {
|
||||
let dataStream = new DataStream();
|
||||
let logStream;
|
||||
const dataStream = new DataStream();
|
||||
|
||||
if (this.options.lmtp) {
|
||||
this._envelope.accepted.forEach((recipient, i) => {
|
||||
let final = i === this._envelope.accepted.length - 1;
|
||||
const final = i === this._envelope.accepted.length - 1;
|
||||
this._responseActions.push(str => {
|
||||
this._actionLMTPStream(recipient, final, str, callback);
|
||||
});
|
||||
@@ -1326,7 +1302,7 @@ class SMTPConnection extends EventEmitter {
|
||||
});
|
||||
|
||||
if (this.options.debug) {
|
||||
logStream = new PassThrough();
|
||||
const logStream = new PassThrough();
|
||||
logStream.on('readable', () => {
|
||||
let chunk;
|
||||
while ((chunk = logStream.read())) {
|
||||
@@ -1604,24 +1580,21 @@ class SMTPConnection extends EventEmitter {
|
||||
* @param {String} str Message from the server
|
||||
*/
|
||||
_actionAUTH_CRAM_MD5(str, callback) {
|
||||
let challengeMatch = str.match(/^334\s+(.+)$/);
|
||||
let challengeString = '';
|
||||
const challengeMatch = str.match(/^334\s+(.+)$/);
|
||||
|
||||
if (!challengeMatch) {
|
||||
return callback(
|
||||
this._formatError('Invalid login sequence while waiting for server challenge string', 'EAUTH', str, 'AUTH CRAM-MD5')
|
||||
);
|
||||
} else {
|
||||
challengeString = challengeMatch[1];
|
||||
}
|
||||
|
||||
// Decode from base64
|
||||
let base64decoded = Buffer.from(challengeString, 'base64').toString('ascii'),
|
||||
hmacMD5 = crypto.createHmac('md5', this._auth.credentials.pass);
|
||||
const base64decoded = Buffer.from(challengeMatch[1], 'base64').toString('ascii');
|
||||
const hmacMD5 = crypto.createHmac('md5', this._auth.credentials.pass);
|
||||
|
||||
hmacMD5.update(base64decoded);
|
||||
|
||||
let prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
|
||||
const prepended = this._auth.credentials.user + ' ' + hmacMD5.digest('hex');
|
||||
|
||||
this._responseActions.push(str => {
|
||||
this._actionAUTH_CRAM_MD5_PASS(str, callback);
|
||||
@@ -1742,39 +1715,29 @@ class SMTPConnection extends EventEmitter {
|
||||
* @param {String} str Message from the server
|
||||
*/
|
||||
_actionMAIL(str, callback) {
|
||||
let message, curRecipient;
|
||||
if (Number(str.charAt(0)) !== 2) {
|
||||
if (this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)) {
|
||||
message = 'Internationalized mailbox name not allowed';
|
||||
} else {
|
||||
message = 'Mail command failed';
|
||||
}
|
||||
const message =
|
||||
this._usingSmtpUtf8 && /^550 /.test(str) && /[\x80-\uFFFF]/.test(this._envelope.from)
|
||||
? 'Internationalized mailbox name not allowed'
|
||||
: 'Mail command failed';
|
||||
return callback(this._formatError(message, 'EENVELOPE', str, 'MAIL FROM'));
|
||||
}
|
||||
|
||||
if (!this._envelope.rcptQueue.length) {
|
||||
return callback(this._formatError("Can't send mail - no recipients defined", 'EENVELOPE', false, 'API'));
|
||||
} else {
|
||||
this._recipientQueue = [];
|
||||
|
||||
if (this._supportedExtensions.includes('PIPELINING')) {
|
||||
while (this._envelope.rcptQueue.length) {
|
||||
curRecipient = this._envelope.rcptQueue.shift();
|
||||
this._recipientQueue.push(curRecipient);
|
||||
this._responseActions.push(str => {
|
||||
this._actionRCPT(str, callback);
|
||||
});
|
||||
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
||||
}
|
||||
} else {
|
||||
curRecipient = this._envelope.rcptQueue.shift();
|
||||
this._recipientQueue.push(curRecipient);
|
||||
this._responseActions.push(str => {
|
||||
this._actionRCPT(str, callback);
|
||||
});
|
||||
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
||||
}
|
||||
}
|
||||
|
||||
this._recipientQueue = [];
|
||||
const usePipelining = this._supportedExtensions.includes('PIPELINING');
|
||||
|
||||
do {
|
||||
const curRecipient = this._envelope.rcptQueue.shift();
|
||||
this._recipientQueue.push(curRecipient);
|
||||
this._responseActions.push(str => {
|
||||
this._actionRCPT(str, callback);
|
||||
});
|
||||
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
||||
} while (usePipelining && this._envelope.rcptQueue.length);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1783,16 +1746,14 @@ class SMTPConnection extends EventEmitter {
|
||||
* @param {String} str Message from the server
|
||||
*/
|
||||
_actionRCPT(str, callback) {
|
||||
let message,
|
||||
err,
|
||||
curRecipient = this._recipientQueue.shift();
|
||||
let err;
|
||||
const curRecipient = this._recipientQueue.shift();
|
||||
if (Number(str.charAt(0)) !== 2) {
|
||||
// this is a soft error
|
||||
if (this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)) {
|
||||
message = 'Internationalized mailbox name not allowed';
|
||||
} else {
|
||||
message = 'Recipient command failed';
|
||||
}
|
||||
const message =
|
||||
this._usingSmtpUtf8 && /^553 /.test(str) && /[\x80-\uFFFF]/.test(curRecipient)
|
||||
? 'Internationalized mailbox name not allowed'
|
||||
: 'Recipient command failed';
|
||||
this._envelope.rejected.push(curRecipient);
|
||||
// store error for the failed recipient
|
||||
err = this._formatError(message, 'EENVELOPE', str, 'RCPT TO');
|
||||
@@ -1815,12 +1776,12 @@ class SMTPConnection extends EventEmitter {
|
||||
return callback(err);
|
||||
}
|
||||
} else if (this._envelope.rcptQueue.length) {
|
||||
curRecipient = this._envelope.rcptQueue.shift();
|
||||
this._recipientQueue.push(curRecipient);
|
||||
const nextRecipient = this._envelope.rcptQueue.shift();
|
||||
this._recipientQueue.push(nextRecipient);
|
||||
this._responseActions.push(str => {
|
||||
this._actionRCPT(str, callback);
|
||||
});
|
||||
this._sendCommand('RCPT TO:<' + curRecipient + '>' + this._getDsnRcptToArgs());
|
||||
this._sendCommand('RCPT TO:<' + nextRecipient + '>' + this._getDsnRcptToArgs());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1836,7 +1797,7 @@ class SMTPConnection extends EventEmitter {
|
||||
return callback(this._formatError('Data command failed', 'EENVELOPE', str, 'DATA'));
|
||||
}
|
||||
|
||||
let response = {
|
||||
const response = {
|
||||
accepted: this._envelope.accepted,
|
||||
rejected: this._envelope.rejected
|
||||
};
|
||||
@@ -1860,12 +1821,9 @@ class SMTPConnection extends EventEmitter {
|
||||
*/
|
||||
_actionSMTPStream(str, callback) {
|
||||
if (Number(str.charAt(0)) !== 2) {
|
||||
// Message failed
|
||||
return callback(this._formatError('Message failed', 'EMESSAGE', str, 'DATA'));
|
||||
} else {
|
||||
// Message sent succesfully
|
||||
return callback(null, str);
|
||||
}
|
||||
return callback(null, str);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
52
node_modules/nodemailer/lib/smtp-pool/index.js
generated
vendored
52
node_modules/nodemailer/lib/smtp-pool/index.js
generated
vendored
@@ -51,11 +51,8 @@ class SMTPPool extends EventEmitter {
|
||||
component: this.options.component || 'smtp-pool'
|
||||
});
|
||||
|
||||
// temporary object
|
||||
let connection = new SMTPConnection(this.options);
|
||||
|
||||
this.name = 'SMTP (pool)';
|
||||
this.version = packageData.version + '[client:' + connection.version + ']';
|
||||
this.version = packageData.version + '[client:' + packageData.version + ']';
|
||||
|
||||
this._rateLimit = {
|
||||
counter: 0,
|
||||
@@ -123,7 +120,7 @@ class SMTPPool extends EventEmitter {
|
||||
*/
|
||||
close() {
|
||||
let connection;
|
||||
let len = this._connections.length;
|
||||
const len = this._connections.length;
|
||||
this._closed = true;
|
||||
|
||||
// clear rate limit timer if it exists
|
||||
@@ -164,7 +161,7 @@ class SMTPPool extends EventEmitter {
|
||||
}
|
||||
|
||||
// make sure that entire queue would be cleaned
|
||||
let invokeCallbacks = () => {
|
||||
const invokeCallbacks = () => {
|
||||
if (!this._queue.length) {
|
||||
this.logger.debug(
|
||||
{
|
||||
@@ -174,7 +171,7 @@ class SMTPPool extends EventEmitter {
|
||||
);
|
||||
return;
|
||||
}
|
||||
let entry = this._queue.shift();
|
||||
const entry = this._queue.shift();
|
||||
if (entry && typeof entry.callback === 'function') {
|
||||
try {
|
||||
entry.callback(new Error('Connection pool was closed'));
|
||||
@@ -201,9 +198,6 @@ class SMTPPool extends EventEmitter {
|
||||
* an available connection, then use this connection to send the mail
|
||||
*/
|
||||
_processMessages() {
|
||||
let connection;
|
||||
let i, len;
|
||||
|
||||
// do nothing if already closed
|
||||
if (this._closed) {
|
||||
return;
|
||||
@@ -220,12 +214,7 @@ class SMTPPool extends EventEmitter {
|
||||
}
|
||||
|
||||
// find first available connection
|
||||
for (i = 0, len = this._connections.length; i < len; i++) {
|
||||
if (this._connections[i].available) {
|
||||
connection = this._connections[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
let connection = this._connections.find(c => c.available);
|
||||
|
||||
if (!connection && this._connections.length < this.options.maxConnections) {
|
||||
connection = this._createConnection();
|
||||
@@ -243,7 +232,7 @@ class SMTPPool extends EventEmitter {
|
||||
this.emit('idle');
|
||||
}
|
||||
|
||||
let entry = (connection.queueEntry = this._queue.shift());
|
||||
const entry = (connection.queueEntry = this._queue.shift());
|
||||
entry.messageId = (connection.queueEntry.mail.message.getHeader('message-id') || '').replace(/[<>\s]/g, '');
|
||||
|
||||
connection.available = false;
|
||||
@@ -294,7 +283,7 @@ class SMTPPool extends EventEmitter {
|
||||
* Creates a new pool resource
|
||||
*/
|
||||
_createConnection() {
|
||||
let connection = new PoolResource(this);
|
||||
const connection = new PoolResource(this);
|
||||
|
||||
connection.id = ++this._connectionCounter;
|
||||
|
||||
@@ -331,7 +320,7 @@ class SMTPPool extends EventEmitter {
|
||||
|
||||
// resource is terminated with an error
|
||||
connection.once('error', err => {
|
||||
if (err.code !== 'EMAXLIMIT') {
|
||||
if (err.code !== errors.EMAXLIMIT) {
|
||||
this.logger.warn(
|
||||
{
|
||||
err,
|
||||
@@ -450,7 +439,7 @@ class SMTPPool extends EventEmitter {
|
||||
}
|
||||
|
||||
_requeueEntryOnConnectionClose(connection) {
|
||||
connection.queueEntry.requeueAttempts = connection.queueEntry.requeueAttempts + 1;
|
||||
connection.queueEntry.requeueAttempts += 1;
|
||||
this.logger.debug(
|
||||
{
|
||||
tnx: 'pool',
|
||||
@@ -484,7 +473,7 @@ class SMTPPool extends EventEmitter {
|
||||
* @param {Object} connection The PoolResource to remove
|
||||
*/
|
||||
_removeConnection(connection) {
|
||||
let index = this._connections.indexOf(connection);
|
||||
const index = this._connections.indexOf(connection);
|
||||
|
||||
if (index !== -1) {
|
||||
this._connections.splice(index, 1);
|
||||
@@ -501,7 +490,7 @@ class SMTPPool extends EventEmitter {
|
||||
return callback();
|
||||
}
|
||||
|
||||
let now = Date.now();
|
||||
const now = Date.now();
|
||||
|
||||
if (this._rateLimit.counter < this._rateLimit.limit) {
|
||||
return callback();
|
||||
@@ -511,7 +500,9 @@ class SMTPPool extends EventEmitter {
|
||||
|
||||
if (this._rateLimit.checkpoint <= now - this._rateLimit.delta) {
|
||||
return this._clearRateLimit();
|
||||
} else if (!this._rateLimit.timeout) {
|
||||
}
|
||||
|
||||
if (!this._rateLimit.timeout) {
|
||||
this._rateLimit.timeout = setTimeout(() => this._clearRateLimit(), this._rateLimit.delta - (now - this._rateLimit.checkpoint));
|
||||
this._rateLimit.checkpoint = now;
|
||||
}
|
||||
@@ -528,7 +519,7 @@ class SMTPPool extends EventEmitter {
|
||||
|
||||
// resume all paused connections
|
||||
while (this._rateLimit.waiting.length) {
|
||||
let cb = this._rateLimit.waiting.shift();
|
||||
const cb = this._rateLimit.waiting.shift();
|
||||
setImmediate(cb);
|
||||
}
|
||||
}
|
||||
@@ -554,7 +545,7 @@ class SMTPPool extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
let auth = new PoolResource(this).auth;
|
||||
const auth = new PoolResource(this).auth;
|
||||
|
||||
this.getSocket(this.options, (err, socketOptions) => {
|
||||
if (err) {
|
||||
@@ -578,13 +569,10 @@ class SMTPPool extends EventEmitter {
|
||||
options.host || '',
|
||||
options.port || ''
|
||||
);
|
||||
options = shared.assign(false, options);
|
||||
Object.keys(socketOptions).forEach(key => {
|
||||
options[key] = socketOptions[key];
|
||||
});
|
||||
options = Object.assign(shared.assign(false, options), socketOptions);
|
||||
}
|
||||
|
||||
let connection = new SMTPConnection(options);
|
||||
const connection = new SMTPConnection(options);
|
||||
let returned = false;
|
||||
|
||||
connection.once('error', err => {
|
||||
@@ -604,7 +592,7 @@ class SMTPPool extends EventEmitter {
|
||||
return callback(new Error('Connection closed'));
|
||||
});
|
||||
|
||||
let finalize = () => {
|
||||
const finalize = () => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
@@ -633,7 +621,7 @@ class SMTPPool extends EventEmitter {
|
||||
finalize();
|
||||
});
|
||||
} else if (!auth && connection.allowsAuth && options.forceAuth) {
|
||||
let err = new Error('Authentication info was not provided');
|
||||
const err = new Error('Authentication info was not provided');
|
||||
err.code = errors.ENOAUTH;
|
||||
|
||||
returned = true;
|
||||
|
||||
20
node_modules/nodemailer/lib/smtp-pool/pool-resource.js
generated
vendored
20
node_modules/nodemailer/lib/smtp-pool/pool-resource.js
generated
vendored
@@ -23,7 +23,7 @@ class PoolResource extends EventEmitter {
|
||||
if (this.options.auth) {
|
||||
switch ((this.options.auth.type || '').toString().toUpperCase()) {
|
||||
case 'OAUTH2': {
|
||||
let oauth2 = new XOAuth2(this.options.auth, this.logger);
|
||||
const oauth2 = new XOAuth2(this.options.auth, this.logger);
|
||||
oauth2.provisionCallback =
|
||||
(this.pool.mailer && this.pool.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
|
||||
this.auth = {
|
||||
@@ -90,10 +90,7 @@ class PoolResource extends EventEmitter {
|
||||
options.port || ''
|
||||
);
|
||||
|
||||
options = assign(false, options);
|
||||
Object.keys(socketOptions).forEach(key => {
|
||||
options[key] = socketOptions[key];
|
||||
});
|
||||
options = Object.assign(assign(false, options), socketOptions);
|
||||
}
|
||||
|
||||
this.connection = new SMTPConnection(options);
|
||||
@@ -114,12 +111,12 @@ class PoolResource extends EventEmitter {
|
||||
}
|
||||
returned = true;
|
||||
|
||||
let timer = setTimeout(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
// still have not returned, this means we have an unexpected connection close
|
||||
let err = new Error('Unexpected socket close');
|
||||
const err = new Error('Unexpected socket close');
|
||||
if (this.connection && this.connection._socket && this.connection._socket.upgrading) {
|
||||
// starttls connection errors
|
||||
err.code = errors.ETLS;
|
||||
@@ -180,10 +177,10 @@ class PoolResource extends EventEmitter {
|
||||
});
|
||||
}
|
||||
|
||||
let envelope = mail.message.getEnvelope();
|
||||
let messageId = mail.message.messageId();
|
||||
const envelope = mail.message.getEnvelope();
|
||||
const messageId = mail.message.messageId();
|
||||
|
||||
let recipients = [].concat(envelope.to || []);
|
||||
const recipients = [].concat(envelope.to || []);
|
||||
if (recipients.length > 3) {
|
||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
||||
}
|
||||
@@ -224,9 +221,8 @@ class PoolResource extends EventEmitter {
|
||||
info.messageId = messageId;
|
||||
|
||||
setImmediate(() => {
|
||||
let err;
|
||||
if (this.messages >= this.options.maxMessages) {
|
||||
err = new Error('Resource exhausted');
|
||||
const err = new Error('Resource exhausted');
|
||||
err.code = errors.EMAXLIMIT;
|
||||
this.connection.close();
|
||||
this.emit('error', err);
|
||||
|
||||
64
node_modules/nodemailer/lib/smtp-transport/index.js
generated
vendored
64
node_modules/nodemailer/lib/smtp-transport/index.js
generated
vendored
@@ -49,11 +49,8 @@ class SMTPTransport extends EventEmitter {
|
||||
component: this.options.component || 'smtp-transport'
|
||||
});
|
||||
|
||||
// temporary object
|
||||
let connection = new SMTPConnection(this.options);
|
||||
|
||||
this.name = 'SMTP';
|
||||
this.version = packageData.version + '[client:' + connection.version + ']';
|
||||
this.version = packageData.version + '[client:' + packageData.version + ']';
|
||||
|
||||
if (this.options.auth) {
|
||||
this.auth = this.getAuth({});
|
||||
@@ -77,24 +74,13 @@ class SMTPTransport extends EventEmitter {
|
||||
return this.auth;
|
||||
}
|
||||
|
||||
let hasAuth = false;
|
||||
let authData = {};
|
||||
const authData = Object.assign(
|
||||
{},
|
||||
this.options.auth && typeof this.options.auth === 'object' ? this.options.auth : {},
|
||||
authOpts && typeof authOpts === 'object' ? authOpts : {}
|
||||
);
|
||||
|
||||
if (this.options.auth && typeof this.options.auth === 'object') {
|
||||
Object.keys(this.options.auth).forEach(key => {
|
||||
hasAuth = true;
|
||||
authData[key] = this.options.auth[key];
|
||||
});
|
||||
}
|
||||
|
||||
if (authOpts && typeof authOpts === 'object') {
|
||||
Object.keys(authOpts).forEach(key => {
|
||||
hasAuth = true;
|
||||
authData[key] = authOpts[key];
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasAuth) {
|
||||
if (Object.keys(authData).length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -103,7 +89,7 @@ class SMTPTransport extends EventEmitter {
|
||||
if (!authData.service && !authData.user) {
|
||||
return false;
|
||||
}
|
||||
let oauth2 = new XOAuth2(authData, this.logger);
|
||||
const oauth2 = new XOAuth2(authData, this.logger);
|
||||
oauth2.provisionCallback = (this.mailer && this.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
|
||||
oauth2.on('token', token => this.mailer.emit('token', token));
|
||||
oauth2.on('error', err => this.emit('error', err));
|
||||
@@ -160,13 +146,10 @@ class SMTPTransport extends EventEmitter {
|
||||
);
|
||||
|
||||
// only copy options if we need to modify it
|
||||
options = shared.assign(false, options);
|
||||
Object.keys(socketOptions).forEach(key => {
|
||||
options[key] = socketOptions[key];
|
||||
});
|
||||
options = Object.assign(shared.assign(false, options), socketOptions);
|
||||
}
|
||||
|
||||
let connection = new SMTPConnection(options);
|
||||
const connection = new SMTPConnection(options);
|
||||
|
||||
connection.once('error', err => {
|
||||
if (returned) {
|
||||
@@ -182,13 +165,13 @@ class SMTPTransport extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
let timer = setTimeout(() => {
|
||||
const timer = setTimeout(() => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
returned = true;
|
||||
// still have not returned, this means we have an unexpected connection close
|
||||
let err = new Error('Unexpected socket close');
|
||||
const err = new Error('Unexpected socket close');
|
||||
if (connection && connection._socket && connection._socket.upgrading) {
|
||||
// starttls connection errors
|
||||
err.code = errors.ETLS;
|
||||
@@ -203,11 +186,11 @@ class SMTPTransport extends EventEmitter {
|
||||
}
|
||||
});
|
||||
|
||||
let sendMessage = () => {
|
||||
let envelope = mail.message.getEnvelope();
|
||||
let messageId = mail.message.messageId();
|
||||
const sendMessage = () => {
|
||||
const envelope = mail.message.getEnvelope();
|
||||
const messageId = mail.message.messageId();
|
||||
|
||||
let recipients = [].concat(envelope.to || []);
|
||||
const recipients = [].concat(envelope.to || []);
|
||||
if (recipients.length > 3) {
|
||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
||||
}
|
||||
@@ -272,7 +255,7 @@ class SMTPTransport extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
let auth = this.getAuth(mail.data.auth);
|
||||
const auth = this.getAuth(mail.data.auth);
|
||||
|
||||
if (auth && (connection.allowsAuth || options.forceAuth)) {
|
||||
connection.login(auth, err => {
|
||||
@@ -335,13 +318,10 @@ class SMTPTransport extends EventEmitter {
|
||||
options.port || ''
|
||||
);
|
||||
|
||||
options = shared.assign(false, options);
|
||||
Object.keys(socketOptions).forEach(key => {
|
||||
options[key] = socketOptions[key];
|
||||
});
|
||||
options = Object.assign(shared.assign(false, options), socketOptions);
|
||||
}
|
||||
|
||||
let connection = new SMTPConnection(options);
|
||||
const connection = new SMTPConnection(options);
|
||||
let returned = false;
|
||||
|
||||
connection.once('error', err => {
|
||||
@@ -361,7 +341,7 @@ class SMTPTransport extends EventEmitter {
|
||||
return callback(new Error('Connection closed'));
|
||||
});
|
||||
|
||||
let finalize = () => {
|
||||
const finalize = () => {
|
||||
if (returned) {
|
||||
return;
|
||||
}
|
||||
@@ -375,7 +355,7 @@ class SMTPTransport extends EventEmitter {
|
||||
return;
|
||||
}
|
||||
|
||||
let authData = this.getAuth({});
|
||||
const authData = this.getAuth({});
|
||||
|
||||
if (authData && (connection.allowsAuth || options.forceAuth)) {
|
||||
connection.login(authData, err => {
|
||||
@@ -392,7 +372,7 @@ class SMTPTransport extends EventEmitter {
|
||||
finalize();
|
||||
});
|
||||
} else if (!authData && connection.allowsAuth && options.forceAuth) {
|
||||
let err = new Error('Authentication info was not provided');
|
||||
const err = new Error('Authentication info was not provided');
|
||||
err.code = errors.ENOAUTH;
|
||||
|
||||
returned = true;
|
||||
|
||||
14
node_modules/nodemailer/lib/stream-transport/index.js
generated
vendored
14
node_modules/nodemailer/lib/stream-transport/index.js
generated
vendored
@@ -18,7 +18,7 @@ class StreamTransport {
|
||||
constructor(options) {
|
||||
options = options || {};
|
||||
|
||||
this.options = options || {};
|
||||
this.options = options;
|
||||
|
||||
this.name = 'StreamTransport';
|
||||
this.version = packageData.version;
|
||||
@@ -40,10 +40,10 @@ class StreamTransport {
|
||||
// We probably need this in the output
|
||||
mail.message.keepBcc = true;
|
||||
|
||||
let envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
let messageId = mail.message.messageId();
|
||||
const envelope = mail.data.envelope || mail.message.getEnvelope();
|
||||
const messageId = mail.message.messageId();
|
||||
|
||||
let recipients = [].concat(envelope.to || []);
|
||||
const recipients = [].concat(envelope.to || []);
|
||||
if (recipients.length > 3) {
|
||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
||||
}
|
||||
@@ -91,13 +91,13 @@ class StreamTransport {
|
||||
);
|
||||
});
|
||||
return done(null, {
|
||||
envelope: mail.data.envelope || mail.message.getEnvelope(),
|
||||
envelope,
|
||||
messageId,
|
||||
message: stream
|
||||
});
|
||||
}
|
||||
|
||||
let chunks = [];
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
stream.on('readable', () => {
|
||||
let chunk;
|
||||
@@ -123,7 +123,7 @@ class StreamTransport {
|
||||
|
||||
stream.on('end', () =>
|
||||
done(null, {
|
||||
envelope: mail.data.envelope || mail.message.getEnvelope(),
|
||||
envelope,
|
||||
messageId,
|
||||
message: Buffer.concat(chunks, chunklen)
|
||||
})
|
||||
|
||||
14
node_modules/nodemailer/lib/well-known/index.js
generated
vendored
14
node_modules/nodemailer/lib/well-known/index.js
generated
vendored
@@ -4,16 +4,17 @@ const services = require('./services.json');
|
||||
const normalized = {};
|
||||
|
||||
Object.keys(services).forEach(key => {
|
||||
let service = services[key];
|
||||
const service = services[key];
|
||||
const normalizedService = normalizeService(service);
|
||||
|
||||
normalized[normalizeKey(key)] = normalizeService(service);
|
||||
normalized[normalizeKey(key)] = normalizedService;
|
||||
|
||||
[].concat(service.aliases || []).forEach(alias => {
|
||||
normalized[normalizeKey(alias)] = normalizeService(service);
|
||||
normalized[normalizeKey(alias)] = normalizedService;
|
||||
});
|
||||
|
||||
[].concat(service.domains || []).forEach(domain => {
|
||||
normalized[normalizeKey(domain)] = normalizeService(service);
|
||||
normalized[normalizeKey(domain)] = normalizedService;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,11 +23,10 @@ function normalizeKey(key) {
|
||||
}
|
||||
|
||||
function normalizeService(service) {
|
||||
let filter = ['domains', 'aliases'];
|
||||
let response = {};
|
||||
const response = {};
|
||||
|
||||
Object.keys(service).forEach(key => {
|
||||
if (filter.indexOf(key) < 0) {
|
||||
if (!['domains', 'aliases'].includes(key)) {
|
||||
response[key] = service[key];
|
||||
}
|
||||
});
|
||||
|
||||
50
node_modules/nodemailer/lib/xoauth2/index.js
generated
vendored
50
node_modules/nodemailer/lib/xoauth2/index.js
generated
vendored
@@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const Stream = require('stream').Stream;
|
||||
const { Stream } = require('stream');
|
||||
const nmfetch = require('../fetch');
|
||||
const crypto = require('crypto');
|
||||
const shared = require('../shared');
|
||||
@@ -42,13 +42,13 @@ class XOAuth2 extends Stream {
|
||||
|
||||
if (options && options.serviceClient) {
|
||||
if (!options.privateKey || !options.user) {
|
||||
let err = new Error('Options "privateKey" and "user" are required for service account!');
|
||||
const err = new Error('Options "privateKey" and "user" are required for service account!');
|
||||
err.code = errors.EOAUTH2;
|
||||
setImmediate(() => this.emit('error', err));
|
||||
return;
|
||||
}
|
||||
|
||||
let serviceRequestTimeout = Math.min(Math.max(Number(this.options.serviceRequestTimeout) || 0, 0), 3600);
|
||||
const serviceRequestTimeout = Math.min(Math.max(Number(this.options.serviceRequestTimeout) || 0, 0), 3600);
|
||||
this.options.serviceRequestTimeout = serviceRequestTimeout || 5 * 60;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ class XOAuth2 extends Stream {
|
||||
if (this.options.expires && Number(this.options.expires)) {
|
||||
this.expires = this.options.expires;
|
||||
} else {
|
||||
let timeout = Math.max(Number(this.options.timeout) || 0, 0);
|
||||
const timeout = Math.max(Number(this.options.timeout) || 0, 0);
|
||||
this.expires = (timeout && Date.now() + timeout * 1000) || 0;
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ class XOAuth2 extends Stream {
|
||||
'Cannot renew access token for %s: No refresh mechanism available',
|
||||
this.options.user
|
||||
);
|
||||
let err = new Error("Can't create new access token for user");
|
||||
const err = new Error("Can't create new access token for user");
|
||||
err.code = errors.EOAUTH2;
|
||||
return callback(err);
|
||||
}
|
||||
@@ -210,8 +210,8 @@ class XOAuth2 extends Stream {
|
||||
let loggedUrlOptions;
|
||||
if (this.options.serviceClient) {
|
||||
// service account - https://developers.google.com/identity/protocols/OAuth2ServiceAccount
|
||||
let iat = Math.floor(Date.now() / 1000); // unix time
|
||||
let tokenData = {
|
||||
const iat = Math.floor(Date.now() / 1000); // unix time
|
||||
const tokenData = {
|
||||
iss: this.options.serviceClient,
|
||||
scope: this.options.scope || 'https://mail.google.com/',
|
||||
sub: this.options.user,
|
||||
@@ -223,7 +223,7 @@ class XOAuth2 extends Stream {
|
||||
try {
|
||||
token = this.jwtSignRS256(tokenData);
|
||||
} catch (_err) {
|
||||
let err = new Error("Can't generate token. Check your auth options");
|
||||
const err = new Error("Can't generate token. Check your auth options");
|
||||
err.code = errors.EOAUTH2;
|
||||
return callback(err);
|
||||
}
|
||||
@@ -239,7 +239,7 @@ class XOAuth2 extends Stream {
|
||||
};
|
||||
} else {
|
||||
if (!this.options.refreshToken) {
|
||||
let err = new Error("Can't create new access token for user");
|
||||
const err = new Error("Can't create new access token for user");
|
||||
err.code = errors.EOAUTH2;
|
||||
return callback(err);
|
||||
}
|
||||
@@ -260,10 +260,8 @@ class XOAuth2 extends Stream {
|
||||
};
|
||||
}
|
||||
|
||||
Object.keys(this.options.customParams).forEach(key => {
|
||||
urlOptions[key] = this.options.customParams[key];
|
||||
loggedUrlOptions[key] = this.options.customParams[key];
|
||||
});
|
||||
Object.assign(urlOptions, this.options.customParams);
|
||||
Object.assign(loggedUrlOptions, this.options.customParams);
|
||||
|
||||
this.logger.debug(
|
||||
{
|
||||
@@ -298,19 +296,15 @@ class XOAuth2 extends Stream {
|
||||
'Response: %s',
|
||||
(body || '').toString()
|
||||
);
|
||||
let err = new Error('Invalid authentication response');
|
||||
const err = new Error('Invalid authentication response');
|
||||
err.code = errors.EOAUTH2;
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
let logData = {};
|
||||
Object.keys(data).forEach(key => {
|
||||
if (key !== 'access_token') {
|
||||
logData[key] = data[key];
|
||||
} else {
|
||||
logData[key] = (data[key] || '').toString().substr(0, 6) + '...';
|
||||
}
|
||||
});
|
||||
const logData = Object.assign({}, data);
|
||||
if (logData.access_token) {
|
||||
logData.access_token = (logData.access_token || '').toString().substr(0, 6) + '...';
|
||||
}
|
||||
|
||||
this.logger.debug(
|
||||
{
|
||||
@@ -331,7 +325,7 @@ class XOAuth2 extends Stream {
|
||||
if (data.error_uri) {
|
||||
errorMessage += ' (' + data.error_uri + ')';
|
||||
}
|
||||
let err = new Error(errorMessage);
|
||||
const err = new Error(errorMessage);
|
||||
err.code = errors.EOAUTH2;
|
||||
return callback(err);
|
||||
}
|
||||
@@ -341,7 +335,7 @@ class XOAuth2 extends Stream {
|
||||
return callback(null, this.accessToken);
|
||||
}
|
||||
|
||||
let err = new Error('No access token');
|
||||
const err = new Error('No access token');
|
||||
err.code = errors.EOAUTH2;
|
||||
return callback(err);
|
||||
});
|
||||
@@ -354,7 +348,7 @@ class XOAuth2 extends Stream {
|
||||
* @return {String} Base64 encoded token for IMAP or SMTP login
|
||||
*/
|
||||
buildXOAuth2Token(accessToken) {
|
||||
let authData = ['user=' + (this.options.user || ''), 'auth=Bearer ' + (accessToken || this.accessToken), '', ''];
|
||||
const authData = ['user=' + (this.options.user || ''), 'auth=Bearer ' + (accessToken || this.accessToken), '', ''];
|
||||
return Buffer.from(authData.join('\x01'), 'utf-8').toString('base64');
|
||||
}
|
||||
|
||||
@@ -373,10 +367,10 @@ class XOAuth2 extends Stream {
|
||||
postRequest(url, payload, params, callback) {
|
||||
let returned = false;
|
||||
|
||||
let chunks = [];
|
||||
const chunks = [];
|
||||
let chunklen = 0;
|
||||
|
||||
let req = nmfetch(url, {
|
||||
const req = nmfetch(url, {
|
||||
method: 'post',
|
||||
headers: params.customHeaders,
|
||||
body: payload,
|
||||
@@ -434,7 +428,7 @@ class XOAuth2 extends Stream {
|
||||
*/
|
||||
jwtSignRS256(payload) {
|
||||
payload = ['{"alg":"RS256","typ":"JWT"}', JSON.stringify(payload)].map(val => this.toBase64URL(val)).join('.');
|
||||
let signature = crypto.createSign('RSA-SHA256').update(payload).sign(this.options.privateKey);
|
||||
const signature = crypto.createSign('RSA-SHA256').update(payload).sign(this.options.privateKey);
|
||||
return payload + '.' + this.toBase64URL(signature);
|
||||
}
|
||||
}
|
||||
|
||||
18
node_modules/nodemailer/package.json
generated
vendored
18
node_modules/nodemailer/package.json
generated
vendored
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "nodemailer",
|
||||
"version": "8.0.2",
|
||||
"version": "8.0.7",
|
||||
"description": "Easy as cake e-mail sending from your Node.js applications",
|
||||
"main": "lib/nodemailer.js",
|
||||
"scripts": {
|
||||
@@ -10,7 +10,8 @@
|
||||
"format:check": "prettier --check \"**/*.{js,json,md}\"",
|
||||
"lint": "eslint .",
|
||||
"lint:fix": "eslint . --fix",
|
||||
"update": "rm -rf node_modules/ package-lock.json && ncu -u && npm install"
|
||||
"update": "rm -rf node_modules/ package-lock.json && ncu -u && npm install",
|
||||
"test:syntax": "docker run --rm -v \"$PWD:/app:ro\" -w /app node:6-alpine node test/syntax-compat.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -26,20 +27,19 @@
|
||||
},
|
||||
"homepage": "https://nodemailer.com/",
|
||||
"devDependencies": {
|
||||
"@aws-sdk/client-sesv2": "3.1004.0",
|
||||
"@aws-sdk/client-sesv2": "3.1037.0",
|
||||
"bunyan": "1.8.15",
|
||||
"c8": "11.0.0",
|
||||
"eslint": "10.0.3",
|
||||
"eslint": "10.2.1",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"globals": "17.4.0",
|
||||
"globals": "17.5.0",
|
||||
"libbase64": "1.3.0",
|
||||
"libmime": "5.3.7",
|
||||
"libmime": "5.3.8",
|
||||
"libqp": "2.1.1",
|
||||
"nodemailer-ntlm-auth": "1.0.4",
|
||||
"prettier": "3.8.1",
|
||||
"prettier": "3.8.3",
|
||||
"proxy": "1.0.2",
|
||||
"proxy-test-server": "1.0.0",
|
||||
"smtp-server": "3.18.1"
|
||||
"smtp-server": "3.18.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0.0"
|
||||
|
||||
Reference in New Issue
Block a user