Compare commits
1 commit
main
...
dev-db_aut
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b7d791da2 |
561
lib/node/node_modules/.package-lock.json
generated
vendored
561
lib/node/node_modules/.package-lock.json
generated
vendored
File diff suppressed because it is too large
Load diff
21
lib/node/node_modules/@types/mocha/LICENSE
generated
vendored
21
lib/node/node_modules/@types/mocha/LICENSE
generated
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) Microsoft Corporation.
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE
|
|
||||||
15
lib/node/node_modules/@types/mocha/README.md
generated
vendored
15
lib/node/node_modules/@types/mocha/README.md
generated
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
# Installation
|
|
||||||
> `npm install --save @types/mocha`
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
This package contains type definitions for mocha (https://mochajs.org).
|
|
||||||
|
|
||||||
# Details
|
|
||||||
Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mocha.
|
|
||||||
|
|
||||||
### Additional Details
|
|
||||||
* Last updated: Wed, 22 Nov 2023 00:24:48 GMT
|
|
||||||
* Dependencies: none
|
|
||||||
|
|
||||||
# Credits
|
|
||||||
These definitions were written by [Kazi Manzur Rashid](https://github.com/kazimanzurrashid), [otiai10](https://github.com/otiai10), [Vadim Macagon](https://github.com/enlight), [Andrew Bradley](https://github.com/cspotcode), [Dmitrii Sorin](https://github.com/1999), [Noah Hummel](https://github.com/strangedev), and [nicojs](https://github.com/nicojs).
|
|
||||||
2916
lib/node/node_modules/@types/mocha/index.d.ts
generated
vendored
2916
lib/node/node_modules/@types/mocha/index.d.ts
generated
vendored
File diff suppressed because it is too large
Load diff
55
lib/node/node_modules/@types/mocha/package.json
generated
vendored
55
lib/node/node_modules/@types/mocha/package.json
generated
vendored
|
|
@ -1,55 +0,0 @@
|
||||||
{
|
|
||||||
"name": "@types/mocha",
|
|
||||||
"version": "10.0.6",
|
|
||||||
"description": "TypeScript definitions for mocha",
|
|
||||||
"homepage": "https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/mocha",
|
|
||||||
"license": "MIT",
|
|
||||||
"contributors": [
|
|
||||||
{
|
|
||||||
"name": "Kazi Manzur Rashid",
|
|
||||||
"githubUsername": "kazimanzurrashid",
|
|
||||||
"url": "https://github.com/kazimanzurrashid"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "otiai10",
|
|
||||||
"githubUsername": "otiai10",
|
|
||||||
"url": "https://github.com/otiai10"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Vadim Macagon",
|
|
||||||
"githubUsername": "enlight",
|
|
||||||
"url": "https://github.com/enlight"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Andrew Bradley",
|
|
||||||
"githubUsername": "cspotcode",
|
|
||||||
"url": "https://github.com/cspotcode"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Dmitrii Sorin",
|
|
||||||
"githubUsername": "1999",
|
|
||||||
"url": "https://github.com/1999"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Noah Hummel",
|
|
||||||
"githubUsername": "strangedev",
|
|
||||||
"url": "https://github.com/strangedev"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "nicojs",
|
|
||||||
"githubUsername": "nicojs",
|
|
||||||
"url": "https://github.com/nicojs"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"main": "",
|
|
||||||
"types": "index.d.ts",
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/DefinitelyTyped/DefinitelyTyped.git",
|
|
||||||
"directory": "types/mocha"
|
|
||||||
},
|
|
||||||
"scripts": {},
|
|
||||||
"dependencies": {},
|
|
||||||
"typesPublisherContentHash": "de34ee99a18094486419f8e3b6c2a3f81f623ecdcbc5f6f4cfe46236c7df5831",
|
|
||||||
"typeScriptVersion": "4.5"
|
|
||||||
}
|
|
||||||
18
lib/node/node_modules/node-abi/abi_registry.json
generated
vendored
18
lib/node/node_modules/node-abi/abi_registry.json
generated
vendored
|
|
@ -98,8 +98,8 @@
|
||||||
"2024-10-29",
|
"2024-10-29",
|
||||||
"2025-10-21"
|
"2025-10-21"
|
||||||
],
|
],
|
||||||
"future": false,
|
"future": true,
|
||||||
"abi": "127"
|
"abi": "124"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"abi": "70",
|
"abi": "70",
|
||||||
|
|
@ -309,20 +309,6 @@
|
||||||
"future": true,
|
"future": true,
|
||||||
"lts": false,
|
"lts": false,
|
||||||
"runtime": "electron",
|
"runtime": "electron",
|
||||||
"target": "31.0.0-alpha.1"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"abi": "123",
|
|
||||||
"future": false,
|
|
||||||
"lts": false,
|
|
||||||
"runtime": "electron",
|
|
||||||
"target": "30.0.0-alpha.1"
|
"target": "30.0.0-alpha.1"
|
||||||
},
|
|
||||||
{
|
|
||||||
"abi": "125",
|
|
||||||
"future": true,
|
|
||||||
"lts": false,
|
|
||||||
"runtime": "electron",
|
|
||||||
"target": "31.0.0-beta.7"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
2
lib/node/node_modules/node-abi/package.json
generated
vendored
2
lib/node/node_modules/node-abi/package.json
generated
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "node-abi",
|
"name": "node-abi",
|
||||||
"version": "3.63.0",
|
"version": "3.57.0",
|
||||||
"description": "Get the Node ABI for a given target and runtime, and vice versa.",
|
"description": "Get the Node ABI for a given target and runtime, and vice versa.",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
6
lib/node/node_modules/nodemailer/.gitattributes
generated
vendored
6
lib/node/node_modules/nodemailer/.gitattributes
generated
vendored
|
|
@ -1,6 +0,0 @@
|
||||||
*.js text eol=lf
|
|
||||||
*.txt text eol=lf
|
|
||||||
*.html text eol=lf
|
|
||||||
*.htm text eol=lf
|
|
||||||
*.ics -text
|
|
||||||
*.bin -text
|
|
||||||
7
lib/node/node_modules/nodemailer/.ncurc.js
generated
vendored
7
lib/node/node_modules/nodemailer/.ncurc.js
generated
vendored
|
|
@ -1,7 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
upgrade: true,
|
|
||||||
reject: [
|
|
||||||
// API changes break existing tests
|
|
||||||
'proxy'
|
|
||||||
]
|
|
||||||
};
|
|
||||||
8
lib/node/node_modules/nodemailer/.prettierrc.js
generated
vendored
8
lib/node/node_modules/nodemailer/.prettierrc.js
generated
vendored
|
|
@ -1,8 +0,0 @@
|
||||||
module.exports = {
|
|
||||||
printWidth: 160,
|
|
||||||
tabWidth: 4,
|
|
||||||
singleQuote: true,
|
|
||||||
endOfLine: 'lf',
|
|
||||||
trailingComma: 'none',
|
|
||||||
arrowParens: 'avoid'
|
|
||||||
};
|
|
||||||
804
lib/node/node_modules/nodemailer/CHANGELOG.md
generated
vendored
804
lib/node/node_modules/nodemailer/CHANGELOG.md
generated
vendored
|
|
@ -1,804 +0,0 @@
|
||||||
# CHANGELOG
|
|
||||||
|
|
||||||
## [6.9.13](https://github.com/nodemailer/nodemailer/compare/v6.9.12...v6.9.13) (2024-03-20)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **tls:** Ensure servername for SMTP ([d66fdd3](https://github.com/nodemailer/nodemailer/commit/d66fdd3dccacc4bc79d697fe9009204cc8d4bde0))
|
|
||||||
|
|
||||||
## [6.9.12](https://github.com/nodemailer/nodemailer/compare/v6.9.11...v6.9.12) (2024-03-08)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **message-generation:** Escape single quote in address names ([4ae5fad](https://github.com/nodemailer/nodemailer/commit/4ae5fadeaac70ba91abf529fcaae65f829a39101))
|
|
||||||
|
|
||||||
## [6.9.11](https://github.com/nodemailer/nodemailer/compare/v6.9.10...v6.9.11) (2024-02-29)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **headers:** Ensure that Content-type is the bottom header ([c7cf97e](https://github.com/nodemailer/nodemailer/commit/c7cf97e5ecc83f8eee773359951df995c9945446))
|
|
||||||
|
|
||||||
## [6.9.10](https://github.com/nodemailer/nodemailer/compare/v6.9.9...v6.9.10) (2024-02-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **data-uri:** Do not use regular expressions for parsing data URI schemes ([12e65e9](https://github.com/nodemailer/nodemailer/commit/12e65e975d80efe6bafe6de4590829b3b5ebb492))
|
|
||||||
* **data-uri:** Moved all data-uri regexes to use the non-regex parseDataUri method ([edd5dfe](https://github.com/nodemailer/nodemailer/commit/edd5dfe5ce9b725f8b8ae2830797f65b2a2b0a33))
|
|
||||||
|
|
||||||
## [6.9.9](https://github.com/nodemailer/nodemailer/compare/v6.9.8...v6.9.9) (2024-02-01)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **security:** Fix issues described in GHSA-9h6g-pr28-7cqp. Do not use eternal matching pattern if only a few occurences are expected ([dd8f5e8](https://github.com/nodemailer/nodemailer/commit/dd8f5e8a4ddc99992e31df76bcff9c590035cd4a))
|
|
||||||
* **tests:** Use native node test runner, added code coverage support, removed grunt ([#1604](https://github.com/nodemailer/nodemailer/issues/1604)) ([be45c1b](https://github.com/nodemailer/nodemailer/commit/be45c1b299d012358d69247019391a02734d70af))
|
|
||||||
|
|
||||||
## [6.9.8](https://github.com/nodemailer/nodemailer/compare/v6.9.7...v6.9.8) (2023-12-30)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **punycode:** do not use native punycode module ([b4d0e0c](https://github.com/nodemailer/nodemailer/commit/b4d0e0c7cc4b15bc4d9e287f91d1bcaca87508b0))
|
|
||||||
|
|
||||||
## [6.9.7](https://github.com/nodemailer/nodemailer/compare/v6.9.6...v6.9.7) (2023-10-22)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **customAuth:** Do not require user and pass to be set for custom authentication schemes (fixes [#1584](https://github.com/nodemailer/nodemailer/issues/1584)) ([41d482c](https://github.com/nodemailer/nodemailer/commit/41d482c3f01e26111b06f3e46351b193db3fb5cb))
|
|
||||||
|
|
||||||
## [6.9.6](https://github.com/nodemailer/nodemailer/compare/v6.9.5...v6.9.6) (2023-10-09)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **inline:** Use 'inline' as the default Content Dispostion value for embedded images ([db32c93](https://github.com/nodemailer/nodemailer/commit/db32c93fefee527bcc239f13056e5d9181a4d8af))
|
|
||||||
* **tests:** Removed Node v12 from test matrix as it is not compatible with the test framework anymore ([7fe0a60](https://github.com/nodemailer/nodemailer/commit/7fe0a608ed6bcb70dc6b2de543ebfc3a30abf984))
|
|
||||||
|
|
||||||
## [6.9.5](https://github.com/nodemailer/nodemailer/compare/v6.9.4...v6.9.5) (2023-09-06)
|
|
||||||
|
|
||||||
|
|
||||||
### Bug Fixes
|
|
||||||
|
|
||||||
* **license:** Updated license year ([da4744e](https://github.com/nodemailer/nodemailer/commit/da4744e491f3a68f4f68e4073684370592630e01))
|
|
||||||
|
|
||||||
## 6.9.4 2023-07-19
|
|
||||||
|
|
||||||
- Renamed SendinBlue to Brevo
|
|
||||||
|
|
||||||
## 6.9.3 2023-05-29
|
|
||||||
|
|
||||||
- Specified license identifier (was defined as MIT, actual value MIT-0)
|
|
||||||
- If SMTP server disconnects with a message, process it and include as part of the response error
|
|
||||||
|
|
||||||
## 6.9.2 2023-05-11
|
|
||||||
|
|
||||||
- Fix uncaught exception on invalid attachment content payload
|
|
||||||
|
|
||||||
## 6.9.1 2023-01-27
|
|
||||||
|
|
||||||
- Fix base64 encoding for emoji bytes in encoded words
|
|
||||||
|
|
||||||
## 6.9.0 2023-01-12
|
|
||||||
|
|
||||||
- Do not throw if failed to resolve IPv4 addresses
|
|
||||||
- Include EHLO extensions in the send response
|
|
||||||
- fix sendMail function: callback should be optional
|
|
||||||
|
|
||||||
## 6.8.0 2022-09-28
|
|
||||||
|
|
||||||
- Add DNS timeout (huksley)
|
|
||||||
- add dns.REFUSED (lucagianfelici)
|
|
||||||
|
|
||||||
## 6.7.8 2022-08-11
|
|
||||||
|
|
||||||
- Allow to use multiple Reply-To addresses
|
|
||||||
|
|
||||||
## 6.7.7 2022-07-06
|
|
||||||
|
|
||||||
- Resolver fixes
|
|
||||||
|
|
||||||
## 6.7.5 2022-05-04
|
|
||||||
|
|
||||||
- No changes, pushing a new README to npmjs.org
|
|
||||||
|
|
||||||
## 6.7.4 2022-04-29
|
|
||||||
|
|
||||||
- Ensure compatibility with Node 18
|
|
||||||
- Replaced Travis with Github Actions
|
|
||||||
|
|
||||||
## 6.7.3 2022-03-21
|
|
||||||
|
|
||||||
- Typo fixes
|
|
||||||
- Added stale issue automation fir Github
|
|
||||||
- Add Infomaniak config to well known service (popod)
|
|
||||||
- Update Outlook/Hotmail host in well known services (popod)
|
|
||||||
- fix: DSN recipient gets ignored (KornKalle)
|
|
||||||
|
|
||||||
## 6.7.2 2021-11-26
|
|
||||||
|
|
||||||
- Fix proxies for account verification
|
|
||||||
|
|
||||||
## 6.7.1 2021-11-15
|
|
||||||
|
|
||||||
- fix verify on ses-transport (stanofsky)
|
|
||||||
|
|
||||||
## 6.7.0 2021-10-11
|
|
||||||
|
|
||||||
- Updated DNS resolving logic. If there are multiple responses for a A/AAAA record, then loop these randomly instead of only caching the first one
|
|
||||||
|
|
||||||
## 6.6.5 2021-09-23
|
|
||||||
|
|
||||||
- Replaced Object.values() and Array.flat() with polyfills to allow using Nodemailer in Node v6+
|
|
||||||
|
|
||||||
## 6.6.4 2021-09-22
|
|
||||||
|
|
||||||
- Better compatibility with IPv6-only SMTP hosts (oxzi)
|
|
||||||
- Fix ses verify for sdk v3 (hannesvdvreken)
|
|
||||||
- Added SECURITY.txt for contact info
|
|
||||||
|
|
||||||
## 6.6.3 2021-07-14
|
|
||||||
|
|
||||||
- Do not show passwords in SMTP transaction logs. All passwords used in logging are replaced by `"/* secret */"`
|
|
||||||
|
|
||||||
## 6.6.1 2021-05-23
|
|
||||||
|
|
||||||
- Fixed address formatting issue where newlines in an email address, if provided via address object, were not properly removed. Reported by tmazeika (#1289)
|
|
||||||
|
|
||||||
## 6.6.0 2021-04-28
|
|
||||||
|
|
||||||
- Added new option `newline` for MailComposer
|
|
||||||
- aws ses connection verification (Ognjen Jevremovic)
|
|
||||||
|
|
||||||
## 6.5.0 2021-02-26
|
|
||||||
|
|
||||||
- Pass through textEncoding to subnodes
|
|
||||||
- Added support for AWS SES v3 SDK
|
|
||||||
- Fixed tests
|
|
||||||
|
|
||||||
## 6.4.18 2021-02-11
|
|
||||||
|
|
||||||
- Updated README
|
|
||||||
|
|
||||||
## 6.4.17 2020-12-11
|
|
||||||
|
|
||||||
- Allow mixing attachments with caendar alternatives
|
|
||||||
|
|
||||||
## 6.4.16 2020-11-12
|
|
||||||
|
|
||||||
- Applied updated prettier formating rules
|
|
||||||
|
|
||||||
## 6.4.15 2020-11-06
|
|
||||||
|
|
||||||
- Minor changes in header key casing
|
|
||||||
|
|
||||||
## 6.4.14 2020-10-14
|
|
||||||
|
|
||||||
- Disabled postinstall script
|
|
||||||
|
|
||||||
## 6.4.13 2020-10-02
|
|
||||||
|
|
||||||
- Fix normalizeHeaderKey method for single node messages
|
|
||||||
|
|
||||||
## 6.4.12 2020-09-30
|
|
||||||
|
|
||||||
- Better handling of attachment filenames that include quote symbols
|
|
||||||
- Includes all information from the oath2 error response in the error message (Normal Gaussian) [1787f227]
|
|
||||||
|
|
||||||
## 6.4.11 2020-07-29
|
|
||||||
|
|
||||||
- Fixed escape sequence handling in address parsing
|
|
||||||
|
|
||||||
## 6.4.10 2020-06-17
|
|
||||||
|
|
||||||
- Fixed RFC822 output for MailComposer when using invalid content-type value. Mostly relevant if message attachments have stragne content-type values set.
|
|
||||||
|
|
||||||
## 6.4.7 2020-05-28
|
|
||||||
|
|
||||||
- Always set charset=utf-8 for Content-Type headers
|
|
||||||
- Catch error when using invalid crypto.sign input
|
|
||||||
|
|
||||||
## 6.4.6 2020-03-20
|
|
||||||
|
|
||||||
- fix: `requeueAttempts=n` should requeue `n` times (Patrick Malouin) [a27ed2f7]
|
|
||||||
|
|
||||||
## 6.4.4 2020-03-01
|
|
||||||
|
|
||||||
- Add `options.forceAuth` for SMTP (Patrick Malouin) [a27ed2f7]
|
|
||||||
|
|
||||||
## 6.4.3 2020-02-22
|
|
||||||
|
|
||||||
- Added an option to specify max number of requeues when connection closes unexpectedly (Igor Sechyn) [8a927f5a]
|
|
||||||
|
|
||||||
## 6.4.2 2019-12-11
|
|
||||||
|
|
||||||
- Fixed bug where array item was used with a potentially empty array
|
|
||||||
|
|
||||||
## 6.4.1 2019-12-07
|
|
||||||
|
|
||||||
- Fix processing server output with unterminated responses
|
|
||||||
|
|
||||||
## 6.4.0 2019-12-04
|
|
||||||
|
|
||||||
- Do not use auth if server does not advertise AUTH support [f419b09d]
|
|
||||||
- add dns.CONNREFUSED (Hiroyuki Okada) [5c4c8ca8]
|
|
||||||
|
|
||||||
## 6.3.1 2019-10-09
|
|
||||||
|
|
||||||
- Ignore "end" events because it might be "error" after it (dex4er) [72bade9]
|
|
||||||
- Set username and password on the connection proxy object correctly (UsamaAshraf) [250b1a8]
|
|
||||||
- Support more DNS errors (madarche) [2391aa4]
|
|
||||||
|
|
||||||
## 6.3.0 2019-07-14
|
|
||||||
|
|
||||||
- Added new option to pass a set of httpHeaders to be sent when fetching attachments. See [PR #1034](https://github.com/nodemailer/nodemailer/pull/1034)
|
|
||||||
|
|
||||||
## 6.2.1 2019-05-24
|
|
||||||
|
|
||||||
- No changes. It is the same as 6.2.0 that was accidentally published as 6.2.1 to npm
|
|
||||||
|
|
||||||
## 6.2.0 2019-05-24
|
|
||||||
|
|
||||||
- Added new option for addressparser: `flatten`. If true then ignores group names and returns a single list of all addresses
|
|
||||||
|
|
||||||
## 6.1.1 2019-04-20
|
|
||||||
|
|
||||||
- Fixed regression bug with missing smtp `authMethod` property
|
|
||||||
|
|
||||||
## 6.1.0 2019-04-06
|
|
||||||
|
|
||||||
- Added new message property `amp` for providing AMP4EMAIL content
|
|
||||||
|
|
||||||
## 6.0.0 2019-03-25
|
|
||||||
|
|
||||||
- SMTPConnection: use removeListener instead of removeAllListeners (xr0master) [ddc4af15]
|
|
||||||
Using removeListener should fix memory leak with Node.js streams
|
|
||||||
|
|
||||||
## 5.1.1 2019-01-09
|
|
||||||
|
|
||||||
- Added missing option argument for custom auth
|
|
||||||
|
|
||||||
## 5.1.0 2019-01-09
|
|
||||||
|
|
||||||
- Official support for custom authentication methods and examples (examples/custom-auth-async.js and examples/custom-auth-cb.js)
|
|
||||||
|
|
||||||
## 5.0.1 2019-01-09
|
|
||||||
|
|
||||||
- Fixed regression error to support Node versions lower than 6.11
|
|
||||||
- Added expiremental custom authentication support
|
|
||||||
|
|
||||||
## 5.0.0 2018-12-28
|
|
||||||
|
|
||||||
- Start using dns.resolve() instead of dns.lookup() for resolving SMTP hostnames. Might be breaking change on some environments so upgrade with care
|
|
||||||
- Show more logs for renewing OAuth2 tokens, previously it was not possible to see what actually failed
|
|
||||||
|
|
||||||
## 4.7.0 2018-11-19
|
|
||||||
|
|
||||||
- Cleaned up List-\* header generation
|
|
||||||
- Fixed 'full' return option for DSN (klaronix) [23b93a3b]
|
|
||||||
- Support promises `for mailcomposer.build()`
|
|
||||||
|
|
||||||
## 4.6.8 2018-08-15
|
|
||||||
|
|
||||||
- Use first IP address from DNS resolution when using a proxy (Limbozz) [d4ca847c]
|
|
||||||
- Return raw email from SES transport (gabegorelick) [3aa08967]
|
|
||||||
|
|
||||||
## 4.6.7 2018-06-15
|
|
||||||
|
|
||||||
- Added option `skipEncoding` to JSONTransport
|
|
||||||
|
|
||||||
## 4.6.6 2018-06-10
|
|
||||||
|
|
||||||
- Fixes mime encoded-word compatibility issue with invalid clients like Zimbra
|
|
||||||
|
|
||||||
## 4.6.5 2018-05-23
|
|
||||||
|
|
||||||
- Fixed broken DKIM stream in Node.js v10
|
|
||||||
- Updated error messages for SMTP responses to not include a newline
|
|
||||||
|
|
||||||
## 4.6.4 2018-03-31
|
|
||||||
|
|
||||||
- Readded logo author link to README that was accidentally removed a while ago
|
|
||||||
|
|
||||||
## 4.6.3 2018-03-13
|
|
||||||
|
|
||||||
- Removed unneeded dependency
|
|
||||||
|
|
||||||
## 4.6.2 2018-03-06
|
|
||||||
|
|
||||||
- When redirecting URL calls then do not include original POST content
|
|
||||||
|
|
||||||
## 4.6.1 2018-03-06
|
|
||||||
|
|
||||||
- Fixed Smtp connection freezing, when trying to send after close / quit (twawszczak) [73d3911c]
|
|
||||||
|
|
||||||
## 4.6.0 2018-02-22
|
|
||||||
|
|
||||||
- Support socks module v2 in addition to v1 [e228bcb2]
|
|
||||||
- Fixed invalid promise return value when using createTestAccount [5524e627]
|
|
||||||
- Allow using local addresses [8f6fa35f]
|
|
||||||
|
|
||||||
## 4.5.0 2018-02-21
|
|
||||||
|
|
||||||
- Added new message transport option `normalizeHeaderKey(key)=>normalizedKey` for custom header formatting
|
|
||||||
|
|
||||||
## 4.4.2 2018-01-20
|
|
||||||
|
|
||||||
- Added sponsors section to README
|
|
||||||
- enclose encodeURIComponent in try..catch to handle invalid urls
|
|
||||||
|
|
||||||
## 4.4.1 2017-12-08
|
|
||||||
|
|
||||||
- Better handling of unexpectedly dropping connections
|
|
||||||
|
|
||||||
## 4.4.0 2017-11-10
|
|
||||||
|
|
||||||
- Changed default behavior for attachment option contentTransferEncoding. If it is unset then base64 encoding is used for the attachment. If it is set to false then previous default applies (base64 for most, 7bit for text)
|
|
||||||
|
|
||||||
## 4.3.1 2017-10-25
|
|
||||||
|
|
||||||
- Fixed a confict with Electron.js where timers do not have unref method
|
|
||||||
|
|
||||||
## 4.3.0 2017-10-23
|
|
||||||
|
|
||||||
- Added new mail object method `mail.normalize(cb)` that should make creating HTTP API based transports much easier
|
|
||||||
|
|
||||||
## 4.2.0 2017-10-13
|
|
||||||
|
|
||||||
- Expose streamed messages size and timers in info response
|
|
||||||
|
|
||||||
## v4.1.3 2017-10-06
|
|
||||||
|
|
||||||
- Allow generating preview links without calling createTestAccount first
|
|
||||||
|
|
||||||
## v4.1.2 2017-10-03
|
|
||||||
|
|
||||||
- No actual changes. Needed to push updated README to npmjs
|
|
||||||
|
|
||||||
## v4.1.1 2017-09-25
|
|
||||||
|
|
||||||
- Fixed JSONTransport attachment handling
|
|
||||||
|
|
||||||
## v4.1.0 2017-08-28
|
|
||||||
|
|
||||||
- Added new methods `createTestAccount` and `getTestMessageUrl` to use autogenerated email accounts from https://Ethereal.email
|
|
||||||
|
|
||||||
## v4.0.1 2017-04-13
|
|
||||||
|
|
||||||
- Fixed issue with LMTP and STARTTLS
|
|
||||||
|
|
||||||
## v4.0.0 2017-04-06
|
|
||||||
|
|
||||||
- License changed from EUPLv1.1 to MIT
|
|
||||||
|
|
||||||
## v3.1.8 2017-03-21
|
|
||||||
|
|
||||||
- Fixed invalid List-\* header generation
|
|
||||||
|
|
||||||
## v3.1.7 2017-03-14
|
|
||||||
|
|
||||||
- Emit an error if STARTTLS ends with connection being closed
|
|
||||||
|
|
||||||
## v3.1.6 2017-03-14
|
|
||||||
|
|
||||||
- Expose last server response for smtpConnection
|
|
||||||
|
|
||||||
## v3.1.5 2017-03-08
|
|
||||||
|
|
||||||
- Fixed SES transport, added missing `response` value
|
|
||||||
|
|
||||||
## v3.1.4 2017-02-26
|
|
||||||
|
|
||||||
- Fixed DKIM calculation for empty body
|
|
||||||
- Ensure linebreak after message content. This fixes DKIM signatures for non-multipart messages where input did not end with a newline
|
|
||||||
|
|
||||||
## v3.1.3 2017-02-17
|
|
||||||
|
|
||||||
- Fixed missing `transport.verify()` methods for SES transport
|
|
||||||
|
|
||||||
## v3.1.2 2017-02-17
|
|
||||||
|
|
||||||
- Added missing error handlers for Sendmail, SES and Stream transports. If a messages contained an invalid URL as attachment then these transports threw an uncatched error
|
|
||||||
|
|
||||||
## v3.1.1 2017-02-13
|
|
||||||
|
|
||||||
- Fixed missing `transport.on('idle')` and `transport.isIdle()` methods for SES transports
|
|
||||||
|
|
||||||
## v3.1.0 2017-02-13
|
|
||||||
|
|
||||||
- Added built-in transport for AWS SES. [Docs](http://localhost:1313/transports/ses/)
|
|
||||||
- Updated stream transport to allow building JSON strings. [Docs](http://localhost:1313/transports/stream/#json-transport)
|
|
||||||
- Added new method _mail.resolveAll_ that fetches all attachments and such to be able to more easily build API-based transports
|
|
||||||
|
|
||||||
## v3.0.2 2017-02-04
|
|
||||||
|
|
||||||
- Fixed a bug with OAuth2 login where error callback was fired twice if getToken was not available.
|
|
||||||
|
|
||||||
## v3.0.1 2017-02-03
|
|
||||||
|
|
||||||
- Fixed a bug where Nodemailer threw an exception if `disableFileAccess` option was used
|
|
||||||
- Added FLOSS [exception declaration](FLOSS_EXCEPTIONS.md)
|
|
||||||
|
|
||||||
## v3.0.0 2017-01-31
|
|
||||||
|
|
||||||
- Initial version of Nodemailer 3
|
|
||||||
|
|
||||||
This update brings a lot of breaking changes:
|
|
||||||
|
|
||||||
- License changed from MIT to **EUPL-1.1**. This was possible as the new version of Nodemailer is a major rewrite. The features I don't have ownership for, were removed or reimplemented. If there's still some snippets in the code that have vague ownership then notify <mailto:andris@kreata.ee> about the conflicting code and I'll fix it.
|
|
||||||
- Requires **Node.js v6+**
|
|
||||||
- All **templating is gone**. It was too confusing to use and to be really universal a huge list of different renderers would be required. Nodemailer is about email, not about parsing different template syntaxes
|
|
||||||
- **No NTLM authentication**. It was too difficult to re-implement. If you still need it then it would be possible to introduce a pluggable SASL interface where you could load the NTLM module in your own code and pass it to Nodemailer. Currently this is not possible.
|
|
||||||
- **OAuth2 authentication** is built in and has a different [configuration](https://nodemailer.com/smtp/oauth2/). You can use both user (3LO) and service (2LO) accounts to generate access tokens from Nodemailer. Additionally there's a new feature to authenticate differently for every message – useful if your application sends on behalf of different users instead of a single sender.
|
|
||||||
- **Improved Calendaring**. Provide an ical file to Nodemailer to send out [calendar events](https://nodemailer.com/message/calendar-events/).
|
|
||||||
|
|
||||||
And also some non-breaking changes:
|
|
||||||
|
|
||||||
- All **dependencies were dropped**. There is exactly 0 dependencies needed to use Nodemailer. This brings the installation time of Nodemailer from NPM down to less than 2 seconds
|
|
||||||
- **Delivery status notifications** added to Nodemailer
|
|
||||||
- Improved and built-in **DKIM** signing of messages. Previously you needed an external module for this and it did quite a lousy job with larger messages
|
|
||||||
- **Stream transport** to return a RFC822 formatted message as a stream. Useful if you want to use Nodemailer as a preprocessor and not for actual delivery.
|
|
||||||
- **Sendmail** transport built-in, no need for external transport plugin
|
|
||||||
|
|
||||||
See [Nodemailer.com](https://nodemailer.com/) for full documentation
|
|
||||||
|
|
||||||
## 2.7.0 2016-12-08
|
|
||||||
|
|
||||||
- Bumped mailcomposer that generates encoded-words differently which might break some tests
|
|
||||||
|
|
||||||
## 2.6.0 2016-09-05
|
|
||||||
|
|
||||||
- Added new options disableFileAccess and disableUrlAccess
|
|
||||||
- Fixed envelope handling where cc/bcc fields were ignored in the envelope object
|
|
||||||
|
|
||||||
## 2.4.2 2016-05-25
|
|
||||||
|
|
||||||
- Removed shrinkwrap file. Seemed to cause more trouble than help
|
|
||||||
|
|
||||||
## 2.4.1 2016-05-12
|
|
||||||
|
|
||||||
- Fixed outdated shrinkwrap file
|
|
||||||
|
|
||||||
## 2.4.0 2016-05-11
|
|
||||||
|
|
||||||
- Bumped mailcomposer module to allow using `false` as attachment filename (suppresses filename usage)
|
|
||||||
- Added NTLM authentication support
|
|
||||||
|
|
||||||
## 2.3.2 2016-04-11
|
|
||||||
|
|
||||||
- Bumped smtp transport modules to get newest smtp-connection that fixes SMTPUTF8 support for internationalized email addresses
|
|
||||||
|
|
||||||
## 2.3.1 2016-04-08
|
|
||||||
|
|
||||||
- Bumped mailcomposer to have better support for message/822 attachments
|
|
||||||
|
|
||||||
## 2.3.0 2016-03-03
|
|
||||||
|
|
||||||
- Fixed a bug with attachment filename that contains mixed unicode and dashes
|
|
||||||
- Added built-in support for proxies by providing a new SMTP option `proxy` that takes a proxy configuration url as its value
|
|
||||||
- Added option `transport` to dynamically load transport plugins
|
|
||||||
- Do not require globally installed grunt-cli
|
|
||||||
|
|
||||||
## 2.2.1 2016-02-20
|
|
||||||
|
|
||||||
- Fixed a bug in SMTP requireTLS option that was broken
|
|
||||||
|
|
||||||
## 2.2.0 2016-02-18
|
|
||||||
|
|
||||||
- Removed the need to use `clone` dependency
|
|
||||||
- Added new method `verify` to check SMTP configuration
|
|
||||||
- Direct transport uses STARTTLS by default, fallbacks to plaintext if STARTTLS fails
|
|
||||||
- Added new message option `list` for setting List-\* headers
|
|
||||||
- Add simple proxy support with `getSocket` method
|
|
||||||
- Added new message option `textEncoding`. If `textEncoding` is not set then detect best encoding automatically
|
|
||||||
- Added new message option `icalEvent` to embed iCalendar events. Example [here](examples/ical-event.js)
|
|
||||||
- Added new attachment option `raw` to use prepared MIME contents instead of generating a new one. This might be useful when you want to handcraft some parts of the message yourself, for example if you want to inject a PGP encrypted message as the contents of a MIME node
|
|
||||||
- Added new message option `raw` to use an existing MIME message instead of generating a new one
|
|
||||||
|
|
||||||
## 2.1.0 2016-02-01
|
|
||||||
|
|
||||||
Republishing 2.1.0-rc.1 as stable. To recap, here's the notable changes between v2.0 and v2.1:
|
|
||||||
|
|
||||||
- Implemented templating support. You can either use a simple built-in renderer or some external advanced renderer, eg. [node-email-templates](https://github.com/niftylettuce/node-email-templates). Templating [docs](http://nodemailer.com/2-0-0-beta/templating/).
|
|
||||||
- Updated smtp-pool to emit 'idle' events in order to handle message queue more effectively
|
|
||||||
- Updated custom header handling, works everywhere the same now, no differences between adding custom headers to the message or to an attachment
|
|
||||||
|
|
||||||
## 2.1.0-rc.1 2016-01-25
|
|
||||||
|
|
||||||
Sneaked in some new features even though it is already rc
|
|
||||||
|
|
||||||
- If a SMTP pool is closed while there are still messages in a queue, the message callbacks are invoked with an error
|
|
||||||
- In case of SMTP pool the transporter emits 'idle' when there is a free connection slot available
|
|
||||||
- Added method `isIdle()` that checks if a pool has still some free connection slots available
|
|
||||||
|
|
||||||
## 2.1.0-rc.0 2016-01-20
|
|
||||||
|
|
||||||
- Bumped dependency versions
|
|
||||||
|
|
||||||
## 2.1.0-beta.3 2016-01-20
|
|
||||||
|
|
||||||
- Added support for node-email-templates templating in addition to the built-in renderer
|
|
||||||
|
|
||||||
## 2.1.0-beta.2 2016-01-20
|
|
||||||
|
|
||||||
- Implemented simple templating feature
|
|
||||||
|
|
||||||
## 2.1.0-beta.1 2016-01-20
|
|
||||||
|
|
||||||
- Allow using prepared header values that are not folded or encoded by Nodemailer
|
|
||||||
|
|
||||||
## 2.1.0-beta.0 2016-01-20
|
|
||||||
|
|
||||||
- Use the same header custom structure for message root, attachments and alternatives
|
|
||||||
- Ensure that Message-Id exists when accessing message
|
|
||||||
- Allow using array values for custom headers (inserts every value in its own row)
|
|
||||||
|
|
||||||
## 2.0.0 2016-01-11
|
|
||||||
|
|
||||||
- Released rc.2 as stable
|
|
||||||
|
|
||||||
## 2.0.0-rc.2 2016-01-04
|
|
||||||
|
|
||||||
- Locked dependencies
|
|
||||||
|
|
||||||
## 2.0.0-beta.2 2016-01-04
|
|
||||||
|
|
||||||
- Updated documentation to reflect changes with SMTP handling
|
|
||||||
- Use beta versions for smtp/pool/direct transports
|
|
||||||
- Updated logging
|
|
||||||
|
|
||||||
## 2.0.0-beta.1 2016-01-03
|
|
||||||
|
|
||||||
- Use bunyan compatible logger instead of the emit('log') style
|
|
||||||
- Outsourced some reusable methods to nodemailer-shared
|
|
||||||
- Support setting direct/smtp/pool with the default configuration
|
|
||||||
|
|
||||||
## 2.0.0-beta.0 2015-12-31
|
|
||||||
|
|
||||||
- Stream errors are not silently swallowed
|
|
||||||
- Do not use format=flowed
|
|
||||||
- Use nodemailer-fetch to fetch URL streams
|
|
||||||
- jshint replaced by eslint
|
|
||||||
|
|
||||||
## v1.11.0 2015-12-28
|
|
||||||
|
|
||||||
Allow connection url based SMTP configurations
|
|
||||||
|
|
||||||
## v1.10.0 2015-11-13
|
|
||||||
|
|
||||||
Added `defaults` argument for `createTransport` to predefine commonn values (eg. `from` address)
|
|
||||||
|
|
||||||
## v1.9.0 2015-11-09
|
|
||||||
|
|
||||||
Returns a Promise for `sendMail` if callback is not defined
|
|
||||||
|
|
||||||
## v1.8.0 2015-10-08
|
|
||||||
|
|
||||||
Added priority option (high, normal, low) for setting Importance header
|
|
||||||
|
|
||||||
## v1.7.0 2015-10-06
|
|
||||||
|
|
||||||
Replaced hyperquest with needle. Fixes issues with compressed data and redirects
|
|
||||||
|
|
||||||
## v1.6.0 2015-10-05
|
|
||||||
|
|
||||||
Maintenance release. Bumped dependencies to get support for unicode filenames for QQ webmail and to support emoji in filenames
|
|
||||||
|
|
||||||
## v1.5.0 2015-09-24
|
|
||||||
|
|
||||||
Use mailcomposer instead of built in solution to generate message sources. Bumped libmime gives better quoted-printable handling.
|
|
||||||
|
|
||||||
## v1.4.0 2015-06-27
|
|
||||||
|
|
||||||
Added new message option `watchHtml` to specify Apple Watch specific HTML part of the message. See [this post](https://litmus.com/blog/how-to-send-hidden-version-email-apple-watch) for details
|
|
||||||
|
|
||||||
## v1.3.4 2015-04-25
|
|
||||||
|
|
||||||
Maintenance release, bumped buildmail version to get fixed format=flowed handling
|
|
||||||
|
|
||||||
## v1.3.3 2015-04-25
|
|
||||||
|
|
||||||
Maintenance release, bumped dependencies
|
|
||||||
|
|
||||||
## v1.3.2 2015-03-09
|
|
||||||
|
|
||||||
Maintenance release, upgraded dependencies. Replaced simplesmtp based tests with smtp-server based ones.
|
|
||||||
|
|
||||||
## v1.3.0 2014-09-12
|
|
||||||
|
|
||||||
Maintenance release, upgrades buildmail and libmime. Allows using functions as transform plugins and fixes issue with unicode filenames in Gmail.
|
|
||||||
|
|
||||||
## v1.2.2 2014-09-05
|
|
||||||
|
|
||||||
Proper handling of data uris as attachments. Attachment `path` property can also be defined as a data uri, not just regular url or file path.
|
|
||||||
|
|
||||||
## v1.2.1 2014-08-21
|
|
||||||
|
|
||||||
Bumped libmime and mailbuild versions to properly handle filenames with spaces (short ascii only filenames with spaces were left unquoted).
|
|
||||||
|
|
||||||
## v1.2.0 2014-08-18
|
|
||||||
|
|
||||||
Allow using encoded strings as attachments. Added new property `encoding` which defines the encoding used for a `content` string. If encoding is set, the content value is converted to a Buffer value using the defined encoding before usage. Useful for including binary attachemnts in JSON formatted email objects.
|
|
||||||
|
|
||||||
## v1.1.2 2014-08-18
|
|
||||||
|
|
||||||
Return deprecatin error for v0.x style configuration
|
|
||||||
|
|
||||||
## v1.1.1 2014-07-30
|
|
||||||
|
|
||||||
Bumped nodemailer-direct-transport dependency. Updated version includes a bugfix for Stream nodes handling. Important only if use direct-transport with Streams (not file paths or urls) as attachment content.
|
|
||||||
|
|
||||||
## v1.1.0 2014-07-29
|
|
||||||
|
|
||||||
Added new method `resolveContent()` to get the html/text/attachment content as a String or Buffer.
|
|
||||||
|
|
||||||
## v1.0.4 2014-07-23
|
|
||||||
|
|
||||||
Bugfix release. HTML node was instered twice if the message consisted of a HTML content (but no text content) + at least one attachment with CID + at least one attachment without CID. In this case the HTML node was inserted both to the root level multipart/mixed section and to the multipart/related sub section
|
|
||||||
|
|
||||||
## v1.0.3 2014-07-16
|
|
||||||
|
|
||||||
Fixed a bug where Nodemailer crashed if the message content type was multipart/related
|
|
||||||
|
|
||||||
## v1.0.2 2014-07-16
|
|
||||||
|
|
||||||
Upgraded nodemailer-smtp-transport to 0.1.11\. The docs state that for SSL you should use 'secure' option but the underlying smtp-connection module used 'secureConnection' for this purpose. Fixed smpt-connection to match the docs.
|
|
||||||
|
|
||||||
## v1.0.1 2014-07-15
|
|
||||||
|
|
||||||
Implemented missing #close method that is passed to the underlying transport object. Required by the smtp pool.
|
|
||||||
|
|
||||||
## v1.0.0 2014-07-15
|
|
||||||
|
|
||||||
Total rewrite. See migration guide here: <http://www.andrisreinman.com/nodemailer-v1-0/#migrationguide>
|
|
||||||
|
|
||||||
## v0.7.1 2014-07-09
|
|
||||||
|
|
||||||
- Upgraded aws-sdk to 2.0.5
|
|
||||||
|
|
||||||
## v0.7.0 2014-06-17
|
|
||||||
|
|
||||||
- Bumped version to v0.7.0
|
|
||||||
- Fix AWS-SES usage [5b6bc144]
|
|
||||||
- Replace current SES with new SES using AWS-SDK (Elanorr) [c79d797a]
|
|
||||||
- Updated README.md about Node Email Templates (niftylettuce) [e52bef81]
|
|
||||||
|
|
||||||
## v0.6.5 2014-05-15
|
|
||||||
|
|
||||||
- Bumped version to v0.6.5
|
|
||||||
- Use tildes instead of carets for dependency listing [5296ce41]
|
|
||||||
- Allow clients to set a custom identityString (venables) [5373287d]
|
|
||||||
- bugfix (adding "-i" to sendmail command line for each new mail) by copying this.args (vrodic) [05a8a9a3]
|
|
||||||
- update copyright (gdi2290) [3a6cba3a]
|
|
||||||
|
|
||||||
## v0.6.4 2014-05-13
|
|
||||||
|
|
||||||
- Bumped version to v0.6.4
|
|
||||||
- added npmignore, bumped dependencies [21bddcd9]
|
|
||||||
- Add AOL to well-known services (msouce) [da7dd3b7]
|
|
||||||
|
|
||||||
## v0.6.3 2014-04-16
|
|
||||||
|
|
||||||
- Bumped version to v0.6.3
|
|
||||||
- Upgraded simplesmtp dependency [dd367f59]
|
|
||||||
|
|
||||||
## v0.6.2 2014-04-09
|
|
||||||
|
|
||||||
- Bumped version to v0.6.2
|
|
||||||
- Added error option to Stub transport [c423acad]
|
|
||||||
- Use SVG npm badge (t3chnoboy) [677117b7]
|
|
||||||
- add SendCloud to well known services (haio) [43c358e0]
|
|
||||||
- High-res build-passing and NPM module badges (sahat) [9fdc37cd]
|
|
||||||
|
|
||||||
## v0.6.1 2014-01-26
|
|
||||||
|
|
||||||
- Bumped version to v0.6.1
|
|
||||||
- Do not throw on multiple errors from sendmail command [c6e2cd12]
|
|
||||||
- Do not require callback for pickup, fixes #238 [93eb3214]
|
|
||||||
- Added AWSSecurityToken information to README, fixes #235 [58e921d1]
|
|
||||||
- Added Nodemailer logo [06b7d1a8]
|
|
||||||
|
|
||||||
## v0.6.0 2013-12-30
|
|
||||||
|
|
||||||
- Bumped version to v0.6.0
|
|
||||||
- Allow defining custom transport methods [ec5b48ce]
|
|
||||||
- Return messageId with responseObject for all built in transport methods [74445cec]
|
|
||||||
- Bumped dependency versions for mailcomposer and readable-stream [9a034c34]
|
|
||||||
- Changed pickup argument name to 'directory' [01c3ea53]
|
|
||||||
- Added support for IIS pickup directory with PICKUP transport (philipproplesch) [36940b59..360a2878]
|
|
||||||
- Applied common styles [9e93a409]
|
|
||||||
- Updated readme [c78075e7]
|
|
||||||
|
|
||||||
## v0.5.15 2013-12-13
|
|
||||||
|
|
||||||
- bumped version to v0.5.15
|
|
||||||
- Updated README, added global options info for setting uo transports [554bb0e5]
|
|
||||||
- Resolve public hostname, if resolveHostname property for a transport object is set to `true` [9023a6e1..4c66b819]
|
|
||||||
|
|
||||||
## v0.5.14 2013-12-05
|
|
||||||
|
|
||||||
- bumped version to v0.5.14
|
|
||||||
- Expose status for direct messages [f0312df6]
|
|
||||||
- Allow to skip the X-Mailer header if xMailer value is set to 'false' [f2c20a68]
|
|
||||||
|
|
||||||
## v0.5.13 2013-12-03
|
|
||||||
|
|
||||||
- bumped version to v0.5.13
|
|
||||||
- Use the name property from the transport object to use for the domain part of message-id values (1598eee9)
|
|
||||||
|
|
||||||
## v0.5.12 2013-12-02
|
|
||||||
|
|
||||||
- bumped version to v0.5.12
|
|
||||||
- Expose transport method and transport module version if available [a495106e]
|
|
||||||
- Added 'he' module instead of using custom html entity decoding [c197d102]
|
|
||||||
- Added xMailer property for transport configuration object to override X-Mailer value [e8733a61]
|
|
||||||
- Updated README, added description for 'mail' method [e1f5f3a6]
|
|
||||||
|
|
||||||
## v0.5.11 2013-11-28
|
|
||||||
|
|
||||||
- bumped version to v0.5.11
|
|
||||||
- Updated mailcomposer version. Replaces ent with he [6a45b790e]
|
|
||||||
|
|
||||||
## v0.5.10 2013-11-26
|
|
||||||
|
|
||||||
- bumped version to v0.5.10
|
|
||||||
- added shorthand function mail() for direct transport type [88129bd7]
|
|
||||||
- minor tweaks and typo fixes [f797409e..ceac0ca4]
|
|
||||||
|
|
||||||
## v0.5.9 2013-11-25
|
|
||||||
|
|
||||||
- bumped version to v0.5.9
|
|
||||||
- Update for 'direct' handling [77b84e2f]
|
|
||||||
- do not require callback to be provided for 'direct' type [ec51c79f]
|
|
||||||
|
|
||||||
## v0.5.8 2013-11-22
|
|
||||||
|
|
||||||
- bumped version to v0.5.8
|
|
||||||
- Added support for 'direct' transport [826f226d..0dbbcbbc]
|
|
||||||
|
|
||||||
## v0.5.7 2013-11-18
|
|
||||||
|
|
||||||
- bumped version to v0.5.7
|
|
||||||
- Replace \r\n by \n in Sendmail transport (rolftimmermans) [fed2089e..616ec90c] A lot of sendmail implementations choke on \r\n newlines and require \n This commit addresses this by transforming all \r\n sequences passed to the sendmail command with \n
|
|
||||||
|
|
||||||
## v0.5.6 2013-11-15
|
|
||||||
|
|
||||||
- bumped version to v0.5.6
|
|
||||||
- Upgraded mailcomposer dependency to 0.2.4 [e5ff9c40]
|
|
||||||
- Removed noCR option [e810d1b8]
|
|
||||||
- Update wellknown.js, added FastMail (k-j-kleist) [cf930f6d]
|
|
||||||
|
|
||||||
## v0.5.5 2013-10-30
|
|
||||||
|
|
||||||
- bumped version to v0.5.5
|
|
||||||
- Updated mailcomposer dependnecy version to 0.2.3
|
|
||||||
- Remove legacy code - node v0.4 is not supported anymore anyway
|
|
||||||
- Use hostname (autodetected or from the options.name property) for Message-Id instead of "Nodemailer" (helps a bit when messages are identified as spam)
|
|
||||||
- Added maxMessages info to README
|
|
||||||
|
|
||||||
## v0.5.4 2013-10-29
|
|
||||||
|
|
||||||
- bumped version to v0.5.4
|
|
||||||
- added "use strict" statements
|
|
||||||
- Added DSN info to README
|
|
||||||
- add support for QQ enterprise email (coderhaoxin)
|
|
||||||
- Add a Bitdeli Badge to README
|
|
||||||
- DSN options Passthrought into simplesmtp. (irvinzz)
|
|
||||||
|
|
||||||
## v0.5.3 2013-10-03
|
|
||||||
|
|
||||||
- bumped version v0.5.3
|
|
||||||
- Using a stub transport to prevent sendmail from being called during a test. (jsdevel)
|
|
||||||
- closes #78: sendmail transport does not work correctly on Unix machines. (jsdevel)
|
|
||||||
- Updated PaaS Support list to include Modulus. (fiveisprime)
|
|
||||||
- Translate self closing break tags to newline (kosmasgiannis)
|
|
||||||
- fix typos (aeosynth)
|
|
||||||
|
|
||||||
## v0.5.2 2013-07-25
|
|
||||||
|
|
||||||
- bumped version v0.5.2
|
|
||||||
- Merge pull request #177 from MrSwitch/master Fixing Amazon SES, fatal error caused by bad connection
|
|
||||||
76
lib/node/node_modules/nodemailer/CODE_OF_CONDUCT.md
generated
vendored
76
lib/node/node_modules/nodemailer/CODE_OF_CONDUCT.md
generated
vendored
|
|
@ -1,76 +0,0 @@
|
||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
In the interest of fostering an open and welcoming environment, we as
|
|
||||||
contributors and maintainers pledge to making participation in our project and
|
|
||||||
our community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
|
||||||
level of experience, education, socio-economic status, nationality, personal
|
|
||||||
appearance, race, religion, or sexual identity and orientation.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to creating a positive environment
|
|
||||||
include:
|
|
||||||
|
|
||||||
* Using welcoming and inclusive language
|
|
||||||
* Being respectful of differing viewpoints and experiences
|
|
||||||
* Gracefully accepting constructive criticism
|
|
||||||
* Focusing on what is best for the community
|
|
||||||
* Showing empathy towards other community members
|
|
||||||
|
|
||||||
Examples of unacceptable behavior by participants include:
|
|
||||||
|
|
||||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
|
||||||
advances
|
|
||||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
|
||||||
* Public or private harassment
|
|
||||||
* Publishing others' private information, such as a physical or electronic
|
|
||||||
address, without explicit permission
|
|
||||||
* Other conduct which could reasonably be considered inappropriate in a
|
|
||||||
professional setting
|
|
||||||
|
|
||||||
## Our Responsibilities
|
|
||||||
|
|
||||||
Project maintainers are responsible for clarifying the standards of acceptable
|
|
||||||
behavior and are expected to take appropriate and fair corrective action in
|
|
||||||
response to any instances of unacceptable behavior.
|
|
||||||
|
|
||||||
Project maintainers have the right and responsibility to remove, edit, or
|
|
||||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
|
||||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
|
||||||
permanently any contributor for other behaviors that they deem inappropriate,
|
|
||||||
threatening, offensive, or harmful.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies both within project spaces and in public spaces
|
|
||||||
when an individual is representing the project or its community. Examples of
|
|
||||||
representing a project or community include using an official project e-mail
|
|
||||||
address, posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event. Representation of a project may be
|
|
||||||
further defined and clarified by project maintainers.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported by contacting the project team at info@nodemailer.com. All
|
|
||||||
complaints will be reviewed and investigated and will result in a response that
|
|
||||||
is deemed necessary and appropriate to the circumstances. The project team is
|
|
||||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
|
||||||
Further details of specific enforcement policies may be posted separately.
|
|
||||||
|
|
||||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
|
||||||
faith may face temporary or permanent repercussions as determined by other
|
|
||||||
members of the project's leadership.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
|
||||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see
|
|
||||||
https://www.contributor-covenant.org/faq
|
|
||||||
16
lib/node/node_modules/nodemailer/LICENSE
generated
vendored
16
lib/node/node_modules/nodemailer/LICENSE
generated
vendored
|
|
@ -1,16 +0,0 @@
|
||||||
Copyright (c) 2011-2023 Andris Reinman
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
86
lib/node/node_modules/nodemailer/README.md
generated
vendored
86
lib/node/node_modules/nodemailer/README.md
generated
vendored
|
|
@ -1,86 +0,0 @@
|
||||||
# Nodemailer
|
|
||||||
|
|
||||||
[](https://nodemailer.com/about/)
|
|
||||||
|
|
||||||
Send emails from Node.js – easy as cake! 🍰✉️
|
|
||||||
|
|
||||||
[](https://nodemailer.com/about/)
|
|
||||||
|
|
||||||
See [nodemailer.com](https://nodemailer.com/) for documentation and terms.
|
|
||||||
|
|
||||||
> [!TIP]
|
|
||||||
> Check out **[EmailEngine](https://emailengine.app/?utm_source=github-nodemailer&utm_campaign=nodemailer&utm_medium=readme-link)** – a self-hosted email gateway that allows making **REST requests against IMAP and SMTP servers**. EmailEngine also sends webhooks whenever something changes on the registered accounts.\
|
|
||||||
> \
|
|
||||||
> Using the email accounts registered with EmailEngine, you can receive and [send emails](https://emailengine.app/sending-emails?utm_source=github-nodemailer&utm_campaign=nodemailer&utm_medium=readme-link). EmailEngine supports OAuth2, delayed sends, opens and clicks tracking, bounce detection, etc. All on top of regular email accounts without an external MTA service.
|
|
||||||
|
|
||||||
## Having an issue?
|
|
||||||
|
|
||||||
#### First review the docs
|
|
||||||
|
|
||||||
Documentation for Nodemailer can be found at [nodemailer.com](https://nodemailer.com/about/).
|
|
||||||
|
|
||||||
#### Nodemailer throws a SyntaxError for "..."
|
|
||||||
|
|
||||||
You are using an older Node.js version than v6.0. Upgrade Node.js to get support for the spread operator. Nodemailer supports all Node.js versions starting from Node.js@v6.0.0.
|
|
||||||
|
|
||||||
#### I'm having issues with Gmail
|
|
||||||
|
|
||||||
Gmail either works well, or it does not work at all. It is probably easier to switch to an alternative service instead of fixing issues with Gmail. If Gmail does not work for you, then don't use it. Read more about it [here](https://nodemailer.com/usage/using-gmail/).
|
|
||||||
|
|
||||||
#### I get ETIMEDOUT errors
|
|
||||||
|
|
||||||
Check your firewall settings. Timeout usually occurs when you try to open a connection to a firewalled port either on the server or on your machine. Some ISPs also block email ports to prevent spamming.
|
|
||||||
|
|
||||||
#### Nodemailer works on one machine but not in another
|
|
||||||
|
|
||||||
It's either a firewall issue, or your SMTP server blocks authentication attempts from some servers.
|
|
||||||
|
|
||||||
#### I get TLS errors
|
|
||||||
|
|
||||||
- If you are running the code on your machine, check your antivirus settings. Antiviruses often mess around with email ports usage. Node.js might not recognize the MITM cert your antivirus is using.
|
|
||||||
- Latest Node versions allow only TLS versions 1.2 and higher. Some servers might still use TLS 1.1 or lower. Check Node.js docs on how to get correct TLS support for your app. You can change this with [tls.minVersion](https://nodejs.org/dist/latest-v16.x/docs/api/tls.html#tls_tls_createsecurecontext_options) option
|
|
||||||
- You might have the wrong value for the `secure` option. This should be set to `true` only for port 465. For every other port, it should be `false`. Setting it to `false` does not mean that Nodemailer would not use TLS. Nodemailer would still try to upgrade the connection to use TLS if the server supports it.
|
|
||||||
- Older Node versions do not fully support the certificate chain of the newest Let's Encrypt certificates. Either set [tls.rejectUnauthorized](https://nodejs.org/dist/latest-v16.x/docs/api/tls.html#tlsconnectoptions-callback) to `false` to skip chain verification or upgrade your Node version
|
|
||||||
|
|
||||||
```
|
|
||||||
let configOptions = {
|
|
||||||
host: "smtp.example.com",
|
|
||||||
port: 587,
|
|
||||||
tls: {
|
|
||||||
rejectUnauthorized: true,
|
|
||||||
minVersion: "TLSv1.2"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### I have issues with DNS / hosts file
|
|
||||||
|
|
||||||
Node.js uses [c-ares](https://nodejs.org/en/docs/meta/topics/dependencies/#c-ares) to resolve domain names, not the DNS library provided by the system, so if you have some custom DNS routing set up, it might be ignored. Nodemailer runs [dns.resolve4()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnsresolve4hostname-options-callback) and [dns.resolve6()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnsresolve6hostname-options-callback) to resolve hostname into an IP address. If both calls fail, then Nodemailer will fall back to [dns.lookup()](https://nodejs.org/dist/latest-v16.x/docs/api/dns.html#dnslookuphostname-options-callback). If this does not work for you, you can hard code the IP address into the configuration like shown below. In that case, Nodemailer would not perform any DNS lookups.
|
|
||||||
|
|
||||||
```
|
|
||||||
let configOptions = {
|
|
||||||
host: "1.2.3.4",
|
|
||||||
port: 465,
|
|
||||||
secure: true,
|
|
||||||
tls: {
|
|
||||||
// must provide server name, otherwise TLS certificate check will fail
|
|
||||||
servername: "example.com"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### I have an issue with TypeScript types
|
|
||||||
|
|
||||||
Nodemailer has official support for Node.js only. For anything related to TypeScript, you need to directly contact the authors of the [type definitions](https://www.npmjs.com/package/@types/nodemailer).
|
|
||||||
|
|
||||||
#### I have a different problem
|
|
||||||
|
|
||||||
If you are having issues with Nodemailer, then the best way to find help would be [Stack Overflow](https://stackoverflow.com/search?q=nodemailer) or revisit the [docs](https://nodemailer.com/about/).
|
|
||||||
|
|
||||||
### License
|
|
||||||
|
|
||||||
Nodemailer is licensed under the **MIT No Attribution license**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
The Nodemailer logo was designed by [Sven Kristjansen](https://www.behance.net/kristjansen).
|
|
||||||
22
lib/node/node_modules/nodemailer/SECURITY.txt
generated
vendored
22
lib/node/node_modules/nodemailer/SECURITY.txt
generated
vendored
|
|
@ -1,22 +0,0 @@
|
||||||
-----BEGIN PGP SIGNED MESSAGE-----
|
|
||||||
Hash: SHA256
|
|
||||||
|
|
||||||
Contact: mailto:andris@reinman.eu
|
|
||||||
Encryption: https://keys.openpgp.org/vks/v1/by-fingerprint/5D952A46E1D8C931F6364E01DC6C83F4D584D364
|
|
||||||
Preferred-Languages: en, et
|
|
||||||
-----BEGIN PGP SIGNATURE-----
|
|
||||||
|
|
||||||
iQIzBAEBCAAdFiEEXZUqRuHYyTH2Nk4B3GyD9NWE02QFAmFDnUgACgkQ3GyD9NWE
|
|
||||||
02RqUA/+MM3afmRYq874C7wp+uN6dTMCvUX5g5zqBZ2yKpFr46L+PYvM7o8TMm5h
|
|
||||||
hmLT2I1zZmi+xezOL3zHFizaw0tKkZIz9cWl3Jrgs0FLp0zOsSz1xucp9Q2tYM/Q
|
|
||||||
vbiP6ys0gbim4tkDGRmZOEiO23s0BuRnmHt7vZg210O+D105Yd8/Ohzbj6PSLBO5
|
|
||||||
W1tA7Xw5t0FQ14NNH5+MKyDIKoCX12n0FmrC6qLTXeojf291UgKhCUPda3LIGTmx
|
|
||||||
mTXz0y68149Mw+JikRCYP8HfGRY9eA4XZrYXF7Bl2T9OJpKD3JAH+69P3xBw19Gn
|
|
||||||
Csaw3twu8P1bxoVGjY4KRrBOp68W8TwZYjWVWbqY6oV8hb/JfrMxa+kaSxRuloFs
|
|
||||||
oL6+phrDSPTWdOj2LlEDBJbPOMeDFzIlsBBcJ/JHCEHTvlHl7LoWr3YuWce9PUwl
|
|
||||||
4r3JUovvaeuJxLgC0vu3WCB3Jeocsl3SreqNkrVc1IjvkSomn3YGm5nCNAd/2F0V
|
|
||||||
exCGRk/8wbkSjAY38GwQ8K/VuFsefWN3L9sVwIMAMu88KFCAN+GzVFiwvyIXehF5
|
|
||||||
eogP9mIXzdQ5YReQjUjApOzGz54XnDyv9RJ3sdvMHosLP+IOg+0q5t9agWv6aqSR
|
|
||||||
2HzCpiQnH/gmM5NS0AU4Koq/L7IBeLu1B8+61/+BiHgZJJmPdgU=
|
|
||||||
=BUZr
|
|
||||||
-----END PGP SIGNATURE-----
|
|
||||||
313
lib/node/node_modules/nodemailer/lib/addressparser/index.js
generated
vendored
313
lib/node/node_modules/nodemailer/lib/addressparser/index.js
generated
vendored
|
|
@ -1,313 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts tokens for a single address into an address object
|
|
||||||
*
|
|
||||||
* @param {Array} tokens Tokens object
|
|
||||||
* @return {Object} Address object
|
|
||||||
*/
|
|
||||||
function _handleAddress(tokens) {
|
|
||||||
let token;
|
|
||||||
let isGroup = false;
|
|
||||||
let state = 'text';
|
|
||||||
let address;
|
|
||||||
let addresses = [];
|
|
||||||
let data = {
|
|
||||||
address: [],
|
|
||||||
comment: [],
|
|
||||||
group: [],
|
|
||||||
text: []
|
|
||||||
};
|
|
||||||
let i;
|
|
||||||
let len;
|
|
||||||
|
|
||||||
// Filter out <addresses>, (comments) and regular text
|
|
||||||
for (i = 0, len = tokens.length; i < len; i++) {
|
|
||||||
token = tokens[i];
|
|
||||||
if (token.type === 'operator') {
|
|
||||||
switch (token.value) {
|
|
||||||
case '<':
|
|
||||||
state = 'address';
|
|
||||||
break;
|
|
||||||
case '(':
|
|
||||||
state = 'comment';
|
|
||||||
break;
|
|
||||||
case ':':
|
|
||||||
state = 'group';
|
|
||||||
isGroup = true;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
state = 'text';
|
|
||||||
}
|
|
||||||
} 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
|
|
||||||
token.value = token.value.replace(/^[^<]*<\s*/, '');
|
|
||||||
}
|
|
||||||
data[state].push(token.value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there is no text but a comment, replace the two
|
|
||||||
if (!data.text.length && data.comment.length) {
|
|
||||||
data.text = data.comment;
|
|
||||||
data.comment = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isGroup) {
|
|
||||||
// http://tools.ietf.org/html/rfc2822#appendix-A.1.3
|
|
||||||
data.text = data.text.join(' ');
|
|
||||||
addresses.push({
|
|
||||||
name: data.text || (address && address.name),
|
|
||||||
group: data.group.length ? addressparser(data.group.join(',')) : []
|
|
||||||
});
|
|
||||||
} 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--) {
|
|
||||||
if (data.text[i].match(/^[^@\s]+@[^@\s]+$/)) {
|
|
||||||
data.address = data.text.splice(i, 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let _regexHandler = function (address) {
|
|
||||||
if (!data.address.length) {
|
|
||||||
data.address = [address.trim()];
|
|
||||||
return ' ';
|
|
||||||
} else {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// still no address
|
|
||||||
if (!data.address.length) {
|
|
||||||
for (i = data.text.length - 1; i >= 0; 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) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If there's still is no text but a comment exixts, 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
|
|
||||||
if (data.address.length > 1) {
|
|
||||||
data.text = data.text.concat(data.address.splice(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Join values with spaces
|
|
||||||
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 || ''
|
|
||||||
};
|
|
||||||
|
|
||||||
if (address.address === address.name) {
|
|
||||||
if ((address.address || '').match(/@/)) {
|
|
||||||
address.name = '';
|
|
||||||
} else {
|
|
||||||
address.address = '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses.push(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a Tokenizer object for tokenizing address field strings
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {String} str Address field string
|
|
||||||
*/
|
|
||||||
class Tokenizer {
|
|
||||||
constructor(str) {
|
|
||||||
this.str = (str || '').toString();
|
|
||||||
this.operatorCurrent = '';
|
|
||||||
this.operatorExpecting = '';
|
|
||||||
this.node = null;
|
|
||||||
this.escaped = false;
|
|
||||||
|
|
||||||
this.list = [];
|
|
||||||
/**
|
|
||||||
* Operator tokens and which tokens are expected to end the sequence
|
|
||||||
*/
|
|
||||||
this.operators = {
|
|
||||||
'"': '"',
|
|
||||||
'(': ')',
|
|
||||||
'<': '>',
|
|
||||||
',': '',
|
|
||||||
':': ';',
|
|
||||||
// Semicolons are not a legal delimiter per the RFC2822 grammar other
|
|
||||||
// than for terminating a group, but they are also not valid for any
|
|
||||||
// other use in this context. Given that some mail clients have
|
|
||||||
// historically allowed the semicolon as a delimiter equivalent to the
|
|
||||||
// comma in their UI, it makes sense to treat them the same as a comma
|
|
||||||
// when used outside of a group.
|
|
||||||
';': ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tokenizes the original input string
|
|
||||||
*
|
|
||||||
* @return {Array} An array of operator|text tokens
|
|
||||||
*/
|
|
||||||
tokenize() {
|
|
||||||
let chr,
|
|
||||||
list = [];
|
|
||||||
for (let i = 0, len = this.str.length; i < len; i++) {
|
|
||||||
chr = this.str.charAt(i);
|
|
||||||
this.checkChar(chr);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.list.forEach(node => {
|
|
||||||
node.value = (node.value || '').toString().trim();
|
|
||||||
if (node.value) {
|
|
||||||
list.push(node);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a character is an operator or text and acts accordingly
|
|
||||||
*
|
|
||||||
* @param {String} chr Character from the address field
|
|
||||||
*/
|
|
||||||
checkChar(chr) {
|
|
||||||
if (this.escaped) {
|
|
||||||
// ignore next condition blocks
|
|
||||||
} else if (chr === this.operatorExpecting) {
|
|
||||||
this.node = {
|
|
||||||
type: 'operator',
|
|
||||||
value: chr
|
|
||||||
};
|
|
||||||
this.list.push(this.node);
|
|
||||||
this.node = null;
|
|
||||||
this.operatorExpecting = '';
|
|
||||||
this.escaped = false;
|
|
||||||
return;
|
|
||||||
} else if (!this.operatorExpecting && chr in this.operators) {
|
|
||||||
this.node = {
|
|
||||||
type: 'operator',
|
|
||||||
value: chr
|
|
||||||
};
|
|
||||||
this.list.push(this.node);
|
|
||||||
this.node = null;
|
|
||||||
this.operatorExpecting = this.operators[chr];
|
|
||||||
this.escaped = false;
|
|
||||||
return;
|
|
||||||
} else if (['"', "'"].includes(this.operatorExpecting) && chr === '\\') {
|
|
||||||
this.escaped = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.node) {
|
|
||||||
this.node = {
|
|
||||||
type: 'text',
|
|
||||||
value: ''
|
|
||||||
};
|
|
||||||
this.list.push(this.node);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chr === '\n') {
|
|
||||||
// Convert newlines to spaces. Carriage return is ignored as \r and \n usually
|
|
||||||
// go together anyway and there already is a WS for \n. Lone \r means something is fishy.
|
|
||||||
chr = ' ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chr.charCodeAt(0) >= 0x21 || [' ', '\t'].includes(chr)) {
|
|
||||||
// skip command bytes
|
|
||||||
this.node.value += chr;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.escaped = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses structured e-mail addresses from an address field
|
|
||||||
*
|
|
||||||
* Example:
|
|
||||||
*
|
|
||||||
* 'Name <address@domain>'
|
|
||||||
*
|
|
||||||
* will be converted to
|
|
||||||
*
|
|
||||||
* [{name: 'Name', address: 'address@domain'}]
|
|
||||||
*
|
|
||||||
* @param {String} str Address field
|
|
||||||
* @return {Array} An array of address objects
|
|
||||||
*/
|
|
||||||
function addressparser(str, options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
let tokenizer = new Tokenizer(str);
|
|
||||||
let tokens = tokenizer.tokenize();
|
|
||||||
|
|
||||||
let addresses = [];
|
|
||||||
let address = [];
|
|
||||||
let parsedAddresses = [];
|
|
||||||
|
|
||||||
tokens.forEach(token => {
|
|
||||||
if (token.type === 'operator' && (token.value === ',' || token.value === ';')) {
|
|
||||||
if (address.length) {
|
|
||||||
addresses.push(address);
|
|
||||||
}
|
|
||||||
address = [];
|
|
||||||
} else {
|
|
||||||
address.push(token);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (address.length) {
|
|
||||||
addresses.push(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
addresses.forEach(address => {
|
|
||||||
address = _handleAddress(address);
|
|
||||||
if (address.length) {
|
|
||||||
parsedAddresses = parsedAddresses.concat(address);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.flatten) {
|
|
||||||
let addresses = [];
|
|
||||||
let walkAddressList = list => {
|
|
||||||
list.forEach(address => {
|
|
||||||
if (address.group) {
|
|
||||||
return walkAddressList(address.group);
|
|
||||||
} else {
|
|
||||||
addresses.push(address);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
walkAddressList(parsedAddresses);
|
|
||||||
return addresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
return parsedAddresses;
|
|
||||||
}
|
|
||||||
|
|
||||||
// expose to the world
|
|
||||||
module.exports = addressparser;
|
|
||||||
142
lib/node/node_modules/nodemailer/lib/base64/index.js
generated
vendored
142
lib/node/node_modules/nodemailer/lib/base64/index.js
generated
vendored
|
|
@ -1,142 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Transform = require('stream').Transform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a Buffer into a base64 encoded string
|
|
||||||
*
|
|
||||||
* @param {Buffer} buffer Buffer to convert
|
|
||||||
* @returns {String} base64 encoded string
|
|
||||||
*/
|
|
||||||
function encode(buffer) {
|
|
||||||
if (typeof buffer === 'string') {
|
|
||||||
buffer = Buffer.from(buffer, 'utf-8');
|
|
||||||
}
|
|
||||||
|
|
||||||
return buffer.toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds soft line breaks to a base64 string
|
|
||||||
*
|
|
||||||
* @param {String} str base64 encoded string that might need line wrapping
|
|
||||||
* @param {Number} [lineLength=76] Maximum allowed length for a line
|
|
||||||
* @returns {String} Soft-wrapped base64 encoded string
|
|
||||||
*/
|
|
||||||
function wrap(str, lineLength) {
|
|
||||||
str = (str || '').toString();
|
|
||||||
lineLength = lineLength || 76;
|
|
||||||
|
|
||||||
if (str.length <= lineLength) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
let result = [];
|
|
||||||
let pos = 0;
|
|
||||||
let chunkLength = lineLength * 1024;
|
|
||||||
while (pos < str.length) {
|
|
||||||
let wrappedLines = str
|
|
||||||
.substr(pos, chunkLength)
|
|
||||||
.replace(new RegExp('.{' + lineLength + '}', 'g'), '$&\r\n')
|
|
||||||
.trim();
|
|
||||||
result.push(wrappedLines);
|
|
||||||
pos += chunkLength;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.join('\r\n').trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a transform stream for encoding data to base64 encoding
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} options Stream options
|
|
||||||
* @param {Number} [options.lineLength=76] Maximum length for lines, set to false to disable wrapping
|
|
||||||
*/
|
|
||||||
class Encoder extends Transform {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
// init Transform
|
|
||||||
this.options = options || {};
|
|
||||||
|
|
||||||
if (this.options.lineLength !== false) {
|
|
||||||
this.options.lineLength = this.options.lineLength || 76;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._curLine = '';
|
|
||||||
this._remainingBytes = false;
|
|
||||||
|
|
||||||
this.inputBytes = 0;
|
|
||||||
this.outputBytes = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(chunk, encoding, done) {
|
|
||||||
if (encoding !== 'buffer') {
|
|
||||||
chunk = Buffer.from(chunk, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chunk || !chunk.length) {
|
|
||||||
return setImmediate(done);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inputBytes += chunk.length;
|
|
||||||
|
|
||||||
if (this._remainingBytes && this._remainingBytes.length) {
|
|
||||||
chunk = Buffer.concat([this._remainingBytes, chunk], this._remainingBytes.length + chunk.length);
|
|
||||||
this._remainingBytes = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunk.length % 3) {
|
|
||||||
this._remainingBytes = chunk.slice(chunk.length - (chunk.length % 3));
|
|
||||||
chunk = chunk.slice(0, chunk.length - (chunk.length % 3));
|
|
||||||
} else {
|
|
||||||
this._remainingBytes = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let b64 = this._curLine + encode(chunk);
|
|
||||||
|
|
||||||
if (this.options.lineLength) {
|
|
||||||
b64 = wrap(b64, this.options.lineLength);
|
|
||||||
|
|
||||||
// remove last line as it is still most probably incomplete
|
|
||||||
let lastLF = b64.lastIndexOf('\n');
|
|
||||||
if (lastLF < 0) {
|
|
||||||
this._curLine = b64;
|
|
||||||
b64 = '';
|
|
||||||
} else if (lastLF === b64.length - 1) {
|
|
||||||
this._curLine = '';
|
|
||||||
} else {
|
|
||||||
this._curLine = b64.substr(lastLF + 1);
|
|
||||||
b64 = b64.substr(0, lastLF + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (b64) {
|
|
||||||
this.outputBytes += b64.length;
|
|
||||||
this.push(Buffer.from(b64, 'ascii'));
|
|
||||||
}
|
|
||||||
|
|
||||||
setImmediate(done);
|
|
||||||
}
|
|
||||||
|
|
||||||
_flush(done) {
|
|
||||||
if (this._remainingBytes && this._remainingBytes.length) {
|
|
||||||
this._curLine += encode(this._remainingBytes);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._curLine) {
|
|
||||||
this._curLine = wrap(this._curLine, this.options.lineLength);
|
|
||||||
this.outputBytes += this._curLine.length;
|
|
||||||
this.push(this._curLine, 'ascii');
|
|
||||||
this._curLine = '';
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expose to the world
|
|
||||||
module.exports = {
|
|
||||||
encode,
|
|
||||||
wrap,
|
|
||||||
Encoder
|
|
||||||
};
|
|
||||||
251
lib/node/node_modules/nodemailer/lib/dkim/index.js
generated
vendored
251
lib/node/node_modules/nodemailer/lib/dkim/index.js
generated
vendored
|
|
@ -1,251 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// FIXME:
|
|
||||||
// replace this Transform mess with a method that pipes input argument to output argument
|
|
||||||
|
|
||||||
const MessageParser = require('./message-parser');
|
|
||||||
const RelaxedBody = require('./relaxed-body');
|
|
||||||
const sign = require('./sign');
|
|
||||||
const PassThrough = require('stream').PassThrough;
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
const DKIM_ALGO = 'sha256';
|
|
||||||
const MAX_MESSAGE_SIZE = 128 * 1024; // buffer messages larger than this to disk
|
|
||||||
|
|
||||||
/*
|
|
||||||
// Usage:
|
|
||||||
|
|
||||||
let dkim = new DKIM({
|
|
||||||
domainName: 'example.com',
|
|
||||||
keySelector: 'key-selector',
|
|
||||||
privateKey,
|
|
||||||
cacheDir: '/tmp'
|
|
||||||
});
|
|
||||||
dkim.sign(input).pipe(process.stdout);
|
|
||||||
|
|
||||||
// Where inputStream is a rfc822 message (either a stream, string or Buffer)
|
|
||||||
// and outputStream is a DKIM signed rfc822 message
|
|
||||||
*/
|
|
||||||
|
|
||||||
class DKIMSigner {
|
|
||||||
constructor(options, keys, input, output) {
|
|
||||||
this.options = options || {};
|
|
||||||
this.keys = keys;
|
|
||||||
|
|
||||||
this.cacheTreshold = Number(this.options.cacheTreshold) || MAX_MESSAGE_SIZE;
|
|
||||||
this.hashAlgo = this.options.hashAlgo || DKIM_ALGO;
|
|
||||||
|
|
||||||
this.cacheDir = this.options.cacheDir || false;
|
|
||||||
|
|
||||||
this.chunks = [];
|
|
||||||
this.chunklen = 0;
|
|
||||||
this.readPos = 0;
|
|
||||||
this.cachePath = this.cacheDir ? path.join(this.cacheDir, 'message.' + Date.now() + '-' + crypto.randomBytes(14).toString('hex')) : false;
|
|
||||||
this.cache = false;
|
|
||||||
|
|
||||||
this.headers = false;
|
|
||||||
this.bodyHash = false;
|
|
||||||
this.parser = false;
|
|
||||||
this.relaxedBody = false;
|
|
||||||
|
|
||||||
this.input = input;
|
|
||||||
this.output = output;
|
|
||||||
this.output.usingCache = false;
|
|
||||||
|
|
||||||
this.hasErrored = false;
|
|
||||||
|
|
||||||
this.input.on('error', err => {
|
|
||||||
this.hasErrored = true;
|
|
||||||
this.cleanup();
|
|
||||||
output.emit('error', err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
cleanup() {
|
|
||||||
if (!this.cache || !this.cachePath) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fs.unlink(this.cachePath, () => false);
|
|
||||||
}
|
|
||||||
|
|
||||||
createReadCache() {
|
|
||||||
// pipe remainings to cache file
|
|
||||||
this.cache = fs.createReadStream(this.cachePath);
|
|
||||||
this.cache.once('error', err => {
|
|
||||||
this.cleanup();
|
|
||||||
this.output.emit('error', err);
|
|
||||||
});
|
|
||||||
this.cache.once('close', () => {
|
|
||||||
this.cleanup();
|
|
||||||
});
|
|
||||||
this.cache.pipe(this.output);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendNextChunk() {
|
|
||||||
if (this.hasErrored) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.readPos >= this.chunks.length) {
|
|
||||||
if (!this.cache) {
|
|
||||||
return this.output.end();
|
|
||||||
}
|
|
||||||
return this.createReadCache();
|
|
||||||
}
|
|
||||||
let chunk = this.chunks[this.readPos++];
|
|
||||||
if (this.output.write(chunk) === false) {
|
|
||||||
return this.output.once('drain', () => {
|
|
||||||
this.sendNextChunk();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
setImmediate(() => this.sendNextChunk());
|
|
||||||
}
|
|
||||||
|
|
||||||
sendSignedOutput() {
|
|
||||||
let keyPos = 0;
|
|
||||||
let 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, {
|
|
||||||
domainName: key.domainName,
|
|
||||||
keySelector: key.keySelector,
|
|
||||||
privateKey: key.privateKey,
|
|
||||||
headerFieldNames: this.options.headerFieldNames,
|
|
||||||
skipFields: this.options.skipFields
|
|
||||||
});
|
|
||||||
if (dkimField) {
|
|
||||||
this.output.write(Buffer.from(dkimField + '\r\n'));
|
|
||||||
}
|
|
||||||
return setImmediate(signNextKey);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.bodyHash && this.headers) {
|
|
||||||
return signNextKey();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.output.write(this.parser.rawHeaders);
|
|
||||||
this.sendNextChunk();
|
|
||||||
}
|
|
||||||
|
|
||||||
createWriteCache() {
|
|
||||||
this.output.usingCache = true;
|
|
||||||
// pipe remainings to cache file
|
|
||||||
this.cache = fs.createWriteStream(this.cachePath);
|
|
||||||
this.cache.once('error', err => {
|
|
||||||
this.cleanup();
|
|
||||||
// drain input
|
|
||||||
this.relaxedBody.unpipe(this.cache);
|
|
||||||
this.relaxedBody.on('readable', () => {
|
|
||||||
while (this.relaxedBody.read() !== null) {
|
|
||||||
// do nothing
|
|
||||||
}
|
|
||||||
});
|
|
||||||
this.hasErrored = true;
|
|
||||||
// emit error
|
|
||||||
this.output.emit('error', err);
|
|
||||||
});
|
|
||||||
this.cache.once('close', () => {
|
|
||||||
this.sendSignedOutput();
|
|
||||||
});
|
|
||||||
this.relaxedBody.removeAllListeners('readable');
|
|
||||||
this.relaxedBody.pipe(this.cache);
|
|
||||||
}
|
|
||||||
|
|
||||||
signStream() {
|
|
||||||
this.parser = new MessageParser();
|
|
||||||
this.relaxedBody = new RelaxedBody({
|
|
||||||
hashAlgo: this.hashAlgo
|
|
||||||
});
|
|
||||||
|
|
||||||
this.parser.on('headers', value => {
|
|
||||||
this.headers = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.relaxedBody.on('hash', value => {
|
|
||||||
this.bodyHash = value;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.relaxedBody.on('readable', () => {
|
|
||||||
let chunk;
|
|
||||||
if (this.cache) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
while ((chunk = this.relaxedBody.read()) !== null) {
|
|
||||||
this.chunks.push(chunk);
|
|
||||||
this.chunklen += chunk.length;
|
|
||||||
if (this.chunklen >= this.cacheTreshold && this.cachePath) {
|
|
||||||
return this.createWriteCache();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.relaxedBody.on('end', () => {
|
|
||||||
if (this.cache) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.sendSignedOutput();
|
|
||||||
});
|
|
||||||
|
|
||||||
this.parser.pipe(this.relaxedBody);
|
|
||||||
setImmediate(() => this.input.pipe(this.parser));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class DKIM {
|
|
||||||
constructor(options) {
|
|
||||||
this.options = options || {};
|
|
||||||
this.keys = [].concat(
|
|
||||||
this.options.keys || {
|
|
||||||
domainName: options.domainName,
|
|
||||||
keySelector: options.keySelector,
|
|
||||||
privateKey: options.privateKey
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
sign(input, extraOptions) {
|
|
||||||
let output = new PassThrough();
|
|
||||||
let inputStream = input;
|
|
||||||
let writeValue = false;
|
|
||||||
|
|
||||||
if (Buffer.isBuffer(input)) {
|
|
||||||
writeValue = input;
|
|
||||||
inputStream = new PassThrough();
|
|
||||||
} else if (typeof input === 'string') {
|
|
||||||
writeValue = Buffer.from(input);
|
|
||||||
inputStream = new PassThrough();
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let signer = new DKIMSigner(options, this.keys, inputStream, output);
|
|
||||||
setImmediate(() => {
|
|
||||||
signer.signStream();
|
|
||||||
if (writeValue) {
|
|
||||||
setImmediate(() => {
|
|
||||||
inputStream.end(writeValue);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DKIM;
|
|
||||||
155
lib/node/node_modules/nodemailer/lib/dkim/message-parser.js
generated
vendored
155
lib/node/node_modules/nodemailer/lib/dkim/message-parser.js
generated
vendored
|
|
@ -1,155 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Transform = require('stream').Transform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MessageParser instance is a transform stream that separates message headers
|
|
||||||
* from the rest of the body. Headers are emitted with the 'headers' event. Message
|
|
||||||
* body is passed on as the resulting stream.
|
|
||||||
*/
|
|
||||||
class MessageParser extends Transform {
|
|
||||||
constructor(options) {
|
|
||||||
super(options);
|
|
||||||
this.lastBytes = Buffer.alloc(4);
|
|
||||||
this.headersParsed = false;
|
|
||||||
this.headerBytes = 0;
|
|
||||||
this.headerChunks = [];
|
|
||||||
this.rawHeaders = false;
|
|
||||||
this.bodySize = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Keeps count of the last 4 bytes in order to detect line breaks on chunk boundaries
|
|
||||||
*
|
|
||||||
* @param {Buffer} data Next data chunk from the stream
|
|
||||||
*/
|
|
||||||
updateLastBytes(data) {
|
|
||||||
let lblen = this.lastBytes.length;
|
|
||||||
let nblen = Math.min(data.length, lblen);
|
|
||||||
|
|
||||||
// shift existing bytes
|
|
||||||
for (let i = 0, len = lblen - nblen; i < len; i++) {
|
|
||||||
this.lastBytes[i] = this.lastBytes[i + nblen];
|
|
||||||
}
|
|
||||||
|
|
||||||
// add new bytes
|
|
||||||
for (let i = 1; i <= nblen; i++) {
|
|
||||||
this.lastBytes[lblen - i] = data[data.length - i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds and removes message headers from the remaining body. We want to keep
|
|
||||||
* headers separated until final delivery to be able to modify these
|
|
||||||
*
|
|
||||||
* @param {Buffer} data Next chunk of data
|
|
||||||
* @return {Boolean} Returns true if headers are already found or false otherwise
|
|
||||||
*/
|
|
||||||
checkHeaders(data) {
|
|
||||||
if (this.headersParsed) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let 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) {
|
|
||||||
chr = this.lastBytes[i];
|
|
||||||
} else {
|
|
||||||
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;
|
|
||||||
if (pr1 === 0x0a) {
|
|
||||||
this.headersParsed = true;
|
|
||||||
headerPos = i - lblen + 1;
|
|
||||||
this.headerBytes += headerPos;
|
|
||||||
break;
|
|
||||||
} else if (pr1 === 0x0d && pr2 === 0x0a) {
|
|
||||||
this.headersParsed = true;
|
|
||||||
headerPos = i - lblen + 1;
|
|
||||||
this.headerBytes += headerPos;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.headersParsed) {
|
|
||||||
this.headerChunks.push(data.slice(0, headerPos));
|
|
||||||
this.rawHeaders = Buffer.concat(this.headerChunks, this.headerBytes);
|
|
||||||
this.headerChunks = null;
|
|
||||||
this.emit('headers', this.parseHeaders());
|
|
||||||
if (data.length - 1 > headerPos) {
|
|
||||||
let 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// store last 4 bytes to catch header break
|
|
||||||
this.updateLastBytes(data);
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(chunk, encoding, callback) {
|
|
||||||
if (!chunk || !chunk.length) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof chunk === 'string') {
|
|
||||||
chunk = Buffer.from(chunk, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
let headersFound;
|
|
||||||
|
|
||||||
try {
|
|
||||||
headersFound = this.checkHeaders(chunk);
|
|
||||||
} catch (E) {
|
|
||||||
return callback(E);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (headersFound) {
|
|
||||||
this.bodySize += chunk.length;
|
|
||||||
this.push(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
setImmediate(callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
_flush(callback) {
|
|
||||||
if (this.headerChunks) {
|
|
||||||
let chunk = Buffer.concat(this.headerChunks, this.headerBytes);
|
|
||||||
this.bodySize += chunk.length;
|
|
||||||
this.push(chunk);
|
|
||||||
this.headerChunks = null;
|
|
||||||
}
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
parseHeaders() {
|
|
||||||
let 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];
|
|
||||||
lines.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
.filter(line => line.trim())
|
|
||||||
.map(line => ({
|
|
||||||
key: line.substr(0, line.indexOf(':')).trim().toLowerCase(),
|
|
||||||
line
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MessageParser;
|
|
||||||
154
lib/node/node_modules/nodemailer/lib/dkim/relaxed-body.js
generated
vendored
154
lib/node/node_modules/nodemailer/lib/dkim/relaxed-body.js
generated
vendored
|
|
@ -1,154 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// streams through a message body and calculates relaxed body hash
|
|
||||||
|
|
||||||
const Transform = require('stream').Transform;
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
class RelaxedBody extends Transform {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
options = options || {};
|
|
||||||
this.chunkBuffer = [];
|
|
||||||
this.chunkBufferLen = 0;
|
|
||||||
this.bodyHash = crypto.createHash(options.hashAlgo || 'sha1');
|
|
||||||
this.remainder = '';
|
|
||||||
this.byteLength = 0;
|
|
||||||
|
|
||||||
this.debug = options.debug;
|
|
||||||
this._debugBody = options.debug ? [] : false;
|
|
||||||
}
|
|
||||||
|
|
||||||
updateHash(chunk) {
|
|
||||||
let bodyStr;
|
|
||||||
|
|
||||||
// find next remainder
|
|
||||||
let nextRemainder = '';
|
|
||||||
|
|
||||||
// This crux finds and removes the spaces from the last line and the newline characters after the last non-empty line
|
|
||||||
// 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];
|
|
||||||
|
|
||||||
if (state === 'file' && (c === 0x0a || c === 0x0d)) {
|
|
||||||
// do nothing, found \n or \r at the end of chunk, stil end of file
|
|
||||||
} else if (state === 'file' && (c === 0x09 || c === 0x20)) {
|
|
||||||
// switch to line ending mode, this is the last non-empty line
|
|
||||||
state = 'line';
|
|
||||||
} else if (state === 'line' && (c === 0x09 || c === 0x20)) {
|
|
||||||
// do nothing, found ' ' or \t at the end of line, keep processing the last non-empty line
|
|
||||||
} else if (state === 'file' || state === 'line') {
|
|
||||||
// non line/file ending character found, switch to body mode
|
|
||||||
state = 'body';
|
|
||||||
if (i === chunk.length - 1) {
|
|
||||||
// final char is not part of line end or file end, so do nothing
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (i === 0) {
|
|
||||||
// reached to the beginning of the chunk, check if it is still about the ending
|
|
||||||
// and if the remainder also matches
|
|
||||||
if (
|
|
||||||
(state === 'file' && (!this.remainder || /[\r\n]$/.test(this.remainder))) ||
|
|
||||||
(state === 'line' && (!this.remainder || /[ \t]$/.test(this.remainder)))
|
|
||||||
) {
|
|
||||||
// keep everything
|
|
||||||
this.remainder += chunk.toString('binary');
|
|
||||||
return;
|
|
||||||
} else if (state === 'line' || state === 'file') {
|
|
||||||
// process existing remainder as normal line but store the current chunk
|
|
||||||
nextRemainder = chunk.toString('binary');
|
|
||||||
chunk = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state !== 'body') {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reached first non ending byte
|
|
||||||
nextRemainder = chunk.slice(i + 1).toString('binary');
|
|
||||||
chunk = chunk.slice(0, i + 1);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
let needsFixing = !!this.remainder;
|
|
||||||
if (chunk && !needsFixing) {
|
|
||||||
// check if we even need to change anything
|
|
||||||
for (let i = 0, len = chunk.length; i < len; i++) {
|
|
||||||
if (i && chunk[i] === 0x0a && chunk[i - 1] !== 0x0d) {
|
|
||||||
// missing \r before \n
|
|
||||||
needsFixing = true;
|
|
||||||
break;
|
|
||||||
} else if (i && chunk[i] === 0x0d && chunk[i - 1] === 0x20) {
|
|
||||||
// trailing WSP found
|
|
||||||
needsFixing = true;
|
|
||||||
break;
|
|
||||||
} else if (i && chunk[i] === 0x20 && chunk[i - 1] === 0x20) {
|
|
||||||
// multiple spaces found, needs to be replaced with just one
|
|
||||||
needsFixing = true;
|
|
||||||
break;
|
|
||||||
} else if (chunk[i] === 0x09) {
|
|
||||||
// TAB found, needs to be replaced with a space
|
|
||||||
needsFixing = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (needsFixing) {
|
|
||||||
bodyStr = this.remainder + (chunk ? chunk.toString('binary') : '');
|
|
||||||
this.remainder = nextRemainder;
|
|
||||||
bodyStr = bodyStr
|
|
||||||
.replace(/\r?\n/g, '\n') // use js line endings
|
|
||||||
.replace(/[ \t]*$/gm, '') // remove line endings, rtrim
|
|
||||||
.replace(/[ \t]+/gm, ' ') // single spaces
|
|
||||||
.replace(/\n/g, '\r\n'); // restore rfc822 line endings
|
|
||||||
chunk = Buffer.from(bodyStr, 'binary');
|
|
||||||
} else if (nextRemainder) {
|
|
||||||
this.remainder = nextRemainder;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.debug) {
|
|
||||||
this._debugBody.push(chunk);
|
|
||||||
}
|
|
||||||
this.bodyHash.update(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(chunk, encoding, callback) {
|
|
||||||
if (!chunk || !chunk.length) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof chunk === 'string') {
|
|
||||||
chunk = Buffer.from(chunk, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.updateHash(chunk);
|
|
||||||
|
|
||||||
this.byteLength += chunk.length;
|
|
||||||
this.push(chunk);
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
_flush(callback) {
|
|
||||||
// generate final hash and emit it
|
|
||||||
if (/[\r\n]$/.test(this.remainder) && this.byteLength > 2) {
|
|
||||||
// add terminating line end
|
|
||||||
this.bodyHash.update(Buffer.from('\r\n'));
|
|
||||||
}
|
|
||||||
if (!this.byteLength) {
|
|
||||||
// emit empty line buffer to keep the stream flowing
|
|
||||||
this.push(Buffer.from('\r\n'));
|
|
||||||
// this.bodyHash.update(Buffer.from('\r\n'));
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('hash', this.bodyHash.digest('base64'), this.debug ? Buffer.concat(this._debugBody) : false);
|
|
||||||
callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = RelaxedBody;
|
|
||||||
117
lib/node/node_modules/nodemailer/lib/dkim/sign.js
generated
vendored
117
lib/node/node_modules/nodemailer/lib/dkim/sign.js
generated
vendored
|
|
@ -1,117 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const punycode = require('../punycode');
|
|
||||||
const mimeFuncs = require('../mime-funcs');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns DKIM signature header line
|
|
||||||
*
|
|
||||||
* @param {Object} headers Parsed headers object from MessageParser
|
|
||||||
* @param {String} bodyHash Base64 encoded hash of the message
|
|
||||||
* @param {Object} options DKIM options
|
|
||||||
* @param {String} options.domainName Domain name to be signed for
|
|
||||||
* @param {String} options.keySelector DKIM key selector to use
|
|
||||||
* @param {String} options.privateKey DKIM private key to use
|
|
||||||
* @return {String} Complete header line
|
|
||||||
*/
|
|
||||||
|
|
||||||
module.exports = (headers, hashAlgo, bodyHash, options) => {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// all listed fields from RFC4871 #5.5
|
|
||||||
let defaultFieldNames =
|
|
||||||
'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
|
|
||||||
'Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:' +
|
|
||||||
'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
|
|
||||||
'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
|
|
||||||
'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
|
|
||||||
'List-Owner:List-Archive';
|
|
||||||
|
|
||||||
let 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;
|
|
||||||
|
|
||||||
canonicalizedHeaderData.headers += 'dkim-signature:' + relaxedHeaderLine(dkimHeader);
|
|
||||||
|
|
||||||
signer = crypto.createSign(('rsa-' + hashAlgo).toUpperCase());
|
|
||||||
signer.update(canonicalizedHeaderData.headers);
|
|
||||||
try {
|
|
||||||
signature = signer.sign(options.privateKey, 'base64');
|
|
||||||
} catch (E) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return dkimHeader + signature.replace(/(^.{73}|.{75}(?!\r?\n|\r))/g, '$&\r\n ').trim();
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.relaxedHeaders = relaxedHeaders;
|
|
||||||
|
|
||||||
function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyHash) {
|
|
||||||
let dkim = [
|
|
||||||
'v=1',
|
|
||||||
'a=rsa-' + hashAlgo,
|
|
||||||
'c=relaxed/relaxed',
|
|
||||||
'd=' + punycode.toASCII(domainName),
|
|
||||||
'q=dns/txt',
|
|
||||||
's=' + keySelector,
|
|
||||||
'bh=' + bodyHash,
|
|
||||||
'h=' + fieldNames
|
|
||||||
].join('; ');
|
|
||||||
|
|
||||||
return mimeFuncs.foldLines('DKIM-Signature: ' + dkim, 76) + ';\r\n b=';
|
|
||||||
}
|
|
||||||
|
|
||||||
function relaxedHeaders(headers, fieldNames, skipFields) {
|
|
||||||
let includedFields = new Set();
|
|
||||||
let skip = new Set();
|
|
||||||
let headerFields = new Map();
|
|
||||||
|
|
||||||
(skipFields || '')
|
|
||||||
.toLowerCase()
|
|
||||||
.split(':')
|
|
||||||
.forEach(field => {
|
|
||||||
skip.add(field.trim());
|
|
||||||
});
|
|
||||||
|
|
||||||
(fieldNames || '')
|
|
||||||
.toLowerCase()
|
|
||||||
.split(':')
|
|
||||||
.filter(field => !skip.has(field.trim()))
|
|
||||||
.forEach(field => {
|
|
||||||
includedFields.add(field.trim());
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let i = headers.length - 1; i >= 0; i--) {
|
|
||||||
let 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 = [];
|
|
||||||
includedFields.forEach(field => {
|
|
||||||
if (headerFields.has(field)) {
|
|
||||||
fields.push(field);
|
|
||||||
headersList.push(field + ':' + headerFields.get(field));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
headers: headersList.join('\r\n') + '\r\n',
|
|
||||||
fieldNames: fields.join(':')
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function relaxedHeaderLine(line) {
|
|
||||||
return line
|
|
||||||
.substr(line.indexOf(':') + 1)
|
|
||||||
.replace(/\r?\n/g, '')
|
|
||||||
.replace(/\s+/g, ' ')
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
281
lib/node/node_modules/nodemailer/lib/fetch/cookies.js
generated
vendored
281
lib/node/node_modules/nodemailer/lib/fetch/cookies.js
generated
vendored
|
|
@ -1,281 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// module to handle cookies
|
|
||||||
|
|
||||||
const urllib = require('url');
|
|
||||||
|
|
||||||
const SESSION_TIMEOUT = 1800; // 30 min
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a biskviit cookie jar for managing cookie values in memory
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} [options] Optional options object
|
|
||||||
*/
|
|
||||||
class Cookies {
|
|
||||||
constructor(options) {
|
|
||||||
this.options = options || {};
|
|
||||||
this.cookies = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stores a cookie string to the cookie storage
|
|
||||||
*
|
|
||||||
* @param {String} cookieStr Value from the 'Set-Cookie:' header
|
|
||||||
* @param {String} url Current URL
|
|
||||||
*/
|
|
||||||
set(cookieStr, url) {
|
|
||||||
let urlparts = urllib.parse(url || '');
|
|
||||||
let cookie = this.parse(cookieStr);
|
|
||||||
let domain;
|
|
||||||
|
|
||||||
if (cookie.domain) {
|
|
||||||
domain = cookie.domain.replace(/^\./, '');
|
|
||||||
|
|
||||||
// do not allow cross origin cookies
|
|
||||||
if (
|
|
||||||
// can't be valid if the requested domain is shorter than current hostname
|
|
||||||
urlparts.hostname.length < domain.length ||
|
|
||||||
// prefix domains with dot to be sure that partial matches are not used
|
|
||||||
('.' + urlparts.hostname).substr(-domain.length + 1) !== '.' + domain
|
|
||||||
) {
|
|
||||||
cookie.domain = urlparts.hostname;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cookie.domain = urlparts.hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cookie.path) {
|
|
||||||
cookie.path = this.getPath(urlparts.pathname);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no expire date, then use sessionTimeout value
|
|
||||||
if (!cookie.expires) {
|
|
||||||
cookie.expires = new Date(Date.now() + (Number(this.options.sessionTimeout || SESSION_TIMEOUT) || SESSION_TIMEOUT) * 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.add(cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns cookie string for the 'Cookie:' header.
|
|
||||||
*
|
|
||||||
* @param {String} url URL to check for
|
|
||||||
* @returns {String} Cookie header or empty string if no matches were found
|
|
||||||
*/
|
|
||||||
get(url) {
|
|
||||||
return this.list(url)
|
|
||||||
.map(cookie => cookie.name + '=' + cookie.value)
|
|
||||||
.join('; ');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Lists all valied cookie objects for the specified URL
|
|
||||||
*
|
|
||||||
* @param {String} url URL to check for
|
|
||||||
* @returns {Array} An array of cookie objects
|
|
||||||
*/
|
|
||||||
list(url) {
|
|
||||||
let result = [];
|
|
||||||
let i;
|
|
||||||
let cookie;
|
|
||||||
|
|
||||||
for (i = this.cookies.length - 1; i >= 0; i--) {
|
|
||||||
cookie = this.cookies[i];
|
|
||||||
|
|
||||||
if (this.isExpired(cookie)) {
|
|
||||||
this.cookies.splice(i, i);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.match(cookie, url)) {
|
|
||||||
result.unshift(cookie);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses cookie string from the 'Set-Cookie:' header
|
|
||||||
*
|
|
||||||
* @param {String} cookieStr String from the 'Set-Cookie:' header
|
|
||||||
* @returns {Object} Cookie object
|
|
||||||
*/
|
|
||||||
parse(cookieStr) {
|
|
||||||
let cookie = {};
|
|
||||||
|
|
||||||
(cookieStr || '')
|
|
||||||
.toString()
|
|
||||||
.split(';')
|
|
||||||
.forEach(cookiePart => {
|
|
||||||
let valueParts = cookiePart.split('=');
|
|
||||||
let key = valueParts.shift().trim().toLowerCase();
|
|
||||||
let value = valueParts.join('=').trim();
|
|
||||||
let domain;
|
|
||||||
|
|
||||||
if (!key) {
|
|
||||||
// skip empty parts
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (key) {
|
|
||||||
case 'expires':
|
|
||||||
value = new Date(value);
|
|
||||||
// ignore date if can not parse it
|
|
||||||
if (value.toString() !== 'Invalid Date') {
|
|
||||||
cookie.expires = value;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'path':
|
|
||||||
cookie.path = value;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'domain':
|
|
||||||
domain = value.toLowerCase();
|
|
||||||
if (domain.length && domain.charAt(0) !== '.') {
|
|
||||||
domain = '.' + domain; // ensure preceeding dot for user set domains
|
|
||||||
}
|
|
||||||
cookie.domain = domain;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'max-age':
|
|
||||||
cookie.expires = new Date(Date.now() + (Number(value) || 0) * 1000);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'secure':
|
|
||||||
cookie.secure = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'httponly':
|
|
||||||
cookie.httponly = true;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
if (!cookie.name) {
|
|
||||||
cookie.name = key;
|
|
||||||
cookie.value = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return cookie;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a cookie object is valid for a specified URL
|
|
||||||
*
|
|
||||||
* @param {Object} cookie Cookie object
|
|
||||||
* @param {String} url URL to check for
|
|
||||||
* @returns {Boolean} true if cookie is valid for specifiec URL
|
|
||||||
*/
|
|
||||||
match(cookie, url) {
|
|
||||||
let urlparts = urllib.parse(url || '');
|
|
||||||
|
|
||||||
// check if hostname matches
|
|
||||||
// .foo.com also matches subdomains, foo.com does not
|
|
||||||
if (
|
|
||||||
urlparts.hostname !== cookie.domain &&
|
|
||||||
(cookie.domain.charAt(0) !== '.' || ('.' + urlparts.hostname).substr(-cookie.domain.length) !== cookie.domain)
|
|
||||||
) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if path matches
|
|
||||||
let path = this.getPath(urlparts.pathname);
|
|
||||||
if (path.substr(0, cookie.path.length) !== cookie.path) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check secure argument
|
|
||||||
if (cookie.secure && urlparts.protocol !== 'https:') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds (or updates/removes if needed) a cookie object to the cookie storage
|
|
||||||
*
|
|
||||||
* @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++) {
|
|
||||||
if (this.compare(this.cookies[i], cookie)) {
|
|
||||||
// check if the cookie needs to be removed instead
|
|
||||||
if (this.isExpired(cookie)) {
|
|
||||||
this.cookies.splice(i, 1); // remove expired/unset cookie
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.cookies[i] = cookie;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// add as new if not already expired
|
|
||||||
if (!this.isExpired(cookie)) {
|
|
||||||
this.cookies.push(cookie);
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if two cookie objects are the same
|
|
||||||
*
|
|
||||||
* @param {Object} a Cookie to check against
|
|
||||||
* @param {Object} b Cookie to check against
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a cookie is expired
|
|
||||||
*
|
|
||||||
* @param {Object} cookie Cookie object to check against
|
|
||||||
* @returns {Boolean} True, if the cookie is expired
|
|
||||||
*/
|
|
||||||
isExpired(cookie) {
|
|
||||||
return (cookie.expires && cookie.expires < new Date()) || !cookie.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns normalized cookie path for an URL path argument
|
|
||||||
*
|
|
||||||
* @param {String} pathname
|
|
||||||
* @returns {String} Normalized path
|
|
||||||
*/
|
|
||||||
getPath(pathname) {
|
|
||||||
let path = (pathname || '/').split('/');
|
|
||||||
path.pop(); // remove filename part
|
|
||||||
path = path.join('/').trim();
|
|
||||||
|
|
||||||
// ensure path prefix /
|
|
||||||
if (path.charAt(0) !== '/') {
|
|
||||||
path = '/' + path;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure path suffix /
|
|
||||||
if (path.substr(-1) !== '/') {
|
|
||||||
path += '/';
|
|
||||||
}
|
|
||||||
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Cookies;
|
|
||||||
274
lib/node/node_modules/nodemailer/lib/fetch/index.js
generated
vendored
274
lib/node/node_modules/nodemailer/lib/fetch/index.js
generated
vendored
|
|
@ -1,274 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const http = require('http');
|
|
||||||
const https = require('https');
|
|
||||||
const urllib = require('url');
|
|
||||||
const zlib = require('zlib');
|
|
||||||
const PassThrough = require('stream').PassThrough;
|
|
||||||
const Cookies = require('./cookies');
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
const net = require('net');
|
|
||||||
|
|
||||||
const MAX_REDIRECTS = 5;
|
|
||||||
|
|
||||||
module.exports = function (url, options) {
|
|
||||||
return nmfetch(url, options);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.Cookies = Cookies;
|
|
||||||
|
|
||||||
function nmfetch(url, options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
options.fetchRes = options.fetchRes || new PassThrough();
|
|
||||||
options.cookies = options.cookies || new Cookies();
|
|
||||||
options.redirects = options.redirects || 0;
|
|
||||||
options.maxRedirects = isNaN(options.maxRedirects) ? MAX_REDIRECTS : options.maxRedirects;
|
|
||||||
|
|
||||||
if (options.cookie) {
|
|
||||||
[].concat(options.cookie || []).forEach(cookie => {
|
|
||||||
options.cookies.set(cookie, url);
|
|
||||||
});
|
|
||||||
options.cookie = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let fetchRes = options.fetchRes;
|
|
||||||
let 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;
|
|
||||||
|
|
||||||
let headers = {
|
|
||||||
'accept-encoding': 'gzip,deflate',
|
|
||||||
'user-agent': 'nodemailer/' + packageData.version
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(options.headers || {}).forEach(key => {
|
|
||||||
headers[key.toLowerCase().trim()] = options.headers[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (options.userAgent) {
|
|
||||||
headers['user-agent'] = options.userAgent;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsed.auth) {
|
|
||||||
headers.Authorization = 'Basic ' + Buffer.from(parsed.auth).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((cookies = options.cookies.get(url))) {
|
|
||||||
headers.cookie = cookies;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.body) {
|
|
||||||
if (options.contentType !== false) {
|
|
||||||
headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof options.body.pipe === 'function') {
|
|
||||||
// it's a stream
|
|
||||||
headers['Transfer-Encoding'] = 'chunked';
|
|
||||||
body = options.body;
|
|
||||||
body.on('error', err => {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finished = true;
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
if (options.body instanceof Buffer) {
|
|
||||||
body = options.body;
|
|
||||||
} else if (typeof options.body === 'object') {
|
|
||||||
try {
|
|
||||||
// encodeURIComponent can fail on invalid input (partial emoji etc.)
|
|
||||||
body = Buffer.from(
|
|
||||||
Object.keys(options.body)
|
|
||||||
.map(key => {
|
|
||||||
let value = options.body[key].toString().trim();
|
|
||||||
return encodeURIComponent(key) + '=' + encodeURIComponent(value);
|
|
||||||
})
|
|
||||||
.join('&')
|
|
||||||
);
|
|
||||||
} catch (E) {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finished = true;
|
|
||||||
E.type = 'FETCH';
|
|
||||||
E.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', E);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
body = Buffer.from(options.body.toString().trim());
|
|
||||||
}
|
|
||||||
|
|
||||||
headers['Content-Type'] = options.contentType || 'application/x-www-form-urlencoded';
|
|
||||||
headers['Content-Length'] = body.length;
|
|
||||||
}
|
|
||||||
// if method is not provided, use POST instead of GET
|
|
||||||
method = (options.method || '').toString().trim().toUpperCase() || 'POST';
|
|
||||||
}
|
|
||||||
|
|
||||||
let req;
|
|
||||||
let reqOptions = {
|
|
||||||
method,
|
|
||||||
host: parsed.hostname,
|
|
||||||
path: parsed.path,
|
|
||||||
port: parsed.port ? parsed.port : parsed.protocol === 'https:' ? 443 : 80,
|
|
||||||
headers,
|
|
||||||
rejectUnauthorized: false,
|
|
||||||
agent: false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (options.tls) {
|
|
||||||
Object.keys(options.tls).forEach(key => {
|
|
||||||
reqOptions[key] = options.tls[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parsed.protocol === 'https:' && parsed.hostname && parsed.hostname !== reqOptions.host && !net.isIP(parsed.hostname) && !reqOptions.servername) {
|
|
||||||
reqOptions.servername = parsed.hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
req = handler.request(reqOptions);
|
|
||||||
} catch (E) {
|
|
||||||
finished = true;
|
|
||||||
setImmediate(() => {
|
|
||||||
E.type = 'FETCH';
|
|
||||||
E.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', E);
|
|
||||||
});
|
|
||||||
return fetchRes;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.timeout) {
|
|
||||||
req.setTimeout(options.timeout, () => {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finished = true;
|
|
||||||
req.abort();
|
|
||||||
let err = new Error('Request Timeout');
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
req.on('error', err => {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finished = true;
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('response', res => {
|
|
||||||
let inflate;
|
|
||||||
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (res.headers['content-encoding']) {
|
|
||||||
case 'gzip':
|
|
||||||
case 'deflate':
|
|
||||||
inflate = zlib.createUnzip();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.headers['set-cookie']) {
|
|
||||||
[].concat(res.headers['set-cookie'] || []).forEach(cookie => {
|
|
||||||
options.cookies.set(cookie, url);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
|
|
||||||
// redirect
|
|
||||||
options.redirects++;
|
|
||||||
if (options.redirects > options.maxRedirects) {
|
|
||||||
finished = true;
|
|
||||||
let err = new Error('Maximum redirect count exceeded');
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
req.abort();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// redirect does not include POST body
|
|
||||||
options.method = 'GET';
|
|
||||||
options.body = false;
|
|
||||||
return nmfetch(urllib.resolve(url, res.headers.location), options);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchRes.statusCode = res.statusCode;
|
|
||||||
fetchRes.headers = res.headers;
|
|
||||||
|
|
||||||
if (res.statusCode >= 300 && !options.allowErrorResponse) {
|
|
||||||
finished = true;
|
|
||||||
let err = new Error('Invalid status code ' + res.statusCode);
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
req.abort();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.on('error', err => {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finished = true;
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
req.abort();
|
|
||||||
});
|
|
||||||
|
|
||||||
if (inflate) {
|
|
||||||
res.pipe(inflate).pipe(fetchRes);
|
|
||||||
inflate.on('error', err => {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finished = true;
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
req.abort();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
res.pipe(fetchRes);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setImmediate(() => {
|
|
||||||
if (body) {
|
|
||||||
try {
|
|
||||||
if (typeof body.pipe === 'function') {
|
|
||||||
return body.pipe(req);
|
|
||||||
} else {
|
|
||||||
req.write(body);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
finished = true;
|
|
||||||
err.type = 'FETCH';
|
|
||||||
err.sourceUrl = url;
|
|
||||||
fetchRes.emit('error', err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
req.end();
|
|
||||||
});
|
|
||||||
|
|
||||||
return fetchRes;
|
|
||||||
}
|
|
||||||
82
lib/node/node_modules/nodemailer/lib/json-transport/index.js
generated
vendored
82
lib/node/node_modules/nodemailer/lib/json-transport/index.js
generated
vendored
|
|
@ -1,82 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
const shared = require('../shared');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a Transport object to generate JSON output
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} optional config parameter
|
|
||||||
*/
|
|
||||||
class JSONTransport {
|
|
||||||
constructor(options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
this.options = options || {};
|
|
||||||
|
|
||||||
this.name = 'JSONTransport';
|
|
||||||
this.version = packageData.version;
|
|
||||||
|
|
||||||
this.logger = shared.getLogger(this.options, {
|
|
||||||
component: this.options.component || 'json-transport'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Compiles a mailcomposer message and forwards it to handler that sends it.</p>
|
|
||||||
*
|
|
||||||
* @param {Object} emailMessage MailComposer object
|
|
||||||
* @param {Function} callback Callback function to run when the sending is completed
|
|
||||||
*/
|
|
||||||
send(mail, done) {
|
|
||||||
// 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 recipients = [].concat(envelope.to || []);
|
|
||||||
if (recipients.length > 3) {
|
|
||||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
|
||||||
}
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Composing JSON structure of %s to <%s>',
|
|
||||||
messageId,
|
|
||||||
recipients.join(', ')
|
|
||||||
);
|
|
||||||
|
|
||||||
setImmediate(() => {
|
|
||||||
mail.normalize((err, data) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Failed building JSON structure for %s. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
return done(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
delete data.envelope;
|
|
||||||
delete data.normalizedHeaders;
|
|
||||||
|
|
||||||
return done(null, {
|
|
||||||
envelope,
|
|
||||||
messageId,
|
|
||||||
message: this.options.skipEncoding ? data : JSON.stringify(data)
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = JSONTransport;
|
|
||||||
565
lib/node/node_modules/nodemailer/lib/mail-composer/index.js
generated
vendored
565
lib/node/node_modules/nodemailer/lib/mail-composer/index.js
generated
vendored
|
|
@ -1,565 +0,0 @@
|
||||||
/* eslint no-undefined: 0 */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const MimeNode = require('../mime-node');
|
|
||||||
const mimeFuncs = require('../mime-funcs');
|
|
||||||
const parseDataURI = require('../shared').parseDataURI;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the object for composing a MimeNode instance out from the mail options
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} mail Mail options
|
|
||||||
*/
|
|
||||||
class MailComposer {
|
|
||||||
constructor(mail) {
|
|
||||||
this.mail = mail || {};
|
|
||||||
this.message = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds MimeNode instance
|
|
||||||
*/
|
|
||||||
compile() {
|
|
||||||
this._alternatives = this.getAlternatives();
|
|
||||||
this._htmlNode = this._alternatives.filter(alternative => /^text\/html\b/i.test(alternative.contentType)).pop();
|
|
||||||
this._attachments = this.getAttachments(!!this._htmlNode);
|
|
||||||
|
|
||||||
this._useRelated = !!(this._htmlNode && this._attachments.related.length);
|
|
||||||
this._useAlternative = this._alternatives.length > 1;
|
|
||||||
this._useMixed = this._attachments.attached.length > 1 || (this._alternatives.length && this._attachments.attached.length === 1);
|
|
||||||
|
|
||||||
// Compose MIME tree
|
|
||||||
if (this.mail.raw) {
|
|
||||||
this.message = new MimeNode('message/rfc822', { newline: this.mail.newline }).setRaw(this.mail.raw);
|
|
||||||
} else if (this._useMixed) {
|
|
||||||
this.message = this._createMixed();
|
|
||||||
} else if (this._useAlternative) {
|
|
||||||
this.message = this._createAlternative();
|
|
||||||
} else if (this._useRelated) {
|
|
||||||
this.message = this._createRelated();
|
|
||||||
} else {
|
|
||||||
this.message = this._createContentNode(
|
|
||||||
false,
|
|
||||||
[]
|
|
||||||
.concat(this._alternatives || [])
|
|
||||||
.concat(this._attachments.attached || [])
|
|
||||||
.shift() || {
|
|
||||||
contentType: 'text/plain',
|
|
||||||
content: ''
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add custom headers
|
|
||||||
if (this.mail.headers) {
|
|
||||||
this.message.addHeader(this.mail.headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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());
|
|
||||||
if (this.mail[key]) {
|
|
||||||
this.message.setHeader(header, this.mail[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sets custom envelope
|
|
||||||
if (this.mail.envelope) {
|
|
||||||
this.message.setEnvelope(this.mail.envelope);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure Message-Id value
|
|
||||||
this.message.messageId();
|
|
||||||
|
|
||||||
return this.message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List all attachments. Resulting attachment objects can be used as input for MimeNode nodes
|
|
||||||
*
|
|
||||||
* @param {Boolean} findRelated If true separate related attachments from attached ones
|
|
||||||
* @returns {Object} An object of arrays (`related` and `attached`)
|
|
||||||
*/
|
|
||||||
getAttachments(findRelated) {
|
|
||||||
let icalEvent, eventObject;
|
|
||||||
let attachments = [].concat(this.mail.attachments || []).map((attachment, i) => {
|
|
||||||
let data;
|
|
||||||
let isMessageNode = /^message\//i.test(attachment.contentType);
|
|
||||||
|
|
||||||
if (/^data:/i.test(attachment.path || attachment.href)) {
|
|
||||||
attachment = this._processDataUrl(attachment);
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentType = attachment.contentType || mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
|
|
||||||
let isImage = /^image\//i.test(contentType);
|
|
||||||
let contentDisposition = attachment.contentDisposition || (isMessageNode || (isImage && attachment.cid) ? 'inline' : 'attachment');
|
|
||||||
|
|
||||||
data = {
|
|
||||||
contentType,
|
|
||||||
contentDisposition,
|
|
||||||
contentTransferEncoding: 'contentTransferEncoding' in attachment ? attachment.contentTransferEncoding : 'base64'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (attachment.filename) {
|
|
||||||
data.filename = attachment.filename;
|
|
||||||
} else if (!isMessageNode && attachment.filename !== false) {
|
|
||||||
data.filename = (attachment.path || attachment.href || '').split('/').pop().split('?').shift() || 'attachment-' + (i + 1);
|
|
||||||
if (data.filename.indexOf('.') < 0) {
|
|
||||||
data.filename += '.' + mimeFuncs.detectExtension(data.contentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^https?:\/\//i.test(attachment.path)) {
|
|
||||||
attachment.href = attachment.path;
|
|
||||||
attachment.path = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.cid) {
|
|
||||||
data.cid = attachment.cid;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.raw) {
|
|
||||||
data.raw = attachment.raw;
|
|
||||||
} else if (attachment.path) {
|
|
||||||
data.content = {
|
|
||||||
path: attachment.path
|
|
||||||
};
|
|
||||||
} else if (attachment.href) {
|
|
||||||
data.content = {
|
|
||||||
href: attachment.href,
|
|
||||||
httpHeaders: attachment.httpHeaders
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
data.content = attachment.content || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.encoding) {
|
|
||||||
data.encoding = attachment.encoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attachment.headers) {
|
|
||||||
data.headers = attachment.headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.mail.icalEvent) {
|
|
||||||
if (
|
|
||||||
typeof this.mail.icalEvent === 'object' &&
|
|
||||||
(this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
|
|
||||||
) {
|
|
||||||
icalEvent = this.mail.icalEvent;
|
|
||||||
} else {
|
|
||||||
icalEvent = {
|
|
||||||
content: this.mail.icalEvent
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
eventObject = {};
|
|
||||||
Object.keys(icalEvent).forEach(key => {
|
|
||||||
eventObject[key] = icalEvent[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
eventObject.contentType = 'application/ics';
|
|
||||||
if (!eventObject.headers) {
|
|
||||||
eventObject.headers = {};
|
|
||||||
}
|
|
||||||
eventObject.filename = eventObject.filename || 'invite.ics';
|
|
||||||
eventObject.headers['Content-Disposition'] = 'attachment';
|
|
||||||
eventObject.headers['Content-Transfer-Encoding'] = 'base64';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!findRelated) {
|
|
||||||
return {
|
|
||||||
attached: attachments.concat(eventObject || []),
|
|
||||||
related: []
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
attached: attachments.filter(attachment => !attachment.cid).concat(eventObject || []),
|
|
||||||
related: attachments.filter(attachment => !!attachment.cid)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* List alternatives. Resulting objects can be used as input for MimeNode nodes
|
|
||||||
*
|
|
||||||
* @returns {Array} An array of alternative elements. Includes the `text` and `html` values as well
|
|
||||||
*/
|
|
||||||
getAlternatives() {
|
|
||||||
let alternatives = [],
|
|
||||||
text,
|
|
||||||
html,
|
|
||||||
watchHtml,
|
|
||||||
amp,
|
|
||||||
icalEvent,
|
|
||||||
eventObject;
|
|
||||||
|
|
||||||
if (this.mail.text) {
|
|
||||||
if (typeof this.mail.text === 'object' && (this.mail.text.content || this.mail.text.path || this.mail.text.href || this.mail.text.raw)) {
|
|
||||||
text = this.mail.text;
|
|
||||||
} else {
|
|
||||||
text = {
|
|
||||||
content: this.mail.text
|
|
||||||
};
|
|
||||||
}
|
|
||||||
text.contentType = 'text/plain; charset=utf-8';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mail.watchHtml) {
|
|
||||||
if (
|
|
||||||
typeof this.mail.watchHtml === 'object' &&
|
|
||||||
(this.mail.watchHtml.content || this.mail.watchHtml.path || this.mail.watchHtml.href || this.mail.watchHtml.raw)
|
|
||||||
) {
|
|
||||||
watchHtml = this.mail.watchHtml;
|
|
||||||
} else {
|
|
||||||
watchHtml = {
|
|
||||||
content: this.mail.watchHtml
|
|
||||||
};
|
|
||||||
}
|
|
||||||
watchHtml.contentType = 'text/watch-html; charset=utf-8';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mail.amp) {
|
|
||||||
if (typeof this.mail.amp === 'object' && (this.mail.amp.content || this.mail.amp.path || this.mail.amp.href || this.mail.amp.raw)) {
|
|
||||||
amp = this.mail.amp;
|
|
||||||
} else {
|
|
||||||
amp = {
|
|
||||||
content: this.mail.amp
|
|
||||||
};
|
|
||||||
}
|
|
||||||
amp.contentType = 'text/x-amp-html; charset=utf-8';
|
|
||||||
}
|
|
||||||
|
|
||||||
// NB! when including attachments with a calendar alternative you might end up in a blank screen on some clients
|
|
||||||
if (this.mail.icalEvent) {
|
|
||||||
if (
|
|
||||||
typeof this.mail.icalEvent === 'object' &&
|
|
||||||
(this.mail.icalEvent.content || this.mail.icalEvent.path || this.mail.icalEvent.href || this.mail.icalEvent.raw)
|
|
||||||
) {
|
|
||||||
icalEvent = this.mail.icalEvent;
|
|
||||||
} else {
|
|
||||||
icalEvent = {
|
|
||||||
content: this.mail.icalEvent
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
eventObject = {};
|
|
||||||
Object.keys(icalEvent).forEach(key => {
|
|
||||||
eventObject[key] = icalEvent[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
if (eventObject.content && typeof eventObject.content === 'object') {
|
|
||||||
// we are going to have the same attachment twice, so mark this to be
|
|
||||||
// resolved just once
|
|
||||||
eventObject.content._resolve = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
eventObject.filename = false;
|
|
||||||
eventObject.contentType = 'text/calendar; charset=utf-8; method=' + (eventObject.method || 'PUBLISH').toString().trim().toUpperCase();
|
|
||||||
if (!eventObject.headers) {
|
|
||||||
eventObject.headers = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.mail.html) {
|
|
||||||
if (typeof this.mail.html === 'object' && (this.mail.html.content || this.mail.html.path || this.mail.html.href || this.mail.html.raw)) {
|
|
||||||
html = this.mail.html;
|
|
||||||
} else {
|
|
||||||
html = {
|
|
||||||
content: this.mail.html
|
|
||||||
};
|
|
||||||
}
|
|
||||||
html.contentType = 'text/html; charset=utf-8';
|
|
||||||
}
|
|
||||||
|
|
||||||
[]
|
|
||||||
.concat(text || [])
|
|
||||||
.concat(watchHtml || [])
|
|
||||||
.concat(amp || [])
|
|
||||||
.concat(html || [])
|
|
||||||
.concat(eventObject || [])
|
|
||||||
.concat(this.mail.alternatives || [])
|
|
||||||
.forEach(alternative => {
|
|
||||||
let data;
|
|
||||||
|
|
||||||
if (/^data:/i.test(alternative.path || alternative.href)) {
|
|
||||||
alternative = this._processDataUrl(alternative);
|
|
||||||
}
|
|
||||||
|
|
||||||
data = {
|
|
||||||
contentType: alternative.contentType || mimeFuncs.detectMimeType(alternative.filename || alternative.path || alternative.href || 'txt'),
|
|
||||||
contentTransferEncoding: alternative.contentTransferEncoding
|
|
||||||
};
|
|
||||||
|
|
||||||
if (alternative.filename) {
|
|
||||||
data.filename = alternative.filename;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (/^https?:\/\//i.test(alternative.path)) {
|
|
||||||
alternative.href = alternative.path;
|
|
||||||
alternative.path = undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alternative.raw) {
|
|
||||||
data.raw = alternative.raw;
|
|
||||||
} else if (alternative.path) {
|
|
||||||
data.content = {
|
|
||||||
path: alternative.path
|
|
||||||
};
|
|
||||||
} else if (alternative.href) {
|
|
||||||
data.content = {
|
|
||||||
href: alternative.href
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
data.content = alternative.content || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alternative.encoding) {
|
|
||||||
data.encoding = alternative.encoding;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (alternative.headers) {
|
|
||||||
data.headers = alternative.headers;
|
|
||||||
}
|
|
||||||
|
|
||||||
alternatives.push(data);
|
|
||||||
});
|
|
||||||
|
|
||||||
return alternatives;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds multipart/mixed node. It should always contain different type of elements on the same level
|
|
||||||
* eg. text + attachments
|
|
||||||
*
|
|
||||||
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
||||||
* @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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this._useAlternative) {
|
|
||||||
this._createAlternative(node);
|
|
||||||
} else if (this._useRelated) {
|
|
||||||
this._createRelated(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
[]
|
|
||||||
.concat((!this._useAlternative && this._alternatives) || [])
|
|
||||||
.concat(this._attachments.attached || [])
|
|
||||||
.forEach(element => {
|
|
||||||
// if the element is a html node from related subpart then ignore it
|
|
||||||
if (!this._useRelated || element !== this._htmlNode) {
|
|
||||||
this._createContentNode(node, element);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds multipart/alternative node. It should always contain same type of elements on the same level
|
|
||||||
* eg. text + html view of the same data
|
|
||||||
*
|
|
||||||
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
||||||
* @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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._alternatives.forEach(alternative => {
|
|
||||||
if (this._useRelated && this._htmlNode === alternative) {
|
|
||||||
this._createRelated(node);
|
|
||||||
} else {
|
|
||||||
this._createContentNode(node, alternative);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Builds multipart/related node. It should always contain html node with related attachments
|
|
||||||
*
|
|
||||||
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
||||||
* @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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._createContentNode(node, this._htmlNode);
|
|
||||||
|
|
||||||
this._attachments.related.forEach(alternative => this._createContentNode(node, alternative));
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a regular node with contents
|
|
||||||
*
|
|
||||||
* @param {Object} parentNode Parent for this note. If it does not exist, a root node is created
|
|
||||||
* @param {Object} element Node data
|
|
||||||
* @returns {Object} MimeNode node element
|
|
||||||
*/
|
|
||||||
_createContentNode(parentNode, element) {
|
|
||||||
element = element || {};
|
|
||||||
element.content = element.content || '';
|
|
||||||
|
|
||||||
let node;
|
|
||||||
let 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
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// add custom headers
|
|
||||||
if (element.headers) {
|
|
||||||
node.addHeader(element.headers);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.cid) {
|
|
||||||
node.setHeader('Content-Id', '<' + element.cid.replace(/[<>]/g, '') + '>');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (element.contentTransferEncoding) {
|
|
||||||
node.setHeader('Content-Transfer-Encoding', element.contentTransferEncoding);
|
|
||||||
} else if (this.mail.encoding && /^text\//i.test(element.contentType)) {
|
|
||||||
node.setHeader('Content-Transfer-Encoding', this.mail.encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!/^text\//i.test(element.contentType) || element.contentDisposition) {
|
|
||||||
node.setHeader(
|
|
||||||
'Content-Disposition',
|
|
||||||
element.contentDisposition || (element.cid && /^image\//i.test(element.contentType) ? 'inline' : 'attachment')
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof element.content === 'string' && !['utf8', 'usascii', 'ascii'].includes(encoding)) {
|
|
||||||
element.content = Buffer.from(element.content, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
// prefer pregenerated raw content
|
|
||||||
if (element.raw) {
|
|
||||||
node.setRaw(element.raw);
|
|
||||||
} else {
|
|
||||||
node.setContent(element.content);
|
|
||||||
}
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses data uri and converts it to a Buffer
|
|
||||||
*
|
|
||||||
* @param {Object} element Content element
|
|
||||||
* @return {Object} Parsed element
|
|
||||||
*/
|
|
||||||
_processDataUrl(element) {
|
|
||||||
let parsedDataUri;
|
|
||||||
if ((element.path || element.href).match(/^data:/)) {
|
|
||||||
parsedDataUri = parseDataURI(element.path || element.href);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!parsedDataUri) {
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
element.content = parsedDataUri.data;
|
|
||||||
element.contentType = element.contentType || parsedDataUri.contentType;
|
|
||||||
|
|
||||||
if ('path' in element) {
|
|
||||||
element.path = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ('href' in element) {
|
|
||||||
element.href = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MailComposer;
|
|
||||||
429
lib/node/node_modules/nodemailer/lib/mailer/index.js
generated
vendored
429
lib/node/node_modules/nodemailer/lib/mailer/index.js
generated
vendored
|
|
@ -1,429 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
|
||||||
const shared = require('../shared');
|
|
||||||
const mimeTypes = require('../mime-funcs/mime-types');
|
|
||||||
const MailComposer = require('../mail-composer');
|
|
||||||
const DKIM = require('../dkim');
|
|
||||||
const httpProxyClient = require('../smtp-connection/http-proxy-client');
|
|
||||||
const util = require('util');
|
|
||||||
const urllib = require('url');
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
const MailMessage = require('./mail-message');
|
|
||||||
const net = require('net');
|
|
||||||
const dns = require('dns');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an object for exposing the Mail API
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} transporter Transport object instance to pass the mails to
|
|
||||||
*/
|
|
||||||
class Mail extends EventEmitter {
|
|
||||||
constructor(transporter, options, defaults) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.options = options || {};
|
|
||||||
this._defaults = defaults || {};
|
|
||||||
|
|
||||||
this._defaultPlugins = {
|
|
||||||
compile: [(...args) => this._convertDataImages(...args)],
|
|
||||||
stream: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this._userPlugins = {
|
|
||||||
compile: [],
|
|
||||||
stream: []
|
|
||||||
};
|
|
||||||
|
|
||||||
this.meta = new Map();
|
|
||||||
|
|
||||||
this.dkim = this.options.dkim ? new DKIM(this.options.dkim) : false;
|
|
||||||
|
|
||||||
this.transporter = transporter;
|
|
||||||
this.transporter.mailer = this;
|
|
||||||
|
|
||||||
this.logger = shared.getLogger(this.options, {
|
|
||||||
component: this.options.component || 'mail'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'create'
|
|
||||||
},
|
|
||||||
'Creating transport: %s',
|
|
||||||
this.getVersionString()
|
|
||||||
);
|
|
||||||
|
|
||||||
// setup emit handlers for the transporter
|
|
||||||
if (typeof this.transporter.on === 'function') {
|
|
||||||
// deprecated log interface
|
|
||||||
this.transporter.on('log', log => {
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'transport'
|
|
||||||
},
|
|
||||||
'%s: %s',
|
|
||||||
log.type,
|
|
||||||
log.message
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// transporter errors
|
|
||||||
this.transporter.on('error', err => {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'transport'
|
|
||||||
},
|
|
||||||
'Transport Error: %s',
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
this.emit('error', err);
|
|
||||||
});
|
|
||||||
|
|
||||||
// indicates if the sender has became idle
|
|
||||||
this.transporter.on('idle', (...args) => {
|
|
||||||
this.emit('idle', ...args);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Optional methods passed to the underlying transport object
|
|
||||||
*/
|
|
||||||
['close', 'isIdle', 'verify'].forEach(method => {
|
|
||||||
this[method] = (...args) => {
|
|
||||||
if (typeof this.transporter[method] === 'function') {
|
|
||||||
if (method === 'verify' && typeof this.getSocket === 'function') {
|
|
||||||
this.transporter.getSocket = this.getSocket;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// setup proxy handling
|
|
||||||
if (this.options.proxy && typeof this.options.proxy === 'string') {
|
|
||||||
this.setupProxy(this.options.proxy);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
use(step, plugin) {
|
|
||||||
step = (step || '').toString();
|
|
||||||
if (!this._userPlugins.hasOwnProperty(step)) {
|
|
||||||
this._userPlugins[step] = [plugin];
|
|
||||||
} else {
|
|
||||||
this._userPlugins[step].push(plugin);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an email using the preselected transport object
|
|
||||||
*
|
|
||||||
* @param {Object} data E-data description
|
|
||||||
* @param {Function?} callback Callback to run once the sending succeeded or failed
|
|
||||||
*/
|
|
||||||
sendMail(data, callback = null) {
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
promise = new Promise((resolve, reject) => {
|
|
||||||
callback = shared.callbackPromise(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof this.getSocket === 'function') {
|
|
||||||
this.transporter.getSocket = this.getSocket;
|
|
||||||
this.getSocket = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mail = new MailMessage(this, data);
|
|
||||||
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'transport',
|
|
||||||
name: this.transporter.name,
|
|
||||||
version: this.transporter.version,
|
|
||||||
action: 'send'
|
|
||||||
},
|
|
||||||
'Sending mail using %s/%s',
|
|
||||||
this.transporter.name,
|
|
||||||
this.transporter.version
|
|
||||||
);
|
|
||||||
|
|
||||||
this._processPlugins('compile', mail, err => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'plugin',
|
|
||||||
action: 'compile'
|
|
||||||
},
|
|
||||||
'PluginCompile Error: %s',
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
mail.message = new MailComposer(mail.data).compile();
|
|
||||||
|
|
||||||
mail.setMailerHeader();
|
|
||||||
mail.setPriorityHeaders();
|
|
||||||
mail.setListHeaders();
|
|
||||||
|
|
||||||
this._processPlugins('stream', mail, err => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'plugin',
|
|
||||||
action: 'stream'
|
|
||||||
},
|
|
||||||
'PluginStream Error: %s',
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mail.data.dkim || this.dkim) {
|
|
||||||
mail.message.processFunc(input => {
|
|
||||||
let dkim = mail.data.dkim ? new DKIM(mail.data.dkim) : this.dkim;
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'DKIM',
|
|
||||||
messageId: mail.message.messageId(),
|
|
||||||
dkimDomains: dkim.keys.map(key => key.keySelector + '.' + key.domainName).join(', ')
|
|
||||||
},
|
|
||||||
'Signing outgoing message with %s keys',
|
|
||||||
dkim.keys.length
|
|
||||||
);
|
|
||||||
return dkim.sign(input, mail.data._dkim);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.transporter.send(mail, (...args) => {
|
|
||||||
if (args[0]) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: args[0],
|
|
||||||
tnx: 'transport',
|
|
||||||
action: 'send'
|
|
||||||
},
|
|
||||||
'Send Error: %s',
|
|
||||||
args[0].message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
callback(...args);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
getVersionString() {
|
|
||||||
return util.format('%s (%s; +%s; %s/%s)', packageData.name, packageData.version, packageData.homepage, this.transporter.name, this.transporter.version);
|
|
||||||
}
|
|
||||||
|
|
||||||
_processPlugins(step, mail, callback) {
|
|
||||||
step = (step || '').toString();
|
|
||||||
|
|
||||||
if (!this._userPlugins.hasOwnProperty(step)) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
let userPlugins = this._userPlugins[step] || [];
|
|
||||||
let defaultPlugins = this._defaultPlugins[step] || [];
|
|
||||||
|
|
||||||
if (userPlugins.length) {
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'transaction',
|
|
||||||
pluginCount: userPlugins.length,
|
|
||||||
step
|
|
||||||
},
|
|
||||||
'Using %s plugins for %s',
|
|
||||||
userPlugins.length,
|
|
||||||
step
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userPlugins.length + defaultPlugins.length === 0) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
let pos = 0;
|
|
||||||
let block = 'default';
|
|
||||||
let processPlugins = () => {
|
|
||||||
let curplugins = block === 'default' ? defaultPlugins : userPlugins;
|
|
||||||
if (pos >= curplugins.length) {
|
|
||||||
if (block === 'default' && userPlugins.length) {
|
|
||||||
block = 'user';
|
|
||||||
pos = 0;
|
|
||||||
curplugins = userPlugins;
|
|
||||||
} else {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let plugin = curplugins[pos++];
|
|
||||||
plugin(mail, err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
processPlugins();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
processPlugins();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up proxy handler for a Nodemailer object
|
|
||||||
*
|
|
||||||
* @param {String} proxyUrl Proxy configuration url
|
|
||||||
*/
|
|
||||||
setupProxy(proxyUrl) {
|
|
||||||
let proxy = urllib.parse(proxyUrl);
|
|
||||||
|
|
||||||
// setup socket handler for the mailer object
|
|
||||||
this.getSocket = (options, callback) => {
|
|
||||||
let protocol = proxy.protocol.replace(/:$/, '').toLowerCase();
|
|
||||||
|
|
||||||
if (this.meta.has('proxy_handler_' + protocol)) {
|
|
||||||
return this.meta.get('proxy_handler_' + protocol)(proxy, options, callback);
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (protocol) {
|
|
||||||
// Connect using a HTTP CONNECT method
|
|
||||||
case 'http':
|
|
||||||
case 'https':
|
|
||||||
httpProxyClient(proxy.href, options.port, options.host, (err, socket) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, {
|
|
||||||
connection: socket
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
case 'socks':
|
|
||||||
case 'socks5':
|
|
||||||
case 'socks4':
|
|
||||||
case 'socks4a': {
|
|
||||||
if (!this.meta.has('proxy_socks_module')) {
|
|
||||||
return callback(new Error('Socks module not loaded'));
|
|
||||||
}
|
|
||||||
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 = {
|
|
||||||
proxy: {
|
|
||||||
ipaddress,
|
|
||||||
port: Number(proxy.port),
|
|
||||||
type: proxyType
|
|
||||||
},
|
|
||||||
[proxyV2 ? 'destination' : 'target']: {
|
|
||||||
host: options.host,
|
|
||||||
port: options.port
|
|
||||||
},
|
|
||||||
command: 'connect'
|
|
||||||
};
|
|
||||||
|
|
||||||
if (proxy.auth) {
|
|
||||||
let username = decodeURIComponent(proxy.auth.split(':').shift());
|
|
||||||
let password = decodeURIComponent(proxy.auth.split(':').pop());
|
|
||||||
if (proxyV2) {
|
|
||||||
connectionOpts.proxy.userId = username;
|
|
||||||
connectionOpts.proxy.password = password;
|
|
||||||
} else if (proxyType === 4) {
|
|
||||||
connectionOpts.userid = username;
|
|
||||||
} else {
|
|
||||||
connectionOpts.authentication = {
|
|
||||||
username,
|
|
||||||
password
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socksClient.createConnection(connectionOpts, (err, info) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, {
|
|
||||||
connection: info.socket || info
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
if (net.isIP(proxy.hostname)) {
|
|
||||||
return connect(proxy.hostname);
|
|
||||||
}
|
|
||||||
|
|
||||||
return dns.resolve(proxy.hostname, (err, address) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
connect(Array.isArray(address) ? address[0] : address);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
callback(new Error('Unknown proxy configuration'));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
_convertDataImages(mail, callback) {
|
|
||||||
if ((!this.options.attachDataUrls && !mail.data.attachDataUrls) || !mail.data.html) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
mail.resolveContent(mail.data, 'html', (err, html) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
let cidCounter = 0;
|
|
||||||
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';
|
|
||||||
if (!mail.data.attachments) {
|
|
||||||
mail.data.attachments = [];
|
|
||||||
}
|
|
||||||
if (!Array.isArray(mail.data.attachments)) {
|
|
||||||
mail.data.attachments = [].concat(mail.data.attachments || []);
|
|
||||||
}
|
|
||||||
mail.data.attachments.push({
|
|
||||||
path: dataUri,
|
|
||||||
cid,
|
|
||||||
filename: 'image-' + ++cidCounter + '.' + mimeTypes.detectExtension(mimeType)
|
|
||||||
});
|
|
||||||
return prefix + 'cid:' + cid;
|
|
||||||
});
|
|
||||||
mail.data.html = html;
|
|
||||||
callback();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key, value) {
|
|
||||||
return this.meta.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
get(key) {
|
|
||||||
return this.meta.get(key);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = Mail;
|
|
||||||
315
lib/node/node_modules/nodemailer/lib/mailer/mail-message.js
generated
vendored
315
lib/node/node_modules/nodemailer/lib/mailer/mail-message.js
generated
vendored
|
|
@ -1,315 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const shared = require('../shared');
|
|
||||||
const MimeNode = require('../mime-node');
|
|
||||||
const mimeFuncs = require('../mime-funcs');
|
|
||||||
|
|
||||||
class MailMessage {
|
|
||||||
constructor(mailer, data) {
|
|
||||||
this.mailer = mailer;
|
|
||||||
this.data = {};
|
|
||||||
this.message = null;
|
|
||||||
|
|
||||||
data = data || {};
|
|
||||||
let options = mailer.options || {};
|
|
||||||
let defaults = mailer._defaults || {};
|
|
||||||
|
|
||||||
Object.keys(data).forEach(key => {
|
|
||||||
this.data[key] = data[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
this.data.headers = this.data.headers || {};
|
|
||||||
|
|
||||||
// apply defaults
|
|
||||||
Object.keys(defaults).forEach(key => {
|
|
||||||
if (!(key in this.data)) {
|
|
||||||
this.data[key] = defaults[key];
|
|
||||||
} else if (key === 'headers') {
|
|
||||||
// headers is a special case. Allow setting individual default headers
|
|
||||||
Object.keys(defaults.headers).forEach(key => {
|
|
||||||
if (!(key in this.data.headers)) {
|
|
||||||
this.data.headers[key] = defaults.headers[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// force specific keys from transporter options
|
|
||||||
['disableFileAccess', 'disableUrlAccess', 'normalizeHeaderKey'].forEach(key => {
|
|
||||||
if (key in options) {
|
|
||||||
this.data[key] = options[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveContent(...args) {
|
|
||||||
return shared.resolveContent(...args);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolveAll(callback) {
|
|
||||||
let keys = [
|
|
||||||
[this.data, 'html'],
|
|
||||||
[this.data, 'text'],
|
|
||||||
[this.data, 'watchHtml'],
|
|
||||||
[this.data, 'amp'],
|
|
||||||
[this.data, 'icalEvent']
|
|
||||||
];
|
|
||||||
|
|
||||||
if (this.data.alternatives && this.data.alternatives.length) {
|
|
||||||
this.data.alternatives.forEach((alternative, i) => {
|
|
||||||
keys.push([this.data.alternatives, i]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.data.attachments && this.data.attachments.length) {
|
|
||||||
this.data.attachments.forEach((attachment, i) => {
|
|
||||||
if (!attachment.filename) {
|
|
||||||
attachment.filename = (attachment.path || attachment.href || '').split('/').pop().split('?').shift() || 'attachment-' + (i + 1);
|
|
||||||
if (attachment.filename.indexOf('.') < 0) {
|
|
||||||
attachment.filename += '.' + mimeFuncs.detectExtension(attachment.contentType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!attachment.contentType) {
|
|
||||||
attachment.contentType = mimeFuncs.detectMimeType(attachment.filename || attachment.path || attachment.href || 'bin');
|
|
||||||
}
|
|
||||||
|
|
||||||
keys.push([this.data.attachments, i]);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let mimeNode = new MimeNode();
|
|
||||||
|
|
||||||
let addressKeys = ['from', 'to', 'cc', 'bcc', 'sender', 'replyTo'];
|
|
||||||
|
|
||||||
addressKeys.forEach(address => {
|
|
||||||
let value;
|
|
||||||
if (this.message) {
|
|
||||||
value = [].concat(mimeNode._parseAddresses(this.message.getHeader(address === 'replyTo' ? 'reply-to' : address)) || []);
|
|
||||||
} else if (this.data[address]) {
|
|
||||||
value = [].concat(mimeNode._parseAddresses(this.data[address]) || []);
|
|
||||||
}
|
|
||||||
if (value && value.length) {
|
|
||||||
this.data[address] = value;
|
|
||||||
} else if (address in this.data) {
|
|
||||||
this.data[address] = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let singleKeys = ['from', 'sender'];
|
|
||||||
singleKeys.forEach(address => {
|
|
||||||
if (this.data[address]) {
|
|
||||||
this.data[address] = this.data[address].shift();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let pos = 0;
|
|
||||||
let resolveNext = () => {
|
|
||||||
if (pos >= keys.length) {
|
|
||||||
return callback(null, this.data);
|
|
||||||
}
|
|
||||||
let args = keys[pos++];
|
|
||||||
if (!args[0] || !args[0][args[1]]) {
|
|
||||||
return resolveNext();
|
|
||||||
}
|
|
||||||
shared.resolveContent(...args, (err, value) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let node = {
|
|
||||||
content: value
|
|
||||||
};
|
|
||||||
if (args[0][args[1]] && typeof args[0][args[1]] === 'object' && !Buffer.isBuffer(args[0][args[1]])) {
|
|
||||||
Object.keys(args[0][args[1]]).forEach(key => {
|
|
||||||
if (!(key in node) && !['content', 'path', 'href', 'raw'].includes(key)) {
|
|
||||||
node[key] = args[0][args[1]][key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
args[0][args[1]] = node;
|
|
||||||
resolveNext();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
setImmediate(() => resolveNext());
|
|
||||||
}
|
|
||||||
|
|
||||||
normalize(callback) {
|
|
||||||
let envelope = this.data.envelope || this.message.getEnvelope();
|
|
||||||
let messageId = this.message.messageId();
|
|
||||||
|
|
||||||
this.resolveAll((err, data) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
data.envelope = envelope;
|
|
||||||
data.messageId = messageId;
|
|
||||||
|
|
||||||
['html', 'text', 'watchHtml', 'amp'].forEach(key => {
|
|
||||||
if (data[key] && data[key].content) {
|
|
||||||
if (typeof data[key].content === 'string') {
|
|
||||||
data[key] = data[key].content;
|
|
||||||
} else if (Buffer.isBuffer(data[key].content)) {
|
|
||||||
data[key] = data[key].content.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.icalEvent && Buffer.isBuffer(data.icalEvent.content)) {
|
|
||||||
data.icalEvent.content = data.icalEvent.content.toString('base64');
|
|
||||||
data.icalEvent.encoding = 'base64';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.alternatives && data.alternatives.length) {
|
|
||||||
data.alternatives.forEach(alternative => {
|
|
||||||
if (alternative && alternative.content && Buffer.isBuffer(alternative.content)) {
|
|
||||||
alternative.content = alternative.content.toString('base64');
|
|
||||||
alternative.encoding = 'base64';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.attachments && data.attachments.length) {
|
|
||||||
data.attachments.forEach(attachment => {
|
|
||||||
if (attachment && attachment.content && Buffer.isBuffer(attachment.content)) {
|
|
||||||
attachment.content = attachment.content.toString('base64');
|
|
||||||
attachment.encoding = 'base64';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
data.normalizedHeaders = {};
|
|
||||||
Object.keys(data.headers || {}).forEach(key => {
|
|
||||||
let value = [].concat(data.headers[key] || []).shift();
|
|
||||||
value = (value && value.value) || value;
|
|
||||||
if (value) {
|
|
||||||
if (['references', 'in-reply-to', 'message-id', 'content-id'].includes(key)) {
|
|
||||||
value = this.message._encodeHeaderValue(key, value);
|
|
||||||
}
|
|
||||||
data.normalizedHeaders[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (data.list && typeof data.list === 'object') {
|
|
||||||
let listHeaders = this._getListHeaders(data.list);
|
|
||||||
listHeaders.forEach(entry => {
|
|
||||||
data.normalizedHeaders[entry.key] = entry.value.map(val => (val && val.value) || val).join(', ');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.references) {
|
|
||||||
data.normalizedHeaders.references = this.message._encodeHeaderValue('references', data.references);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.inReplyTo) {
|
|
||||||
data.normalizedHeaders['in-reply-to'] = this.message._encodeHeaderValue('in-reply-to', data.inReplyTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(null, data);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
setMailerHeader() {
|
|
||||||
if (!this.message || !this.data.xMailer) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.message.setHeader('X-Mailer', this.data.xMailer);
|
|
||||||
}
|
|
||||||
|
|
||||||
setPriorityHeaders() {
|
|
||||||
if (!this.message || !this.data.priority) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
switch ((this.data.priority || '').toString().toLowerCase()) {
|
|
||||||
case 'high':
|
|
||||||
this.message.setHeader('X-Priority', '1 (Highest)');
|
|
||||||
this.message.setHeader('X-MSMail-Priority', 'High');
|
|
||||||
this.message.setHeader('Importance', 'High');
|
|
||||||
break;
|
|
||||||
case 'low':
|
|
||||||
this.message.setHeader('X-Priority', '5 (Lowest)');
|
|
||||||
this.message.setHeader('X-MSMail-Priority', 'Low');
|
|
||||||
this.message.setHeader('Importance', 'Low');
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// do not add anything, since all messages are 'Normal' by default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setListHeaders() {
|
|
||||||
if (!this.message || !this.data.list || typeof this.data.list !== 'object') {
|
|
||||||
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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_getListHeaders(listData) {
|
|
||||||
// make sure an url looks like <protocol:url>
|
|
||||||
return Object.keys(listData).map(key => ({
|
|
||||||
key: 'list-' + key.toLowerCase().trim(),
|
|
||||||
value: [].concat(listData[key] || []).map(value => ({
|
|
||||||
prepared: true,
|
|
||||||
foldLines: true,
|
|
||||||
value: []
|
|
||||||
.concat(value || [])
|
|
||||||
.map(value => {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
value = {
|
|
||||||
url: value
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value && value.url) {
|
|
||||||
if (key.toLowerCase().trim() === 'id') {
|
|
||||||
// List-ID: "comment" <domain>
|
|
||||||
let comment = value.comment || '';
|
|
||||||
if (mimeFuncs.isPlainText(comment)) {
|
|
||||||
comment = '"' + comment + '"';
|
|
||||||
} else {
|
|
||||||
comment = mimeFuncs.encodeWord(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (value.comment ? comment + ' ' : '') + this._formatListUrl(value.url).replace(/^<[^:]+\/{,2}/, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// List-*: <http://domain> (comment)
|
|
||||||
let comment = value.comment || '';
|
|
||||||
if (!mimeFuncs.isPlainText(comment)) {
|
|
||||||
comment = mimeFuncs.encodeWord(comment);
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._formatListUrl(value.url) + (value.comment ? ' (' + comment + ')' : '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return '';
|
|
||||||
})
|
|
||||||
.filter(value => value)
|
|
||||||
.join(', ')
|
|
||||||
}))
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_formatListUrl(url) {
|
|
||||||
url = url.replace(/[\s<]+|[\s>]+/g, '');
|
|
||||||
if (/^(https?|mailto|ftp):/.test(url)) {
|
|
||||||
return '<' + url + '>';
|
|
||||||
}
|
|
||||||
if (/^[^@]+@[^@]+$/.test(url)) {
|
|
||||||
return '<mailto:' + url + '>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return '<http://' + url + '>';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = MailMessage;
|
|
||||||
625
lib/node/node_modules/nodemailer/lib/mime-funcs/index.js
generated
vendored
625
lib/node/node_modules/nodemailer/lib/mime-funcs/index.js
generated
vendored
|
|
@ -1,625 +0,0 @@
|
||||||
/* eslint no-control-regex:0 */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const base64 = require('../base64');
|
|
||||||
const qp = require('../qp');
|
|
||||||
const mimeTypes = require('./mime-types');
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
/**
|
|
||||||
* Checks if a value is plaintext string (uses only printable 7bit chars)
|
|
||||||
*
|
|
||||||
* @param {String} value String to be tested
|
|
||||||
* @returns {Boolean} true if it is a plaintext string
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a multi line string containes lines longer than the selected value.
|
|
||||||
*
|
|
||||||
* Useful when detecting if a mail message needs any processing at all –
|
|
||||||
* if only plaintext characters are used and lines are short, then there is
|
|
||||||
* no need to encode the values in any way. If the value is plaintext but has
|
|
||||||
* longer lines then allowed, then use format=flowed
|
|
||||||
*
|
|
||||||
* @param {Number} lineLength Max line length to check for
|
|
||||||
* @returns {Boolean} Returns true if there is at least one line longer than lineLength chars
|
|
||||||
*/
|
|
||||||
hasLongerLines(str, lineLength) {
|
|
||||||
if (str.length > 128 * 1024) {
|
|
||||||
// do not test strings longer than 128kB
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return new RegExp('^.{' + (lineLength + 1) + ',}', 'm').test(str);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a string or an Buffer to an UTF-8 MIME Word (rfc2047)
|
|
||||||
*
|
|
||||||
* @param {String|Buffer} data String to be encoded
|
|
||||||
* @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
|
|
||||||
* @param {Number} [maxLength=0] If set, split mime words into several chunks if needed
|
|
||||||
* @return {String} Single or several mime words joined together
|
|
||||||
*/
|
|
||||||
encodeWord(data, mimeWordEncoding, maxLength) {
|
|
||||||
mimeWordEncoding = (mimeWordEncoding || 'Q').toString().toUpperCase().trim().charAt(0);
|
|
||||||
maxLength = maxLength || 0;
|
|
||||||
|
|
||||||
let encodedStr;
|
|
||||||
let toCharset = 'UTF-8';
|
|
||||||
|
|
||||||
if (maxLength && maxLength > 7 + toCharset.length) {
|
|
||||||
maxLength -= 7 + toCharset.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
if (chr === ' ') {
|
|
||||||
return '_';
|
|
||||||
} else {
|
|
||||||
return '=' + (ord.length === 1 ? '0' + ord : ord);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (mimeWordEncoding === 'B') {
|
|
||||||
encodedStr = typeof data === 'string' ? data : base64.encode(data);
|
|
||||||
maxLength = maxLength ? Math.max(3, ((maxLength - (maxLength % 4)) / 4) * 3) : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxLength && (mimeWordEncoding !== 'B' ? encodedStr : base64.encode(data)).length > maxLength) {
|
|
||||||
if (mimeWordEncoding === 'Q') {
|
|
||||||
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 = [];
|
|
||||||
let lpart = '';
|
|
||||||
for (let i = 0, len = encodedStr.length; i < len; i++) {
|
|
||||||
let chr = encodedStr.charAt(i);
|
|
||||||
|
|
||||||
if (/[\ud83c\ud83d\ud83e]/.test(chr) && i < len - 1) {
|
|
||||||
// composite emoji byte, so add the next byte as well
|
|
||||||
chr += encodedStr.charAt(++i);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if we can add this character to the existing string
|
|
||||||
// without breaking byte length limit
|
|
||||||
if (Buffer.byteLength(lpart + chr) <= maxLength || i === 0) {
|
|
||||||
lpart += chr;
|
|
||||||
} else {
|
|
||||||
// we hit the length limit, so push the existing string and start over
|
|
||||||
parts.push(base64.encode(lpart));
|
|
||||||
lpart = chr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lpart) {
|
|
||||||
parts.push(base64.encode(lpart));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parts.length > 1) {
|
|
||||||
encodedStr = parts.join('?= =?' + toCharset + '?' + mimeWordEncoding + '?');
|
|
||||||
} else {
|
|
||||||
encodedStr = parts.join('');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (mimeWordEncoding === 'B') {
|
|
||||||
encodedStr = base64.encode(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return '=?' + toCharset + '?' + mimeWordEncoding + '?' + encodedStr + (encodedStr.substr(-2) === '?=' ? '' : '?=');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finds word sequences with non ascii text and converts these to mime words
|
|
||||||
*
|
|
||||||
* @param {String} value String to be encoded
|
|
||||||
* @param {String} mimeWordEncoding='Q' Encoding for the mime word, either Q or B
|
|
||||||
* @param {Number} [maxLength=0] If set, split mime words into several chunks if needed
|
|
||||||
* @param {Boolean} [encodeAll=false] If true and the value needs encoding then encodes entire string, not just the smallest match
|
|
||||||
* @return {String} String with possible mime words
|
|
||||||
*/
|
|
||||||
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])/);
|
|
||||||
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]*$/);
|
|
||||||
if (!lastMatch) {
|
|
||||||
// should not happen
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let startIndex =
|
|
||||||
firstMatch.index +
|
|
||||||
(
|
|
||||||
firstMatch[0].match(/[^\s]/) || {
|
|
||||||
index: 0
|
|
||||||
}
|
|
||||||
).index;
|
|
||||||
let endIndex = lastMatch.index + (lastMatch[1] || '').length;
|
|
||||||
|
|
||||||
encodedValue =
|
|
||||||
(startIndex ? value.substr(0, startIndex) : '') +
|
|
||||||
this.encodeWord(value.substring(startIndex, endIndex), mimeWordEncoding || 'Q', maxLength) +
|
|
||||||
(endIndex < value.length ? value.substr(endIndex) : '');
|
|
||||||
|
|
||||||
return encodedValue;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Joins parsed header value together as 'value; param1=value1; param2=value2'
|
|
||||||
* PS: We are following RFC 822 for the list of special characters that we need to keep in quotes.
|
|
||||||
* Refer: https://www.w3.org/Protocols/rfc1341/4_Content-Type.html
|
|
||||||
* @param {Object} structured Parsed header value
|
|
||||||
* @return {String} joined header value
|
|
||||||
*/
|
|
||||||
buildHeaderValue(structured) {
|
|
||||||
let 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];
|
|
||||||
if (!this.isPlainText(value, true) || value.length >= 75) {
|
|
||||||
this.buildHeaderParam(param, value, 50).forEach(encodedParam => {
|
|
||||||
if (!/[\s"\\;:/=(),<>@[\]?]|^[-']|'$/.test(encodedParam.value) || encodedParam.key.substr(-1) === '*') {
|
|
||||||
paramsArray.push(encodedParam.key + '=' + encodedParam.value);
|
|
||||||
} else {
|
|
||||||
paramsArray.push(encodedParam.key + '=' + JSON.stringify(encodedParam.value));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else if (/[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
|
|
||||||
paramsArray.push(param + '=' + JSON.stringify(value));
|
|
||||||
} else {
|
|
||||||
paramsArray.push(param + '=' + value);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return structured.value + (paramsArray.length ? '; ' + paramsArray.join('; ') : '');
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a string or an Buffer to an UTF-8 Parameter Value Continuation encoding (rfc2231)
|
|
||||||
* Useful for splitting long parameter values.
|
|
||||||
*
|
|
||||||
* For example
|
|
||||||
* title="unicode string"
|
|
||||||
* becomes
|
|
||||||
* title*0*=utf-8''unicode
|
|
||||||
* title*1*=%20string
|
|
||||||
*
|
|
||||||
* @param {String|Buffer} data String to be encoded
|
|
||||||
* @param {Number} [maxLength=50] Max length for generated chunks
|
|
||||||
* @param {String} [fromCharset='UTF-8'] Source sharacter set
|
|
||||||
* @return {Array} A list of encoded keys and headers
|
|
||||||
*/
|
|
||||||
buildHeaderParam(key, data, maxLength) {
|
|
||||||
let list = [];
|
|
||||||
let encodedStr = typeof data === 'string' ? data : (data || '').toString();
|
|
||||||
let encodedStrArr;
|
|
||||||
let chr, ord;
|
|
||||||
let line;
|
|
||||||
let startPos = 0;
|
|
||||||
let i, len;
|
|
||||||
|
|
||||||
maxLength = maxLength || 50;
|
|
||||||
|
|
||||||
// process ascii only text
|
|
||||||
if (this.isPlainText(data, true)) {
|
|
||||||
// check if conversion is even needed
|
|
||||||
if (encodedStr.length <= maxLength) {
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
key,
|
|
||||||
value: encodedStr
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
encodedStr = encodedStr.replace(new RegExp('.{' + maxLength + '}', 'g'), str => {
|
|
||||||
list.push({
|
|
||||||
line: str
|
|
||||||
});
|
|
||||||
return '';
|
|
||||||
});
|
|
||||||
|
|
||||||
if (encodedStr) {
|
|
||||||
list.push({
|
|
||||||
line: encodedStr
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (/[\uD800-\uDBFF]/.test(encodedStr)) {
|
|
||||||
// string containts surrogate pairs, so normalize it to an array of bytes
|
|
||||||
encodedStrArr = [];
|
|
||||||
for (i = 0, len = encodedStr.length; i < len; i++) {
|
|
||||||
chr = encodedStr.charAt(i);
|
|
||||||
ord = chr.charCodeAt(0);
|
|
||||||
if (ord >= 0xd800 && ord <= 0xdbff && i < len - 1) {
|
|
||||||
chr += encodedStr.charAt(i + 1);
|
|
||||||
encodedStrArr.push(chr);
|
|
||||||
i++;
|
|
||||||
} else {
|
|
||||||
encodedStrArr.push(chr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
encodedStr = encodedStrArr;
|
|
||||||
}
|
|
||||||
|
|
||||||
// first line includes the charset and language info and needs to be encoded
|
|
||||||
// even if it does not contain any unicode characters
|
|
||||||
line = 'utf-8\x27\x27';
|
|
||||||
let encoded = true;
|
|
||||||
startPos = 0;
|
|
||||||
|
|
||||||
// process text with unicode or special chars
|
|
||||||
for (i = 0, len = encodedStr.length; i < len; i++) {
|
|
||||||
chr = encodedStr[i];
|
|
||||||
|
|
||||||
if (encoded) {
|
|
||||||
chr = this.safeEncodeURIComponent(chr);
|
|
||||||
} else {
|
|
||||||
// try to urlencode current char
|
|
||||||
chr = chr === ' ' ? chr : this.safeEncodeURIComponent(chr);
|
|
||||||
// By default it is not required to encode a line, the need
|
|
||||||
// only appears when the string contains unicode or special chars
|
|
||||||
// in this case we start processing the line over and encode all chars
|
|
||||||
if (chr !== encodedStr[i]) {
|
|
||||||
// Check if it is even possible to add the encoded char to the line
|
|
||||||
// If not, there is no reason to use this line, just push it to the list
|
|
||||||
// and start a new line with the char that needs encoding
|
|
||||||
if ((this.safeEncodeURIComponent(line) + chr).length >= maxLength) {
|
|
||||||
list.push({
|
|
||||||
line,
|
|
||||||
encoded
|
|
||||||
});
|
|
||||||
line = '';
|
|
||||||
startPos = i - 1;
|
|
||||||
} else {
|
|
||||||
encoded = true;
|
|
||||||
i = startPos;
|
|
||||||
line = '';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if the line is already too long, push it to the list and start a new one
|
|
||||||
if ((line + chr).length >= maxLength) {
|
|
||||||
list.push({
|
|
||||||
line,
|
|
||||||
encoded
|
|
||||||
});
|
|
||||||
line = chr = encodedStr[i] === ' ' ? ' ' : this.safeEncodeURIComponent(encodedStr[i]);
|
|
||||||
if (chr === encodedStr[i]) {
|
|
||||||
encoded = false;
|
|
||||||
startPos = i - 1;
|
|
||||||
} else {
|
|
||||||
encoded = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
line += chr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line) {
|
|
||||||
list.push({
|
|
||||||
line,
|
|
||||||
encoded
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list.map((item, i) => ({
|
|
||||||
// encoded lines: {name}*{part}*
|
|
||||||
// unencoded lines: {name}*{part}
|
|
||||||
// if any line needs to be encoded then the first line (part==0) is always encoded
|
|
||||||
key: key + '*' + i + (item.encoded ? '*' : ''),
|
|
||||||
value: item.line
|
|
||||||
}));
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parses a header value with key=value arguments into a structured
|
|
||||||
* object.
|
|
||||||
*
|
|
||||||
* parseHeaderValue('content-type: text/plain; CHARSET='UTF-8'') ->
|
|
||||||
* {
|
|
||||||
* 'value': 'text/plain',
|
|
||||||
* 'params': {
|
|
||||||
* 'charset': 'UTF-8'
|
|
||||||
* }
|
|
||||||
* }
|
|
||||||
*
|
|
||||||
* @param {String} str Header value
|
|
||||||
* @return {Object} Header value as a parsed structure
|
|
||||||
*/
|
|
||||||
parseHeaderValue(str) {
|
|
||||||
let response = {
|
|
||||||
value: false,
|
|
||||||
params: {}
|
|
||||||
};
|
|
||||||
let key = false;
|
|
||||||
let value = '';
|
|
||||||
let type = 'value';
|
|
||||||
let quote = false;
|
|
||||||
let escaped = false;
|
|
||||||
let chr;
|
|
||||||
|
|
||||||
for (let i = 0, len = str.length; i < len; i++) {
|
|
||||||
chr = str.charAt(i);
|
|
||||||
if (type === 'key') {
|
|
||||||
if (chr === '=') {
|
|
||||||
key = value.trim().toLowerCase();
|
|
||||||
type = 'value';
|
|
||||||
value = '';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
value += chr;
|
|
||||||
} else {
|
|
||||||
if (escaped) {
|
|
||||||
value += chr;
|
|
||||||
} else if (chr === '\\') {
|
|
||||||
escaped = true;
|
|
||||||
continue;
|
|
||||||
} else if (quote && chr === quote) {
|
|
||||||
quote = false;
|
|
||||||
} else if (!quote && chr === '"') {
|
|
||||||
quote = chr;
|
|
||||||
} else if (!quote && chr === ';') {
|
|
||||||
if (key === false) {
|
|
||||||
response.value = value.trim();
|
|
||||||
} else {
|
|
||||||
response.params[key] = value.trim();
|
|
||||||
}
|
|
||||||
type = 'key';
|
|
||||||
value = '';
|
|
||||||
} else {
|
|
||||||
value += chr;
|
|
||||||
}
|
|
||||||
escaped = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type === 'value') {
|
|
||||||
if (key === false) {
|
|
||||||
response.value = value.trim();
|
|
||||||
} else {
|
|
||||||
response.params[key] = value.trim();
|
|
||||||
}
|
|
||||||
} else if (value.trim()) {
|
|
||||||
response.params[value.trim().toLowerCase()] = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// handle parameter value continuations
|
|
||||||
// https://tools.ietf.org/html/rfc2231#section-3
|
|
||||||
|
|
||||||
// preprocess values
|
|
||||||
Object.keys(response.params).forEach(key => {
|
|
||||||
let actualKey, nr, match, value;
|
|
||||||
if ((match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/))) {
|
|
||||||
actualKey = key.substr(0, match.index);
|
|
||||||
nr = Number(match[2] || match[3]) || 0;
|
|
||||||
|
|
||||||
if (!response.params[actualKey] || typeof response.params[actualKey] !== 'object') {
|
|
||||||
response.params[actualKey] = {
|
|
||||||
charset: false,
|
|
||||||
values: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
value = response.params[key];
|
|
||||||
|
|
||||||
if (nr === 0 && match[0].substr(-1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
|
|
||||||
response.params[actualKey].charset = match[1] || 'iso-8859-1';
|
|
||||||
value = match[2];
|
|
||||||
}
|
|
||||||
|
|
||||||
response.params[actualKey].values[nr] = value;
|
|
||||||
|
|
||||||
// remove the old reference
|
|
||||||
delete response.params[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// concatenate split rfc2231 strings and convert encoded strings to mime encoded words
|
|
||||||
Object.keys(response.params).forEach(key => {
|
|
||||||
let value;
|
|
||||||
if (response.params[key] && Array.isArray(response.params[key].values)) {
|
|
||||||
value = response.params[key].values.map(val => val || '').join('');
|
|
||||||
|
|
||||||
if (response.params[key].charset) {
|
|
||||||
// convert "%AB" to "=?charset?Q?=AB?="
|
|
||||||
response.params[key] =
|
|
||||||
'=?' +
|
|
||||||
response.params[key].charset +
|
|
||||||
'?Q?' +
|
|
||||||
value
|
|
||||||
// fix invalidly encoded chars
|
|
||||||
.replace(/[=?_\s]/g, s => {
|
|
||||||
let c = s.charCodeAt(0).toString(16);
|
|
||||||
if (s === ' ') {
|
|
||||||
return '_';
|
|
||||||
} else {
|
|
||||||
return '%' + (c.length < 2 ? '0' : '') + c;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
// change from urlencoding to percent encoding
|
|
||||||
.replace(/%/g, '=') +
|
|
||||||
'?=';
|
|
||||||
} else {
|
|
||||||
response.params[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns file extension for a content type string. If no suitable extensions
|
|
||||||
* are found, 'bin' is used as the default extension
|
|
||||||
*
|
|
||||||
* @param {String} mimeType Content type to be checked for
|
|
||||||
* @return {String} File extension
|
|
||||||
*/
|
|
||||||
detectExtension: mimeType => mimeTypes.detectExtension(mimeType),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns content type for a file extension. If no suitable content types
|
|
||||||
* are found, 'application/octet-stream' is used as the default content type
|
|
||||||
*
|
|
||||||
* @param {String} extension Extension to be checked for
|
|
||||||
* @return {String} File extension
|
|
||||||
*/
|
|
||||||
detectMimeType: extension => mimeTypes.detectMimeType(extension),
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Folds long lines, useful for folding header lines (afterSpace=false) and
|
|
||||||
* flowed text (afterSpace=true)
|
|
||||||
*
|
|
||||||
* @param {String} str String to be folded
|
|
||||||
* @param {Number} [lineLength=76] Maximum length of a line
|
|
||||||
* @param {Boolean} afterSpace If true, leave a space in th end of a line
|
|
||||||
* @return {String} String with folded lines
|
|
||||||
*/
|
|
||||||
foldLines(str, lineLength, afterSpace) {
|
|
||||||
str = (str || '').toString();
|
|
||||||
lineLength = lineLength || 76;
|
|
||||||
|
|
||||||
let pos = 0,
|
|
||||||
len = str.length,
|
|
||||||
result = '',
|
|
||||||
line,
|
|
||||||
match;
|
|
||||||
|
|
||||||
while (pos < len) {
|
|
||||||
line = str.substr(pos, lineLength);
|
|
||||||
if (line.length < lineLength) {
|
|
||||||
result += line;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if ((match = line.match(/^[^\n\r]*(\r?\n|\r)/))) {
|
|
||||||
line = match[0];
|
|
||||||
result += line;
|
|
||||||
pos += line.length;
|
|
||||||
continue;
|
|
||||||
} else if ((match = line.match(/(\s+)[^\s]*$/)) && match[0].length - (afterSpace ? (match[1] || '').length : 0) < line.length) {
|
|
||||||
line = line.substr(0, line.length - (match[0].length - (afterSpace ? (match[1] || '').length : 0)));
|
|
||||||
} else if ((match = str.substr(pos + line.length).match(/^[^\s]+(\s*)/))) {
|
|
||||||
line = line + match[0].substr(0, match[0].length - (!afterSpace ? (match[1] || '').length : 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
result += line;
|
|
||||||
pos += line.length;
|
|
||||||
if (pos < len) {
|
|
||||||
result += '\r\n';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Splits a mime encoded string. Needed for dividing mime words into smaller chunks
|
|
||||||
*
|
|
||||||
* @param {String} str Mime encoded string to be split up
|
|
||||||
* @param {Number} maxlen Maximum length of characters for one part (minimum 12)
|
|
||||||
* @return {Array} Split string
|
|
||||||
*/
|
|
||||||
splitMimeEncodedString: (str, maxlen) => {
|
|
||||||
let curLine,
|
|
||||||
match,
|
|
||||||
chr,
|
|
||||||
done,
|
|
||||||
lines = [];
|
|
||||||
|
|
||||||
// require at least 12 symbols to fit possible 4 octet UTF-8 sequences
|
|
||||||
maxlen = Math.max(maxlen || 0, 12);
|
|
||||||
|
|
||||||
while (str.length) {
|
|
||||||
curLine = str.substr(0, maxlen);
|
|
||||||
|
|
||||||
// move incomplete escaped char back to main
|
|
||||||
if ((match = curLine.match(/[=][0-9A-F]?$/i))) {
|
|
||||||
curLine = curLine.substr(0, match.index);
|
|
||||||
}
|
|
||||||
|
|
||||||
done = false;
|
|
||||||
while (!done) {
|
|
||||||
done = true;
|
|
||||||
// check if not middle of a unicode char sequence
|
|
||||||
if ((match = str.substr(curLine.length).match(/^[=]([0-9A-F]{2})/i))) {
|
|
||||||
chr = parseInt(match[1], 16);
|
|
||||||
// invalid sequence, move one char back anc recheck
|
|
||||||
if (chr < 0xc2 && chr > 0x7f) {
|
|
||||||
curLine = curLine.substr(0, curLine.length - 3);
|
|
||||||
done = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (curLine.length) {
|
|
||||||
lines.push(curLine);
|
|
||||||
}
|
|
||||||
str = str.substr(curLine.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
return lines;
|
|
||||||
},
|
|
||||||
|
|
||||||
encodeURICharComponent: chr => {
|
|
||||||
let res = '';
|
|
||||||
let ord = chr.charCodeAt(0).toString(16).toUpperCase();
|
|
||||||
|
|
||||||
if (ord.length % 2) {
|
|
||||||
ord = '0' + ord;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ord.length > 2) {
|
|
||||||
for (let i = 0, len = ord.length / 2; i < len; i++) {
|
|
||||||
res += '%' + ord.substr(i, 2);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
res += '%' + ord;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
},
|
|
||||||
|
|
||||||
safeEncodeURIComponent(str) {
|
|
||||||
str = (str || '').toString();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// might throw if we try to encode invalid sequences, eg. partial emoji
|
|
||||||
str = encodeURIComponent(str);
|
|
||||||
} catch (E) {
|
|
||||||
// should never run
|
|
||||||
return str.replace(/[^\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]+/g, '');
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure chars that are not handled by encodeURICompent are converted as well
|
|
||||||
return str.replace(/[\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]/g, chr => this.encodeURICharComponent(chr));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
2102
lib/node/node_modules/nodemailer/lib/mime-funcs/mime-types.js
generated
vendored
2102
lib/node/node_modules/nodemailer/lib/mime-funcs/mime-types.js
generated
vendored
File diff suppressed because it is too large
Load diff
1314
lib/node/node_modules/nodemailer/lib/mime-node/index.js
generated
vendored
1314
lib/node/node_modules/nodemailer/lib/mime-node/index.js
generated
vendored
File diff suppressed because it is too large
Load diff
33
lib/node/node_modules/nodemailer/lib/mime-node/last-newline.js
generated
vendored
33
lib/node/node_modules/nodemailer/lib/mime-node/last-newline.js
generated
vendored
|
|
@ -1,33 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Transform = require('stream').Transform;
|
|
||||||
|
|
||||||
class LastNewline extends Transform {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.lastByte = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(chunk, encoding, done) {
|
|
||||||
if (chunk.length) {
|
|
||||||
this.lastByte = chunk[chunk.length - 1];
|
|
||||||
}
|
|
||||||
|
|
||||||
this.push(chunk);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
_flush(done) {
|
|
||||||
if (this.lastByte === 0x0a) {
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
if (this.lastByte === 0x0d) {
|
|
||||||
this.push(Buffer.from('\n'));
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
this.push(Buffer.from('\r\n'));
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = LastNewline;
|
|
||||||
43
lib/node/node_modules/nodemailer/lib/mime-node/le-unix.js
generated
vendored
43
lib/node/node_modules/nodemailer/lib/mime-node/le-unix.js
generated
vendored
|
|
@ -1,43 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const stream = require('stream');
|
|
||||||
const Transform = stream.Transform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that only <LF> is used for linebreaks
|
|
||||||
*
|
|
||||||
* @param {Object} options Stream options
|
|
||||||
*/
|
|
||||||
class LeWindows extends Transform {
|
|
||||||
constructor(options) {
|
|
||||||
super(options);
|
|
||||||
// init Transform
|
|
||||||
this.options = options || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes dots
|
|
||||||
*/
|
|
||||||
_transform(chunk, encoding, done) {
|
|
||||||
let buf;
|
|
||||||
let lastPos = 0;
|
|
||||||
|
|
||||||
for (let i = 0, len = chunk.length; i < len; i++) {
|
|
||||||
if (chunk[i] === 0x0d) {
|
|
||||||
// \n
|
|
||||||
buf = chunk.slice(lastPos, i);
|
|
||||||
lastPos = i + 1;
|
|
||||||
this.push(buf);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (lastPos && lastPos < chunk.length) {
|
|
||||||
buf = chunk.slice(lastPos);
|
|
||||||
this.push(buf);
|
|
||||||
} else if (!lastPos) {
|
|
||||||
this.push(chunk);
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = LeWindows;
|
|
||||||
52
lib/node/node_modules/nodemailer/lib/mime-node/le-windows.js
generated
vendored
52
lib/node/node_modules/nodemailer/lib/mime-node/le-windows.js
generated
vendored
|
|
@ -1,52 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const stream = require('stream');
|
|
||||||
const Transform = stream.Transform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensures that only <CR><LF> sequences are used for linebreaks
|
|
||||||
*
|
|
||||||
* @param {Object} options Stream options
|
|
||||||
*/
|
|
||||||
class LeWindows extends Transform {
|
|
||||||
constructor(options) {
|
|
||||||
super(options);
|
|
||||||
// init Transform
|
|
||||||
this.options = options || {};
|
|
||||||
this.lastByte = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes dots
|
|
||||||
*/
|
|
||||||
_transform(chunk, encoding, done) {
|
|
||||||
let buf;
|
|
||||||
let lastPos = 0;
|
|
||||||
|
|
||||||
for (let i = 0, len = chunk.length; i < len; i++) {
|
|
||||||
if (chunk[i] === 0x0a) {
|
|
||||||
// \n
|
|
||||||
if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
|
|
||||||
if (i > lastPos) {
|
|
||||||
buf = chunk.slice(lastPos, i);
|
|
||||||
this.push(buf);
|
|
||||||
}
|
|
||||||
this.push(Buffer.from('\r\n'));
|
|
||||||
lastPos = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (lastPos && lastPos < chunk.length) {
|
|
||||||
buf = chunk.slice(lastPos);
|
|
||||||
this.push(buf);
|
|
||||||
} else if (!lastPos) {
|
|
||||||
this.push(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastByte = chunk[chunk.length - 1];
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = LeWindows;
|
|
||||||
143
lib/node/node_modules/nodemailer/lib/nodemailer.js
generated
vendored
143
lib/node/node_modules/nodemailer/lib/nodemailer.js
generated
vendored
|
|
@ -1,143 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Mailer = require('./mailer');
|
|
||||||
const shared = require('./shared');
|
|
||||||
const SMTPPool = require('./smtp-pool');
|
|
||||||
const SMTPTransport = require('./smtp-transport');
|
|
||||||
const SendmailTransport = require('./sendmail-transport');
|
|
||||||
const StreamTransport = require('./stream-transport');
|
|
||||||
const JSONTransport = require('./json-transport');
|
|
||||||
const SESTransport = require('./ses-transport');
|
|
||||||
const nmfetch = require('./fetch');
|
|
||||||
const packageData = require('../package.json');
|
|
||||||
|
|
||||||
const ETHEREAL_API = (process.env.ETHEREAL_API || 'https://api.nodemailer.com').replace(/\/+$/, '');
|
|
||||||
const ETHEREAL_WEB = (process.env.ETHEREAL_WEB || 'https://ethereal.email').replace(/\/+$/, '');
|
|
||||||
const ETHEREAL_CACHE = ['true', 'yes', 'y', '1'].includes((process.env.ETHEREAL_CACHE || 'yes').toString().trim().toLowerCase());
|
|
||||||
|
|
||||||
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
|
|
||||||
(typeof transporter === 'object' && typeof transporter.send !== 'function') ||
|
|
||||||
// provided transporter looks like a connection url
|
|
||||||
(typeof transporter === 'string' && /^(smtps?|direct):/i.test(transporter))
|
|
||||||
) {
|
|
||||||
if ((urlConfig = typeof transporter === 'string' ? transporter : transporter.url)) {
|
|
||||||
// parse a configuration URL into configuration options
|
|
||||||
options = shared.parseConnectionUrl(urlConfig);
|
|
||||||
} else {
|
|
||||||
options = transporter;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.pool) {
|
|
||||||
transporter = new SMTPPool(options);
|
|
||||||
} else if (options.sendmail) {
|
|
||||||
transporter = new SendmailTransport(options);
|
|
||||||
} else if (options.streamTransport) {
|
|
||||||
transporter = new StreamTransport(options);
|
|
||||||
} else if (options.jsonTransport) {
|
|
||||||
transporter = new JSONTransport(options);
|
|
||||||
} else if (options.SES) {
|
|
||||||
transporter = new SESTransport(options);
|
|
||||||
} else {
|
|
||||||
transporter = new SMTPTransport(options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mailer = new Mailer(transporter, options, defaults);
|
|
||||||
|
|
||||||
return mailer;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.createTestAccount = function (apiUrl, callback) {
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (!callback && typeof apiUrl === 'function') {
|
|
||||||
callback = apiUrl;
|
|
||||||
apiUrl = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
promise = new Promise((resolve, reject) => {
|
|
||||||
callback = shared.callbackPromise(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ETHEREAL_CACHE && testAccount) {
|
|
||||||
setImmediate(() => callback(null, testAccount));
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
apiUrl = apiUrl || ETHEREAL_API;
|
|
||||||
|
|
||||||
let chunks = [];
|
|
||||||
let chunklen = 0;
|
|
||||||
|
|
||||||
let req = nmfetch(apiUrl + '/user', {
|
|
||||||
contentType: 'application/json',
|
|
||||||
method: 'POST',
|
|
||||||
body: Buffer.from(
|
|
||||||
JSON.stringify({
|
|
||||||
requestor: packageData.name,
|
|
||||||
version: packageData.version
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('readable', () => {
|
|
||||||
let chunk;
|
|
||||||
while ((chunk = req.read()) !== null) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
chunklen += chunk.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
req.once('error', err => callback(err));
|
|
||||||
|
|
||||||
req.once('end', () => {
|
|
||||||
let res = Buffer.concat(chunks, chunklen);
|
|
||||||
let data;
|
|
||||||
let err;
|
|
||||||
try {
|
|
||||||
data = JSON.parse(res.toString());
|
|
||||||
} catch (E) {
|
|
||||||
err = E;
|
|
||||||
}
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
if (data.status !== 'success' || data.error) {
|
|
||||||
return callback(new Error(data.error || 'Request failed'));
|
|
||||||
}
|
|
||||||
delete data.status;
|
|
||||||
testAccount = data;
|
|
||||||
callback(null, testAccount);
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.getTestMessageUrl = function (info) {
|
|
||||||
if (!info || !info.response) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let infoProps = new Map();
|
|
||||||
info.response.replace(/\[([^\]]+)\]$/, (m, props) => {
|
|
||||||
props.replace(/\b([A-Z0-9]+)=([^\s]+)/g, (m, key, value) => {
|
|
||||||
infoProps.set(key, value);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
if (infoProps.has('STATUS') && infoProps.has('MSGID')) {
|
|
||||||
return (testAccount.web || ETHEREAL_WEB) + '/message/' + infoProps.get('MSGID');
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
460
lib/node/node_modules/nodemailer/lib/punycode/index.js
generated
vendored
460
lib/node/node_modules/nodemailer/lib/punycode/index.js
generated
vendored
|
|
@ -1,460 +0,0 @@
|
||||||
/*
|
|
||||||
|
|
||||||
Copied from https://github.com/mathiasbynens/punycode.js/blob/ef3505c8abb5143a00d53ce59077c9f7f4b2ac47/punycode.js
|
|
||||||
|
|
||||||
Copyright Mathias Bynens <https://mathiasbynens.be/>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining
|
|
||||||
a copy of this software and associated documentation files (the
|
|
||||||
"Software"), to deal in the Software without restriction, including
|
|
||||||
without limitation the rights to use, copy, modify, merge, publish,
|
|
||||||
distribute, sublicense, and/or sell copies of the Software, and to
|
|
||||||
permit persons to whom the Software is furnished to do so, subject to
|
|
||||||
the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be
|
|
||||||
included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
||||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
||||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
||||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
||||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
||||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
||||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
|
|
||||||
*/
|
|
||||||
/* eslint callback-return: 0, no-bitwise: 0, eqeqeq: 0, prefer-arrow-callback: 0, object-shorthand: 0 */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/** Highest positive signed 32-bit float value */
|
|
||||||
const maxInt = 2147483647; // aka. 0x7FFFFFFF or 2^31-1
|
|
||||||
|
|
||||||
/** Bootstring parameters */
|
|
||||||
const base = 36;
|
|
||||||
const tMin = 1;
|
|
||||||
const tMax = 26;
|
|
||||||
const skew = 38;
|
|
||||||
const damp = 700;
|
|
||||||
const initialBias = 72;
|
|
||||||
const initialN = 128; // 0x80
|
|
||||||
const delimiter = '-'; // '\x2D'
|
|
||||||
|
|
||||||
/** Regular expressions */
|
|
||||||
const regexPunycode = /^xn--/;
|
|
||||||
const regexNonASCII = /[^\0-\x7F]/; // Note: U+007F DEL is excluded too.
|
|
||||||
const regexSeparators = /[\x2E\u3002\uFF0E\uFF61]/g; // RFC 3490 separators
|
|
||||||
|
|
||||||
/** Error messages */
|
|
||||||
const errors = {
|
|
||||||
overflow: 'Overflow: input needs wider integers to process',
|
|
||||||
'not-basic': 'Illegal input >= 0x80 (not a basic code point)',
|
|
||||||
'invalid-input': 'Invalid input'
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Convenience shortcuts */
|
|
||||||
const baseMinusTMin = base - tMin;
|
|
||||||
const floor = Math.floor;
|
|
||||||
const stringFromCharCode = String.fromCharCode;
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic error utility function.
|
|
||||||
* @private
|
|
||||||
* @param {String} type The error type.
|
|
||||||
* @returns {Error} Throws a `RangeError` with the applicable error message.
|
|
||||||
*/
|
|
||||||
function error(type) {
|
|
||||||
throw new RangeError(errors[type]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A generic `Array#map` utility function.
|
|
||||||
* @private
|
|
||||||
* @param {Array} array The array to iterate over.
|
|
||||||
* @param {Function} callback The function that gets called for every array
|
|
||||||
* item.
|
|
||||||
* @returns {Array} A new array of values returned by the callback function.
|
|
||||||
*/
|
|
||||||
function map(array, callback) {
|
|
||||||
const result = [];
|
|
||||||
let length = array.length;
|
|
||||||
while (length--) {
|
|
||||||
result[length] = callback(array[length]);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple `Array#map`-like wrapper to work with domain name strings or email
|
|
||||||
* addresses.
|
|
||||||
* @private
|
|
||||||
* @param {String} domain The domain name or email address.
|
|
||||||
* @param {Function} callback The function that gets called for every
|
|
||||||
* character.
|
|
||||||
* @returns {String} A new string of characters returned by the callback
|
|
||||||
* function.
|
|
||||||
*/
|
|
||||||
function mapDomain(domain, callback) {
|
|
||||||
const parts = domain.split('@');
|
|
||||||
let result = '';
|
|
||||||
if (parts.length > 1) {
|
|
||||||
// In email addresses, only the domain name should be punycoded. Leave
|
|
||||||
// the local part (i.e. everything up to `@`) intact.
|
|
||||||
result = parts[0] + '@';
|
|
||||||
domain = parts[1];
|
|
||||||
}
|
|
||||||
// Avoid `split(regex)` for IE8 compatibility. See #17.
|
|
||||||
domain = domain.replace(regexSeparators, '\x2E');
|
|
||||||
const labels = domain.split('.');
|
|
||||||
const encoded = map(labels, callback).join('.');
|
|
||||||
return result + encoded;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an array containing the numeric code points of each Unicode
|
|
||||||
* character in the string. While JavaScript uses UCS-2 internally,
|
|
||||||
* this function will convert a pair of surrogate halves (each of which
|
|
||||||
* UCS-2 exposes as separate characters) into a single code point,
|
|
||||||
* matching UTF-16.
|
|
||||||
* @see `punycode.ucs2.encode`
|
|
||||||
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
|
||||||
* @memberOf punycode.ucs2
|
|
||||||
* @name decode
|
|
||||||
* @param {String} string The Unicode input string (UCS-2).
|
|
||||||
* @returns {Array} The new array of code points.
|
|
||||||
*/
|
|
||||||
function ucs2decode(string) {
|
|
||||||
const output = [];
|
|
||||||
let counter = 0;
|
|
||||||
const length = string.length;
|
|
||||||
while (counter < length) {
|
|
||||||
const value = string.charCodeAt(counter++);
|
|
||||||
if (value >= 0xd800 && value <= 0xdbff && counter < length) {
|
|
||||||
// It's a high surrogate, and there is a next character.
|
|
||||||
const extra = string.charCodeAt(counter++);
|
|
||||||
if ((extra & 0xfc00) == 0xdc00) {
|
|
||||||
// Low surrogate.
|
|
||||||
output.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);
|
|
||||||
} else {
|
|
||||||
// It's an unmatched surrogate; only append this code unit, in case the
|
|
||||||
// next code unit is the high surrogate of a surrogate pair.
|
|
||||||
output.push(value);
|
|
||||||
counter--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
output.push(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return output;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a string based on an array of numeric code points.
|
|
||||||
* @see `punycode.ucs2.decode`
|
|
||||||
* @memberOf punycode.ucs2
|
|
||||||
* @name encode
|
|
||||||
* @param {Array} codePoints The array of numeric code points.
|
|
||||||
* @returns {String} The new Unicode string (UCS-2).
|
|
||||||
*/
|
|
||||||
const ucs2encode = codePoints => String.fromCodePoint(...codePoints);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a basic code point into a digit/integer.
|
|
||||||
* @see `digitToBasic()`
|
|
||||||
* @private
|
|
||||||
* @param {Number} codePoint The basic numeric code point value.
|
|
||||||
* @returns {Number} The numeric value of a basic code point (for use in
|
|
||||||
* representing integers) in the range `0` to `base - 1`, or `base` if
|
|
||||||
* the code point does not represent a value.
|
|
||||||
*/
|
|
||||||
const basicToDigit = function (codePoint) {
|
|
||||||
if (codePoint >= 0x30 && codePoint < 0x3a) {
|
|
||||||
return 26 + (codePoint - 0x30);
|
|
||||||
}
|
|
||||||
if (codePoint >= 0x41 && codePoint < 0x5b) {
|
|
||||||
return codePoint - 0x41;
|
|
||||||
}
|
|
||||||
if (codePoint >= 0x61 && codePoint < 0x7b) {
|
|
||||||
return codePoint - 0x61;
|
|
||||||
}
|
|
||||||
return base;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a digit/integer into a basic code point.
|
|
||||||
* @see `basicToDigit()`
|
|
||||||
* @private
|
|
||||||
* @param {Number} digit The numeric value of a basic code point.
|
|
||||||
* @returns {Number} The basic code point whose value (when used for
|
|
||||||
* representing integers) is `digit`, which needs to be in the range
|
|
||||||
* `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
|
|
||||||
* used; else, the lowercase form is used. The behavior is undefined
|
|
||||||
* if `flag` is non-zero and `digit` has no uppercase form.
|
|
||||||
*/
|
|
||||||
const digitToBasic = function (digit, flag) {
|
|
||||||
// 0..25 map to ASCII a..z or A..Z
|
|
||||||
// 26..35 map to ASCII 0..9
|
|
||||||
return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bias adaptation function as per section 3.4 of RFC 3492.
|
|
||||||
* https://tools.ietf.org/html/rfc3492#section-3.4
|
|
||||||
* @private
|
|
||||||
*/
|
|
||||||
const adapt = function (delta, numPoints, firstTime) {
|
|
||||||
let k = 0;
|
|
||||||
delta = firstTime ? floor(delta / damp) : delta >> 1;
|
|
||||||
delta += floor(delta / numPoints);
|
|
||||||
for (; /* no initialization */ delta > (baseMinusTMin * tMax) >> 1; k += base) {
|
|
||||||
delta = floor(delta / baseMinusTMin);
|
|
||||||
}
|
|
||||||
return floor(k + ((baseMinusTMin + 1) * delta) / (delta + skew));
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Punycode string of ASCII-only symbols to a string of Unicode
|
|
||||||
* symbols.
|
|
||||||
* @memberOf punycode
|
|
||||||
* @param {String} input The Punycode string of ASCII-only symbols.
|
|
||||||
* @returns {String} The resulting string of Unicode symbols.
|
|
||||||
*/
|
|
||||||
const decode = function (input) {
|
|
||||||
// Don't use UCS-2.
|
|
||||||
const output = [];
|
|
||||||
const inputLength = input.length;
|
|
||||||
let i = 0;
|
|
||||||
let n = initialN;
|
|
||||||
let bias = initialBias;
|
|
||||||
|
|
||||||
// Handle the basic code points: let `basic` be the number of input code
|
|
||||||
// points before the last delimiter, or `0` if there is none, then copy
|
|
||||||
// the first basic code points to the output.
|
|
||||||
|
|
||||||
let basic = input.lastIndexOf(delimiter);
|
|
||||||
if (basic < 0) {
|
|
||||||
basic = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let j = 0; j < basic; ++j) {
|
|
||||||
// if it's not a basic code point
|
|
||||||
if (input.charCodeAt(j) >= 0x80) {
|
|
||||||
error('not-basic');
|
|
||||||
}
|
|
||||||
output.push(input.charCodeAt(j));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main decoding loop: start just after the last delimiter if any basic code
|
|
||||||
// points were copied; start at the beginning otherwise.
|
|
||||||
|
|
||||||
for (let index = basic > 0 ? basic + 1 : 0; index < inputLength /* no final expression */; ) {
|
|
||||||
// `index` is the index of the next character to be consumed.
|
|
||||||
// Decode a generalized variable-length integer into `delta`,
|
|
||||||
// which gets added to `i`. The overflow checking is easier
|
|
||||||
// if we increase `i` as we go, then subtract off its starting
|
|
||||||
// value at the end to obtain `delta`.
|
|
||||||
const oldi = i;
|
|
||||||
for (let w = 1, k = base /* no condition */; ; k += base) {
|
|
||||||
if (index >= inputLength) {
|
|
||||||
error('invalid-input');
|
|
||||||
}
|
|
||||||
|
|
||||||
const digit = basicToDigit(input.charCodeAt(index++));
|
|
||||||
|
|
||||||
if (digit >= base) {
|
|
||||||
error('invalid-input');
|
|
||||||
}
|
|
||||||
if (digit > floor((maxInt - i) / w)) {
|
|
||||||
error('overflow');
|
|
||||||
}
|
|
||||||
|
|
||||||
i += digit * w;
|
|
||||||
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
|
|
||||||
|
|
||||||
if (digit < t) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseMinusT = base - t;
|
|
||||||
if (w > floor(maxInt / baseMinusT)) {
|
|
||||||
error('overflow');
|
|
||||||
}
|
|
||||||
|
|
||||||
w *= baseMinusT;
|
|
||||||
}
|
|
||||||
|
|
||||||
const out = output.length + 1;
|
|
||||||
bias = adapt(i - oldi, out, oldi == 0);
|
|
||||||
|
|
||||||
// `i` was supposed to wrap around from `out` to `0`,
|
|
||||||
// incrementing `n` each time, so we'll fix that now:
|
|
||||||
if (floor(i / out) > maxInt - n) {
|
|
||||||
error('overflow');
|
|
||||||
}
|
|
||||||
|
|
||||||
n += floor(i / out);
|
|
||||||
i %= out;
|
|
||||||
|
|
||||||
// Insert `n` at position `i` of the output.
|
|
||||||
output.splice(i++, 0, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
return String.fromCodePoint(...output);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a string of Unicode symbols (e.g. a domain name label) to a
|
|
||||||
* Punycode string of ASCII-only symbols.
|
|
||||||
* @memberOf punycode
|
|
||||||
* @param {String} input The string of Unicode symbols.
|
|
||||||
* @returns {String} The resulting Punycode string of ASCII-only symbols.
|
|
||||||
*/
|
|
||||||
const encode = function (input) {
|
|
||||||
const output = [];
|
|
||||||
|
|
||||||
// Convert the input in UCS-2 to an array of Unicode code points.
|
|
||||||
input = ucs2decode(input);
|
|
||||||
|
|
||||||
// Cache the length.
|
|
||||||
const inputLength = input.length;
|
|
||||||
|
|
||||||
// Initialize the state.
|
|
||||||
let n = initialN;
|
|
||||||
let delta = 0;
|
|
||||||
let bias = initialBias;
|
|
||||||
|
|
||||||
// Handle the basic code points.
|
|
||||||
for (const currentValue of input) {
|
|
||||||
if (currentValue < 0x80) {
|
|
||||||
output.push(stringFromCharCode(currentValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const basicLength = output.length;
|
|
||||||
let handledCPCount = basicLength;
|
|
||||||
|
|
||||||
// `handledCPCount` is the number of code points that have been handled;
|
|
||||||
// `basicLength` is the number of basic code points.
|
|
||||||
|
|
||||||
// Finish the basic string with a delimiter unless it's empty.
|
|
||||||
if (basicLength) {
|
|
||||||
output.push(delimiter);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main encoding loop:
|
|
||||||
while (handledCPCount < inputLength) {
|
|
||||||
// All non-basic code points < n have been handled already. Find the next
|
|
||||||
// larger one:
|
|
||||||
let m = maxInt;
|
|
||||||
for (const currentValue of input) {
|
|
||||||
if (currentValue >= n && currentValue < m) {
|
|
||||||
m = currentValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Increase `delta` enough to advance the decoder's <n,i> state to <m,0>,
|
|
||||||
// but guard against overflow.
|
|
||||||
const handledCPCountPlusOne = handledCPCount + 1;
|
|
||||||
if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) {
|
|
||||||
error('overflow');
|
|
||||||
}
|
|
||||||
|
|
||||||
delta += (m - n) * handledCPCountPlusOne;
|
|
||||||
n = m;
|
|
||||||
|
|
||||||
for (const currentValue of input) {
|
|
||||||
if (currentValue < n && ++delta > maxInt) {
|
|
||||||
error('overflow');
|
|
||||||
}
|
|
||||||
if (currentValue === n) {
|
|
||||||
// Represent delta as a generalized variable-length integer.
|
|
||||||
let q = delta;
|
|
||||||
for (let k = base /* no condition */; ; k += base) {
|
|
||||||
const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
|
|
||||||
if (q < t) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
const qMinusT = q - t;
|
|
||||||
const baseMinusT = base - t;
|
|
||||||
output.push(stringFromCharCode(digitToBasic(t + (qMinusT % baseMinusT), 0)));
|
|
||||||
q = floor(qMinusT / baseMinusT);
|
|
||||||
}
|
|
||||||
|
|
||||||
output.push(stringFromCharCode(digitToBasic(q, 0)));
|
|
||||||
bias = adapt(delta, handledCPCountPlusOne, handledCPCount === basicLength);
|
|
||||||
delta = 0;
|
|
||||||
++handledCPCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
++delta;
|
|
||||||
++n;
|
|
||||||
}
|
|
||||||
return output.join('');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Punycode string representing a domain name or an email address
|
|
||||||
* to Unicode. Only the Punycoded parts of the input will be converted, i.e.
|
|
||||||
* it doesn't matter if you call it on a string that has already been
|
|
||||||
* converted to Unicode.
|
|
||||||
* @memberOf punycode
|
|
||||||
* @param {String} input The Punycoded domain name or email address to
|
|
||||||
* convert to Unicode.
|
|
||||||
* @returns {String} The Unicode representation of the given Punycode
|
|
||||||
* string.
|
|
||||||
*/
|
|
||||||
const toUnicode = function (input) {
|
|
||||||
return mapDomain(input, function (string) {
|
|
||||||
return regexPunycode.test(string) ? decode(string.slice(4).toLowerCase()) : string;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a Unicode string representing a domain name or an email address to
|
|
||||||
* Punycode. Only the non-ASCII parts of the domain name will be converted,
|
|
||||||
* i.e. it doesn't matter if you call it with a domain that's already in
|
|
||||||
* ASCII.
|
|
||||||
* @memberOf punycode
|
|
||||||
* @param {String} input The domain name or email address to convert, as a
|
|
||||||
* Unicode string.
|
|
||||||
* @returns {String} The Punycode representation of the given domain name or
|
|
||||||
* email address.
|
|
||||||
*/
|
|
||||||
const toASCII = function (input) {
|
|
||||||
return mapDomain(input, function (string) {
|
|
||||||
return regexNonASCII.test(string) ? 'xn--' + encode(string) : string;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/*--------------------------------------------------------------------------*/
|
|
||||||
|
|
||||||
/** Define the public API */
|
|
||||||
const punycode = {
|
|
||||||
/**
|
|
||||||
* A string representing the current Punycode.js version number.
|
|
||||||
* @memberOf punycode
|
|
||||||
* @type String
|
|
||||||
*/
|
|
||||||
version: '2.3.1',
|
|
||||||
/**
|
|
||||||
* An object of methods to convert from JavaScript's internal character
|
|
||||||
* representation (UCS-2) to Unicode code points, and back.
|
|
||||||
* @see <https://mathiasbynens.be/notes/javascript-encoding>
|
|
||||||
* @memberOf punycode
|
|
||||||
* @type Object
|
|
||||||
*/
|
|
||||||
ucs2: {
|
|
||||||
decode: ucs2decode,
|
|
||||||
encode: ucs2encode
|
|
||||||
},
|
|
||||||
decode: decode,
|
|
||||||
encode: encode,
|
|
||||||
toASCII: toASCII,
|
|
||||||
toUnicode: toUnicode
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = punycode;
|
|
||||||
219
lib/node/node_modules/nodemailer/lib/qp/index.js
generated
vendored
219
lib/node/node_modules/nodemailer/lib/qp/index.js
generated
vendored
|
|
@ -1,219 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Transform = require('stream').Transform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a Buffer into a Quoted-Printable encoded string
|
|
||||||
*
|
|
||||||
* @param {Buffer} buffer Buffer to convert
|
|
||||||
* @returns {String} Quoted-Printable encoded string
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
|
|
||||||
for (let i = 0, len = buffer.length; i < len; i++) {
|
|
||||||
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) && !((ord === 0x20 || ord === 0x09) && (i === len - 1 || buffer[i + 1] === 0x0a || buffer[i + 1] === 0x0d))) {
|
|
||||||
result += String.fromCharCode(ord);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
result += '=' + (ord < 0x10 ? '0' : '') + ord.toString(16).toUpperCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds soft line breaks to a Quoted-Printable string
|
|
||||||
*
|
|
||||||
* @param {String} str Quoted-Printable encoded string that might need line wrapping
|
|
||||||
* @param {Number} [lineLength=76] Maximum allowed length for a line
|
|
||||||
* @returns {String} Soft-wrapped Quoted-Printable encoded string
|
|
||||||
*/
|
|
||||||
function wrap(str, lineLength) {
|
|
||||||
str = (str || '').toString();
|
|
||||||
lineLength = lineLength || 76;
|
|
||||||
|
|
||||||
if (str.length <= lineLength) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
|
|
||||||
let pos = 0;
|
|
||||||
let len = str.length;
|
|
||||||
let match, code, line;
|
|
||||||
let lineMargin = Math.floor(lineLength / 3);
|
|
||||||
let result = '';
|
|
||||||
|
|
||||||
// insert soft linebreaks where needed
|
|
||||||
while (pos < len) {
|
|
||||||
line = str.substr(pos, lineLength);
|
|
||||||
if ((match = line.match(/\r\n/))) {
|
|
||||||
line = line.substr(0, match.index + match[0].length);
|
|
||||||
result += line;
|
|
||||||
pos += line.length;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.substr(-1) === '\n') {
|
|
||||||
// nothing to change here
|
|
||||||
result += line;
|
|
||||||
pos += line.length;
|
|
||||||
continue;
|
|
||||||
} else 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.,!?]*$/))) {
|
|
||||||
// truncate to nearest space
|
|
||||||
line = line.substr(0, line.length - (match[0].length - 1));
|
|
||||||
} else if (line.match(/[=][\da-f]{0,2}$/i)) {
|
|
||||||
// push incomplete encoding sequences to the next line
|
|
||||||
if ((match = line.match(/[=][\da-f]{0,1}$/i))) {
|
|
||||||
line = line.substr(0, line.length - match[0].length);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensure that utf-8 sequences are not split
|
|
||||||
while (line.length > 3 && line.length < len - pos && !line.match(/^(?:=[\da-f]{2}){1,4}$/i) && (match = line.match(/[=][\da-f]{2}$/gi))) {
|
|
||||||
code = parseInt(match[0].substr(1, 2), 16);
|
|
||||||
if (code < 128) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
line = line.substr(0, line.length - 3);
|
|
||||||
|
|
||||||
if (code >= 0xc0) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pos + line.length < len && line.substr(-1) !== '\n') {
|
|
||||||
if (line.length === lineLength && line.match(/[=][\da-f]{2}$/i)) {
|
|
||||||
line = line.substr(0, line.length - 3);
|
|
||||||
} else if (line.length === lineLength) {
|
|
||||||
line = line.substr(0, line.length - 1);
|
|
||||||
}
|
|
||||||
pos += line.length;
|
|
||||||
line += '=\r\n';
|
|
||||||
} else {
|
|
||||||
pos += line.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += line;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper function to check if a number is inside provided ranges
|
|
||||||
*
|
|
||||||
* @param {Number} nr Number to check for
|
|
||||||
* @param {Array} ranges An Array of allowed values
|
|
||||||
* @returns {Boolean} True if the value was found inside allowed ranges, false otherwise
|
|
||||||
*/
|
|
||||||
function checkRanges(nr, ranges) {
|
|
||||||
for (let i = ranges.length - 1; i >= 0; i--) {
|
|
||||||
if (!ranges[i].length) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (ranges[i].length === 1 && nr === ranges[i][0]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a transform stream for encoding data to Quoted-Printable encoding
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} options Stream options
|
|
||||||
* @param {Number} [options.lineLength=76] Maximum length for lines, set to false to disable wrapping
|
|
||||||
*/
|
|
||||||
class Encoder extends Transform {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
// init Transform
|
|
||||||
this.options = options || {};
|
|
||||||
|
|
||||||
if (this.options.lineLength !== false) {
|
|
||||||
this.options.lineLength = this.options.lineLength || 76;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._curLine = '';
|
|
||||||
|
|
||||||
this.inputBytes = 0;
|
|
||||||
this.outputBytes = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
_transform(chunk, encoding, done) {
|
|
||||||
let qp;
|
|
||||||
|
|
||||||
if (encoding !== 'buffer') {
|
|
||||||
chunk = Buffer.from(chunk, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!chunk || !chunk.length) {
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inputBytes += chunk.length;
|
|
||||||
|
|
||||||
if (this.options.lineLength) {
|
|
||||||
qp = this._curLine + encode(chunk);
|
|
||||||
qp = wrap(qp, this.options.lineLength);
|
|
||||||
qp = qp.replace(/(^|\n)([^\n]*)$/, (match, lineBreak, lastLine) => {
|
|
||||||
this._curLine = lastLine;
|
|
||||||
return lineBreak;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (qp) {
|
|
||||||
this.outputBytes += qp.length;
|
|
||||||
this.push(qp);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qp = encode(chunk);
|
|
||||||
this.outputBytes += qp.length;
|
|
||||||
this.push(qp, 'ascii');
|
|
||||||
}
|
|
||||||
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
_flush(done) {
|
|
||||||
if (this._curLine) {
|
|
||||||
this.outputBytes += this._curLine.length;
|
|
||||||
this.push(this._curLine, 'ascii');
|
|
||||||
}
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expose to the world
|
|
||||||
module.exports = {
|
|
||||||
encode,
|
|
||||||
wrap,
|
|
||||||
Encoder
|
|
||||||
};
|
|
||||||
210
lib/node/node_modules/nodemailer/lib/sendmail-transport/index.js
generated
vendored
210
lib/node/node_modules/nodemailer/lib/sendmail-transport/index.js
generated
vendored
|
|
@ -1,210 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const spawn = require('child_process').spawn;
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
const shared = require('../shared');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a Transport object for Sendmail
|
|
||||||
*
|
|
||||||
* Possible options can be the following:
|
|
||||||
*
|
|
||||||
* * **path** optional path to sendmail binary
|
|
||||||
* * **newline** either 'windows' or 'unix'
|
|
||||||
* * **args** an array of arguments for the sendmail binary
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} optional config parameter for Sendmail
|
|
||||||
*/
|
|
||||||
class SendmailTransport {
|
|
||||||
constructor(options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
// use a reference to spawn for mocking purposes
|
|
||||||
this._spawn = spawn;
|
|
||||||
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>Compiles a mailcomposer message and forwards it to handler that sends it.</p>
|
|
||||||
*
|
|
||||||
* @param {Object} emailMessage MailComposer object
|
|
||||||
* @param {Function} callback Callback function to run when the sending is completed
|
|
||||||
*/
|
|
||||||
send(mail, done) {
|
|
||||||
// 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;
|
|
||||||
let returned;
|
|
||||||
|
|
||||||
const hasInvalidAddresses = []
|
|
||||||
.concat(envelope.from || [])
|
|
||||||
.concat(envelope.to || [])
|
|
||||||
.some(addr => /^-/.test(addr));
|
|
||||||
if (hasInvalidAddresses) {
|
|
||||||
return done(new Error('Can not send mail. Invalid envelope addresses.'));
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
let callback = err => {
|
|
||||||
if (returned) {
|
|
||||||
// ignore any additional responses, already done
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
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'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
try {
|
|
||||||
sendmail = this._spawn(this.path, args);
|
|
||||||
} catch (E) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: E,
|
|
||||||
tnx: 'spawn',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Error occurred while spawning sendmail. %s',
|
|
||||||
E.message
|
|
||||||
);
|
|
||||||
return callback(E);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sendmail) {
|
|
||||||
sendmail.on('error', err => {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'spawn',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Error occurred when sending message %s. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
sendmail.once('exit', code => {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'stdin',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Error sending message %s to sendmail. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
sendmail.once('close', callback);
|
|
||||||
|
|
||||||
sendmail.stdin.on('error', err => {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'stdin',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Error occurred when piping message %s to sendmail. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
let recipients = [].concat(envelope.to || []);
|
|
||||||
if (recipients.length > 3) {
|
|
||||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
|
||||||
}
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Sending message %s to <%s>',
|
|
||||||
messageId,
|
|
||||||
recipients.join(', ')
|
|
||||||
);
|
|
||||||
|
|
||||||
let sourceStream = mail.message.createReadStream();
|
|
||||||
sourceStream.once('error', err => {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'stdin',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Error occurred when generating message %s. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
sendmail.kill('SIGINT'); // do not deliver the message
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
sourceStream.pipe(sendmail.stdin);
|
|
||||||
} else {
|
|
||||||
return callback(new Error('sendmail was not found'));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SendmailTransport;
|
|
||||||
349
lib/node/node_modules/nodemailer/lib/ses-transport/index.js
generated
vendored
349
lib/node/node_modules/nodemailer/lib/ses-transport/index.js
generated
vendored
|
|
@ -1,349 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
const shared = require('../shared');
|
|
||||||
const LeWindows = require('../mime-node/le-windows');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a Transport object for AWS SES
|
|
||||||
*
|
|
||||||
* Possible options can be the following:
|
|
||||||
*
|
|
||||||
* * **sendingRate** optional Number specifying how many messages per second should be delivered to SES
|
|
||||||
* * **maxConnections** optional Number specifying max number of parallel connections to SES
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} optional config parameter
|
|
||||||
*/
|
|
||||||
class SESTransport extends EventEmitter {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
this.options = options || {};
|
|
||||||
this.ses = this.options.SES;
|
|
||||||
|
|
||||||
this.name = 'SESTransport';
|
|
||||||
this.version = packageData.version;
|
|
||||||
|
|
||||||
this.logger = shared.getLogger(this.options, {
|
|
||||||
component: this.options.component || 'ses-transport'
|
|
||||||
});
|
|
||||||
|
|
||||||
// parallel sending connections
|
|
||||||
this.maxConnections = Number(this.options.maxConnections) || Infinity;
|
|
||||||
this.connections = 0;
|
|
||||||
|
|
||||||
// max messages per second
|
|
||||||
this.sendingRate = Number(this.options.sendingRate) || Infinity;
|
|
||||||
this.sendingRateTTL = null;
|
|
||||||
this.rateInterval = 1000; // milliseconds
|
|
||||||
this.rateMessages = [];
|
|
||||||
|
|
||||||
this.pending = [];
|
|
||||||
|
|
||||||
this.idling = true;
|
|
||||||
|
|
||||||
setImmediate(() => {
|
|
||||||
if (this.idling) {
|
|
||||||
this.emit('idle');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Schedules a sending of a message
|
|
||||||
*
|
|
||||||
* @param {Object} emailMessage MailComposer object
|
|
||||||
* @param {Function} callback Callback function to run when the sending is completed
|
|
||||||
*/
|
|
||||||
send(mail, callback) {
|
|
||||||
if (this.connections >= this.maxConnections) {
|
|
||||||
this.idling = false;
|
|
||||||
return this.pending.push({
|
|
||||||
mail,
|
|
||||||
callback
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._checkSendingRate()) {
|
|
||||||
this.idling = false;
|
|
||||||
return this.pending.push({
|
|
||||||
mail,
|
|
||||||
callback
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this._send(mail, (...args) => {
|
|
||||||
setImmediate(() => callback(...args));
|
|
||||||
this._sent();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkRatedQueue() {
|
|
||||||
if (this.connections >= this.maxConnections || !this._checkSendingRate()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.pending.length) {
|
|
||||||
if (!this.idling) {
|
|
||||||
this.idling = true;
|
|
||||||
this.emit('idle');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let next = this.pending.shift();
|
|
||||||
this._send(next.mail, (...args) => {
|
|
||||||
setImmediate(() => next.callback(...args));
|
|
||||||
this._sent();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_checkSendingRate() {
|
|
||||||
clearTimeout(this.sendingRateTTL);
|
|
||||||
|
|
||||||
let now = Date.now();
|
|
||||||
let oldest = false;
|
|
||||||
// delete older messages
|
|
||||||
for (let i = this.rateMessages.length - 1; i >= 0; i--) {
|
|
||||||
if (this.rateMessages[i].ts >= now - this.rateInterval && (!oldest || this.rateMessages[i].ts < oldest)) {
|
|
||||||
oldest = this.rateMessages[i].ts;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.rateMessages[i].ts < now - this.rateInterval && !this.rateMessages[i].pending) {
|
|
||||||
this.rateMessages.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.rateMessages.length < this.sendingRate) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let delay = Math.max(oldest + 1001, now + 20);
|
|
||||||
this.sendingRateTTL = setTimeout(() => this._checkRatedQueue(), now - delay);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.sendingRateTTL.unref();
|
|
||||||
} catch (E) {
|
|
||||||
// Ignore. Happens on envs with non-node timer implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_sent() {
|
|
||||||
this.connections--;
|
|
||||||
this._checkRatedQueue();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are free slots in the queue
|
|
||||||
*/
|
|
||||||
isIdle() {
|
|
||||||
return this.idling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compiles a mailcomposer message and forwards it to SES
|
|
||||||
*
|
|
||||||
* @param {Object} emailMessage MailComposer object
|
|
||||||
* @param {Function} callback Callback function to run when the sending is completed
|
|
||||||
*/
|
|
||||||
_send(mail, callback) {
|
|
||||||
let statObject = {
|
|
||||||
ts: Date.now(),
|
|
||||||
pending: true
|
|
||||||
};
|
|
||||||
this.connections++;
|
|
||||||
this.rateMessages.push(statObject);
|
|
||||||
|
|
||||||
let envelope = mail.data.envelope || mail.message.getEnvelope();
|
|
||||||
let messageId = mail.message.messageId();
|
|
||||||
|
|
||||||
let recipients = [].concat(envelope.to || []);
|
|
||||||
if (recipients.length > 3) {
|
|
||||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
|
||||||
}
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Sending message %s to <%s>',
|
|
||||||
messageId,
|
|
||||||
recipients.join(', ')
|
|
||||||
);
|
|
||||||
|
|
||||||
let getRawMessage = next => {
|
|
||||||
// do not use Message-ID and Date in DKIM signature
|
|
||||||
if (!mail.data._dkim) {
|
|
||||||
mail.data._dkim = {};
|
|
||||||
}
|
|
||||||
if (mail.data._dkim.skipFields && typeof mail.data._dkim.skipFields === 'string') {
|
|
||||||
mail.data._dkim.skipFields += ':date:message-id';
|
|
||||||
} else {
|
|
||||||
mail.data._dkim.skipFields = 'date:message-id';
|
|
||||||
}
|
|
||||||
|
|
||||||
let sourceStream = mail.message.createReadStream();
|
|
||||||
let stream = sourceStream.pipe(new LeWindows());
|
|
||||||
let chunks = [];
|
|
||||||
let chunklen = 0;
|
|
||||||
|
|
||||||
stream.on('readable', () => {
|
|
||||||
let chunk;
|
|
||||||
while ((chunk = stream.read()) !== null) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
chunklen += chunk.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
sourceStream.once('error', err => stream.emit('error', err));
|
|
||||||
|
|
||||||
stream.once('error', err => {
|
|
||||||
next(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.once('end', () => next(null, Buffer.concat(chunks, chunklen)));
|
|
||||||
};
|
|
||||||
|
|
||||||
setImmediate(() =>
|
|
||||||
getRawMessage((err, raw) => {
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Failed creating message for %s. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
statObject.pending = false;
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let sesMessage = {
|
|
||||||
RawMessage: {
|
|
||||||
// required
|
|
||||||
Data: raw // required
|
|
||||||
},
|
|
||||||
Source: envelope.from,
|
|
||||||
Destinations: envelope.to
|
|
||||||
};
|
|
||||||
|
|
||||||
Object.keys(mail.data.ses || {}).forEach(key => {
|
|
||||||
sesMessage[key] = mail.data.ses[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
let ses = (this.ses.aws ? this.ses.ses : this.ses) || {};
|
|
||||||
let aws = this.ses.aws || {};
|
|
||||||
|
|
||||||
let getRegion = cb => {
|
|
||||||
if (ses.config && typeof ses.config.region === 'function') {
|
|
||||||
// promise
|
|
||||||
return ses.config
|
|
||||||
.region()
|
|
||||||
.then(region => cb(null, region))
|
|
||||||
.catch(err => cb(err));
|
|
||||||
}
|
|
||||||
return cb(null, (ses.config && ses.config.region) || 'us-east-1');
|
|
||||||
};
|
|
||||||
|
|
||||||
getRegion((err, region) => {
|
|
||||||
if (err || !region) {
|
|
||||||
region = 'us-east-1';
|
|
||||||
}
|
|
||||||
|
|
||||||
let sendPromise;
|
|
||||||
if (typeof ses.send === 'function' && aws.SendRawEmailCommand) {
|
|
||||||
// v3 API
|
|
||||||
sendPromise = ses.send(new aws.SendRawEmailCommand(sesMessage));
|
|
||||||
} else {
|
|
||||||
// v2 API
|
|
||||||
sendPromise = ses.sendRawEmail(sesMessage).promise();
|
|
||||||
}
|
|
||||||
|
|
||||||
sendPromise
|
|
||||||
.then(data => {
|
|
||||||
if (region === 'us-east-1') {
|
|
||||||
region = 'email';
|
|
||||||
}
|
|
||||||
|
|
||||||
statObject.pending = false;
|
|
||||||
callback(null, {
|
|
||||||
envelope: {
|
|
||||||
from: envelope.from,
|
|
||||||
to: envelope.to
|
|
||||||
},
|
|
||||||
messageId: '<' + data.MessageId + (!/@/.test(data.MessageId) ? '@' + region + '.amazonses.com' : '') + '>',
|
|
||||||
response: data.MessageId,
|
|
||||||
raw
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(err => {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'send'
|
|
||||||
},
|
|
||||||
'Send error for %s: %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
statObject.pending = false;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies SES configuration
|
|
||||||
*
|
|
||||||
* @param {Function} callback Callback function
|
|
||||||
*/
|
|
||||||
verify(callback) {
|
|
||||||
let promise;
|
|
||||||
let ses = (this.ses.aws ? this.ses.ses : this.ses) || {};
|
|
||||||
let aws = this.ses.aws || {};
|
|
||||||
|
|
||||||
const sesMessage = {
|
|
||||||
RawMessage: {
|
|
||||||
// required
|
|
||||||
Data: 'From: invalid@invalid\r\nTo: invalid@invalid\r\n Subject: Invalid\r\n\r\nInvalid'
|
|
||||||
},
|
|
||||||
Source: 'invalid@invalid',
|
|
||||||
Destinations: ['invalid@invalid']
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
promise = new Promise((resolve, reject) => {
|
|
||||||
callback = shared.callbackPromise(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
const cb = err => {
|
|
||||||
if (err && (err.code || err.Code) !== 'InvalidParameterValue') {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof ses.send === 'function' && aws.SendRawEmailCommand) {
|
|
||||||
// v3 API
|
|
||||||
sesMessage.RawMessage.Data = Buffer.from(sesMessage.RawMessage.Data);
|
|
||||||
ses.send(new aws.SendRawEmailCommand(sesMessage), cb);
|
|
||||||
} else {
|
|
||||||
// v2 API
|
|
||||||
ses.sendRawEmail(sesMessage, cb);
|
|
||||||
}
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = SESTransport;
|
|
||||||
688
lib/node/node_modules/nodemailer/lib/shared/index.js
generated
vendored
688
lib/node/node_modules/nodemailer/lib/shared/index.js
generated
vendored
|
|
@ -1,688 +0,0 @@
|
||||||
/* eslint no-console: 0 */
|
|
||||||
|
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const urllib = require('url');
|
|
||||||
const util = require('util');
|
|
||||||
const fs = require('fs');
|
|
||||||
const nmfetch = require('../fetch');
|
|
||||||
const dns = require('dns');
|
|
||||||
const net = require('net');
|
|
||||||
const os = require('os');
|
|
||||||
|
|
||||||
const DNS_TTL = 5 * 60 * 1000;
|
|
||||||
|
|
||||||
let networkInterfaces;
|
|
||||||
try {
|
|
||||||
networkInterfaces = os.networkInterfaces();
|
|
||||||
} catch (err) {
|
|
||||||
// fails on some systems
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports.networkInterfaces = networkInterfaces;
|
|
||||||
|
|
||||||
const isFamilySupported = (family, allowInternal) => {
|
|
||||||
let networkInterfaces = module.exports.networkInterfaces;
|
|
||||||
if (!networkInterfaces) {
|
|
||||||
// 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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resolver = (family, hostname, options, callback) => {
|
|
||||||
options = options || {};
|
|
||||||
const familySupported = isFamilySupported(family, options.allowInternalNetworkInterfaces);
|
|
||||||
|
|
||||||
if (!familySupported) {
|
|
||||||
return callback(null, []);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resolver = dns.Resolver ? new dns.Resolver(options) : dns;
|
|
||||||
resolver['resolve' + family](hostname, (err, addresses) => {
|
|
||||||
if (err) {
|
|
||||||
switch (err.code) {
|
|
||||||
case dns.NODATA:
|
|
||||||
case dns.NOTFOUND:
|
|
||||||
case dns.NOTIMP:
|
|
||||||
case dns.SERVFAIL:
|
|
||||||
case dns.CONNREFUSED:
|
|
||||||
case dns.REFUSED:
|
|
||||||
case 'EAI_AGAIN':
|
|
||||||
return callback(null, []);
|
|
||||||
}
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return callback(null, Array.isArray(addresses) ? addresses : [].concat(addresses || []));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const dnsCache = (module.exports.dnsCache = new Map());
|
|
||||||
|
|
||||||
const formatDNSValue = (value, extra) => {
|
|
||||||
if (!value) {
|
|
||||||
return Object.assign({}, extra || {});
|
|
||||||
}
|
|
||||||
|
|
||||||
return Object.assign(
|
|
||||||
{
|
|
||||||
servername: value.servername,
|
|
||||||
host:
|
|
||||||
!value.addresses || !value.addresses.length
|
|
||||||
? null
|
|
||||||
: value.addresses.length === 1
|
|
||||||
? value.addresses[0]
|
|
||||||
: value.addresses[Math.floor(Math.random() * value.addresses.length)]
|
|
||||||
},
|
|
||||||
extra || {}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.resolveHostname = (options, callback) => {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
if (!options.host && options.servername) {
|
|
||||||
options.host = options.servername;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!options.host || net.isIP(options.host)) {
|
|
||||||
// nothing to do here
|
|
||||||
let value = {
|
|
||||||
addresses: [options.host],
|
|
||||||
servername: options.servername || false
|
|
||||||
};
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(value, {
|
|
||||||
cached: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let cached;
|
|
||||||
if (dnsCache.has(options.host)) {
|
|
||||||
cached = dnsCache.get(options.host);
|
|
||||||
|
|
||||||
if (!cached.expires || cached.expires >= Date.now()) {
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(cached.value, {
|
|
||||||
cached: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
resolver(4, options.host, options, (err, addresses) => {
|
|
||||||
if (err) {
|
|
||||||
if (cached) {
|
|
||||||
// ignore error, use expired value
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(cached.value, {
|
|
||||||
cached: true,
|
|
||||||
error: err
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addresses && addresses.length) {
|
|
||||||
let value = {
|
|
||||||
addresses,
|
|
||||||
servername: options.servername || options.host
|
|
||||||
};
|
|
||||||
|
|
||||||
dnsCache.set(options.host, {
|
|
||||||
value,
|
|
||||||
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(value, {
|
|
||||||
cached: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
resolver(6, options.host, options, (err, addresses) => {
|
|
||||||
if (err) {
|
|
||||||
if (cached) {
|
|
||||||
// ignore error, use expired value
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(cached.value, {
|
|
||||||
cached: true,
|
|
||||||
error: err
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (addresses && addresses.length) {
|
|
||||||
let value = {
|
|
||||||
addresses,
|
|
||||||
servername: options.servername || options.host
|
|
||||||
};
|
|
||||||
|
|
||||||
dnsCache.set(options.host, {
|
|
||||||
value,
|
|
||||||
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(value, {
|
|
||||||
cached: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
dns.lookup(options.host, { all: true }, (err, addresses) => {
|
|
||||||
if (err) {
|
|
||||||
if (cached) {
|
|
||||||
// ignore error, use expired value
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(cached.value, {
|
|
||||||
cached: true,
|
|
||||||
error: err
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let address = addresses
|
|
||||||
? addresses
|
|
||||||
.filter(addr => isFamilySupported(addr.family))
|
|
||||||
.map(addr => addr.address)
|
|
||||||
.shift()
|
|
||||||
: false;
|
|
||||||
|
|
||||||
if (addresses && addresses.length && !address) {
|
|
||||||
// there are addresses but none can be used
|
|
||||||
console.warn(`Failed to resolve IPv${addresses[0].family} addresses with current network`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!address && cached) {
|
|
||||||
// nothing was found, fallback to cached value
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(cached.value, {
|
|
||||||
cached: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let value = {
|
|
||||||
addresses: address ? [address] : [options.host],
|
|
||||||
servername: options.servername || options.host
|
|
||||||
};
|
|
||||||
|
|
||||||
dnsCache.set(options.host, {
|
|
||||||
value,
|
|
||||||
expires: Date.now() + (options.dnsTtl || DNS_TTL)
|
|
||||||
});
|
|
||||||
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(value, {
|
|
||||||
cached: false
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
if (cached) {
|
|
||||||
// ignore error, use expired value
|
|
||||||
return callback(
|
|
||||||
null,
|
|
||||||
formatDNSValue(cached.value, {
|
|
||||||
cached: true,
|
|
||||||
error: err
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Parses connection url to a structured configuration object
|
|
||||||
*
|
|
||||||
* @param {String} str Connection url
|
|
||||||
* @return {Object} Configuration object
|
|
||||||
*/
|
|
||||||
module.exports.parseConnectionUrl = str => {
|
|
||||||
str = str || '';
|
|
||||||
let options = {};
|
|
||||||
|
|
||||||
[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;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isNaN(url.port) && Number(url.port)) {
|
|
||||||
options.port = Number(url.port);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url.hostname) {
|
|
||||||
options.host = url.hostname;
|
|
||||||
}
|
|
||||||
|
|
||||||
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];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
logger[level](entry, message, ...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a bunyan-compatible logger interface. Uses either provided logger or
|
|
||||||
* creates a default console logger
|
|
||||||
*
|
|
||||||
* @param {Object} [options] Options object that might include 'logger' value
|
|
||||||
* @return {Object} bunyan compatible logger
|
|
||||||
*/
|
|
||||||
module.exports.getLogger = (options, defaults) => {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
let response = {};
|
|
||||||
let levels = ['trace', 'debug', 'info', 'warn', 'error', 'fatal'];
|
|
||||||
|
|
||||||
if (!options.logger) {
|
|
||||||
// use vanity logger
|
|
||||||
levels.forEach(level => {
|
|
||||||
response[level] = () => false;
|
|
||||||
});
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
let logger = options.logger;
|
|
||||||
|
|
||||||
if (options.logger === true) {
|
|
||||||
// create console logger
|
|
||||||
logger = createDefaultLogger(levels);
|
|
||||||
}
|
|
||||||
|
|
||||||
levels.forEach(level => {
|
|
||||||
response[level] = (data, message, ...args) => {
|
|
||||||
module.exports._logFunc(logger, level, defaults, data, message, ...args);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper for creating a callback that either resolves or rejects a promise
|
|
||||||
* based on input
|
|
||||||
*
|
|
||||||
* @param {Function} resolve Function to run if callback is called
|
|
||||||
* @param {Function} reject Function to run if callback ends with an error
|
|
||||||
*/
|
|
||||||
module.exports.callbackPromise = (resolve, reject) =>
|
|
||||||
function () {
|
|
||||||
let args = Array.from(arguments);
|
|
||||||
let err = args.shift();
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(...args);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.parseDataURI = uri => {
|
|
||||||
let input = uri;
|
|
||||||
let commaPos = input.indexOf(',');
|
|
||||||
if (!commaPos) {
|
|
||||||
return uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
let data = input.substring(commaPos + 1);
|
|
||||||
let metaStr = input.substring('data:'.length, commaPos);
|
|
||||||
|
|
||||||
let encoding;
|
|
||||||
|
|
||||||
let metaEntries = metaStr.split(';');
|
|
||||||
let lastMetaEntry = metaEntries.length > 1 ? metaEntries[metaEntries.length - 1] : false;
|
|
||||||
if (lastMetaEntry && lastMetaEntry.indexOf('=') < 0) {
|
|
||||||
encoding = lastMetaEntry.toLowerCase();
|
|
||||||
metaEntries.pop();
|
|
||||||
}
|
|
||||||
|
|
||||||
let contentType = metaEntries.shift() || 'application/octet-stream';
|
|
||||||
let params = {};
|
|
||||||
for (let entry of metaEntries) {
|
|
||||||
let sep = entry.indexOf('=');
|
|
||||||
if (sep >= 0) {
|
|
||||||
let key = entry.substring(0, sep);
|
|
||||||
let value = entry.substring(sep + 1);
|
|
||||||
params[key] = value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (encoding) {
|
|
||||||
case 'base64':
|
|
||||||
data = Buffer.from(data, 'base64');
|
|
||||||
break;
|
|
||||||
case 'utf8':
|
|
||||||
data = Buffer.from(data);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
try {
|
|
||||||
data = Buffer.from(decodeURIComponent(data));
|
|
||||||
} catch (err) {
|
|
||||||
data = Buffer.from(data);
|
|
||||||
}
|
|
||||||
data = Buffer.from(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { data, encoding, contentType, params };
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves a String or a Buffer value for content value. Useful if the value
|
|
||||||
* is a Stream or a file or an URL. If the value is a Stream, overwrites
|
|
||||||
* the stream object with the resolved value (you can't stream a value twice).
|
|
||||||
*
|
|
||||||
* This is useful when you want to create a plugin that needs a content value,
|
|
||||||
* for example the `html` or `text` value as a String or a Buffer but not as
|
|
||||||
* a file path or an URL.
|
|
||||||
*
|
|
||||||
* @param {Object} data An object or an Array you want to resolve an element for
|
|
||||||
* @param {String|Number} key Property name or an Array index
|
|
||||||
* @param {Function} callback Callback function with (err, value)
|
|
||||||
*/
|
|
||||||
module.exports.resolveContent = (data, key, callback) => {
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
promise = new Promise((resolve, reject) => {
|
|
||||||
callback = module.exports.callbackPromise(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let content = (data && data[key] && data[key].content) || data[key];
|
|
||||||
let contentStream;
|
|
||||||
let encoding = ((typeof data[key] === 'object' && data[key].encoding) || 'utf8')
|
|
||||||
.toString()
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[-_\s]/g, '');
|
|
||||||
|
|
||||||
if (!content) {
|
|
||||||
return callback(null, content);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof content === 'object') {
|
|
||||||
if (typeof content.pipe === 'function') {
|
|
||||||
return resolveStream(content, (err, value) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
// we can't stream twice the same content, so we need
|
|
||||||
// to replace the stream object with the streaming result
|
|
||||||
if (data[key].content) {
|
|
||||||
data[key].content = value;
|
|
||||||
} else {
|
|
||||||
data[key] = value;
|
|
||||||
}
|
|
||||||
callback(null, value);
|
|
||||||
});
|
|
||||||
} else if (/^https?:\/\//i.test(content.path || content.href)) {
|
|
||||||
contentStream = nmfetch(content.path || content.href);
|
|
||||||
return resolveStream(contentStream, callback);
|
|
||||||
} else if (/^data:/i.test(content.path || content.href)) {
|
|
||||||
let parsedDataUri = module.exports.parseDataURI(content.path || content.href);
|
|
||||||
|
|
||||||
if (!parsedDataUri || !parsedDataUri.data) {
|
|
||||||
return callback(null, Buffer.from(0));
|
|
||||||
}
|
|
||||||
return callback(null, parsedDataUri.data);
|
|
||||||
} else if (content.path) {
|
|
||||||
return resolveStream(fs.createReadStream(content.path), callback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof data[key].content === 'string' && !['utf8', 'usascii', 'ascii'].includes(encoding)) {
|
|
||||||
content = Buffer.from(data[key].content, encoding);
|
|
||||||
}
|
|
||||||
|
|
||||||
// default action, return as is
|
|
||||||
setImmediate(() => callback(null, content));
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Copies properties from source objects to target objects
|
|
||||||
*/
|
|
||||||
module.exports.assign = function (/* target, ... sources */) {
|
|
||||||
let args = Array.from(arguments);
|
|
||||||
let 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];
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
target[key] = source[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
return target;
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.encodeXText = str => {
|
|
||||||
// ! 0x21
|
|
||||||
// + 0x2B
|
|
||||||
// = 0x3D
|
|
||||||
// ~ 0x7E
|
|
||||||
if (!/[^\x21-\x2A\x2C-\x3C\x3E-\x7E]/.test(str)) {
|
|
||||||
return str;
|
|
||||||
}
|
|
||||||
let buf = Buffer.from(str);
|
|
||||||
let result = '';
|
|
||||||
for (let i = 0, len = buf.length; i < len; i++) {
|
|
||||||
let c = buf[i];
|
|
||||||
if (c < 0x21 || c > 0x7e || c === 0x2b || c === 0x3d) {
|
|
||||||
result += '+' + (c < 0x10 ? '0' : '') + c.toString(16).toUpperCase();
|
|
||||||
} else {
|
|
||||||
result += String.fromCharCode(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Streams a stream value into a Buffer
|
|
||||||
*
|
|
||||||
* @param {Object} stream Readable stream
|
|
||||||
* @param {Function} callback Callback function with (err, value)
|
|
||||||
*/
|
|
||||||
function resolveStream(stream, callback) {
|
|
||||||
let responded = false;
|
|
||||||
let chunks = [];
|
|
||||||
let chunklen = 0;
|
|
||||||
|
|
||||||
stream.on('error', err => {
|
|
||||||
if (responded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
responded = true;
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('readable', () => {
|
|
||||||
let chunk;
|
|
||||||
while ((chunk = stream.read()) !== null) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
chunklen += chunk.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('end', () => {
|
|
||||||
if (responded) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
responded = true;
|
|
||||||
|
|
||||||
let value;
|
|
||||||
|
|
||||||
try {
|
|
||||||
value = Buffer.concat(chunks, chunklen);
|
|
||||||
} catch (E) {
|
|
||||||
return callback(E);
|
|
||||||
}
|
|
||||||
callback(null, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a bunyan-like logger that prints to console
|
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
levels.forEach(level => {
|
|
||||||
let levelName = level.toUpperCase();
|
|
||||||
if (levelName.length < levelMaxLen) {
|
|
||||||
levelName += ' '.repeat(levelMaxLen - levelName.length);
|
|
||||||
}
|
|
||||||
levelNames.set(level, levelName);
|
|
||||||
});
|
|
||||||
|
|
||||||
let print = (level, entry, message, ...args) => {
|
|
||||||
let prefix = '';
|
|
||||||
if (entry) {
|
|
||||||
if (entry.tnx === 'server') {
|
|
||||||
prefix = 'S: ';
|
|
||||||
} else if (entry.tnx === 'client') {
|
|
||||||
prefix = 'C: ';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.sid) {
|
|
||||||
prefix = '[' + entry.sid + '] ' + prefix;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (entry.cid) {
|
|
||||||
prefix = '[#' + entry.cid + '] ' + prefix;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
message = util.format(message, ...args);
|
|
||||||
message.split(/\r?\n/).forEach(line => {
|
|
||||||
console.log('[%s] %s %s', new Date().toISOString().substr(0, 19).replace(/T/, ' '), levelNames.get(level), prefix + line);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
let logger = {};
|
|
||||||
levels.forEach(level => {
|
|
||||||
logger[level] = print.bind(null, level);
|
|
||||||
});
|
|
||||||
|
|
||||||
return logger;
|
|
||||||
}
|
|
||||||
108
lib/node/node_modules/nodemailer/lib/smtp-connection/data-stream.js
generated
vendored
108
lib/node/node_modules/nodemailer/lib/smtp-connection/data-stream.js
generated
vendored
|
|
@ -1,108 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const stream = require('stream');
|
|
||||||
const Transform = stream.Transform;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes dots in the beginning of lines. Ends the stream with <CR><LF>.<CR><LF>
|
|
||||||
* Also makes sure that only <CR><LF> sequences are used for linebreaks
|
|
||||||
*
|
|
||||||
* @param {Object} options Stream options
|
|
||||||
*/
|
|
||||||
class DataStream extends Transform {
|
|
||||||
constructor(options) {
|
|
||||||
super(options);
|
|
||||||
// init Transform
|
|
||||||
this.options = options || {};
|
|
||||||
this._curLine = '';
|
|
||||||
|
|
||||||
this.inByteCount = 0;
|
|
||||||
this.outByteCount = 0;
|
|
||||||
this.lastByte = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Escapes dots
|
|
||||||
*/
|
|
||||||
_transform(chunk, encoding, done) {
|
|
||||||
let chunks = [];
|
|
||||||
let chunklen = 0;
|
|
||||||
let i,
|
|
||||||
len,
|
|
||||||
lastPos = 0;
|
|
||||||
let buf;
|
|
||||||
|
|
||||||
if (!chunk || !chunk.length) {
|
|
||||||
return done();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof chunk === 'string') {
|
|
||||||
chunk = Buffer.from(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inByteCount += chunk.length;
|
|
||||||
|
|
||||||
for (i = 0, len = chunk.length; i < len; i++) {
|
|
||||||
if (chunk[i] === 0x2e) {
|
|
||||||
// .
|
|
||||||
if ((i && chunk[i - 1] === 0x0a) || (!i && (!this.lastByte || this.lastByte === 0x0a))) {
|
|
||||||
buf = chunk.slice(lastPos, i + 1);
|
|
||||||
chunks.push(buf);
|
|
||||||
chunks.push(Buffer.from('.'));
|
|
||||||
chunklen += buf.length + 1;
|
|
||||||
lastPos = i + 1;
|
|
||||||
}
|
|
||||||
} else if (chunk[i] === 0x0a) {
|
|
||||||
// .
|
|
||||||
if ((i && chunk[i - 1] !== 0x0d) || (!i && this.lastByte !== 0x0d)) {
|
|
||||||
if (i > lastPos) {
|
|
||||||
buf = chunk.slice(lastPos, i);
|
|
||||||
chunks.push(buf);
|
|
||||||
chunklen += buf.length + 2;
|
|
||||||
} else {
|
|
||||||
chunklen += 2;
|
|
||||||
}
|
|
||||||
chunks.push(Buffer.from('\r\n'));
|
|
||||||
lastPos = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (chunklen) {
|
|
||||||
// add last piece
|
|
||||||
if (lastPos < chunk.length) {
|
|
||||||
buf = chunk.slice(lastPos);
|
|
||||||
chunks.push(buf);
|
|
||||||
chunklen += buf.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.outByteCount += chunklen;
|
|
||||||
this.push(Buffer.concat(chunks, chunklen));
|
|
||||||
} else {
|
|
||||||
this.outByteCount += chunk.length;
|
|
||||||
this.push(chunk);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.lastByte = chunk[chunk.length - 1];
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Finalizes the stream with a dot on a single line
|
|
||||||
*/
|
|
||||||
_flush(done) {
|
|
||||||
let buf;
|
|
||||||
if (this.lastByte === 0x0a) {
|
|
||||||
buf = Buffer.from('.\r\n');
|
|
||||||
} else if (this.lastByte === 0x0d) {
|
|
||||||
buf = Buffer.from('\n.\r\n');
|
|
||||||
} else {
|
|
||||||
buf = Buffer.from('\r\n.\r\n');
|
|
||||||
}
|
|
||||||
this.outByteCount += buf.length;
|
|
||||||
this.push(buf);
|
|
||||||
done();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = DataStream;
|
|
||||||
143
lib/node/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js
generated
vendored
143
lib/node/node_modules/nodemailer/lib/smtp-connection/http-proxy-client.js
generated
vendored
|
|
@ -1,143 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Minimal HTTP/S proxy client
|
|
||||||
*/
|
|
||||||
|
|
||||||
const net = require('net');
|
|
||||||
const tls = require('tls');
|
|
||||||
const urllib = require('url');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Establishes proxied connection to destinationPort
|
|
||||||
*
|
|
||||||
* httpProxyClient("http://localhost:3128/", 80, "google.com", function(err, socket){
|
|
||||||
* socket.write("GET / HTTP/1.0\r\n\r\n");
|
|
||||||
* });
|
|
||||||
*
|
|
||||||
* @param {String} proxyUrl proxy configuration, etg "http://proxy.host:3128/"
|
|
||||||
* @param {Number} destinationPort Port to open in destination host
|
|
||||||
* @param {String} destinationHost Destination hostname
|
|
||||||
* @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);
|
|
||||||
|
|
||||||
// create a socket connection to the proxy server
|
|
||||||
let options;
|
|
||||||
let connect;
|
|
||||||
let socket;
|
|
||||||
|
|
||||||
options = {
|
|
||||||
host: proxy.hostname,
|
|
||||||
port: Number(proxy.port) ? Number(proxy.port) : proxy.protocol === 'https:' ? 443 : 80
|
|
||||||
};
|
|
||||||
|
|
||||||
if (proxy.protocol === 'https:') {
|
|
||||||
// we can use untrusted proxies as long as we verify actual SMTP certificates
|
|
||||||
options.rejectUnauthorized = false;
|
|
||||||
connect = tls.connect.bind(tls);
|
|
||||||
} else {
|
|
||||||
connect = net.connect.bind(net);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 => {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
finished = true;
|
|
||||||
try {
|
|
||||||
socket.destroy();
|
|
||||||
} catch (E) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
callback(err);
|
|
||||||
};
|
|
||||||
|
|
||||||
let timeoutErr = () => {
|
|
||||||
let err = new Error('Proxy socket timed out');
|
|
||||||
err.code = 'ETIMEDOUT';
|
|
||||||
tempSocketErr(err);
|
|
||||||
};
|
|
||||||
|
|
||||||
socket = connect(options, () => {
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let reqHeaders = {
|
|
||||||
Host: destinationHost + ':' + destinationPort,
|
|
||||||
Connection: 'close'
|
|
||||||
};
|
|
||||||
if (proxy.auth) {
|
|
||||||
reqHeaders['Proxy-Authorization'] = 'Basic ' + Buffer.from(proxy.auth).toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.write(
|
|
||||||
// HTTP method
|
|
||||||
'CONNECT ' +
|
|
||||||
destinationHost +
|
|
||||||
':' +
|
|
||||||
destinationPort +
|
|
||||||
' HTTP/1.1\r\n' +
|
|
||||||
// HTTP request headers
|
|
||||||
Object.keys(reqHeaders)
|
|
||||||
.map(key => key + ': ' + reqHeaders[key])
|
|
||||||
.join('\r\n') +
|
|
||||||
// End request
|
|
||||||
'\r\n\r\n'
|
|
||||||
);
|
|
||||||
|
|
||||||
let headers = '';
|
|
||||||
let onSocketData = chunk => {
|
|
||||||
let match;
|
|
||||||
let remainder;
|
|
||||||
|
|
||||||
if (finished) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
headers += chunk.toString('binary');
|
|
||||||
if ((match = headers.match(/\r\n\r\n/))) {
|
|
||||||
socket.removeListener('data', onSocketData);
|
|
||||||
|
|
||||||
remainder = headers.substr(match.index + match[0].length);
|
|
||||||
headers = headers.substr(0, match.index);
|
|
||||||
if (remainder) {
|
|
||||||
socket.unshift(Buffer.from(remainder, 'binary'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// proxy connection is now established
|
|
||||||
finished = true;
|
|
||||||
|
|
||||||
// check response code
|
|
||||||
match = headers.match(/^HTTP\/\d+\.\d+ (\d+)/i);
|
|
||||||
if (!match || (match[1] || '').charAt(0) !== '2') {
|
|
||||||
try {
|
|
||||||
socket.destroy();
|
|
||||||
} catch (E) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return callback(new Error('Invalid response from proxy' + ((match && ': ' + match[1]) || '')));
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.removeListener('error', tempSocketErr);
|
|
||||||
socket.removeListener('timeout', timeoutErr);
|
|
||||||
socket.setTimeout(0);
|
|
||||||
|
|
||||||
return callback(null, socket);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
socket.on('data', onSocketData);
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.setTimeout(httpProxyClient.timeout || 30 * 1000);
|
|
||||||
socket.on('timeout', timeoutErr);
|
|
||||||
|
|
||||||
socket.once('error', tempSocketErr);
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = httpProxyClient;
|
|
||||||
1825
lib/node/node_modules/nodemailer/lib/smtp-connection/index.js
generated
vendored
1825
lib/node/node_modules/nodemailer/lib/smtp-connection/index.js
generated
vendored
File diff suppressed because it is too large
Load diff
648
lib/node/node_modules/nodemailer/lib/smtp-pool/index.js
generated
vendored
648
lib/node/node_modules/nodemailer/lib/smtp-pool/index.js
generated
vendored
|
|
@ -1,648 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
|
||||||
const PoolResource = require('./pool-resource');
|
|
||||||
const SMTPConnection = require('../smtp-connection');
|
|
||||||
const wellKnown = require('../well-known');
|
|
||||||
const shared = require('../shared');
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a SMTP pool transport object for Nodemailer
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} options SMTP Connection options
|
|
||||||
*/
|
|
||||||
class SMTPPool extends EventEmitter {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
if (typeof options === 'string') {
|
|
||||||
options = {
|
|
||||||
url: options
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let urlData;
|
|
||||||
let service = options.service;
|
|
||||||
|
|
||||||
if (typeof options.getSocket === 'function') {
|
|
||||||
this.getSocket = options.getSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.url) {
|
|
||||||
urlData = shared.parseConnectionUrl(options.url);
|
|
||||||
service = service || urlData.service;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.options = shared.assign(
|
|
||||||
false, // create new object
|
|
||||||
options, // regular options
|
|
||||||
urlData, // url options
|
|
||||||
service && wellKnown(service) // wellknown options
|
|
||||||
);
|
|
||||||
|
|
||||||
this.options.maxConnections = this.options.maxConnections || 5;
|
|
||||||
this.options.maxMessages = this.options.maxMessages || 100;
|
|
||||||
|
|
||||||
this.logger = shared.getLogger(this.options, {
|
|
||||||
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._rateLimit = {
|
|
||||||
counter: 0,
|
|
||||||
timeout: null,
|
|
||||||
waiting: [],
|
|
||||||
checkpoint: false,
|
|
||||||
delta: Number(this.options.rateDelta) || 1000,
|
|
||||||
limit: Number(this.options.rateLimit) || 0
|
|
||||||
};
|
|
||||||
this._closed = false;
|
|
||||||
this._queue = [];
|
|
||||||
this._connections = [];
|
|
||||||
this._connectionCounter = 0;
|
|
||||||
|
|
||||||
this.idling = true;
|
|
||||||
|
|
||||||
setImmediate(() => {
|
|
||||||
if (this.idling) {
|
|
||||||
this.emit('idle');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder function for creating proxy sockets. This method immediatelly returns
|
|
||||||
* without a socket
|
|
||||||
*
|
|
||||||
* @param {Object} options Connection options
|
|
||||||
* @param {Function} callback Callback function to run with the socket keys
|
|
||||||
*/
|
|
||||||
getSocket(options, callback) {
|
|
||||||
// return immediatelly
|
|
||||||
return setImmediate(() => callback(null, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Queues an e-mail to be sent using the selected settings
|
|
||||||
*
|
|
||||||
* @param {Object} mail Mail object
|
|
||||||
* @param {Function} callback Callback function
|
|
||||||
*/
|
|
||||||
send(mail, callback) {
|
|
||||||
if (this._closed) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
this._queue.push({
|
|
||||||
mail,
|
|
||||||
requeueAttempts: 0,
|
|
||||||
callback
|
|
||||||
});
|
|
||||||
|
|
||||||
if (this.idling && this._queue.length >= this.options.maxConnections) {
|
|
||||||
this.idling = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setImmediate(() => this._processMessages());
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes all connections in the pool. If there is a message being sent, the connection
|
|
||||||
* is closed later
|
|
||||||
*/
|
|
||||||
close() {
|
|
||||||
let connection;
|
|
||||||
let len = this._connections.length;
|
|
||||||
this._closed = true;
|
|
||||||
|
|
||||||
// clear rate limit timer if it exists
|
|
||||||
clearTimeout(this._rateLimit.timeout);
|
|
||||||
|
|
||||||
if (!len && !this._queue.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove all available connections
|
|
||||||
for (let i = len - 1; i >= 0; i--) {
|
|
||||||
if (this._connections[i] && this._connections[i].available) {
|
|
||||||
connection = this._connections[i];
|
|
||||||
connection.close();
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'connection',
|
|
||||||
cid: connection.id,
|
|
||||||
action: 'removed'
|
|
||||||
},
|
|
||||||
'Connection #%s removed',
|
|
||||||
connection.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (len && !this._connections.length) {
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'connection'
|
|
||||||
},
|
|
||||||
'All connections removed'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this._queue.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// make sure that entire queue would be cleaned
|
|
||||||
let invokeCallbacks = () => {
|
|
||||||
if (!this._queue.length) {
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'connection'
|
|
||||||
},
|
|
||||||
'Pending queue entries cleared'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let entry = this._queue.shift();
|
|
||||||
if (entry && typeof entry.callback === 'function') {
|
|
||||||
try {
|
|
||||||
entry.callback(new Error('Connection pool was closed'));
|
|
||||||
} catch (E) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: E,
|
|
||||||
tnx: 'callback',
|
|
||||||
cid: connection.id
|
|
||||||
},
|
|
||||||
'Callback error for #%s: %s',
|
|
||||||
connection.id,
|
|
||||||
E.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setImmediate(invokeCallbacks);
|
|
||||||
};
|
|
||||||
setImmediate(invokeCallbacks);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check the queue and available connections. If there is a message to be sent and there is
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// do nothing if queue is empty
|
|
||||||
if (!this._queue.length) {
|
|
||||||
if (!this.idling) {
|
|
||||||
// no pending jobs
|
|
||||||
this.idling = true;
|
|
||||||
this.emit('idle');
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// find first available connection
|
|
||||||
for (i = 0, len = this._connections.length; i < len; i++) {
|
|
||||||
if (this._connections[i].available) {
|
|
||||||
connection = this._connections[i];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connection && this._connections.length < this.options.maxConnections) {
|
|
||||||
connection = this._createConnection();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!connection) {
|
|
||||||
// no more free connection slots available
|
|
||||||
this.idling = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// check if there is free space in the processing queue
|
|
||||||
if (!this.idling && this._queue.length < this.options.maxConnections) {
|
|
||||||
this.idling = true;
|
|
||||||
this.emit('idle');
|
|
||||||
}
|
|
||||||
|
|
||||||
let entry = (connection.queueEntry = this._queue.shift());
|
|
||||||
entry.messageId = (connection.queueEntry.mail.message.getHeader('message-id') || '').replace(/[<>\s]/g, '');
|
|
||||||
|
|
||||||
connection.available = false;
|
|
||||||
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'pool',
|
|
||||||
cid: connection.id,
|
|
||||||
messageId: entry.messageId,
|
|
||||||
action: 'assign'
|
|
||||||
},
|
|
||||||
'Assigned message <%s> to #%s (%s)',
|
|
||||||
entry.messageId,
|
|
||||||
connection.id,
|
|
||||||
connection.messages + 1
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this._rateLimit.limit) {
|
|
||||||
this._rateLimit.counter++;
|
|
||||||
if (!this._rateLimit.checkpoint) {
|
|
||||||
this._rateLimit.checkpoint = Date.now();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
connection.send(entry.mail, (err, info) => {
|
|
||||||
// only process callback if current handler is not changed
|
|
||||||
if (entry === connection.queueEntry) {
|
|
||||||
try {
|
|
||||||
entry.callback(err, info);
|
|
||||||
} catch (E) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: E,
|
|
||||||
tnx: 'callback',
|
|
||||||
cid: connection.id
|
|
||||||
},
|
|
||||||
'Callback error for #%s: %s',
|
|
||||||
connection.id,
|
|
||||||
E.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
connection.queueEntry = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new pool resource
|
|
||||||
*/
|
|
||||||
_createConnection() {
|
|
||||||
let connection = new PoolResource(this);
|
|
||||||
|
|
||||||
connection.id = ++this._connectionCounter;
|
|
||||||
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'pool',
|
|
||||||
cid: connection.id,
|
|
||||||
action: 'conection'
|
|
||||||
},
|
|
||||||
'Created new pool resource #%s',
|
|
||||||
connection.id
|
|
||||||
);
|
|
||||||
|
|
||||||
// resource comes available
|
|
||||||
connection.on('available', () => {
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'connection',
|
|
||||||
cid: connection.id,
|
|
||||||
action: 'available'
|
|
||||||
},
|
|
||||||
'Connection #%s became available',
|
|
||||||
connection.id
|
|
||||||
);
|
|
||||||
|
|
||||||
if (this._closed) {
|
|
||||||
// if already closed run close() that will remove this connections from connections list
|
|
||||||
this.close();
|
|
||||||
} else {
|
|
||||||
// check if there's anything else to send
|
|
||||||
this._processMessages();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// resource is terminated with an error
|
|
||||||
connection.once('error', err => {
|
|
||||||
if (err.code !== 'EMAXLIMIT') {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'pool',
|
|
||||||
cid: connection.id
|
|
||||||
},
|
|
||||||
'Pool Error for #%s: %s',
|
|
||||||
connection.id,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'pool',
|
|
||||||
cid: connection.id,
|
|
||||||
action: 'maxlimit'
|
|
||||||
},
|
|
||||||
'Max messages limit exchausted for #%s',
|
|
||||||
connection.id
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (connection.queueEntry) {
|
|
||||||
try {
|
|
||||||
connection.queueEntry.callback(err);
|
|
||||||
} catch (E) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: E,
|
|
||||||
tnx: 'callback',
|
|
||||||
cid: connection.id
|
|
||||||
},
|
|
||||||
'Callback error for #%s: %s',
|
|
||||||
connection.id,
|
|
||||||
E.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
connection.queueEntry = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// remove the erroneus connection from connections list
|
|
||||||
this._removeConnection(connection);
|
|
||||||
|
|
||||||
this._continueProcessing();
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.once('close', () => {
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'connection',
|
|
||||||
cid: connection.id,
|
|
||||||
action: 'closed'
|
|
||||||
},
|
|
||||||
'Connection #%s was closed',
|
|
||||||
connection.id
|
|
||||||
);
|
|
||||||
|
|
||||||
this._removeConnection(connection);
|
|
||||||
|
|
||||||
if (connection.queueEntry) {
|
|
||||||
// If the connection closed when sending, add the message to the queue again
|
|
||||||
// if max number of requeues is not reached yet
|
|
||||||
// Note that we must wait a bit.. because the callback of the 'error' handler might be called
|
|
||||||
// in the next event loop
|
|
||||||
setTimeout(() => {
|
|
||||||
if (connection.queueEntry) {
|
|
||||||
if (this._shouldRequeuOnConnectionClose(connection.queueEntry)) {
|
|
||||||
this._requeueEntryOnConnectionClose(connection);
|
|
||||||
} else {
|
|
||||||
this._failDeliveryOnConnectionClose(connection);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this._continueProcessing();
|
|
||||||
}, 50);
|
|
||||||
} else {
|
|
||||||
this._continueProcessing();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this._connections.push(connection);
|
|
||||||
|
|
||||||
return connection;
|
|
||||||
}
|
|
||||||
|
|
||||||
_shouldRequeuOnConnectionClose(queueEntry) {
|
|
||||||
if (this.options.maxRequeues === undefined || this.options.maxRequeues < 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return queueEntry.requeueAttempts < this.options.maxRequeues;
|
|
||||||
}
|
|
||||||
|
|
||||||
_failDeliveryOnConnectionClose(connection) {
|
|
||||||
if (connection.queueEntry && connection.queueEntry.callback) {
|
|
||||||
try {
|
|
||||||
connection.queueEntry.callback(new Error('Reached maximum number of retries after connection was closed'));
|
|
||||||
} catch (E) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: E,
|
|
||||||
tnx: 'callback',
|
|
||||||
messageId: connection.queueEntry.messageId,
|
|
||||||
cid: connection.id
|
|
||||||
},
|
|
||||||
'Callback error for #%s: %s',
|
|
||||||
connection.id,
|
|
||||||
E.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
connection.queueEntry = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
_requeueEntryOnConnectionClose(connection) {
|
|
||||||
connection.queueEntry.requeueAttempts = connection.queueEntry.requeueAttempts + 1;
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'pool',
|
|
||||||
cid: connection.id,
|
|
||||||
messageId: connection.queueEntry.messageId,
|
|
||||||
action: 'requeue'
|
|
||||||
},
|
|
||||||
'Re-queued message <%s> for #%s. Attempt: #%s',
|
|
||||||
connection.queueEntry.messageId,
|
|
||||||
connection.id,
|
|
||||||
connection.queueEntry.requeueAttempts
|
|
||||||
);
|
|
||||||
this._queue.unshift(connection.queueEntry);
|
|
||||||
connection.queueEntry = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Continue to process message if the pool hasn't closed
|
|
||||||
*/
|
|
||||||
_continueProcessing() {
|
|
||||||
if (this._closed) {
|
|
||||||
this.close();
|
|
||||||
} else {
|
|
||||||
setTimeout(() => this._processMessages(), 100);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove resource from pool
|
|
||||||
*
|
|
||||||
* @param {Object} connection The PoolResource to remove
|
|
||||||
*/
|
|
||||||
_removeConnection(connection) {
|
|
||||||
let index = this._connections.indexOf(connection);
|
|
||||||
|
|
||||||
if (index !== -1) {
|
|
||||||
this._connections.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if connections have hit current rate limit and if so, queues the availability callback
|
|
||||||
*
|
|
||||||
* @param {Function} callback Callback function to run once rate limiter has been cleared
|
|
||||||
*/
|
|
||||||
_checkRateLimit(callback) {
|
|
||||||
if (!this._rateLimit.limit) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
let now = Date.now();
|
|
||||||
|
|
||||||
if (this._rateLimit.counter < this._rateLimit.limit) {
|
|
||||||
return callback();
|
|
||||||
}
|
|
||||||
|
|
||||||
this._rateLimit.waiting.push(callback);
|
|
||||||
|
|
||||||
if (this._rateLimit.checkpoint <= now - this._rateLimit.delta) {
|
|
||||||
return this._clearRateLimit();
|
|
||||||
} else if (!this._rateLimit.timeout) {
|
|
||||||
this._rateLimit.timeout = setTimeout(() => this._clearRateLimit(), this._rateLimit.delta - (now - this._rateLimit.checkpoint));
|
|
||||||
this._rateLimit.checkpoint = now;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears current rate limit limitation and runs paused callback
|
|
||||||
*/
|
|
||||||
_clearRateLimit() {
|
|
||||||
clearTimeout(this._rateLimit.timeout);
|
|
||||||
this._rateLimit.timeout = null;
|
|
||||||
this._rateLimit.counter = 0;
|
|
||||||
this._rateLimit.checkpoint = false;
|
|
||||||
|
|
||||||
// resume all paused connections
|
|
||||||
while (this._rateLimit.waiting.length) {
|
|
||||||
let cb = this._rateLimit.waiting.shift();
|
|
||||||
setImmediate(cb);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if there are free slots in the queue
|
|
||||||
*/
|
|
||||||
isIdle() {
|
|
||||||
return this.idling;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies SMTP configuration
|
|
||||||
*
|
|
||||||
* @param {Function} callback Callback function
|
|
||||||
*/
|
|
||||||
verify(callback) {
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
promise = new Promise((resolve, reject) => {
|
|
||||||
callback = shared.callbackPromise(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let auth = new PoolResource(this).auth;
|
|
||||||
|
|
||||||
this.getSocket(this.options, (err, socketOptions) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = this.options;
|
|
||||||
if (socketOptions && socketOptions.connection) {
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'proxy',
|
|
||||||
remoteAddress: socketOptions.connection.remoteAddress,
|
|
||||||
remotePort: socketOptions.connection.remotePort,
|
|
||||||
destHost: options.host || '',
|
|
||||||
destPort: options.port || '',
|
|
||||||
action: 'connected'
|
|
||||||
},
|
|
||||||
'Using proxied socket from %s:%s to %s:%s',
|
|
||||||
socketOptions.connection.remoteAddress,
|
|
||||||
socketOptions.connection.remotePort,
|
|
||||||
options.host || '',
|
|
||||||
options.port || ''
|
|
||||||
);
|
|
||||||
options = shared.assign(false, options);
|
|
||||||
Object.keys(socketOptions).forEach(key => {
|
|
||||||
options[key] = socketOptions[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection = new SMTPConnection(options);
|
|
||||||
let returned = false;
|
|
||||||
|
|
||||||
connection.once('error', err => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.once('end', () => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
return callback(new Error('Connection closed'));
|
|
||||||
});
|
|
||||||
|
|
||||||
let finalize = () => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
connection.quit();
|
|
||||||
return callback(null, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
connection.connect(() => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auth && (connection.allowsAuth || options.forceAuth)) {
|
|
||||||
connection.login(auth, err => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize();
|
|
||||||
});
|
|
||||||
} else if (!auth && connection.allowsAuth && options.forceAuth) {
|
|
||||||
let err = new Error('Authentication info was not provided');
|
|
||||||
err.code = 'NoAuth';
|
|
||||||
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
} else {
|
|
||||||
finalize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expose to the world
|
|
||||||
module.exports = SMTPPool;
|
|
||||||
253
lib/node/node_modules/nodemailer/lib/smtp-pool/pool-resource.js
generated
vendored
253
lib/node/node_modules/nodemailer/lib/smtp-pool/pool-resource.js
generated
vendored
|
|
@ -1,253 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const SMTPConnection = require('../smtp-connection');
|
|
||||||
const assign = require('../shared').assign;
|
|
||||||
const XOAuth2 = require('../xoauth2');
|
|
||||||
const EventEmitter = require('events');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an element for the pool
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} options SMTPPool instance
|
|
||||||
*/
|
|
||||||
class PoolResource extends EventEmitter {
|
|
||||||
constructor(pool) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.pool = pool;
|
|
||||||
this.options = pool.options;
|
|
||||||
this.logger = this.pool.logger;
|
|
||||||
|
|
||||||
if (this.options.auth) {
|
|
||||||
switch ((this.options.auth.type || '').toString().toUpperCase()) {
|
|
||||||
case 'OAUTH2': {
|
|
||||||
let oauth2 = new XOAuth2(this.options.auth, this.logger);
|
|
||||||
oauth2.provisionCallback = (this.pool.mailer && this.pool.mailer.get('oauth2_provision_cb')) || oauth2.provisionCallback;
|
|
||||||
this.auth = {
|
|
||||||
type: 'OAUTH2',
|
|
||||||
user: this.options.auth.user,
|
|
||||||
oauth2,
|
|
||||||
method: 'XOAUTH2'
|
|
||||||
};
|
|
||||||
oauth2.on('token', token => this.pool.mailer.emit('token', token));
|
|
||||||
oauth2.on('error', err => this.emit('error', err));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if (!this.options.auth.user && !this.options.auth.pass) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.auth = {
|
|
||||||
type: (this.options.auth.type || '').toString().toUpperCase() || 'LOGIN',
|
|
||||||
user: this.options.auth.user,
|
|
||||||
credentials: {
|
|
||||||
user: this.options.auth.user || '',
|
|
||||||
pass: this.options.auth.pass,
|
|
||||||
options: this.options.auth.options
|
|
||||||
},
|
|
||||||
method: (this.options.auth.method || '').trim().toUpperCase() || this.options.authMethod || false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
this._connection = false;
|
|
||||||
this._connected = false;
|
|
||||||
|
|
||||||
this.messages = 0;
|
|
||||||
this.available = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initiates a connection to the SMTP server
|
|
||||||
*
|
|
||||||
* @param {Function} callback Callback function to run once the connection is established or failed
|
|
||||||
*/
|
|
||||||
connect(callback) {
|
|
||||||
this.pool.getSocket(this.options, (err, socketOptions) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let returned = false;
|
|
||||||
let options = this.options;
|
|
||||||
if (socketOptions && socketOptions.connection) {
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'proxy',
|
|
||||||
remoteAddress: socketOptions.connection.remoteAddress,
|
|
||||||
remotePort: socketOptions.connection.remotePort,
|
|
||||||
destHost: options.host || '',
|
|
||||||
destPort: options.port || '',
|
|
||||||
action: 'connected'
|
|
||||||
},
|
|
||||||
'Using proxied socket from %s:%s to %s:%s',
|
|
||||||
socketOptions.connection.remoteAddress,
|
|
||||||
socketOptions.connection.remotePort,
|
|
||||||
options.host || '',
|
|
||||||
options.port || ''
|
|
||||||
);
|
|
||||||
|
|
||||||
options = assign(false, options);
|
|
||||||
Object.keys(socketOptions).forEach(key => {
|
|
||||||
options[key] = socketOptions[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection = new SMTPConnection(options);
|
|
||||||
|
|
||||||
this.connection.once('error', err => {
|
|
||||||
this.emit('error', err);
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.once('end', () => {
|
|
||||||
this.close();
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
|
|
||||||
let timer = setTimeout(() => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// still have not returned, this means we have an unexpected connection close
|
|
||||||
let err = new Error('Unexpected socket close');
|
|
||||||
if (this.connection && this.connection._socket && this.connection._socket.upgrading) {
|
|
||||||
// starttls connection errors
|
|
||||||
err.code = 'ETLS';
|
|
||||||
}
|
|
||||||
callback(err);
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
try {
|
|
||||||
timer.unref();
|
|
||||||
} catch (E) {
|
|
||||||
// Ignore. Happens on envs with non-node timer implementation
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.connection.connect(() => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.auth && (this.connection.allowsAuth || options.forceAuth)) {
|
|
||||||
this.connection.login(this.auth, err => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
this.connection.close();
|
|
||||||
this.emit('error', err);
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
this._connected = true;
|
|
||||||
callback(null, true);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
returned = true;
|
|
||||||
this._connected = true;
|
|
||||||
return callback(null, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an e-mail to be sent using the selected settings
|
|
||||||
*
|
|
||||||
* @param {Object} mail Mail object
|
|
||||||
* @param {Function} callback Callback function
|
|
||||||
*/
|
|
||||||
send(mail, callback) {
|
|
||||||
if (!this._connected) {
|
|
||||||
return this.connect(err => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
return this.send(mail, callback);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let envelope = mail.message.getEnvelope();
|
|
||||||
let messageId = mail.message.messageId();
|
|
||||||
|
|
||||||
let recipients = [].concat(envelope.to || []);
|
|
||||||
if (recipients.length > 3) {
|
|
||||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
|
||||||
}
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'send',
|
|
||||||
messageId,
|
|
||||||
cid: this.id
|
|
||||||
},
|
|
||||||
'Sending message %s using #%s to <%s>',
|
|
||||||
messageId,
|
|
||||||
this.id,
|
|
||||||
recipients.join(', ')
|
|
||||||
);
|
|
||||||
|
|
||||||
if (mail.data.dsn) {
|
|
||||||
envelope.dsn = mail.data.dsn;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.connection.send(envelope, mail.message.createReadStream(), (err, info) => {
|
|
||||||
this.messages++;
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
this.connection.close();
|
|
||||||
this.emit('error', err);
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
info.envelope = {
|
|
||||||
from: envelope.from,
|
|
||||||
to: envelope.to
|
|
||||||
};
|
|
||||||
info.messageId = messageId;
|
|
||||||
|
|
||||||
setImmediate(() => {
|
|
||||||
let err;
|
|
||||||
if (this.messages >= this.options.maxMessages) {
|
|
||||||
err = new Error('Resource exhausted');
|
|
||||||
err.code = 'EMAXLIMIT';
|
|
||||||
this.connection.close();
|
|
||||||
this.emit('error', err);
|
|
||||||
} else {
|
|
||||||
this.pool._checkRateLimit(() => {
|
|
||||||
this.available = true;
|
|
||||||
this.emit('available');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
callback(null, info);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Closes the connection
|
|
||||||
*/
|
|
||||||
close() {
|
|
||||||
this._connected = false;
|
|
||||||
if (this.auth && this.auth.oauth2) {
|
|
||||||
this.auth.oauth2.removeAllListeners();
|
|
||||||
}
|
|
||||||
if (this.connection) {
|
|
||||||
this.connection.close();
|
|
||||||
}
|
|
||||||
this.emit('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = PoolResource;
|
|
||||||
416
lib/node/node_modules/nodemailer/lib/smtp-transport/index.js
generated
vendored
416
lib/node/node_modules/nodemailer/lib/smtp-transport/index.js
generated
vendored
|
|
@ -1,416 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const EventEmitter = require('events');
|
|
||||||
const SMTPConnection = require('../smtp-connection');
|
|
||||||
const wellKnown = require('../well-known');
|
|
||||||
const shared = require('../shared');
|
|
||||||
const XOAuth2 = require('../xoauth2');
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a SMTP transport object for Nodemailer
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} options Connection options
|
|
||||||
*/
|
|
||||||
class SMTPTransport extends EventEmitter {
|
|
||||||
constructor(options) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
if (typeof options === 'string') {
|
|
||||||
options = {
|
|
||||||
url: options
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
let urlData;
|
|
||||||
let service = options.service;
|
|
||||||
|
|
||||||
if (typeof options.getSocket === 'function') {
|
|
||||||
this.getSocket = options.getSocket;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options.url) {
|
|
||||||
urlData = shared.parseConnectionUrl(options.url);
|
|
||||||
service = service || urlData.service;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.options = shared.assign(
|
|
||||||
false, // create new object
|
|
||||||
options, // regular options
|
|
||||||
urlData, // url options
|
|
||||||
service && wellKnown(service) // wellknown options
|
|
||||||
);
|
|
||||||
|
|
||||||
this.logger = shared.getLogger(this.options, {
|
|
||||||
component: this.options.component || 'smtp-transport'
|
|
||||||
});
|
|
||||||
|
|
||||||
// temporary object
|
|
||||||
let connection = new SMTPConnection(this.options);
|
|
||||||
|
|
||||||
this.name = 'SMTP';
|
|
||||||
this.version = packageData.version + '[client:' + connection.version + ']';
|
|
||||||
|
|
||||||
if (this.options.auth) {
|
|
||||||
this.auth = this.getAuth({});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder function for creating proxy sockets. This method immediatelly returns
|
|
||||||
* without a socket
|
|
||||||
*
|
|
||||||
* @param {Object} options Connection options
|
|
||||||
* @param {Function} callback Callback function to run with the socket keys
|
|
||||||
*/
|
|
||||||
getSocket(options, callback) {
|
|
||||||
// return immediatelly
|
|
||||||
return setImmediate(() => callback(null, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
getAuth(authOpts) {
|
|
||||||
if (!authOpts) {
|
|
||||||
return this.auth;
|
|
||||||
}
|
|
||||||
|
|
||||||
let hasAuth = false;
|
|
||||||
let authData = {};
|
|
||||||
|
|
||||||
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) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
switch ((authData.type || '').toString().toUpperCase()) {
|
|
||||||
case 'OAUTH2': {
|
|
||||||
if (!authData.service && !authData.user) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
let 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));
|
|
||||||
return {
|
|
||||||
type: 'OAUTH2',
|
|
||||||
user: authData.user,
|
|
||||||
oauth2,
|
|
||||||
method: 'XOAUTH2'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return {
|
|
||||||
type: (authData.type || '').toString().toUpperCase() || 'LOGIN',
|
|
||||||
user: authData.user,
|
|
||||||
credentials: {
|
|
||||||
user: authData.user || '',
|
|
||||||
pass: authData.pass,
|
|
||||||
options: authData.options
|
|
||||||
},
|
|
||||||
method: (authData.method || '').trim().toUpperCase() || this.options.authMethod || false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends an e-mail using the selected settings
|
|
||||||
*
|
|
||||||
* @param {Object} mail Mail object
|
|
||||||
* @param {Function} callback Callback function
|
|
||||||
*/
|
|
||||||
send(mail, callback) {
|
|
||||||
this.getSocket(this.options, (err, socketOptions) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let returned = false;
|
|
||||||
let options = this.options;
|
|
||||||
if (socketOptions && socketOptions.connection) {
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'proxy',
|
|
||||||
remoteAddress: socketOptions.connection.remoteAddress,
|
|
||||||
remotePort: socketOptions.connection.remotePort,
|
|
||||||
destHost: options.host || '',
|
|
||||||
destPort: options.port || '',
|
|
||||||
action: 'connected'
|
|
||||||
},
|
|
||||||
'Using proxied socket from %s:%s to %s:%s',
|
|
||||||
socketOptions.connection.remoteAddress,
|
|
||||||
socketOptions.connection.remotePort,
|
|
||||||
options.host || '',
|
|
||||||
options.port || ''
|
|
||||||
);
|
|
||||||
|
|
||||||
// only copy options if we need to modify it
|
|
||||||
options = shared.assign(false, options);
|
|
||||||
Object.keys(socketOptions).forEach(key => {
|
|
||||||
options[key] = socketOptions[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection = new SMTPConnection(options);
|
|
||||||
|
|
||||||
connection.once('error', err => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.once('end', () => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let 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');
|
|
||||||
if (connection && connection._socket && connection._socket.upgrading) {
|
|
||||||
// starttls connection errors
|
|
||||||
err.code = 'ETLS';
|
|
||||||
}
|
|
||||||
callback(err);
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
try {
|
|
||||||
timer.unref();
|
|
||||||
} catch (E) {
|
|
||||||
// Ignore. Happens on envs with non-node timer implementation
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let sendMessage = () => {
|
|
||||||
let envelope = mail.message.getEnvelope();
|
|
||||||
let messageId = mail.message.messageId();
|
|
||||||
|
|
||||||
let recipients = [].concat(envelope.to || []);
|
|
||||||
if (recipients.length > 3) {
|
|
||||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mail.data.dsn) {
|
|
||||||
envelope.dsn = mail.data.dsn;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Sending message %s to <%s>',
|
|
||||||
messageId,
|
|
||||||
recipients.join(', ')
|
|
||||||
);
|
|
||||||
|
|
||||||
connection.send(envelope, mail.message.createReadStream(), (err, info) => {
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
if (err) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'send'
|
|
||||||
},
|
|
||||||
'Send error for %s: %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
info.envelope = {
|
|
||||||
from: envelope.from,
|
|
||||||
to: envelope.to
|
|
||||||
};
|
|
||||||
info.messageId = messageId;
|
|
||||||
try {
|
|
||||||
return callback(null, info);
|
|
||||||
} catch (E) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: E,
|
|
||||||
tnx: 'callback'
|
|
||||||
},
|
|
||||||
'Callback error for %s: %s',
|
|
||||||
messageId,
|
|
||||||
E.message
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
connection.connect(() => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let auth = this.getAuth(mail.data.auth);
|
|
||||||
|
|
||||||
if (auth && (connection.allowsAuth || options.forceAuth)) {
|
|
||||||
connection.login(auth, err => {
|
|
||||||
if (auth && auth !== this.auth && auth.oauth2) {
|
|
||||||
auth.oauth2.removeAllListeners();
|
|
||||||
}
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
sendMessage();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Verifies SMTP configuration
|
|
||||||
*
|
|
||||||
* @param {Function} callback Callback function
|
|
||||||
*/
|
|
||||||
verify(callback) {
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (!callback) {
|
|
||||||
promise = new Promise((resolve, reject) => {
|
|
||||||
callback = shared.callbackPromise(resolve, reject);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.getSocket(this.options, (err, socketOptions) => {
|
|
||||||
if (err) {
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
let options = this.options;
|
|
||||||
if (socketOptions && socketOptions.connection) {
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'proxy',
|
|
||||||
remoteAddress: socketOptions.connection.remoteAddress,
|
|
||||||
remotePort: socketOptions.connection.remotePort,
|
|
||||||
destHost: options.host || '',
|
|
||||||
destPort: options.port || '',
|
|
||||||
action: 'connected'
|
|
||||||
},
|
|
||||||
'Using proxied socket from %s:%s to %s:%s',
|
|
||||||
socketOptions.connection.remoteAddress,
|
|
||||||
socketOptions.connection.remotePort,
|
|
||||||
options.host || '',
|
|
||||||
options.port || ''
|
|
||||||
);
|
|
||||||
|
|
||||||
options = shared.assign(false, options);
|
|
||||||
Object.keys(socketOptions).forEach(key => {
|
|
||||||
options[key] = socketOptions[key];
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let connection = new SMTPConnection(options);
|
|
||||||
let returned = false;
|
|
||||||
|
|
||||||
connection.once('error', err => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
connection.once('end', () => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
return callback(new Error('Connection closed'));
|
|
||||||
});
|
|
||||||
|
|
||||||
let finalize = () => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
connection.quit();
|
|
||||||
return callback(null, true);
|
|
||||||
};
|
|
||||||
|
|
||||||
connection.connect(() => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let authData = this.getAuth({});
|
|
||||||
|
|
||||||
if (authData && (connection.allowsAuth || options.forceAuth)) {
|
|
||||||
connection.login(authData, err => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (err) {
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
finalize();
|
|
||||||
});
|
|
||||||
} else if (!authData && connection.allowsAuth && options.forceAuth) {
|
|
||||||
let err = new Error('Authentication info was not provided');
|
|
||||||
err.code = 'NoAuth';
|
|
||||||
|
|
||||||
returned = true;
|
|
||||||
connection.close();
|
|
||||||
return callback(err);
|
|
||||||
} else {
|
|
||||||
finalize();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return promise;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases resources
|
|
||||||
*/
|
|
||||||
close() {
|
|
||||||
if (this.auth && this.auth.oauth2) {
|
|
||||||
this.auth.oauth2.removeAllListeners();
|
|
||||||
}
|
|
||||||
this.emit('close');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// expose to the world
|
|
||||||
module.exports = SMTPTransport;
|
|
||||||
135
lib/node/node_modules/nodemailer/lib/stream-transport/index.js
generated
vendored
135
lib/node/node_modules/nodemailer/lib/stream-transport/index.js
generated
vendored
|
|
@ -1,135 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const packageData = require('../../package.json');
|
|
||||||
const shared = require('../shared');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a Transport object for streaming
|
|
||||||
*
|
|
||||||
* Possible options can be the following:
|
|
||||||
*
|
|
||||||
* * **buffer** if true, then returns the message as a Buffer object instead of a stream
|
|
||||||
* * **newline** either 'windows' or 'unix'
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} optional config parameter
|
|
||||||
*/
|
|
||||||
class StreamTransport {
|
|
||||||
constructor(options) {
|
|
||||||
options = options || {};
|
|
||||||
|
|
||||||
this.options = options || {};
|
|
||||||
|
|
||||||
this.name = 'StreamTransport';
|
|
||||||
this.version = packageData.version;
|
|
||||||
|
|
||||||
this.logger = shared.getLogger(this.options, {
|
|
||||||
component: this.options.component || 'stream-transport'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.winbreak = ['win', 'windows', 'dos', '\r\n'].includes((options.newline || '').toString().toLowerCase());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compiles a mailcomposer message and forwards it to handler that sends it
|
|
||||||
*
|
|
||||||
* @param {Object} emailMessage MailComposer object
|
|
||||||
* @param {Function} callback Callback function to run when the sending is completed
|
|
||||||
*/
|
|
||||||
send(mail, done) {
|
|
||||||
// We probably need this in the output
|
|
||||||
mail.message.keepBcc = true;
|
|
||||||
|
|
||||||
let envelope = mail.data.envelope || mail.message.getEnvelope();
|
|
||||||
let messageId = mail.message.messageId();
|
|
||||||
|
|
||||||
let recipients = [].concat(envelope.to || []);
|
|
||||||
if (recipients.length > 3) {
|
|
||||||
recipients.push('...and ' + recipients.splice(2).length + ' more');
|
|
||||||
}
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Sending message %s to <%s> using %s line breaks',
|
|
||||||
messageId,
|
|
||||||
recipients.join(', '),
|
|
||||||
this.winbreak ? '<CR><LF>' : '<LF>'
|
|
||||||
);
|
|
||||||
|
|
||||||
setImmediate(() => {
|
|
||||||
let stream;
|
|
||||||
|
|
||||||
try {
|
|
||||||
stream = mail.message.createReadStream();
|
|
||||||
} catch (E) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: E,
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Creating send stream failed for %s. %s',
|
|
||||||
messageId,
|
|
||||||
E.message
|
|
||||||
);
|
|
||||||
return done(E);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.options.buffer) {
|
|
||||||
stream.once('error', err => {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Failed creating message for %s. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
});
|
|
||||||
return done(null, {
|
|
||||||
envelope: mail.data.envelope || mail.message.getEnvelope(),
|
|
||||||
messageId,
|
|
||||||
message: stream
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let chunks = [];
|
|
||||||
let chunklen = 0;
|
|
||||||
stream.on('readable', () => {
|
|
||||||
let chunk;
|
|
||||||
while ((chunk = stream.read()) !== null) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
chunklen += chunk.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.once('error', err => {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err,
|
|
||||||
tnx: 'send',
|
|
||||||
messageId
|
|
||||||
},
|
|
||||||
'Failed creating message for %s. %s',
|
|
||||||
messageId,
|
|
||||||
err.message
|
|
||||||
);
|
|
||||||
return done(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
stream.on('end', () =>
|
|
||||||
done(null, {
|
|
||||||
envelope: mail.data.envelope || mail.message.getEnvelope(),
|
|
||||||
messageId,
|
|
||||||
message: Buffer.concat(chunks, chunklen)
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = StreamTransport;
|
|
||||||
47
lib/node/node_modules/nodemailer/lib/well-known/index.js
generated
vendored
47
lib/node/node_modules/nodemailer/lib/well-known/index.js
generated
vendored
|
|
@ -1,47 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const services = require('./services.json');
|
|
||||||
const normalized = {};
|
|
||||||
|
|
||||||
Object.keys(services).forEach(key => {
|
|
||||||
let service = services[key];
|
|
||||||
|
|
||||||
normalized[normalizeKey(key)] = normalizeService(service);
|
|
||||||
|
|
||||||
[].concat(service.aliases || []).forEach(alias => {
|
|
||||||
normalized[normalizeKey(alias)] = normalizeService(service);
|
|
||||||
});
|
|
||||||
|
|
||||||
[].concat(service.domains || []).forEach(domain => {
|
|
||||||
normalized[normalizeKey(domain)] = normalizeService(service);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function normalizeKey(key) {
|
|
||||||
return key.replace(/[^a-zA-Z0-9.-]/g, '').toLowerCase();
|
|
||||||
}
|
|
||||||
|
|
||||||
function normalizeService(service) {
|
|
||||||
let filter = ['domains', 'aliases'];
|
|
||||||
let response = {};
|
|
||||||
|
|
||||||
Object.keys(service).forEach(key => {
|
|
||||||
if (filter.indexOf(key) < 0) {
|
|
||||||
response[key] = service[key];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Resolves SMTP config for given key. Key can be a name (like 'Gmail'), alias (like 'Google Mail') or
|
|
||||||
* an email address (like 'test@googlemail.com').
|
|
||||||
*
|
|
||||||
* @param {String} key [description]
|
|
||||||
* @returns {Object} SMTP config or false if not found
|
|
||||||
*/
|
|
||||||
module.exports = function (key) {
|
|
||||||
key = normalizeKey(key.split('@').pop());
|
|
||||||
return normalized[key] || false;
|
|
||||||
};
|
|
||||||
343
lib/node/node_modules/nodemailer/lib/well-known/services.json
generated
vendored
343
lib/node/node_modules/nodemailer/lib/well-known/services.json
generated
vendored
|
|
@ -1,343 +0,0 @@
|
||||||
{
|
|
||||||
"1und1": {
|
|
||||||
"host": "smtp.1und1.de",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true,
|
|
||||||
"authMethod": "LOGIN"
|
|
||||||
},
|
|
||||||
|
|
||||||
"Aliyun": {
|
|
||||||
"domains": ["aliyun.com"],
|
|
||||||
"host": "smtp.aliyun.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"AOL": {
|
|
||||||
"domains": ["aol.com"],
|
|
||||||
"host": "smtp.aol.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"Bluewin": {
|
|
||||||
"host": "smtpauths.bluewin.ch",
|
|
||||||
"domains": ["bluewin.ch"],
|
|
||||||
"port": 465
|
|
||||||
},
|
|
||||||
|
|
||||||
"DebugMail": {
|
|
||||||
"host": "debugmail.io",
|
|
||||||
"port": 25
|
|
||||||
},
|
|
||||||
|
|
||||||
"DynectEmail": {
|
|
||||||
"aliases": ["Dynect"],
|
|
||||||
"host": "smtp.dynect.net",
|
|
||||||
"port": 25
|
|
||||||
},
|
|
||||||
|
|
||||||
"Ethereal": {
|
|
||||||
"aliases": ["ethereal.email"],
|
|
||||||
"host": "smtp.ethereal.email",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"FastMail": {
|
|
||||||
"domains": ["fastmail.fm"],
|
|
||||||
"host": "smtp.fastmail.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Forward Email": {
|
|
||||||
"aliases": ["FE", "ForwardEmail"],
|
|
||||||
"domains": ["forwardemail.net"],
|
|
||||||
"host": "smtp.forwardemail.net",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"GandiMail": {
|
|
||||||
"aliases": ["Gandi", "Gandi Mail"],
|
|
||||||
"host": "mail.gandi.net",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"Gmail": {
|
|
||||||
"aliases": ["Google Mail"],
|
|
||||||
"domains": ["gmail.com", "googlemail.com"],
|
|
||||||
"host": "smtp.gmail.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Godaddy": {
|
|
||||||
"host": "smtpout.secureserver.net",
|
|
||||||
"port": 25
|
|
||||||
},
|
|
||||||
|
|
||||||
"GodaddyAsia": {
|
|
||||||
"host": "smtp.asia.secureserver.net",
|
|
||||||
"port": 25
|
|
||||||
},
|
|
||||||
|
|
||||||
"GodaddyEurope": {
|
|
||||||
"host": "smtp.europe.secureserver.net",
|
|
||||||
"port": 25
|
|
||||||
},
|
|
||||||
|
|
||||||
"hot.ee": {
|
|
||||||
"host": "mail.hot.ee"
|
|
||||||
},
|
|
||||||
|
|
||||||
"Hotmail": {
|
|
||||||
"aliases": ["Outlook", "Outlook.com", "Hotmail.com"],
|
|
||||||
"domains": ["hotmail.com", "outlook.com"],
|
|
||||||
"host": "smtp-mail.outlook.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"iCloud": {
|
|
||||||
"aliases": ["Me", "Mac"],
|
|
||||||
"domains": ["me.com", "mac.com"],
|
|
||||||
"host": "smtp.mail.me.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"Infomaniak": {
|
|
||||||
"host": "mail.infomaniak.com",
|
|
||||||
"domains": ["ik.me", "ikmail.com", "etik.com"],
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"mail.ee": {
|
|
||||||
"host": "smtp.mail.ee"
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mail.ru": {
|
|
||||||
"host": "smtp.mail.ru",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mailcatch.app": {
|
|
||||||
"host": "sandbox-smtp.mailcatch.app",
|
|
||||||
"port": 2525
|
|
||||||
},
|
|
||||||
|
|
||||||
"Maildev": {
|
|
||||||
"port": 1025,
|
|
||||||
"ignoreTLS": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mailgun": {
|
|
||||||
"host": "smtp.mailgun.org",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mailjet": {
|
|
||||||
"host": "in.mailjet.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mailosaur": {
|
|
||||||
"host": "mailosaur.io",
|
|
||||||
"port": 25
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mailtrap": {
|
|
||||||
"host": "smtp.mailtrap.io",
|
|
||||||
"port": 2525
|
|
||||||
},
|
|
||||||
|
|
||||||
"Mandrill": {
|
|
||||||
"host": "smtp.mandrillapp.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"Naver": {
|
|
||||||
"host": "smtp.naver.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"One": {
|
|
||||||
"host": "send.one.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"OpenMailBox": {
|
|
||||||
"aliases": ["OMB", "openmailbox.org"],
|
|
||||||
"host": "smtp.openmailbox.org",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Outlook365": {
|
|
||||||
"host": "smtp.office365.com",
|
|
||||||
"port": 587,
|
|
||||||
"secure": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"OhMySMTP": {
|
|
||||||
"host": "smtp.ohmysmtp.com",
|
|
||||||
"port": 587,
|
|
||||||
"secure": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"Postmark": {
|
|
||||||
"aliases": ["PostmarkApp"],
|
|
||||||
"host": "smtp.postmarkapp.com",
|
|
||||||
"port": 2525
|
|
||||||
},
|
|
||||||
|
|
||||||
"qiye.aliyun": {
|
|
||||||
"host": "smtp.mxhichina.com",
|
|
||||||
"port": "465",
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"QQ": {
|
|
||||||
"domains": ["qq.com"],
|
|
||||||
"host": "smtp.qq.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"QQex": {
|
|
||||||
"aliases": ["QQ Enterprise"],
|
|
||||||
"domains": ["exmail.qq.com"],
|
|
||||||
"host": "smtp.exmail.qq.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SendCloud": {
|
|
||||||
"host": "smtp.sendcloud.net",
|
|
||||||
"port": 2525
|
|
||||||
},
|
|
||||||
|
|
||||||
"SendGrid": {
|
|
||||||
"host": "smtp.sendgrid.net",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"SendinBlue": {
|
|
||||||
"aliases": ["Brevo"],
|
|
||||||
"host": "smtp-relay.brevo.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"SendPulse": {
|
|
||||||
"host": "smtp-pulse.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES": {
|
|
||||||
"host": "email-smtp.us-east-1.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-US-EAST-1": {
|
|
||||||
"host": "email-smtp.us-east-1.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-US-WEST-2": {
|
|
||||||
"host": "email-smtp.us-west-2.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-EU-WEST-1": {
|
|
||||||
"host": "email-smtp.eu-west-1.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-AP-SOUTH-1": {
|
|
||||||
"host": "email-smtp.ap-south-1.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-AP-NORTHEAST-1": {
|
|
||||||
"host": "email-smtp.ap-northeast-1.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-AP-NORTHEAST-2": {
|
|
||||||
"host": "email-smtp.ap-northeast-2.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-AP-NORTHEAST-3": {
|
|
||||||
"host": "email-smtp.ap-northeast-3.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-AP-SOUTHEAST-1": {
|
|
||||||
"host": "email-smtp.ap-southeast-1.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"SES-AP-SOUTHEAST-2": {
|
|
||||||
"host": "email-smtp.ap-southeast-2.amazonaws.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Sparkpost": {
|
|
||||||
"aliases": ["SparkPost", "SparkPost Mail"],
|
|
||||||
"domains": ["sparkpost.com"],
|
|
||||||
"host": "smtp.sparkpostmail.com",
|
|
||||||
"port": 587,
|
|
||||||
"secure": false
|
|
||||||
},
|
|
||||||
|
|
||||||
"Tipimail": {
|
|
||||||
"host": "smtp.tipimail.com",
|
|
||||||
"port": 587
|
|
||||||
},
|
|
||||||
|
|
||||||
"Yahoo": {
|
|
||||||
"domains": ["yahoo.com"],
|
|
||||||
"host": "smtp.mail.yahoo.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Yandex": {
|
|
||||||
"domains": ["yandex.ru"],
|
|
||||||
"host": "smtp.yandex.ru",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"Zoho": {
|
|
||||||
"host": "smtp.zoho.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true,
|
|
||||||
"authMethod": "LOGIN"
|
|
||||||
},
|
|
||||||
|
|
||||||
"126": {
|
|
||||||
"host": "smtp.126.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
},
|
|
||||||
|
|
||||||
"163": {
|
|
||||||
"host": "smtp.163.com",
|
|
||||||
"port": 465,
|
|
||||||
"secure": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
376
lib/node/node_modules/nodemailer/lib/xoauth2/index.js
generated
vendored
376
lib/node/node_modules/nodemailer/lib/xoauth2/index.js
generated
vendored
|
|
@ -1,376 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
const Stream = require('stream').Stream;
|
|
||||||
const nmfetch = require('../fetch');
|
|
||||||
const crypto = require('crypto');
|
|
||||||
const shared = require('../shared');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* XOAUTH2 access_token generator for Gmail.
|
|
||||||
* Create client ID for web applications in Google API console to use it.
|
|
||||||
* See Offline Access for receiving the needed refreshToken for an user
|
|
||||||
* https://developers.google.com/accounts/docs/OAuth2WebServer#offline
|
|
||||||
*
|
|
||||||
* Usage for generating access tokens with a custom method using provisionCallback:
|
|
||||||
* provisionCallback(user, renew, callback)
|
|
||||||
* * user is the username to get the token for
|
|
||||||
* * renew is a boolean that if true indicates that existing token failed and needs to be renewed
|
|
||||||
* * callback is the callback to run with (error, accessToken [, expires])
|
|
||||||
* * accessToken is a string
|
|
||||||
* * expires is an optional expire time in milliseconds
|
|
||||||
* If provisionCallback is used, then Nodemailer does not try to attempt generating the token by itself
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @param {Object} options Client information for token generation
|
|
||||||
* @param {String} options.user User e-mail address
|
|
||||||
* @param {String} options.clientId Client ID value
|
|
||||||
* @param {String} options.clientSecret Client secret value
|
|
||||||
* @param {String} options.refreshToken Refresh token for an user
|
|
||||||
* @param {String} options.accessUrl Endpoint for token generation, defaults to 'https://accounts.google.com/o/oauth2/token'
|
|
||||||
* @param {String} options.accessToken An existing valid accessToken
|
|
||||||
* @param {String} options.privateKey Private key for JSW
|
|
||||||
* @param {Number} options.expires Optional Access Token expire time in ms
|
|
||||||
* @param {Number} options.timeout Optional TTL for Access Token in seconds
|
|
||||||
* @param {Function} options.provisionCallback Function to run when a new access token is required
|
|
||||||
*/
|
|
||||||
class XOAuth2 extends Stream {
|
|
||||||
constructor(options, logger) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this.options = options || {};
|
|
||||||
|
|
||||||
if (options && options.serviceClient) {
|
|
||||||
if (!options.privateKey || !options.user) {
|
|
||||||
setImmediate(() => this.emit('error', new Error('Options "privateKey" and "user" are required for service account!')));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let serviceRequestTimeout = Math.min(Math.max(Number(this.options.serviceRequestTimeout) || 0, 0), 3600);
|
|
||||||
this.options.serviceRequestTimeout = serviceRequestTimeout || 5 * 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.logger = shared.getLogger(
|
|
||||||
{
|
|
||||||
logger
|
|
||||||
},
|
|
||||||
{
|
|
||||||
component: this.options.component || 'OAuth2'
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
this.provisionCallback = typeof this.options.provisionCallback === 'function' ? this.options.provisionCallback : false;
|
|
||||||
|
|
||||||
this.options.accessUrl = this.options.accessUrl || 'https://accounts.google.com/o/oauth2/token';
|
|
||||||
this.options.customHeaders = this.options.customHeaders || {};
|
|
||||||
this.options.customParams = this.options.customParams || {};
|
|
||||||
|
|
||||||
this.accessToken = this.options.accessToken || false;
|
|
||||||
|
|
||||||
if (this.options.expires && Number(this.options.expires)) {
|
|
||||||
this.expires = this.options.expires;
|
|
||||||
} else {
|
|
||||||
let timeout = Math.max(Number(this.options.timeout) || 0, 0);
|
|
||||||
this.expires = (timeout && Date.now() + timeout * 1000) || 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns or generates (if previous has expired) a XOAuth2 token
|
|
||||||
*
|
|
||||||
* @param {Boolean} renew If false then use cached access token (if available)
|
|
||||||
* @param {Function} callback Callback function with error object and token string
|
|
||||||
*/
|
|
||||||
getToken(renew, callback) {
|
|
||||||
if (!renew && this.accessToken && (!this.expires || this.expires > Date.now())) {
|
|
||||||
return callback(null, this.accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
let generateCallback = (...args) => {
|
|
||||||
if (args[0]) {
|
|
||||||
this.logger.error(
|
|
||||||
{
|
|
||||||
err: args[0],
|
|
||||||
tnx: 'OAUTH2',
|
|
||||||
user: this.options.user,
|
|
||||||
action: 'renew'
|
|
||||||
},
|
|
||||||
'Failed generating new Access Token for %s',
|
|
||||||
this.options.user
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
this.logger.info(
|
|
||||||
{
|
|
||||||
tnx: 'OAUTH2',
|
|
||||||
user: this.options.user,
|
|
||||||
action: 'renew'
|
|
||||||
},
|
|
||||||
'Generated new Access Token for %s',
|
|
||||||
this.options.user
|
|
||||||
);
|
|
||||||
}
|
|
||||||
callback(...args);
|
|
||||||
};
|
|
||||||
|
|
||||||
if (this.provisionCallback) {
|
|
||||||
this.provisionCallback(this.options.user, !!renew, (err, accessToken, expires) => {
|
|
||||||
if (!err && accessToken) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
this.expires = expires || 0;
|
|
||||||
}
|
|
||||||
generateCallback(err, accessToken);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
this.generateToken(generateCallback);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates token values
|
|
||||||
*
|
|
||||||
* @param {String} accessToken New access token
|
|
||||||
* @param {Number} timeout Access token lifetime in seconds
|
|
||||||
*
|
|
||||||
* Emits 'token': { user: User email-address, accessToken: the new accessToken, timeout: TTL in seconds}
|
|
||||||
*/
|
|
||||||
updateToken(accessToken, timeout) {
|
|
||||||
this.accessToken = accessToken;
|
|
||||||
timeout = Math.max(Number(timeout) || 0, 0);
|
|
||||||
this.expires = (timeout && Date.now() + timeout * 1000) || 0;
|
|
||||||
|
|
||||||
this.emit('token', {
|
|
||||||
user: this.options.user,
|
|
||||||
accessToken: accessToken || '',
|
|
||||||
expires: this.expires
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generates a new XOAuth2 token with the credentials provided at initialization
|
|
||||||
*
|
|
||||||
* @param {Function} callback Callback function with error object and token string
|
|
||||||
*/
|
|
||||||
generateToken(callback) {
|
|
||||||
let urlOptions;
|
|
||||||
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 = {
|
|
||||||
iss: this.options.serviceClient,
|
|
||||||
scope: this.options.scope || 'https://mail.google.com/',
|
|
||||||
sub: this.options.user,
|
|
||||||
aud: this.options.accessUrl,
|
|
||||||
iat,
|
|
||||||
exp: iat + this.options.serviceRequestTimeout
|
|
||||||
};
|
|
||||||
let token;
|
|
||||||
try {
|
|
||||||
token = this.jwtSignRS256(tokenData);
|
|
||||||
} catch (err) {
|
|
||||||
return callback(new Error('Can\x27t generate token. Check your auth options'));
|
|
||||||
}
|
|
||||||
|
|
||||||
urlOptions = {
|
|
||||||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
||||||
assertion: token
|
|
||||||
};
|
|
||||||
|
|
||||||
loggedUrlOptions = {
|
|
||||||
grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
|
|
||||||
assertion: tokenData
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
if (!this.options.refreshToken) {
|
|
||||||
return callback(new Error('Can\x27t create new access token for user'));
|
|
||||||
}
|
|
||||||
|
|
||||||
// web app - https://developers.google.com/identity/protocols/OAuth2WebServer
|
|
||||||
urlOptions = {
|
|
||||||
client_id: this.options.clientId || '',
|
|
||||||
client_secret: this.options.clientSecret || '',
|
|
||||||
refresh_token: this.options.refreshToken,
|
|
||||||
grant_type: 'refresh_token'
|
|
||||||
};
|
|
||||||
|
|
||||||
loggedUrlOptions = {
|
|
||||||
client_id: this.options.clientId || '',
|
|
||||||
client_secret: (this.options.clientSecret || '').substr(0, 6) + '...',
|
|
||||||
refresh_token: (this.options.refreshToken || '').substr(0, 6) + '...',
|
|
||||||
grant_type: 'refresh_token'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.keys(this.options.customParams).forEach(key => {
|
|
||||||
urlOptions[key] = this.options.customParams[key];
|
|
||||||
loggedUrlOptions[key] = this.options.customParams[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'OAUTH2',
|
|
||||||
user: this.options.user,
|
|
||||||
action: 'generate'
|
|
||||||
},
|
|
||||||
'Requesting token using: %s',
|
|
||||||
JSON.stringify(loggedUrlOptions)
|
|
||||||
);
|
|
||||||
|
|
||||||
this.postRequest(this.options.accessUrl, urlOptions, this.options, (error, body) => {
|
|
||||||
let data;
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
return callback(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
data = JSON.parse(body.toString());
|
|
||||||
} catch (E) {
|
|
||||||
return callback(E);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!data || typeof data !== 'object') {
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'OAUTH2',
|
|
||||||
user: this.options.user,
|
|
||||||
action: 'post'
|
|
||||||
},
|
|
||||||
'Response: %s',
|
|
||||||
(body || '').toString()
|
|
||||||
);
|
|
||||||
return callback(new Error('Invalid authentication response'));
|
|
||||||
}
|
|
||||||
|
|
||||||
let logData = {};
|
|
||||||
Object.keys(data).forEach(key => {
|
|
||||||
if (key !== 'access_token') {
|
|
||||||
logData[key] = data[key];
|
|
||||||
} else {
|
|
||||||
logData[key] = (data[key] || '').toString().substr(0, 6) + '...';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.logger.debug(
|
|
||||||
{
|
|
||||||
tnx: 'OAUTH2',
|
|
||||||
user: this.options.user,
|
|
||||||
action: 'post'
|
|
||||||
},
|
|
||||||
'Response: %s',
|
|
||||||
JSON.stringify(logData)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (data.error) {
|
|
||||||
// Error Response : https://tools.ietf.org/html/rfc6749#section-5.2
|
|
||||||
let errorMessage = data.error;
|
|
||||||
if (data.error_description) {
|
|
||||||
errorMessage += ': ' + data.error_description;
|
|
||||||
}
|
|
||||||
if (data.error_uri) {
|
|
||||||
errorMessage += ' (' + data.error_uri + ')';
|
|
||||||
}
|
|
||||||
return callback(new Error(errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.access_token) {
|
|
||||||
this.updateToken(data.access_token, data.expires_in);
|
|
||||||
return callback(null, this.accessToken);
|
|
||||||
}
|
|
||||||
|
|
||||||
return callback(new Error('No access token'));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts an access_token and user id into a base64 encoded XOAuth2 token
|
|
||||||
*
|
|
||||||
* @param {String} [accessToken] Access token string
|
|
||||||
* @return {String} Base64 encoded token for IMAP or SMTP login
|
|
||||||
*/
|
|
||||||
buildXOAuth2Token(accessToken) {
|
|
||||||
let authData = ['user=' + (this.options.user || ''), 'auth=Bearer ' + (accessToken || this.accessToken), '', ''];
|
|
||||||
return Buffer.from(authData.join('\x01'), 'utf-8').toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Custom POST request handler.
|
|
||||||
* This is only needed to keep paths short in Windows – usually this module
|
|
||||||
* is a dependency of a dependency and if it tries to require something
|
|
||||||
* like the request module the paths get way too long to handle for Windows.
|
|
||||||
* As we do only a simple POST request we do not actually require complicated
|
|
||||||
* logic support (no redirects, no nothing) anyway.
|
|
||||||
*
|
|
||||||
* @param {String} url Url to POST to
|
|
||||||
* @param {String|Buffer} payload Payload to POST
|
|
||||||
* @param {Function} callback Callback function with (err, buff)
|
|
||||||
*/
|
|
||||||
postRequest(url, payload, params, callback) {
|
|
||||||
let returned = false;
|
|
||||||
|
|
||||||
let chunks = [];
|
|
||||||
let chunklen = 0;
|
|
||||||
|
|
||||||
let req = nmfetch(url, {
|
|
||||||
method: 'post',
|
|
||||||
headers: params.customHeaders,
|
|
||||||
body: payload,
|
|
||||||
allowErrorResponse: true
|
|
||||||
});
|
|
||||||
|
|
||||||
req.on('readable', () => {
|
|
||||||
let chunk;
|
|
||||||
while ((chunk = req.read()) !== null) {
|
|
||||||
chunks.push(chunk);
|
|
||||||
chunklen += chunk.length;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
req.once('error', err => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
return callback(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
req.once('end', () => {
|
|
||||||
if (returned) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
returned = true;
|
|
||||||
return callback(null, Buffer.concat(chunks, chunklen));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Encodes a buffer or a string into Base64url format
|
|
||||||
*
|
|
||||||
* @param {Buffer|String} data The data to convert
|
|
||||||
* @return {String} The encoded string
|
|
||||||
*/
|
|
||||||
toBase64URL(data) {
|
|
||||||
if (typeof data === 'string') {
|
|
||||||
data = Buffer.from(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data
|
|
||||||
.toString('base64')
|
|
||||||
.replace(/[=]+/g, '') // remove '='s
|
|
||||||
.replace(/\+/g, '-') // '+' → '-'
|
|
||||||
.replace(/\//g, '_'); // '/' → '_'
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a JSON Web Token signed with RS256 (SHA256 + RSA)
|
|
||||||
*
|
|
||||||
* @param {Object} payload The payload to include in the generated token
|
|
||||||
* @return {String} The generated and signed token
|
|
||||||
*/
|
|
||||||
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);
|
|
||||||
return payload + '.' + this.toBase64URL(signature);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = XOAuth2;
|
|
||||||
43
lib/node/node_modules/nodemailer/package.json
generated
vendored
43
lib/node/node_modules/nodemailer/package.json
generated
vendored
|
|
@ -1,43 +0,0 @@
|
||||||
{
|
|
||||||
"name": "nodemailer",
|
|
||||||
"version": "6.9.13",
|
|
||||||
"description": "Easy as cake e-mail sending from your Node.js applications",
|
|
||||||
"main": "lib/nodemailer.js",
|
|
||||||
"scripts": {
|
|
||||||
"test": "node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js",
|
|
||||||
"test:coverage": "c8 node --test --test-concurrency=1 test/**/*.test.js test/**/*-test.js",
|
|
||||||
"lint": "eslint .",
|
|
||||||
"update": "rm -rf node_modules/ package-lock.json && ncu -u && npm install"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/nodemailer/nodemailer.git"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"Nodemailer"
|
|
||||||
],
|
|
||||||
"author": "Andris Reinman",
|
|
||||||
"license": "MIT-0",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/nodemailer/nodemailer/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://nodemailer.com/",
|
|
||||||
"devDependencies": {
|
|
||||||
"@aws-sdk/client-ses": "3.529.1",
|
|
||||||
"bunyan": "1.8.15",
|
|
||||||
"c8": "9.1.0",
|
|
||||||
"eslint": "8.57.0",
|
|
||||||
"eslint-config-nodemailer": "1.2.0",
|
|
||||||
"eslint-config-prettier": "9.1.0",
|
|
||||||
"libbase64": "1.3.0",
|
|
||||||
"libmime": "5.3.4",
|
|
||||||
"libqp": "2.1.0",
|
|
||||||
"nodemailer-ntlm-auth": "1.0.4",
|
|
||||||
"proxy": "1.0.2",
|
|
||||||
"proxy-test-server": "1.0.0",
|
|
||||||
"smtp-server": "3.13.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=6.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
lib/node/node_modules/pg-cloudflare/LICENSE
generated
vendored
21
lib/node/node_modules/pg-cloudflare/LICENSE
generated
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2010 - 2021 Brian Carlson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
33
lib/node/node_modules/pg-cloudflare/README.md
generated
vendored
33
lib/node/node_modules/pg-cloudflare/README.md
generated
vendored
|
|
@ -1,33 +0,0 @@
|
||||||
# pg-cloudflare
|
|
||||||
|
|
||||||
A socket implementation that can run on Cloudflare Workers using native TCP connections.
|
|
||||||
|
|
||||||
## install
|
|
||||||
|
|
||||||
```
|
|
||||||
npm i --save-dev pg-cloudflare
|
|
||||||
```
|
|
||||||
|
|
||||||
### license
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2023 Brian M. Carlson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
||||||
2
lib/node/node_modules/pg-cloudflare/dist/empty.d.ts
generated
vendored
2
lib/node/node_modules/pg-cloudflare/dist/empty.d.ts
generated
vendored
|
|
@ -1,2 +0,0 @@
|
||||||
declare const _default: {};
|
|
||||||
export default _default;
|
|
||||||
4
lib/node/node_modules/pg-cloudflare/dist/empty.js
generated
vendored
4
lib/node/node_modules/pg-cloudflare/dist/empty.js
generated
vendored
|
|
@ -1,4 +0,0 @@
|
||||||
// This is an empty module that is served up when outside of a workerd environment
|
|
||||||
// See the `exports` field in package.json
|
|
||||||
export default {};
|
|
||||||
//# sourceMappingURL=empty.js.map
|
|
||||||
1
lib/node/node_modules/pg-cloudflare/dist/empty.js.map
generated
vendored
1
lib/node/node_modules/pg-cloudflare/dist/empty.js.map
generated
vendored
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"file":"empty.js","sourceRoot":"","sources":["../src/empty.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,0CAA0C;AAC1C,eAAe,EAAE,CAAA"}
|
|
||||||
31
lib/node/node_modules/pg-cloudflare/dist/index.d.ts
generated
vendored
31
lib/node/node_modules/pg-cloudflare/dist/index.d.ts
generated
vendored
|
|
@ -1,31 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
/// <reference types="node" />
|
|
||||||
import { TlsOptions } from 'cloudflare:sockets';
|
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
/**
|
|
||||||
* Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
|
|
||||||
*/
|
|
||||||
export declare class CloudflareSocket extends EventEmitter {
|
|
||||||
readonly ssl: boolean;
|
|
||||||
writable: boolean;
|
|
||||||
destroyed: boolean;
|
|
||||||
private _upgrading;
|
|
||||||
private _upgraded;
|
|
||||||
private _cfSocket;
|
|
||||||
private _cfWriter;
|
|
||||||
private _cfReader;
|
|
||||||
constructor(ssl: boolean);
|
|
||||||
setNoDelay(): this;
|
|
||||||
setKeepAlive(): this;
|
|
||||||
ref(): this;
|
|
||||||
unref(): this;
|
|
||||||
connect(port: number, host: string, connectListener?: (...args: unknown[]) => void): Promise<this | undefined>;
|
|
||||||
_listen(): Promise<void>;
|
|
||||||
_listenOnce(): Promise<void>;
|
|
||||||
write(data: Uint8Array | string, encoding?: BufferEncoding, callback?: (...args: unknown[]) => void): true | void;
|
|
||||||
end(data?: Buffer, encoding?: BufferEncoding, callback?: (...args: unknown[]) => void): this;
|
|
||||||
destroy(reason: string): this;
|
|
||||||
startTls(options: TlsOptions): void;
|
|
||||||
_addClosedHandler(): void;
|
|
||||||
}
|
|
||||||
146
lib/node/node_modules/pg-cloudflare/dist/index.js
generated
vendored
146
lib/node/node_modules/pg-cloudflare/dist/index.js
generated
vendored
|
|
@ -1,146 +0,0 @@
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
/**
|
|
||||||
* Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
|
|
||||||
*/
|
|
||||||
export class CloudflareSocket extends EventEmitter {
|
|
||||||
constructor(ssl) {
|
|
||||||
super();
|
|
||||||
this.ssl = ssl;
|
|
||||||
this.writable = false;
|
|
||||||
this.destroyed = false;
|
|
||||||
this._upgrading = false;
|
|
||||||
this._upgraded = false;
|
|
||||||
this._cfSocket = null;
|
|
||||||
this._cfWriter = null;
|
|
||||||
this._cfReader = null;
|
|
||||||
}
|
|
||||||
setNoDelay() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
setKeepAlive() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
ref() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
unref() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
async connect(port, host, connectListener) {
|
|
||||||
try {
|
|
||||||
log('connecting');
|
|
||||||
if (connectListener)
|
|
||||||
this.once('connect', connectListener);
|
|
||||||
const options = this.ssl ? { secureTransport: 'starttls' } : {};
|
|
||||||
const { connect } = await import('cloudflare:sockets');
|
|
||||||
this._cfSocket = connect(`${host}:${port}`, options);
|
|
||||||
this._cfWriter = this._cfSocket.writable.getWriter();
|
|
||||||
this._addClosedHandler();
|
|
||||||
this._cfReader = this._cfSocket.readable.getReader();
|
|
||||||
if (this.ssl) {
|
|
||||||
this._listenOnce().catch((e) => this.emit('error', e));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._listen().catch((e) => this.emit('error', e));
|
|
||||||
}
|
|
||||||
await this._cfWriter.ready;
|
|
||||||
log('socket ready');
|
|
||||||
this.writable = true;
|
|
||||||
this.emit('connect');
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
this.emit('error', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async _listen() {
|
|
||||||
while (true) {
|
|
||||||
log('awaiting receive from CF socket');
|
|
||||||
const { done, value } = await this._cfReader.read();
|
|
||||||
log('CF socket received:', done, value);
|
|
||||||
if (done) {
|
|
||||||
log('done');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.emit('data', Buffer.from(value));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
async _listenOnce() {
|
|
||||||
log('awaiting first receive from CF socket');
|
|
||||||
const { done, value } = await this._cfReader.read();
|
|
||||||
log('First CF socket received:', done, value);
|
|
||||||
this.emit('data', Buffer.from(value));
|
|
||||||
}
|
|
||||||
write(data, encoding = 'utf8', callback = () => { }) {
|
|
||||||
if (data.length === 0)
|
|
||||||
return callback();
|
|
||||||
if (typeof data === 'string')
|
|
||||||
data = Buffer.from(data, encoding);
|
|
||||||
log('sending data direct:', data);
|
|
||||||
this._cfWriter.write(data).then(() => {
|
|
||||||
log('data sent');
|
|
||||||
callback();
|
|
||||||
}, (err) => {
|
|
||||||
log('send error', err);
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
end(data = Buffer.alloc(0), encoding = 'utf8', callback = () => { }) {
|
|
||||||
log('ending CF socket');
|
|
||||||
this.write(data, encoding, (err) => {
|
|
||||||
this._cfSocket.close();
|
|
||||||
if (callback)
|
|
||||||
callback(err);
|
|
||||||
});
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
destroy(reason) {
|
|
||||||
log('destroying CF socket', reason);
|
|
||||||
this.destroyed = true;
|
|
||||||
return this.end();
|
|
||||||
}
|
|
||||||
startTls(options) {
|
|
||||||
if (this._upgraded) {
|
|
||||||
// Don't try to upgrade again.
|
|
||||||
this.emit('error', 'Cannot call `startTls()` more than once on a socket');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this._cfWriter.releaseLock();
|
|
||||||
this._cfReader.releaseLock();
|
|
||||||
this._upgrading = true;
|
|
||||||
this._cfSocket = this._cfSocket.startTls(options);
|
|
||||||
this._cfWriter = this._cfSocket.writable.getWriter();
|
|
||||||
this._cfReader = this._cfSocket.readable.getReader();
|
|
||||||
this._addClosedHandler();
|
|
||||||
this._listen().catch((e) => this.emit('error', e));
|
|
||||||
}
|
|
||||||
_addClosedHandler() {
|
|
||||||
this._cfSocket.closed.then(() => {
|
|
||||||
if (!this._upgrading) {
|
|
||||||
log('CF socket closed');
|
|
||||||
this._cfSocket = null;
|
|
||||||
this.emit('close');
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
this._upgrading = false;
|
|
||||||
this._upgraded = true;
|
|
||||||
}
|
|
||||||
}).catch((e) => this.emit('error', e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
const debug = false;
|
|
||||||
function dump(data) {
|
|
||||||
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
|
|
||||||
const hex = Buffer.from(data).toString('hex');
|
|
||||||
const str = new TextDecoder().decode(data);
|
|
||||||
return `\n>>> STR: "${str.replace(/\n/g, '\\n')}"\n>>> HEX: ${hex}\n`;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function log(...args) {
|
|
||||||
debug && console.log(...args.map(dump));
|
|
||||||
}
|
|
||||||
//# sourceMappingURL=index.js.map
|
|
||||||
1
lib/node/node_modules/pg-cloudflare/dist/index.js.map
generated
vendored
1
lib/node/node_modules/pg-cloudflare/dist/index.js.map
generated
vendored
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAA;AAErC;;GAEG;AACH,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IAUhD,YAAqB,GAAY;QAC/B,KAAK,EAAE,CAAA;QADY,QAAG,GAAH,GAAG,CAAS;QATjC,aAAQ,GAAG,KAAK,CAAA;QAChB,cAAS,GAAG,KAAK,CAAA;QAET,eAAU,GAAG,KAAK,CAAA;QAClB,cAAS,GAAG,KAAK,CAAA;QACjB,cAAS,GAAkB,IAAI,CAAA;QAC/B,cAAS,GAAuC,IAAI,CAAA;QACpD,cAAS,GAAuC,IAAI,CAAA;IAI5D,CAAC;IAED,UAAU;QACR,OAAO,IAAI,CAAA;IACb,CAAC;IACD,YAAY;QACV,OAAO,IAAI,CAAA;IACb,CAAC;IACD,GAAG;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IACD,KAAK;QACH,OAAO,IAAI,CAAA;IACb,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAY,EAAE,IAAY,EAAE,eAA8C;QACtF,IAAI;YACF,GAAG,CAAC,YAAY,CAAC,CAAA;YACjB,IAAI,eAAe;gBAAE,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,eAAe,CAAC,CAAA;YAE1D,MAAM,OAAO,GAAkB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,eAAe,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAA;YAC9E,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,MAAM,CAAC,oBAAoB,CAAC,CAAA;YACtD,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,IAAI,EAAE,EAAE,OAAO,CAAC,CAAA;YACpD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;YACpD,IAAI,CAAC,iBAAiB,EAAE,CAAA;YAExB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;YACpD,IAAI,IAAI,CAAC,GAAG,EAAE;gBACZ,IAAI,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;aACvD;iBAAM;gBACL,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;aACnD;YAED,MAAM,IAAI,CAAC,SAAU,CAAC,KAAK,CAAA;YAC3B,GAAG,CAAC,cAAc,CAAC,CAAA;YACnB,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAA;YACpB,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;YAEpB,OAAO,IAAI,CAAA;SACZ;QAAC,OAAO,CAAC,EAAE;YACV,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAA;SACtB;IACH,CAAC;IAED,KAAK,CAAC,OAAO;QACX,OAAO,IAAI,EAAE;YACX,GAAG,CAAC,iCAAiC,CAAC,CAAA;YACtC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,SAAU,CAAC,IAAI,EAAE,CAAA;YACpD,GAAG,CAAC,qBAAqB,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;YACvC,IAAI,IAAI,EAAE;gBACR,GAAG,CAAC,MAAM,CAAC,CAAA;gBACX,MAAK;aACN;YACD,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;SACtC;IACH,CAAC;IAED,KAAK,CAAC,WAAW;QACf,GAAG,CAAC,uCAAuC,CAAC,CAAA;QAC5C,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,SAAU,CAAC,IAAI,EAAE,CAAA;QACpD,GAAG,CAAC,2BAA2B,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;QAC7C,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;IACvC,CAAC;IAED,KAAK,CACH,IAAyB,EACzB,WAA2B,MAAM,EACjC,WAAyC,GAAG,EAAE,GAAE,CAAC;QAEjD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,QAAQ,EAAE,CAAA;QACxC,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAA;QAEhE,GAAG,CAAC,sBAAsB,EAAE,IAAI,CAAC,CAAA;QACjC,IAAI,CAAC,SAAU,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,CAC9B,GAAG,EAAE;YACH,GAAG,CAAC,WAAW,CAAC,CAAA;YAChB,QAAQ,EAAE,CAAA;QACZ,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;YACN,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,CAAA;YACtB,QAAQ,CAAC,GAAG,CAAC,CAAA;QACf,CAAC,CACF,CAAA;QACD,OAAO,IAAI,CAAA;IACb,CAAC;IAED,GAAG,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,WAA2B,MAAM,EAAE,WAAyC,GAAG,EAAE,GAAE,CAAC;QAC9G,GAAG,CAAC,kBAAkB,CAAC,CAAA;QACvB,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,GAAG,EAAE,EAAE;YACjC,IAAI,CAAC,SAAU,CAAC,KAAK,EAAE,CAAA;YACvB,IAAI,QAAQ;gBAAE,QAAQ,CAAC,GAAG,CAAC,CAAA;QAC7B,CAAC,CAAC,CAAA;QACF,OAAO,IAAI,CAAA;IACb,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,GAAG,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAAA;QACnC,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;QACrB,OAAO,IAAI,CAAC,GAAG,EAAE,CAAA;IACnB,CAAC;IAED,QAAQ,CAAC,OAAmB;QAC1B,IAAI,IAAI,CAAC,SAAS,EAAE;YAClB,8BAA8B;YAC9B,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,qDAAqD,CAAC,CAAA;YACzE,OAAM;SACP;QACD,IAAI,CAAC,SAAU,CAAC,WAAW,EAAE,CAAA;QAC7B,IAAI,CAAC,SAAU,CAAC,WAAW,EAAE,CAAA;QAC7B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAA;QACtB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAU,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QAClD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;QACpD,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAA;QACpD,IAAI,CAAC,iBAAiB,EAAE,CAAA;QACxB,IAAI,CAAC,OAAO,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;IACpD,CAAC;IAED,iBAAiB;QACf,IAAI,CAAC,SAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE;YAC/B,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE;gBACpB,GAAG,CAAC,kBAAkB,CAAC,CAAA;gBACvB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;gBACrB,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;aACnB;iBAAM;gBACL,IAAI,CAAC,UAAU,GAAG,KAAK,CAAA;gBACvB,IAAI,CAAC,SAAS,GAAG,IAAI,CAAA;aACtB;QACH,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAA;IACxC,CAAC;CACF;AAED,MAAM,KAAK,GAAG,KAAK,CAAA;AAEnB,SAAS,IAAI,CAAC,IAAa;IACzB,IAAI,IAAI,YAAY,UAAU,IAAI,IAAI,YAAY,WAAW,EAAE;QAC7D,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAA;QAC7C,MAAM,GAAG,GAAG,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC1C,OAAO,eAAe,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,eAAe,GAAG,IAAI,CAAA;KACtE;SAAM;QACL,OAAO,IAAI,CAAA;KACZ;AACH,CAAC;AAED,SAAS,GAAG,CAAC,GAAG,IAAe;IAC7B,KAAK,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAA;AACzC,CAAC"}
|
|
||||||
32
lib/node/node_modules/pg-cloudflare/package.json
generated
vendored
32
lib/node/node_modules/pg-cloudflare/package.json
generated
vendored
|
|
@ -1,32 +0,0 @@
|
||||||
{
|
|
||||||
"name": "pg-cloudflare",
|
|
||||||
"version": "1.1.1",
|
|
||||||
"description": "A socket implementation that can run on Cloudflare Workers using native TCP connections.",
|
|
||||||
"main": "dist/empty.js",
|
|
||||||
"types": "dist/index.d.ts",
|
|
||||||
"license": "MIT",
|
|
||||||
"devDependencies": {
|
|
||||||
"ts-node": "^8.5.4",
|
|
||||||
"typescript": "^4.0.3"
|
|
||||||
},
|
|
||||||
"exports": {
|
|
||||||
"workerd": "./dist/index.js",
|
|
||||||
"default": "./dist/empty.js"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"build": "tsc",
|
|
||||||
"build:watch": "tsc --watch",
|
|
||||||
"prepublish": "yarn build",
|
|
||||||
"test": "echo e2e test in pg package"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/brianc/node-postgres.git",
|
|
||||||
"directory": "packages/pg-cloudflare"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"/dist/*{js,ts,map}",
|
|
||||||
"/src"
|
|
||||||
],
|
|
||||||
"gitHead": "eaafac36dc8f4a13f1fecc9e3420d35559fd8e2b"
|
|
||||||
}
|
|
||||||
3
lib/node/node_modules/pg-cloudflare/src/empty.ts
generated
vendored
3
lib/node/node_modules/pg-cloudflare/src/empty.ts
generated
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
// This is an empty module that is served up when outside of a workerd environment
|
|
||||||
// See the `exports` field in package.json
|
|
||||||
export default {}
|
|
||||||
164
lib/node/node_modules/pg-cloudflare/src/index.ts
generated
vendored
164
lib/node/node_modules/pg-cloudflare/src/index.ts
generated
vendored
|
|
@ -1,164 +0,0 @@
|
||||||
import { SocketOptions, Socket, TlsOptions } from 'cloudflare:sockets'
|
|
||||||
import { EventEmitter } from 'events'
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wrapper around the Cloudflare built-in socket that can be used by the `Connection`.
|
|
||||||
*/
|
|
||||||
export class CloudflareSocket extends EventEmitter {
|
|
||||||
writable = false
|
|
||||||
destroyed = false
|
|
||||||
|
|
||||||
private _upgrading = false
|
|
||||||
private _upgraded = false
|
|
||||||
private _cfSocket: Socket | null = null
|
|
||||||
private _cfWriter: WritableStreamDefaultWriter | null = null
|
|
||||||
private _cfReader: ReadableStreamDefaultReader | null = null
|
|
||||||
|
|
||||||
constructor(readonly ssl: boolean) {
|
|
||||||
super()
|
|
||||||
}
|
|
||||||
|
|
||||||
setNoDelay() {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
setKeepAlive() {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
ref() {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
unref() {
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
async connect(port: number, host: string, connectListener?: (...args: unknown[]) => void) {
|
|
||||||
try {
|
|
||||||
log('connecting')
|
|
||||||
if (connectListener) this.once('connect', connectListener)
|
|
||||||
|
|
||||||
const options: SocketOptions = this.ssl ? { secureTransport: 'starttls' } : {}
|
|
||||||
const { connect } = await import('cloudflare:sockets')
|
|
||||||
this._cfSocket = connect(`${host}:${port}`, options)
|
|
||||||
this._cfWriter = this._cfSocket.writable.getWriter()
|
|
||||||
this._addClosedHandler()
|
|
||||||
|
|
||||||
this._cfReader = this._cfSocket.readable.getReader()
|
|
||||||
if (this.ssl) {
|
|
||||||
this._listenOnce().catch((e) => this.emit('error', e))
|
|
||||||
} else {
|
|
||||||
this._listen().catch((e) => this.emit('error', e))
|
|
||||||
}
|
|
||||||
|
|
||||||
await this._cfWriter!.ready
|
|
||||||
log('socket ready')
|
|
||||||
this.writable = true
|
|
||||||
this.emit('connect')
|
|
||||||
|
|
||||||
return this
|
|
||||||
} catch (e) {
|
|
||||||
this.emit('error', e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _listen() {
|
|
||||||
while (true) {
|
|
||||||
log('awaiting receive from CF socket')
|
|
||||||
const { done, value } = await this._cfReader!.read()
|
|
||||||
log('CF socket received:', done, value)
|
|
||||||
if (done) {
|
|
||||||
log('done')
|
|
||||||
break
|
|
||||||
}
|
|
||||||
this.emit('data', Buffer.from(value))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async _listenOnce() {
|
|
||||||
log('awaiting first receive from CF socket')
|
|
||||||
const { done, value } = await this._cfReader!.read()
|
|
||||||
log('First CF socket received:', done, value)
|
|
||||||
this.emit('data', Buffer.from(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
write(
|
|
||||||
data: Uint8Array | string,
|
|
||||||
encoding: BufferEncoding = 'utf8',
|
|
||||||
callback: (...args: unknown[]) => void = () => {}
|
|
||||||
) {
|
|
||||||
if (data.length === 0) return callback()
|
|
||||||
if (typeof data === 'string') data = Buffer.from(data, encoding)
|
|
||||||
|
|
||||||
log('sending data direct:', data)
|
|
||||||
this._cfWriter!.write(data).then(
|
|
||||||
() => {
|
|
||||||
log('data sent')
|
|
||||||
callback()
|
|
||||||
},
|
|
||||||
(err) => {
|
|
||||||
log('send error', err)
|
|
||||||
callback(err)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
end(data = Buffer.alloc(0), encoding: BufferEncoding = 'utf8', callback: (...args: unknown[]) => void = () => {}) {
|
|
||||||
log('ending CF socket')
|
|
||||||
this.write(data, encoding, (err) => {
|
|
||||||
this._cfSocket!.close()
|
|
||||||
if (callback) callback(err)
|
|
||||||
})
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
|
|
||||||
destroy(reason: string) {
|
|
||||||
log('destroying CF socket', reason)
|
|
||||||
this.destroyed = true
|
|
||||||
return this.end()
|
|
||||||
}
|
|
||||||
|
|
||||||
startTls(options: TlsOptions) {
|
|
||||||
if (this._upgraded) {
|
|
||||||
// Don't try to upgrade again.
|
|
||||||
this.emit('error', 'Cannot call `startTls()` more than once on a socket')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this._cfWriter!.releaseLock()
|
|
||||||
this._cfReader!.releaseLock()
|
|
||||||
this._upgrading = true
|
|
||||||
this._cfSocket = this._cfSocket!.startTls(options)
|
|
||||||
this._cfWriter = this._cfSocket.writable.getWriter()
|
|
||||||
this._cfReader = this._cfSocket.readable.getReader()
|
|
||||||
this._addClosedHandler()
|
|
||||||
this._listen().catch((e) => this.emit('error', e))
|
|
||||||
}
|
|
||||||
|
|
||||||
_addClosedHandler() {
|
|
||||||
this._cfSocket!.closed.then(() => {
|
|
||||||
if (!this._upgrading) {
|
|
||||||
log('CF socket closed')
|
|
||||||
this._cfSocket = null
|
|
||||||
this.emit('close')
|
|
||||||
} else {
|
|
||||||
this._upgrading = false
|
|
||||||
this._upgraded = true
|
|
||||||
}
|
|
||||||
}).catch((e) => this.emit('error', e))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const debug = false
|
|
||||||
|
|
||||||
function dump(data: unknown) {
|
|
||||||
if (data instanceof Uint8Array || data instanceof ArrayBuffer) {
|
|
||||||
const hex = Buffer.from(data).toString('hex')
|
|
||||||
const str = new TextDecoder().decode(data)
|
|
||||||
return `\n>>> STR: "${str.replace(/\n/g, '\\n')}"\n>>> HEX: ${hex}\n`
|
|
||||||
} else {
|
|
||||||
return data
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function log(...args: unknown[]) {
|
|
||||||
debug && console.log(...args.map(dump))
|
|
||||||
}
|
|
||||||
25
lib/node/node_modules/pg-cloudflare/src/types.d.ts
generated
vendored
25
lib/node/node_modules/pg-cloudflare/src/types.d.ts
generated
vendored
|
|
@ -1,25 +0,0 @@
|
||||||
declare module 'cloudflare:sockets' {
|
|
||||||
export class Socket {
|
|
||||||
public readonly readable: any
|
|
||||||
public readonly writable: any
|
|
||||||
public readonly closed: Promise<void>
|
|
||||||
public close(): Promise<void>
|
|
||||||
public startTls(options: TlsOptions): Socket
|
|
||||||
}
|
|
||||||
|
|
||||||
export type TlsOptions = {
|
|
||||||
expectedServerHostname?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SocketAddress = {
|
|
||||||
hostname: string
|
|
||||||
port: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SocketOptions = {
|
|
||||||
secureTransport?: 'off' | 'on' | 'starttls'
|
|
||||||
allowHalfOpen?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export function connect(address: string | SocketAddress, options?: SocketOptions): Socket
|
|
||||||
}
|
|
||||||
21
lib/node/node_modules/pg-connection-string/LICENSE
generated
vendored
21
lib/node/node_modules/pg-connection-string/LICENSE
generated
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014 Iced Development
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
77
lib/node/node_modules/pg-connection-string/README.md
generated
vendored
77
lib/node/node_modules/pg-connection-string/README.md
generated
vendored
|
|
@ -1,77 +0,0 @@
|
||||||
pg-connection-string
|
|
||||||
====================
|
|
||||||
|
|
||||||
[](https://nodei.co/npm/pg-connection-string/)
|
|
||||||
|
|
||||||
[](https://travis-ci.org/iceddev/pg-connection-string)
|
|
||||||
[](https://coveralls.io/github/iceddev/pg-connection-string?branch=master)
|
|
||||||
|
|
||||||
Functions for dealing with a PostgresSQL connection string
|
|
||||||
|
|
||||||
`parse` method taken from [node-postgres](https://github.com/brianc/node-postgres.git)
|
|
||||||
Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com)
|
|
||||||
MIT License
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```js
|
|
||||||
var parse = require('pg-connection-string').parse;
|
|
||||||
|
|
||||||
var config = parse('postgres://someuser:somepassword@somehost:381/somedatabase')
|
|
||||||
```
|
|
||||||
|
|
||||||
The resulting config contains a subset of the following properties:
|
|
||||||
|
|
||||||
* `host` - Postgres server hostname or, for UNIX domain sockets, the socket filename
|
|
||||||
* `port` - port on which to connect
|
|
||||||
* `user` - User with which to authenticate to the server
|
|
||||||
* `password` - Corresponding password
|
|
||||||
* `database` - Database name within the server
|
|
||||||
* `client_encoding` - string encoding the client will use
|
|
||||||
* `ssl`, either a boolean or an object with properties
|
|
||||||
* `rejectUnauthorized`
|
|
||||||
* `cert`
|
|
||||||
* `key`
|
|
||||||
* `ca`
|
|
||||||
* any other query parameters (for example, `application_name`) are preserved intact.
|
|
||||||
|
|
||||||
## Connection Strings
|
|
||||||
|
|
||||||
The short summary of acceptable URLs is:
|
|
||||||
|
|
||||||
* `socket:<path>?<query>` - UNIX domain socket
|
|
||||||
* `postgres://<user>:<password>@<host>:<port>/<database>?<query>` - TCP connection
|
|
||||||
|
|
||||||
But see below for more details.
|
|
||||||
|
|
||||||
### UNIX Domain Sockets
|
|
||||||
|
|
||||||
When user and password are not given, the socket path follows `socket:`, as in `socket:/var/run/pgsql`.
|
|
||||||
This form can be shortened to just a path: `/var/run/pgsql`.
|
|
||||||
|
|
||||||
When user and password are given, they are included in the typical URL positions, with an empty `host`, as in `socket://user:pass@/var/run/pgsql`.
|
|
||||||
|
|
||||||
Query parameters follow a `?` character, including the following special query parameters:
|
|
||||||
|
|
||||||
* `db=<database>` - sets the database name (urlencoded)
|
|
||||||
* `encoding=<encoding>` - sets the `client_encoding` property
|
|
||||||
|
|
||||||
### TCP Connections
|
|
||||||
|
|
||||||
TCP connections to the Postgres server are indicated with `pg:` or `postgres:` schemes (in fact, any scheme but `socket:` is accepted).
|
|
||||||
If username and password are included, they should be urlencoded.
|
|
||||||
The database name, however, should *not* be urlencoded.
|
|
||||||
|
|
||||||
Query parameters follow a `?` character, including the following special query parameters:
|
|
||||||
* `host=<host>` - sets `host` property, overriding the URL's host
|
|
||||||
* `encoding=<encoding>` - sets the `client_encoding` property
|
|
||||||
* `ssl=1`, `ssl=true`, `ssl=0`, `ssl=false` - sets `ssl` to true or false, accordingly
|
|
||||||
* `sslmode=<sslmode>`
|
|
||||||
* `sslmode=disable` - sets `ssl` to false
|
|
||||||
* `sslmode=no-verify` - sets `ssl` to `{ rejectUnauthorized: false }`
|
|
||||||
* `sslmode=prefer`, `sslmode=require`, `sslmode=verify-ca`, `sslmode=verify-full` - sets `ssl` to true
|
|
||||||
* `sslcert=<filename>` - reads data from the given file and includes the result as `ssl.cert`
|
|
||||||
* `sslkey=<filename>` - reads data from the given file and includes the result as `ssl.key`
|
|
||||||
* `sslrootcert=<filename>` - reads data from the given file and includes the result as `ssl.ca`
|
|
||||||
|
|
||||||
A bare relative URL, such as `salesdata`, will indicate a database name while leaving other properties empty.
|
|
||||||
15
lib/node/node_modules/pg-connection-string/index.d.ts
generated
vendored
15
lib/node/node_modules/pg-connection-string/index.d.ts
generated
vendored
|
|
@ -1,15 +0,0 @@
|
||||||
export function parse(connectionString: string): ConnectionOptions
|
|
||||||
|
|
||||||
export interface ConnectionOptions {
|
|
||||||
host: string | null
|
|
||||||
password?: string
|
|
||||||
user?: string
|
|
||||||
port?: string | null
|
|
||||||
database: string | null | undefined
|
|
||||||
client_encoding?: string
|
|
||||||
ssl?: boolean | string
|
|
||||||
|
|
||||||
application_name?: string
|
|
||||||
fallback_application_name?: string
|
|
||||||
options?: string
|
|
||||||
}
|
|
||||||
112
lib/node/node_modules/pg-connection-string/index.js
generated
vendored
112
lib/node/node_modules/pg-connection-string/index.js
generated
vendored
|
|
@ -1,112 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
//Parse method copied from https://github.com/brianc/node-postgres
|
|
||||||
//Copyright (c) 2010-2014 Brian Carlson (brian.m.carlson@gmail.com)
|
|
||||||
//MIT License
|
|
||||||
|
|
||||||
//parses a connection string
|
|
||||||
function parse(str) {
|
|
||||||
//unix socket
|
|
||||||
if (str.charAt(0) === '/') {
|
|
||||||
const config = str.split(' ')
|
|
||||||
return { host: config[0], database: config[1] }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for empty host in URL
|
|
||||||
|
|
||||||
const config = {}
|
|
||||||
let result
|
|
||||||
let dummyHost = false
|
|
||||||
if (/ |%[^a-f0-9]|%[a-f0-9][^a-f0-9]/i.test(str)) {
|
|
||||||
// Ensure spaces are encoded as %20
|
|
||||||
str = encodeURI(str).replace(/\%25(\d\d)/g, '%$1')
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
result = new URL(str, 'postgres://base')
|
|
||||||
} catch (e) {
|
|
||||||
// The URL is invalid so try again with a dummy host
|
|
||||||
result = new URL(str.replace('@/', '@___DUMMY___/'), 'postgres://base')
|
|
||||||
dummyHost = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// We'd like to use Object.fromEntries() here but Node.js 10 does not support it
|
|
||||||
for (const entry of result.searchParams.entries()) {
|
|
||||||
config[entry[0]] = entry[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
config.user = config.user || decodeURIComponent(result.username)
|
|
||||||
config.password = config.password || decodeURIComponent(result.password)
|
|
||||||
|
|
||||||
if (result.protocol == 'socket:') {
|
|
||||||
config.host = decodeURI(result.pathname)
|
|
||||||
config.database = result.searchParams.get('db')
|
|
||||||
config.client_encoding = result.searchParams.get('encoding')
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
const hostname = dummyHost ? '' : result.hostname
|
|
||||||
if (!config.host) {
|
|
||||||
// Only set the host if there is no equivalent query param.
|
|
||||||
config.host = decodeURIComponent(hostname)
|
|
||||||
} else if (hostname && /^%2f/i.test(hostname)) {
|
|
||||||
// Only prepend the hostname to the pathname if it is not a URL encoded Unix socket host.
|
|
||||||
result.pathname = hostname + result.pathname
|
|
||||||
}
|
|
||||||
if (!config.port) {
|
|
||||||
// Only set the port if there is no equivalent query param.
|
|
||||||
config.port = result.port
|
|
||||||
}
|
|
||||||
|
|
||||||
const pathname = result.pathname.slice(1) || null
|
|
||||||
config.database = pathname ? decodeURI(pathname) : null
|
|
||||||
|
|
||||||
if (config.ssl === 'true' || config.ssl === '1') {
|
|
||||||
config.ssl = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.ssl === '0') {
|
|
||||||
config.ssl = false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.sslcert || config.sslkey || config.sslrootcert || config.sslmode) {
|
|
||||||
config.ssl = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only try to load fs if we expect to read from the disk
|
|
||||||
const fs = config.sslcert || config.sslkey || config.sslrootcert ? require('fs') : null
|
|
||||||
|
|
||||||
if (config.sslcert) {
|
|
||||||
config.ssl.cert = fs.readFileSync(config.sslcert).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.sslkey) {
|
|
||||||
config.ssl.key = fs.readFileSync(config.sslkey).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.sslrootcert) {
|
|
||||||
config.ssl.ca = fs.readFileSync(config.sslrootcert).toString()
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (config.sslmode) {
|
|
||||||
case 'disable': {
|
|
||||||
config.ssl = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'prefer':
|
|
||||||
case 'require':
|
|
||||||
case 'verify-ca':
|
|
||||||
case 'verify-full': {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
case 'no-verify': {
|
|
||||||
config.ssl.rejectUnauthorized = false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return config
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = parse
|
|
||||||
|
|
||||||
parse.parse = parse
|
|
||||||
40
lib/node/node_modules/pg-connection-string/package.json
generated
vendored
40
lib/node/node_modules/pg-connection-string/package.json
generated
vendored
|
|
@ -1,40 +0,0 @@
|
||||||
{
|
|
||||||
"name": "pg-connection-string",
|
|
||||||
"version": "2.6.4",
|
|
||||||
"description": "Functions for dealing with a PostgresSQL connection string",
|
|
||||||
"main": "./index.js",
|
|
||||||
"types": "./index.d.ts",
|
|
||||||
"scripts": {
|
|
||||||
"test": "istanbul cover _mocha && npm run check-coverage",
|
|
||||||
"check-coverage": "istanbul check-coverage --statements 100 --branches 100 --lines 100 --functions 100",
|
|
||||||
"coveralls": "cat ./coverage/lcov.info | ./node_modules/.bin/coveralls"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/brianc/node-postgres.git",
|
|
||||||
"directory": "packages/pg-connection-string"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"pg",
|
|
||||||
"connection",
|
|
||||||
"string",
|
|
||||||
"parse"
|
|
||||||
],
|
|
||||||
"author": "Blaine Bublitz <blaine@iceddev.com> (http://iceddev.com/)",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/brianc/node-postgres/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/brianc/node-postgres/tree/master/packages/pg-connection-string",
|
|
||||||
"devDependencies": {
|
|
||||||
"chai": "^4.1.1",
|
|
||||||
"coveralls": "^3.0.4",
|
|
||||||
"istanbul": "^0.4.5",
|
|
||||||
"mocha": "^7.1.2"
|
|
||||||
},
|
|
||||||
"files": [
|
|
||||||
"index.js",
|
|
||||||
"index.d.ts"
|
|
||||||
],
|
|
||||||
"gitHead": "a37a93bf7990220517a40cf16b8e72d4c3e6cef5"
|
|
||||||
}
|
|
||||||
13
lib/node/node_modules/pg-int8/LICENSE
generated
vendored
13
lib/node/node_modules/pg-int8/LICENSE
generated
vendored
|
|
@ -1,13 +0,0 @@
|
||||||
Copyright © 2017, Charmander <~@charmander.me>
|
|
||||||
|
|
||||||
Permission to use, copy, modify, and/or distribute this software for any
|
|
||||||
purpose with or without fee is hereby granted, provided that the above
|
|
||||||
copyright notice and this permission notice appear in all copies.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
||||||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
|
||||||
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
||||||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
||||||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
||||||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
||||||
PERFORMANCE OF THIS SOFTWARE.
|
|
||||||
16
lib/node/node_modules/pg-int8/README.md
generated
vendored
16
lib/node/node_modules/pg-int8/README.md
generated
vendored
|
|
@ -1,16 +0,0 @@
|
||||||
[![Build status][ci image]][ci]
|
|
||||||
|
|
||||||
64-bit big-endian signed integer-to-string conversion designed for [pg][].
|
|
||||||
|
|
||||||
```js
|
|
||||||
const readInt8 = require('pg-int8');
|
|
||||||
|
|
||||||
readInt8(Buffer.from([0, 1, 2, 3, 4, 5, 6, 7]))
|
|
||||||
// '283686952306183'
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
[pg]: https://github.com/brianc/node-postgres
|
|
||||||
|
|
||||||
[ci]: https://travis-ci.org/charmander/pg-int8
|
|
||||||
[ci image]: https://api.travis-ci.org/charmander/pg-int8.svg
|
|
||||||
100
lib/node/node_modules/pg-int8/index.js
generated
vendored
100
lib/node/node_modules/pg-int8/index.js
generated
vendored
|
|
@ -1,100 +0,0 @@
|
||||||
'use strict';
|
|
||||||
|
|
||||||
// selected so (BASE - 1) * 0x100000000 + 0xffffffff is a safe integer
|
|
||||||
var BASE = 1000000;
|
|
||||||
|
|
||||||
function readInt8(buffer) {
|
|
||||||
var high = buffer.readInt32BE(0);
|
|
||||||
var low = buffer.readUInt32BE(4);
|
|
||||||
var sign = '';
|
|
||||||
|
|
||||||
if (high < 0) {
|
|
||||||
high = ~high + (low === 0);
|
|
||||||
low = (~low + 1) >>> 0;
|
|
||||||
sign = '-';
|
|
||||||
}
|
|
||||||
|
|
||||||
var result = '';
|
|
||||||
var carry;
|
|
||||||
var t;
|
|
||||||
var digits;
|
|
||||||
var pad;
|
|
||||||
var l;
|
|
||||||
var i;
|
|
||||||
|
|
||||||
{
|
|
||||||
carry = high % BASE;
|
|
||||||
high = high / BASE >>> 0;
|
|
||||||
|
|
||||||
t = 0x100000000 * carry + low;
|
|
||||||
low = t / BASE >>> 0;
|
|
||||||
digits = '' + (t - BASE * low);
|
|
||||||
|
|
||||||
if (low === 0 && high === 0) {
|
|
||||||
return sign + digits + result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pad = '';
|
|
||||||
l = 6 - digits.length;
|
|
||||||
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
pad += '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
result = pad + digits + result;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
carry = high % BASE;
|
|
||||||
high = high / BASE >>> 0;
|
|
||||||
|
|
||||||
t = 0x100000000 * carry + low;
|
|
||||||
low = t / BASE >>> 0;
|
|
||||||
digits = '' + (t - BASE * low);
|
|
||||||
|
|
||||||
if (low === 0 && high === 0) {
|
|
||||||
return sign + digits + result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pad = '';
|
|
||||||
l = 6 - digits.length;
|
|
||||||
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
pad += '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
result = pad + digits + result;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
carry = high % BASE;
|
|
||||||
high = high / BASE >>> 0;
|
|
||||||
|
|
||||||
t = 0x100000000 * carry + low;
|
|
||||||
low = t / BASE >>> 0;
|
|
||||||
digits = '' + (t - BASE * low);
|
|
||||||
|
|
||||||
if (low === 0 && high === 0) {
|
|
||||||
return sign + digits + result;
|
|
||||||
}
|
|
||||||
|
|
||||||
pad = '';
|
|
||||||
l = 6 - digits.length;
|
|
||||||
|
|
||||||
for (i = 0; i < l; i++) {
|
|
||||||
pad += '0';
|
|
||||||
}
|
|
||||||
|
|
||||||
result = pad + digits + result;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
carry = high % BASE;
|
|
||||||
t = 0x100000000 * carry + low;
|
|
||||||
digits = '' + t % BASE;
|
|
||||||
|
|
||||||
return sign + digits + result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = readInt8;
|
|
||||||
24
lib/node/node_modules/pg-int8/package.json
generated
vendored
24
lib/node/node_modules/pg-int8/package.json
generated
vendored
|
|
@ -1,24 +0,0 @@
|
||||||
{
|
|
||||||
"name": "pg-int8",
|
|
||||||
"version": "1.0.1",
|
|
||||||
"description": "64-bit big-endian signed integer-to-string conversion",
|
|
||||||
"bugs": "https://github.com/charmander/pg-int8/issues",
|
|
||||||
"license": "ISC",
|
|
||||||
"files": [
|
|
||||||
"index.js"
|
|
||||||
],
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "https://github.com/charmander/pg-int8"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"test": "tap test"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@charmander/eslint-config-base": "1.0.2",
|
|
||||||
"tap": "10.7.3"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
21
lib/node/node_modules/pg-pool/LICENSE
generated
vendored
21
lib/node/node_modules/pg-pool/LICENSE
generated
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2017 Brian M. Carlson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
376
lib/node/node_modules/pg-pool/README.md
generated
vendored
376
lib/node/node_modules/pg-pool/README.md
generated
vendored
|
|
@ -1,376 +0,0 @@
|
||||||
# pg-pool
|
|
||||||
[](https://travis-ci.org/brianc/node-pg-pool)
|
|
||||||
|
|
||||||
A connection pool for node-postgres
|
|
||||||
|
|
||||||
## install
|
|
||||||
```sh
|
|
||||||
npm i pg-pool pg
|
|
||||||
```
|
|
||||||
|
|
||||||
## use
|
|
||||||
|
|
||||||
### create
|
|
||||||
|
|
||||||
to use pg-pool you must first create an instance of a pool
|
|
||||||
|
|
||||||
```js
|
|
||||||
var Pool = require('pg-pool')
|
|
||||||
|
|
||||||
// by default the pool uses the same
|
|
||||||
// configuration as whatever `pg` version you have installed
|
|
||||||
var pool = new Pool()
|
|
||||||
|
|
||||||
// you can pass properties to the pool
|
|
||||||
// these properties are passed unchanged to both the node-postgres Client constructor
|
|
||||||
// and the node-pool (https://github.com/coopernurse/node-pool) constructor
|
|
||||||
// allowing you to fully configure the behavior of both
|
|
||||||
var pool2 = new Pool({
|
|
||||||
database: 'postgres',
|
|
||||||
user: 'brianc',
|
|
||||||
password: 'secret!',
|
|
||||||
port: 5432,
|
|
||||||
ssl: true,
|
|
||||||
max: 20, // set pool max size to 20
|
|
||||||
idleTimeoutMillis: 1000, // close idle clients after 1 second
|
|
||||||
connectionTimeoutMillis: 1000, // return an error after 1 second if connection could not be established
|
|
||||||
maxUses: 7500, // close (and replace) a connection after it has been used 7500 times (see below for discussion)
|
|
||||||
})
|
|
||||||
|
|
||||||
//you can supply a custom client constructor
|
|
||||||
//if you want to use the native postgres client
|
|
||||||
var NativeClient = require('pg').native.Client
|
|
||||||
var nativePool = new Pool({ Client: NativeClient })
|
|
||||||
|
|
||||||
//you can even pool pg-native clients directly
|
|
||||||
var PgNativeClient = require('pg-native')
|
|
||||||
var pgNativePool = new Pool({ Client: PgNativeClient })
|
|
||||||
```
|
|
||||||
|
|
||||||
##### Note:
|
|
||||||
The Pool constructor does not support passing a Database URL as the parameter. To use pg-pool on heroku, for example, you need to parse the URL into a config object. Here is an example of how to parse a Database URL.
|
|
||||||
|
|
||||||
```js
|
|
||||||
const Pool = require('pg-pool');
|
|
||||||
const url = require('url')
|
|
||||||
|
|
||||||
const params = url.parse(process.env.DATABASE_URL);
|
|
||||||
const auth = params.auth.split(':');
|
|
||||||
|
|
||||||
const config = {
|
|
||||||
user: auth[0],
|
|
||||||
password: auth[1],
|
|
||||||
host: params.hostname,
|
|
||||||
port: params.port,
|
|
||||||
database: params.pathname.split('/')[1],
|
|
||||||
ssl: true
|
|
||||||
};
|
|
||||||
|
|
||||||
const pool = new Pool(config);
|
|
||||||
|
|
||||||
/*
|
|
||||||
Transforms, 'postgres://DBuser:secret@DBHost:#####/myDB', into
|
|
||||||
config = {
|
|
||||||
user: 'DBuser',
|
|
||||||
password: 'secret',
|
|
||||||
host: 'DBHost',
|
|
||||||
port: '#####',
|
|
||||||
database: 'myDB',
|
|
||||||
ssl: true
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
```
|
|
||||||
|
|
||||||
### acquire clients with a promise
|
|
||||||
|
|
||||||
pg-pool supports a fully promise-based api for acquiring clients
|
|
||||||
|
|
||||||
```js
|
|
||||||
var pool = new Pool()
|
|
||||||
pool.connect().then(client => {
|
|
||||||
client.query('select $1::text as name', ['pg-pool']).then(res => {
|
|
||||||
client.release()
|
|
||||||
console.log('hello from', res.rows[0].name)
|
|
||||||
})
|
|
||||||
.catch(e => {
|
|
||||||
client.release()
|
|
||||||
console.error('query error', e.message, e.stack)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### plays nice with async/await
|
|
||||||
|
|
||||||
this ends up looking much nicer if you're using [co](https://github.com/tj/co) or async/await:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// with async/await
|
|
||||||
(async () => {
|
|
||||||
var pool = new Pool()
|
|
||||||
var client = await pool.connect()
|
|
||||||
try {
|
|
||||||
var result = await client.query('select $1::text as name', ['brianc'])
|
|
||||||
console.log('hello from', result.rows[0])
|
|
||||||
} finally {
|
|
||||||
client.release()
|
|
||||||
}
|
|
||||||
})().catch(e => console.error(e.message, e.stack))
|
|
||||||
|
|
||||||
// with co
|
|
||||||
co(function * () {
|
|
||||||
var client = yield pool.connect()
|
|
||||||
try {
|
|
||||||
var result = yield client.query('select $1::text as name', ['brianc'])
|
|
||||||
console.log('hello from', result.rows[0])
|
|
||||||
} finally {
|
|
||||||
client.release()
|
|
||||||
}
|
|
||||||
}).catch(e => console.error(e.message, e.stack))
|
|
||||||
```
|
|
||||||
|
|
||||||
### your new favorite helper method
|
|
||||||
|
|
||||||
because its so common to just run a query and return the client to the pool afterward pg-pool has this built-in:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var pool = new Pool()
|
|
||||||
var time = await pool.query('SELECT NOW()')
|
|
||||||
var name = await pool.query('select $1::text as name', ['brianc'])
|
|
||||||
console.log(name.rows[0].name, 'says hello at', time.rows[0].now)
|
|
||||||
```
|
|
||||||
|
|
||||||
you can also use a callback here if you'd like:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var pool = new Pool()
|
|
||||||
pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
|
|
||||||
console.log(res.rows[0].name) // brianc
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
__pro tip:__ unless you need to run a transaction (which requires a single client for multiple queries) or you
|
|
||||||
have some other edge case like [streaming rows](https://github.com/brianc/node-pg-query-stream) or using a [cursor](https://github.com/brianc/node-pg-cursor)
|
|
||||||
you should almost always just use `pool.query`. Its easy, it does the right thing :tm:, and wont ever forget to return
|
|
||||||
clients back to the pool after the query is done.
|
|
||||||
|
|
||||||
### drop-in backwards compatible
|
|
||||||
|
|
||||||
pg-pool still and will always support the traditional callback api for acquiring a client. This is the exact API node-postgres has shipped with for years:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var pool = new Pool()
|
|
||||||
pool.connect((err, client, done) => {
|
|
||||||
if (err) return done(err)
|
|
||||||
|
|
||||||
client.query('SELECT $1::text as name', ['pg-pool'], (err, res) => {
|
|
||||||
done()
|
|
||||||
if (err) {
|
|
||||||
return console.error('query error', err.message, err.stack)
|
|
||||||
}
|
|
||||||
console.log('hello from', res.rows[0].name)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### shut it down
|
|
||||||
|
|
||||||
When you are finished with the pool if all the clients are idle the pool will close them after `config.idleTimeoutMillis` and your app
|
|
||||||
will shutdown gracefully. If you don't want to wait for the timeout you can end the pool as follows:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var pool = new Pool()
|
|
||||||
var client = await pool.connect()
|
|
||||||
console.log(await client.query('select now()'))
|
|
||||||
client.release()
|
|
||||||
await pool.end()
|
|
||||||
```
|
|
||||||
|
|
||||||
### a note on instances
|
|
||||||
|
|
||||||
The pool should be a __long-lived object__ in your application. Generally you'll want to instantiate one pool when your app starts up and use the same instance of the pool throughout the lifetime of your application. If you are frequently creating a new pool within your code you likely don't have your pool initialization code in the correct place. Example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// assume this is a file in your program at ./your-app/lib/db.js
|
|
||||||
|
|
||||||
// correct usage: create the pool and let it live
|
|
||||||
// 'globally' here, controlling access to it through exported methods
|
|
||||||
var pool = new pg.Pool()
|
|
||||||
|
|
||||||
// this is the right way to export the query method
|
|
||||||
module.exports.query = (text, values) => {
|
|
||||||
console.log('query:', text, values)
|
|
||||||
return pool.query(text, values)
|
|
||||||
}
|
|
||||||
|
|
||||||
// this would be the WRONG way to export the connect method
|
|
||||||
module.exports.connect = () => {
|
|
||||||
// notice how we would be creating a pool instance here
|
|
||||||
// every time we called 'connect' to get a new client?
|
|
||||||
// that's a bad thing & results in creating an unbounded
|
|
||||||
// number of pools & therefore connections
|
|
||||||
var aPool = new pg.Pool()
|
|
||||||
return aPool.connect()
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### events
|
|
||||||
|
|
||||||
Every instance of a `Pool` is an event emitter. These instances emit the following events:
|
|
||||||
|
|
||||||
#### error
|
|
||||||
|
|
||||||
Emitted whenever an idle client in the pool encounters an error. This is common when your PostgreSQL server shuts down, reboots, or a network partition otherwise causes it to become unavailable while your pool has connected clients.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const Pool = require('pg-pool')
|
|
||||||
const pool = new Pool()
|
|
||||||
|
|
||||||
// attach an error handler to the pool for when a connected, idle client
|
|
||||||
// receives an error by being disconnected, etc
|
|
||||||
pool.on('error', function(error, client) {
|
|
||||||
// handle this in the same way you would treat process.on('uncaughtException')
|
|
||||||
// it is supplied the error as well as the idle client which received the error
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### connect
|
|
||||||
|
|
||||||
Fired whenever the pool creates a __new__ `pg.Client` instance and successfully connects it to the backend.
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
```js
|
|
||||||
const Pool = require('pg-pool')
|
|
||||||
const pool = new Pool()
|
|
||||||
|
|
||||||
var count = 0
|
|
||||||
|
|
||||||
pool.on('connect', client => {
|
|
||||||
client.count = count++
|
|
||||||
})
|
|
||||||
|
|
||||||
pool
|
|
||||||
.connect()
|
|
||||||
.then(client => {
|
|
||||||
return client
|
|
||||||
.query('SELECT $1::int AS "clientCount"', [client.count])
|
|
||||||
.then(res => console.log(res.rows[0].clientCount)) // outputs 0
|
|
||||||
.then(() => client)
|
|
||||||
})
|
|
||||||
.then(client => client.release())
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
#### acquire
|
|
||||||
|
|
||||||
Fired whenever the a client is acquired from the pool
|
|
||||||
|
|
||||||
Example:
|
|
||||||
|
|
||||||
This allows you to count the number of clients which have ever been acquired from the pool.
|
|
||||||
|
|
||||||
```js
|
|
||||||
var Pool = require('pg-pool')
|
|
||||||
var pool = new Pool()
|
|
||||||
|
|
||||||
var acquireCount = 0
|
|
||||||
pool.on('acquire', function (client) {
|
|
||||||
acquireCount++
|
|
||||||
})
|
|
||||||
|
|
||||||
var connectCount = 0
|
|
||||||
pool.on('connect', function () {
|
|
||||||
connectCount++
|
|
||||||
})
|
|
||||||
|
|
||||||
for (var i = 0; i < 200; i++) {
|
|
||||||
pool.query('SELECT NOW()')
|
|
||||||
}
|
|
||||||
|
|
||||||
setTimeout(function () {
|
|
||||||
console.log('connect count:', connectCount) // output: connect count: 10
|
|
||||||
console.log('acquire count:', acquireCount) // output: acquire count: 200
|
|
||||||
}, 100)
|
|
||||||
|
|
||||||
```
|
|
||||||
|
|
||||||
### environment variables
|
|
||||||
|
|
||||||
pg-pool & node-postgres support some of the same environment variables as `psql` supports. The most common are:
|
|
||||||
|
|
||||||
```
|
|
||||||
PGDATABASE=my_db
|
|
||||||
PGUSER=username
|
|
||||||
PGPASSWORD="my awesome password"
|
|
||||||
PGPORT=5432
|
|
||||||
PGSSLMODE=require
|
|
||||||
```
|
|
||||||
|
|
||||||
Usually I will export these into my local environment via a `.env` file with environment settings or export them in `~/.bash_profile` or something similar. This way I get configurability which works with both the postgres suite of tools (`psql`, `pg_dump`, `pg_restore`) and node, I can vary the environment variables locally and in production, and it supports the concept of a [12-factor app](http://12factor.net/) out of the box.
|
|
||||||
|
|
||||||
## bring your own promise
|
|
||||||
|
|
||||||
In versions of node `<=0.12.x` there is no native promise implementation available globally. You can polyfill the promise globally like this:
|
|
||||||
|
|
||||||
```js
|
|
||||||
// first run `npm install promise-polyfill --save
|
|
||||||
if (typeof Promise == 'undefined') {
|
|
||||||
global.Promise = require('promise-polyfill')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
You can use any other promise implementation you'd like. The pool also allows you to configure the promise implementation on a per-pool level:
|
|
||||||
|
|
||||||
```js
|
|
||||||
var bluebirdPool = new Pool({
|
|
||||||
Promise: require('bluebird')
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
__please note:__ in node `<=0.12.x` the pool will throw if you do not provide a promise constructor in one of the two ways mentioned above. In node `>=4.0.0` the pool will use the native promise implementation by default; however, the two methods above still allow you to "bring your own."
|
|
||||||
|
|
||||||
## maxUses and read-replica autoscaling (e.g. AWS Aurora)
|
|
||||||
|
|
||||||
The maxUses config option can help an application instance rebalance load against a replica set that has been auto-scaled after the connection pool is already full of healthy connections.
|
|
||||||
|
|
||||||
The mechanism here is that a connection is considered "expended" after it has been acquired and released `maxUses` number of times. Depending on the load on your system, this means there will be an approximate time in which any given connection will live, thus creating a window for rebalancing.
|
|
||||||
|
|
||||||
Imagine a scenario where you have 10 app instances providing an API running against a replica cluster of 3 that are accessed via a round-robin DNS entry. Each instance runs a connection pool size of 20. With an ambient load of 50 requests per second, the connection pool will likely fill up in a few minutes with healthy connections.
|
|
||||||
|
|
||||||
If you have weekly bursts of traffic which peak at 1,000 requests per second, you might want to grow your replicas to 10 during this period. Without setting `maxUses`, the new replicas will not be adopted by the app servers without an intervention -- namely, restarting each in turn in order to build up new connection pools that are balanced against all the replicas. Adding additional app server instances will help to some extent because they will adopt all the replicas in an even way, but the initial app servers will continue to focus additional load on the original replicas.
|
|
||||||
|
|
||||||
This is where the `maxUses` configuration option comes into play. Setting `maxUses` to 7500 will ensure that over a period of 30 minutes or so the new replicas will be adopted as the pre-existing connections are closed and replaced with new ones, thus creating a window for eventual balance.
|
|
||||||
|
|
||||||
You'll want to test based on your own scenarios, but one way to make a first guess at `maxUses` is to identify an acceptable window for rebalancing and then solve for the value:
|
|
||||||
|
|
||||||
```
|
|
||||||
maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize
|
|
||||||
```
|
|
||||||
|
|
||||||
In the example above, assuming we acquire and release 1 connection per request and we are aiming for a 30 minute rebalancing window:
|
|
||||||
|
|
||||||
```
|
|
||||||
maxUses = rebalanceWindowSeconds * totalRequestsPerSecond / numAppInstances / poolSize
|
|
||||||
7200 = 1800 * 1000 / 10 / 25
|
|
||||||
```
|
|
||||||
|
|
||||||
## tests
|
|
||||||
|
|
||||||
To run tests clone the repo, `npm i` in the working dir, and then run `npm test`
|
|
||||||
|
|
||||||
## contributions
|
|
||||||
|
|
||||||
I love contributions. Please make sure they have tests, and submit a PR. If you're not sure if the issue is worth it or will be accepted it never hurts to open an issue to begin the conversation. If you're interested in keeping up with node-postgres releated stuff, you can follow me on twitter at [@briancarlson](https://twitter.com/briancarlson) - I generally announce any noteworthy updates there.
|
|
||||||
|
|
||||||
## license
|
|
||||||
|
|
||||||
The MIT License (MIT)
|
|
||||||
Copyright (c) 2016 Brian M. Carlson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
||||||
467
lib/node/node_modules/pg-pool/index.js
generated
vendored
467
lib/node/node_modules/pg-pool/index.js
generated
vendored
|
|
@ -1,467 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const EventEmitter = require('events').EventEmitter
|
|
||||||
|
|
||||||
const NOOP = function () {}
|
|
||||||
|
|
||||||
const removeWhere = (list, predicate) => {
|
|
||||||
const i = list.findIndex(predicate)
|
|
||||||
|
|
||||||
return i === -1 ? undefined : list.splice(i, 1)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
class IdleItem {
|
|
||||||
constructor(client, idleListener, timeoutId) {
|
|
||||||
this.client = client
|
|
||||||
this.idleListener = idleListener
|
|
||||||
this.timeoutId = timeoutId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class PendingItem {
|
|
||||||
constructor(callback) {
|
|
||||||
this.callback = callback
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function throwOnDoubleRelease() {
|
|
||||||
throw new Error('Release called on client which has already been released to the pool.')
|
|
||||||
}
|
|
||||||
|
|
||||||
function promisify(Promise, callback) {
|
|
||||||
if (callback) {
|
|
||||||
return { callback: callback, result: undefined }
|
|
||||||
}
|
|
||||||
let rej
|
|
||||||
let res
|
|
||||||
const cb = function (err, client) {
|
|
||||||
err ? rej(err) : res(client)
|
|
||||||
}
|
|
||||||
const result = new Promise(function (resolve, reject) {
|
|
||||||
res = resolve
|
|
||||||
rej = reject
|
|
||||||
}).catch((err) => {
|
|
||||||
// replace the stack trace that leads to `TCP.onStreamRead` with one that leads back to the
|
|
||||||
// application that created the query
|
|
||||||
Error.captureStackTrace(err)
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
return { callback: cb, result: result }
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeIdleListener(pool, client) {
|
|
||||||
return function idleListener(err) {
|
|
||||||
err.client = client
|
|
||||||
|
|
||||||
client.removeListener('error', idleListener)
|
|
||||||
client.on('error', () => {
|
|
||||||
pool.log('additional client error after disconnection due to error', err)
|
|
||||||
})
|
|
||||||
pool._remove(client)
|
|
||||||
// TODO - document that once the pool emits an error
|
|
||||||
// the client has already been closed & purged and is unusable
|
|
||||||
pool.emit('error', err, client)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Pool extends EventEmitter {
|
|
||||||
constructor(options, Client) {
|
|
||||||
super()
|
|
||||||
this.options = Object.assign({}, options)
|
|
||||||
|
|
||||||
if (options != null && 'password' in options) {
|
|
||||||
// "hiding" the password so it doesn't show up in stack traces
|
|
||||||
// or if the client is console.logged
|
|
||||||
Object.defineProperty(this.options, 'password', {
|
|
||||||
configurable: true,
|
|
||||||
enumerable: false,
|
|
||||||
writable: true,
|
|
||||||
value: options.password,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (options != null && options.ssl && options.ssl.key) {
|
|
||||||
// "hiding" the ssl->key so it doesn't show up in stack traces
|
|
||||||
// or if the client is console.logged
|
|
||||||
Object.defineProperty(this.options.ssl, 'key', {
|
|
||||||
enumerable: false,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
this.options.max = this.options.max || this.options.poolSize || 10
|
|
||||||
this.options.maxUses = this.options.maxUses || Infinity
|
|
||||||
this.options.allowExitOnIdle = this.options.allowExitOnIdle || false
|
|
||||||
this.options.maxLifetimeSeconds = this.options.maxLifetimeSeconds || 0
|
|
||||||
this.log = this.options.log || function () {}
|
|
||||||
this.Client = this.options.Client || Client || require('pg').Client
|
|
||||||
this.Promise = this.options.Promise || global.Promise
|
|
||||||
|
|
||||||
if (typeof this.options.idleTimeoutMillis === 'undefined') {
|
|
||||||
this.options.idleTimeoutMillis = 10000
|
|
||||||
}
|
|
||||||
|
|
||||||
this._clients = []
|
|
||||||
this._idle = []
|
|
||||||
this._expired = new WeakSet()
|
|
||||||
this._pendingQueue = []
|
|
||||||
this._endCallback = undefined
|
|
||||||
this.ending = false
|
|
||||||
this.ended = false
|
|
||||||
}
|
|
||||||
|
|
||||||
_isFull() {
|
|
||||||
return this._clients.length >= this.options.max
|
|
||||||
}
|
|
||||||
|
|
||||||
_pulseQueue() {
|
|
||||||
this.log('pulse queue')
|
|
||||||
if (this.ended) {
|
|
||||||
this.log('pulse queue ended')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.ending) {
|
|
||||||
this.log('pulse queue on ending')
|
|
||||||
if (this._idle.length) {
|
|
||||||
this._idle.slice().map((item) => {
|
|
||||||
this._remove(item.client)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (!this._clients.length) {
|
|
||||||
this.ended = true
|
|
||||||
this._endCallback()
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// if we don't have any waiting, do nothing
|
|
||||||
if (!this._pendingQueue.length) {
|
|
||||||
this.log('no queued requests')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// if we don't have any idle clients and we have no more room do nothing
|
|
||||||
if (!this._idle.length && this._isFull()) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const pendingItem = this._pendingQueue.shift()
|
|
||||||
if (this._idle.length) {
|
|
||||||
const idleItem = this._idle.pop()
|
|
||||||
clearTimeout(idleItem.timeoutId)
|
|
||||||
const client = idleItem.client
|
|
||||||
client.ref && client.ref()
|
|
||||||
const idleListener = idleItem.idleListener
|
|
||||||
|
|
||||||
return this._acquireClient(client, pendingItem, idleListener, false)
|
|
||||||
}
|
|
||||||
if (!this._isFull()) {
|
|
||||||
return this.newClient(pendingItem)
|
|
||||||
}
|
|
||||||
throw new Error('unexpected condition')
|
|
||||||
}
|
|
||||||
|
|
||||||
_remove(client) {
|
|
||||||
const removed = removeWhere(this._idle, (item) => item.client === client)
|
|
||||||
|
|
||||||
if (removed !== undefined) {
|
|
||||||
clearTimeout(removed.timeoutId)
|
|
||||||
}
|
|
||||||
|
|
||||||
this._clients = this._clients.filter((c) => c !== client)
|
|
||||||
client.end()
|
|
||||||
this.emit('remove', client)
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(cb) {
|
|
||||||
if (this.ending) {
|
|
||||||
const err = new Error('Cannot use a pool after calling end on the pool')
|
|
||||||
return cb ? cb(err) : this.Promise.reject(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = promisify(this.Promise, cb)
|
|
||||||
const result = response.result
|
|
||||||
|
|
||||||
// if we don't have to connect a new client, don't do so
|
|
||||||
if (this._isFull() || this._idle.length) {
|
|
||||||
// if we have idle clients schedule a pulse immediately
|
|
||||||
if (this._idle.length) {
|
|
||||||
process.nextTick(() => this._pulseQueue())
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.options.connectionTimeoutMillis) {
|
|
||||||
this._pendingQueue.push(new PendingItem(response.callback))
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
const queueCallback = (err, res, done) => {
|
|
||||||
clearTimeout(tid)
|
|
||||||
response.callback(err, res, done)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pendingItem = new PendingItem(queueCallback)
|
|
||||||
|
|
||||||
// set connection timeout on checking out an existing client
|
|
||||||
const tid = setTimeout(() => {
|
|
||||||
// remove the callback from pending waiters because
|
|
||||||
// we're going to call it with a timeout error
|
|
||||||
removeWhere(this._pendingQueue, (i) => i.callback === queueCallback)
|
|
||||||
pendingItem.timedOut = true
|
|
||||||
response.callback(new Error('timeout exceeded when trying to connect'))
|
|
||||||
}, this.options.connectionTimeoutMillis)
|
|
||||||
|
|
||||||
this._pendingQueue.push(pendingItem)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
this.newClient(new PendingItem(response.callback))
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
newClient(pendingItem) {
|
|
||||||
const client = new this.Client(this.options)
|
|
||||||
this._clients.push(client)
|
|
||||||
const idleListener = makeIdleListener(this, client)
|
|
||||||
|
|
||||||
this.log('checking client timeout')
|
|
||||||
|
|
||||||
// connection timeout logic
|
|
||||||
let tid
|
|
||||||
let timeoutHit = false
|
|
||||||
if (this.options.connectionTimeoutMillis) {
|
|
||||||
tid = setTimeout(() => {
|
|
||||||
this.log('ending client due to timeout')
|
|
||||||
timeoutHit = true
|
|
||||||
// force kill the node driver, and let libpq do its teardown
|
|
||||||
client.connection ? client.connection.stream.destroy() : client.end()
|
|
||||||
}, this.options.connectionTimeoutMillis)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.log('connecting new client')
|
|
||||||
client.connect((err) => {
|
|
||||||
if (tid) {
|
|
||||||
clearTimeout(tid)
|
|
||||||
}
|
|
||||||
client.on('error', idleListener)
|
|
||||||
if (err) {
|
|
||||||
this.log('client failed to connect', err)
|
|
||||||
// remove the dead client from our list of clients
|
|
||||||
this._clients = this._clients.filter((c) => c !== client)
|
|
||||||
if (timeoutHit) {
|
|
||||||
err.message = 'Connection terminated due to connection timeout'
|
|
||||||
}
|
|
||||||
|
|
||||||
// this client won’t be released, so move on immediately
|
|
||||||
this._pulseQueue()
|
|
||||||
|
|
||||||
if (!pendingItem.timedOut) {
|
|
||||||
pendingItem.callback(err, undefined, NOOP)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.log('new client connected')
|
|
||||||
|
|
||||||
if (this.options.maxLifetimeSeconds !== 0) {
|
|
||||||
const maxLifetimeTimeout = setTimeout(() => {
|
|
||||||
this.log('ending client due to expired lifetime')
|
|
||||||
this._expired.add(client)
|
|
||||||
const idleIndex = this._idle.findIndex((idleItem) => idleItem.client === client)
|
|
||||||
if (idleIndex !== -1) {
|
|
||||||
this._acquireClient(
|
|
||||||
client,
|
|
||||||
new PendingItem((err, client, clientRelease) => clientRelease()),
|
|
||||||
idleListener,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, this.options.maxLifetimeSeconds * 1000)
|
|
||||||
|
|
||||||
maxLifetimeTimeout.unref()
|
|
||||||
client.once('end', () => clearTimeout(maxLifetimeTimeout))
|
|
||||||
}
|
|
||||||
|
|
||||||
return this._acquireClient(client, pendingItem, idleListener, true)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// acquire a client for a pending work item
|
|
||||||
_acquireClient(client, pendingItem, idleListener, isNew) {
|
|
||||||
if (isNew) {
|
|
||||||
this.emit('connect', client)
|
|
||||||
}
|
|
||||||
|
|
||||||
this.emit('acquire', client)
|
|
||||||
|
|
||||||
client.release = this._releaseOnce(client, idleListener)
|
|
||||||
|
|
||||||
client.removeListener('error', idleListener)
|
|
||||||
|
|
||||||
if (!pendingItem.timedOut) {
|
|
||||||
if (isNew && this.options.verify) {
|
|
||||||
this.options.verify(client, (err) => {
|
|
||||||
if (err) {
|
|
||||||
client.release(err)
|
|
||||||
return pendingItem.callback(err, undefined, NOOP)
|
|
||||||
}
|
|
||||||
|
|
||||||
pendingItem.callback(undefined, client, client.release)
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
pendingItem.callback(undefined, client, client.release)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (isNew && this.options.verify) {
|
|
||||||
this.options.verify(client, client.release)
|
|
||||||
} else {
|
|
||||||
client.release()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// returns a function that wraps _release and throws if called more than once
|
|
||||||
_releaseOnce(client, idleListener) {
|
|
||||||
let released = false
|
|
||||||
|
|
||||||
return (err) => {
|
|
||||||
if (released) {
|
|
||||||
throwOnDoubleRelease()
|
|
||||||
}
|
|
||||||
|
|
||||||
released = true
|
|
||||||
this._release(client, idleListener, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// release a client back to the poll, include an error
|
|
||||||
// to remove it from the pool
|
|
||||||
_release(client, idleListener, err) {
|
|
||||||
client.on('error', idleListener)
|
|
||||||
|
|
||||||
client._poolUseCount = (client._poolUseCount || 0) + 1
|
|
||||||
|
|
||||||
this.emit('release', err, client)
|
|
||||||
|
|
||||||
// TODO(bmc): expose a proper, public interface _queryable and _ending
|
|
||||||
if (err || this.ending || !client._queryable || client._ending || client._poolUseCount >= this.options.maxUses) {
|
|
||||||
if (client._poolUseCount >= this.options.maxUses) {
|
|
||||||
this.log('remove expended client')
|
|
||||||
}
|
|
||||||
this._remove(client)
|
|
||||||
this._pulseQueue()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const isExpired = this._expired.has(client)
|
|
||||||
if (isExpired) {
|
|
||||||
this.log('remove expired client')
|
|
||||||
this._expired.delete(client)
|
|
||||||
this._remove(client)
|
|
||||||
this._pulseQueue()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// idle timeout
|
|
||||||
let tid
|
|
||||||
if (this.options.idleTimeoutMillis) {
|
|
||||||
tid = setTimeout(() => {
|
|
||||||
this.log('remove idle client')
|
|
||||||
this._remove(client)
|
|
||||||
}, this.options.idleTimeoutMillis)
|
|
||||||
|
|
||||||
if (this.options.allowExitOnIdle) {
|
|
||||||
// allow Node to exit if this is all that's left
|
|
||||||
tid.unref()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.options.allowExitOnIdle) {
|
|
||||||
client.unref()
|
|
||||||
}
|
|
||||||
|
|
||||||
this._idle.push(new IdleItem(client, idleListener, tid))
|
|
||||||
this._pulseQueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
query(text, values, cb) {
|
|
||||||
// guard clause against passing a function as the first parameter
|
|
||||||
if (typeof text === 'function') {
|
|
||||||
const response = promisify(this.Promise, text)
|
|
||||||
setImmediate(function () {
|
|
||||||
return response.callback(new Error('Passing a function as the first parameter to pool.query is not supported'))
|
|
||||||
})
|
|
||||||
return response.result
|
|
||||||
}
|
|
||||||
|
|
||||||
// allow plain text query without values
|
|
||||||
if (typeof values === 'function') {
|
|
||||||
cb = values
|
|
||||||
values = undefined
|
|
||||||
}
|
|
||||||
const response = promisify(this.Promise, cb)
|
|
||||||
cb = response.callback
|
|
||||||
|
|
||||||
this.connect((err, client) => {
|
|
||||||
if (err) {
|
|
||||||
return cb(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
let clientReleased = false
|
|
||||||
const onError = (err) => {
|
|
||||||
if (clientReleased) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
clientReleased = true
|
|
||||||
client.release(err)
|
|
||||||
cb(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client.once('error', onError)
|
|
||||||
this.log('dispatching query')
|
|
||||||
try {
|
|
||||||
client.query(text, values, (err, res) => {
|
|
||||||
this.log('query dispatched')
|
|
||||||
client.removeListener('error', onError)
|
|
||||||
if (clientReleased) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
clientReleased = true
|
|
||||||
client.release(err)
|
|
||||||
if (err) {
|
|
||||||
return cb(err)
|
|
||||||
}
|
|
||||||
return cb(undefined, res)
|
|
||||||
})
|
|
||||||
} catch (err) {
|
|
||||||
client.release(err)
|
|
||||||
return cb(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
return response.result
|
|
||||||
}
|
|
||||||
|
|
||||||
end(cb) {
|
|
||||||
this.log('ending')
|
|
||||||
if (this.ending) {
|
|
||||||
const err = new Error('Called end on pool more than once')
|
|
||||||
return cb ? cb(err) : this.Promise.reject(err)
|
|
||||||
}
|
|
||||||
this.ending = true
|
|
||||||
const promised = promisify(this.Promise, cb)
|
|
||||||
this._endCallback = promised.callback
|
|
||||||
this._pulseQueue()
|
|
||||||
return promised.result
|
|
||||||
}
|
|
||||||
|
|
||||||
get waitingCount() {
|
|
||||||
return this._pendingQueue.length
|
|
||||||
}
|
|
||||||
|
|
||||||
get idleCount() {
|
|
||||||
return this._idle.length
|
|
||||||
}
|
|
||||||
|
|
||||||
get expiredCount() {
|
|
||||||
return this._clients.reduce((acc, client) => acc + (this._expired.has(client) ? 1 : 0), 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
get totalCount() {
|
|
||||||
return this._clients.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
module.exports = Pool
|
|
||||||
41
lib/node/node_modules/pg-pool/package.json
generated
vendored
41
lib/node/node_modules/pg-pool/package.json
generated
vendored
|
|
@ -1,41 +0,0 @@
|
||||||
{
|
|
||||||
"name": "pg-pool",
|
|
||||||
"version": "3.6.2",
|
|
||||||
"description": "Connection pool for node-postgres",
|
|
||||||
"main": "index.js",
|
|
||||||
"directories": {
|
|
||||||
"test": "test"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"test": " node_modules/.bin/mocha"
|
|
||||||
},
|
|
||||||
"repository": {
|
|
||||||
"type": "git",
|
|
||||||
"url": "git://github.com/brianc/node-postgres.git",
|
|
||||||
"directory": "packages/pg-pool"
|
|
||||||
},
|
|
||||||
"keywords": [
|
|
||||||
"pg",
|
|
||||||
"postgres",
|
|
||||||
"pool",
|
|
||||||
"database"
|
|
||||||
],
|
|
||||||
"author": "Brian M. Carlson",
|
|
||||||
"license": "MIT",
|
|
||||||
"bugs": {
|
|
||||||
"url": "https://github.com/brianc/node-pg-pool/issues"
|
|
||||||
},
|
|
||||||
"homepage": "https://github.com/brianc/node-pg-pool#readme",
|
|
||||||
"devDependencies": {
|
|
||||||
"bluebird": "3.4.1",
|
|
||||||
"co": "4.6.0",
|
|
||||||
"expect.js": "0.3.1",
|
|
||||||
"lodash": "^4.17.11",
|
|
||||||
"mocha": "^7.1.2",
|
|
||||||
"pg-cursor": "^1.3.0"
|
|
||||||
},
|
|
||||||
"peerDependencies": {
|
|
||||||
"pg": ">=8.0"
|
|
||||||
},
|
|
||||||
"gitHead": "b03c071d2d15af259e1e008e9628191c865e58fa"
|
|
||||||
}
|
|
||||||
42
lib/node/node_modules/pg-pool/test/bring-your-own-promise.js
generated
vendored
42
lib/node/node_modules/pg-pool/test/bring-your-own-promise.js
generated
vendored
|
|
@ -1,42 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const co = require('co')
|
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
const BluebirdPromise = require('bluebird')
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
const checkType = (promise) => {
|
|
||||||
expect(promise).to.be.a(BluebirdPromise)
|
|
||||||
return promise.catch((e) => undefined)
|
|
||||||
}
|
|
||||||
|
|
||||||
describe('Bring your own promise', function () {
|
|
||||||
it(
|
|
||||||
'uses supplied promise for operations',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ Promise: BluebirdPromise })
|
|
||||||
const client1 = yield checkType(pool.connect())
|
|
||||||
client1.release()
|
|
||||||
yield checkType(pool.query('SELECT NOW()'))
|
|
||||||
const client2 = yield checkType(pool.connect())
|
|
||||||
// TODO - make sure pg supports BYOP as well
|
|
||||||
client2.release()
|
|
||||||
yield checkType(pool.end())
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'uses promises in errors',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ Promise: BluebirdPromise, port: 48484 })
|
|
||||||
yield checkType(pool.connect())
|
|
||||||
yield checkType(pool.end())
|
|
||||||
yield checkType(pool.connect())
|
|
||||||
yield checkType(pool.query())
|
|
||||||
yield checkType(pool.end())
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
29
lib/node/node_modules/pg-pool/test/connection-strings.js
generated
vendored
29
lib/node/node_modules/pg-pool/test/connection-strings.js
generated
vendored
|
|
@ -1,29 +0,0 @@
|
||||||
const expect = require('expect.js')
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('Connection strings', function () {
|
|
||||||
it('pool delegates connectionString property to client', function (done) {
|
|
||||||
const connectionString = 'postgres://foo:bar@baz:1234/xur'
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
// use a fake client so we can check we're passed the connectionString
|
|
||||||
Client: function (args) {
|
|
||||||
expect(args.connectionString).to.equal(connectionString)
|
|
||||||
return {
|
|
||||||
connect: function (cb) {
|
|
||||||
cb(new Error('testing'))
|
|
||||||
},
|
|
||||||
on: function () {},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
connectionString: connectionString,
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.connect(function (err, client) {
|
|
||||||
expect(err).to.not.be(undefined)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
229
lib/node/node_modules/pg-pool/test/connection-timeout.js
generated
vendored
229
lib/node/node_modules/pg-pool/test/connection-timeout.js
generated
vendored
|
|
@ -1,229 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const net = require('net')
|
|
||||||
const co = require('co')
|
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
const before = require('mocha').before
|
|
||||||
const after = require('mocha').after
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('connection timeout', () => {
|
|
||||||
const connectionFailure = new Error('Temporary connection failure')
|
|
||||||
|
|
||||||
before((done) => {
|
|
||||||
this.server = net.createServer((socket) => {
|
|
||||||
socket.on('data', () => {
|
|
||||||
// discard any buffered data or the server wont terminate
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
this.server.listen(() => {
|
|
||||||
this.port = this.server.address().port
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
after((done) => {
|
|
||||||
this.server.close(done)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should callback with an error if timeout is passed', (done) => {
|
|
||||||
const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(err.message).to.contain('timeout')
|
|
||||||
expect(client).to.equal(undefined)
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should reject promise with an error if timeout is passed', (done) => {
|
|
||||||
const pool = new Pool({ connectionTimeoutMillis: 10, port: this.port, host: 'localhost' })
|
|
||||||
pool.connect().catch((err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(err.message).to.contain('timeout')
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it(
|
|
||||||
'should handle multiple timeouts',
|
|
||||||
co.wrap(
|
|
||||||
function* () {
|
|
||||||
const errors = []
|
|
||||||
const pool = new Pool({ connectionTimeoutMillis: 1, port: this.port, host: 'localhost' })
|
|
||||||
for (var i = 0; i < 15; i++) {
|
|
||||||
try {
|
|
||||||
yield pool.connect()
|
|
||||||
} catch (e) {
|
|
||||||
errors.push(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expect(errors).to.have.length(15)
|
|
||||||
}.bind(this)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
it('should timeout on checkout of used connection', (done) => {
|
|
||||||
const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(client).to.not.be(undefined)
|
|
||||||
pool.connect((err, client) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(client).to.be(undefined)
|
|
||||||
release()
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not break further pending checkouts on a timeout', (done) => {
|
|
||||||
const pool = new Pool({ connectionTimeoutMillis: 200, max: 1 })
|
|
||||||
pool.connect((err, client, releaseOuter) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
|
|
||||||
pool.connect((err, client) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(client).to.be(undefined)
|
|
||||||
releaseOuter()
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
pool.connect((err, client, releaseInner) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(client).to.not.be(undefined)
|
|
||||||
releaseInner()
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
}, 100)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should timeout on query if all clients are busy', (done) => {
|
|
||||||
const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(client).to.not.be(undefined)
|
|
||||||
pool.query('select now()', (err, result) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(result).to.be(undefined)
|
|
||||||
release()
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should recover from timeout errors', (done) => {
|
|
||||||
const pool = new Pool({ connectionTimeoutMillis: 100, max: 1 })
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(client).to.not.be(undefined)
|
|
||||||
pool.query('select now()', (err, result) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(result).to.be(undefined)
|
|
||||||
release()
|
|
||||||
pool.query('select $1::text as name', ['brianc'], (err, res) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(res.rows).to.have.length(1)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('continues processing after a connection failure', (done) => {
|
|
||||||
const Client = require('pg').Client
|
|
||||||
const orgConnect = Client.prototype.connect
|
|
||||||
let called = false
|
|
||||||
|
|
||||||
Client.prototype.connect = function (cb) {
|
|
||||||
// Simulate a failure on first call
|
|
||||||
if (!called) {
|
|
||||||
called = true
|
|
||||||
|
|
||||||
return setTimeout(() => {
|
|
||||||
cb(connectionFailure)
|
|
||||||
}, 100)
|
|
||||||
}
|
|
||||||
// And pass-through the second call
|
|
||||||
orgConnect.call(this, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
Client: Client,
|
|
||||||
connectionTimeoutMillis: 1000,
|
|
||||||
max: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
expect(err).to.be(connectionFailure)
|
|
||||||
|
|
||||||
pool.query('select $1::text as name', ['brianc'], (err, res) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(res.rows).to.have.length(1)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('releases newly connected clients if the queued already timed out', (done) => {
|
|
||||||
const Client = require('pg').Client
|
|
||||||
|
|
||||||
const orgConnect = Client.prototype.connect
|
|
||||||
|
|
||||||
let connection = 0
|
|
||||||
|
|
||||||
Client.prototype.connect = function (cb) {
|
|
||||||
// Simulate a failure on first call
|
|
||||||
if (connection === 0) {
|
|
||||||
connection++
|
|
||||||
|
|
||||||
return setTimeout(() => {
|
|
||||||
cb(connectionFailure)
|
|
||||||
}, 300)
|
|
||||||
}
|
|
||||||
|
|
||||||
// And second connect taking > connection timeout
|
|
||||||
if (connection === 1) {
|
|
||||||
connection++
|
|
||||||
|
|
||||||
return setTimeout(() => {
|
|
||||||
orgConnect.call(this, cb)
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
|
|
||||||
orgConnect.call(this, cb)
|
|
||||||
}
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
Client: Client,
|
|
||||||
connectionTimeoutMillis: 1000,
|
|
||||||
max: 1,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Direct connect
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
expect(err).to.be(connectionFailure)
|
|
||||||
})
|
|
||||||
|
|
||||||
// Queued
|
|
||||||
let called = 0
|
|
||||||
pool.connect((err, client, release) => {
|
|
||||||
// Verify the callback is only called once
|
|
||||||
expect(called++).to.be(0)
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
|
|
||||||
pool.query('select $1::text as name', ['brianc'], (err, res) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(res.rows).to.have.length(1)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
40
lib/node/node_modules/pg-pool/test/ending.js
generated
vendored
40
lib/node/node_modules/pg-pool/test/ending.js
generated
vendored
|
|
@ -1,40 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const co = require('co')
|
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('pool ending', () => {
|
|
||||||
it('ends without being used', (done) => {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('ends with a promise', () => {
|
|
||||||
return new Pool().end()
|
|
||||||
})
|
|
||||||
|
|
||||||
it(
|
|
||||||
'ends with clients',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool()
|
|
||||||
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
|
|
||||||
expect(res.rows[0].name).to.equal('brianc')
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'allows client to finish',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool()
|
|
||||||
const query = pool.query('SELECT $1::text as name', ['brianc'])
|
|
||||||
yield pool.end()
|
|
||||||
const res = yield query
|
|
||||||
expect(res.rows[0].name).to.equal('brianc')
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
260
lib/node/node_modules/pg-pool/test/error-handling.js
generated
vendored
260
lib/node/node_modules/pg-pool/test/error-handling.js
generated
vendored
|
|
@ -1,260 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const net = require('net')
|
|
||||||
const co = require('co')
|
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('pool error handling', function () {
|
|
||||||
it('Should complete these queries without dying', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
let errors = 0
|
|
||||||
let shouldGet = 0
|
|
||||||
function runErrorQuery() {
|
|
||||||
shouldGet++
|
|
||||||
return new Promise(function (resolve, reject) {
|
|
||||||
pool
|
|
||||||
.query("SELECT 'asd'+1 ")
|
|
||||||
.then(function (res) {
|
|
||||||
reject(res) // this should always error
|
|
||||||
})
|
|
||||||
.catch(function (err) {
|
|
||||||
errors++
|
|
||||||
resolve(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
const ps = []
|
|
||||||
for (let i = 0; i < 5; i++) {
|
|
||||||
ps.push(runErrorQuery())
|
|
||||||
}
|
|
||||||
Promise.all(ps).then(function () {
|
|
||||||
expect(shouldGet).to.eql(errors)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('Catches errors in client.query', async function () {
|
|
||||||
let caught = false
|
|
||||||
const pool = new Pool()
|
|
||||||
try {
|
|
||||||
await pool.query(null)
|
|
||||||
} catch (e) {
|
|
||||||
caught = true
|
|
||||||
}
|
|
||||||
pool.end()
|
|
||||||
expect(caught).to.be(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('calling release more than once', () => {
|
|
||||||
it(
|
|
||||||
'should throw each time',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool()
|
|
||||||
const client = yield pool.connect()
|
|
||||||
client.release()
|
|
||||||
expect(() => client.release()).to.throwError()
|
|
||||||
expect(() => client.release()).to.throwError()
|
|
||||||
return yield pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it('should throw each time with callbacks', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
|
|
||||||
pool.connect(function (err, client, clientDone) {
|
|
||||||
expect(err).not.to.be.an(Error)
|
|
||||||
clientDone()
|
|
||||||
|
|
||||||
expect(() => clientDone()).to.throwError()
|
|
||||||
expect(() => clientDone()).to.throwError()
|
|
||||||
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('using an ended pool', () => {
|
|
||||||
it('rejects all additional promises', (done) => {
|
|
||||||
const pool = new Pool()
|
|
||||||
const promises = []
|
|
||||||
pool.end().then(() => {
|
|
||||||
const squash = (promise) => promise.catch((e) => 'okay!')
|
|
||||||
promises.push(squash(pool.connect()))
|
|
||||||
promises.push(squash(pool.query('SELECT NOW()')))
|
|
||||||
promises.push(squash(pool.end()))
|
|
||||||
Promise.all(promises).then((res) => {
|
|
||||||
expect(res).to.eql(['okay!', 'okay!', 'okay!'])
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('returns an error on all additional callbacks', (done) => {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.end(() => {
|
|
||||||
pool.query('SELECT *', (err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
pool.connect((err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
pool.end((err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('error from idle client', () => {
|
|
||||||
it(
|
|
||||||
'removes client from pool',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool()
|
|
||||||
const client = yield pool.connect()
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
client.release()
|
|
||||||
yield new Promise((resolve, reject) => {
|
|
||||||
process.nextTick(() => {
|
|
||||||
let poolError
|
|
||||||
pool.once('error', (err) => {
|
|
||||||
poolError = err
|
|
||||||
})
|
|
||||||
|
|
||||||
let clientError
|
|
||||||
client.once('error', (err) => {
|
|
||||||
clientError = err
|
|
||||||
})
|
|
||||||
|
|
||||||
client.emit('error', new Error('expected'))
|
|
||||||
|
|
||||||
expect(clientError.message).to.equal('expected')
|
|
||||||
expect(poolError.message).to.equal('expected')
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
pool.end().then(resolve, reject)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('error from in-use client', () => {
|
|
||||||
it(
|
|
||||||
'keeps the client in the pool',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool()
|
|
||||||
const client = yield pool.connect()
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
|
|
||||||
yield new Promise((resolve, reject) => {
|
|
||||||
process.nextTick(() => {
|
|
||||||
let poolError
|
|
||||||
pool.once('error', (err) => {
|
|
||||||
poolError = err
|
|
||||||
})
|
|
||||||
|
|
||||||
let clientError
|
|
||||||
client.once('error', (err) => {
|
|
||||||
clientError = err
|
|
||||||
})
|
|
||||||
|
|
||||||
client.emit('error', new Error('expected'))
|
|
||||||
|
|
||||||
expect(clientError.message).to.equal('expected')
|
|
||||||
expect(poolError).not.to.be.ok()
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
client.release()
|
|
||||||
pool.end().then(resolve, reject)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('passing a function to pool.query', () => {
|
|
||||||
it('calls back with error', (done) => {
|
|
||||||
const pool = new Pool()
|
|
||||||
console.log('passing fn to query')
|
|
||||||
pool.query((err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('pool with lots of errors', () => {
|
|
||||||
it(
|
|
||||||
'continues to work and provide new clients',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ max: 1 })
|
|
||||||
const errors = []
|
|
||||||
for (var i = 0; i < 20; i++) {
|
|
||||||
try {
|
|
||||||
yield pool.query('invalid sql')
|
|
||||||
} catch (err) {
|
|
||||||
errors.push(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
expect(errors).to.have.length(20)
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
expect(pool.query).to.be.a(Function)
|
|
||||||
const res = yield pool.query('SELECT $1::text as name', ['brianc'])
|
|
||||||
expect(res.rows).to.have.length(1)
|
|
||||||
expect(res.rows[0].name).to.equal('brianc')
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should continue with queued items after a connection failure', (done) => {
|
|
||||||
const closeServer = net
|
|
||||||
.createServer((socket) => {
|
|
||||||
socket.destroy()
|
|
||||||
})
|
|
||||||
.unref()
|
|
||||||
|
|
||||||
closeServer.listen(() => {
|
|
||||||
const pool = new Pool({ max: 1, port: closeServer.address().port, host: 'localhost' })
|
|
||||||
pool.connect((err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
if (err.code) {
|
|
||||||
expect(err.code).to.be('ECONNRESET')
|
|
||||||
}
|
|
||||||
})
|
|
||||||
pool.connect((err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
if (err.code) {
|
|
||||||
expect(err.code).to.be('ECONNRESET')
|
|
||||||
}
|
|
||||||
closeServer.close(() => {
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('handles post-checkout client failures in pool.query', (done) => {
|
|
||||||
const pool = new Pool({ max: 1 })
|
|
||||||
pool.on('error', () => {
|
|
||||||
// We double close the connection in this test, prevent exception caused by that
|
|
||||||
})
|
|
||||||
pool.query('SELECT pg_sleep(5)', [], (err) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
pool._clients[0].end()
|
|
||||||
}, 1000)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
124
lib/node/node_modules/pg-pool/test/events.js
generated
vendored
124
lib/node/node_modules/pg-pool/test/events.js
generated
vendored
|
|
@ -1,124 +0,0 @@
|
||||||
'use strict'
|
|
||||||
|
|
||||||
const expect = require('expect.js')
|
|
||||||
const EventEmitter = require('events').EventEmitter
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('events', function () {
|
|
||||||
it('emits connect before callback', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
let emittedClient = false
|
|
||||||
pool.on('connect', function (client) {
|
|
||||||
emittedClient = client
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
if (err) return done(err)
|
|
||||||
release()
|
|
||||||
pool.end()
|
|
||||||
expect(client).to.be(emittedClient)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits "connect" only with a successful connection', function () {
|
|
||||||
const pool = new Pool({
|
|
||||||
// This client will always fail to connect
|
|
||||||
Client: mockClient({
|
|
||||||
connect: function (cb) {
|
|
||||||
process.nextTick(() => {
|
|
||||||
cb(new Error('bad news'))
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
})
|
|
||||||
pool.on('connect', function () {
|
|
||||||
throw new Error('should never get here')
|
|
||||||
})
|
|
||||||
return pool.connect().catch((e) => expect(e.message).to.equal('bad news'))
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits acquire every time a client is acquired', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
let acquireCount = 0
|
|
||||||
pool.on('acquire', function (client) {
|
|
||||||
expect(client).to.be.ok()
|
|
||||||
acquireCount++
|
|
||||||
})
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
if (err) return done(err)
|
|
||||||
release()
|
|
||||||
})
|
|
||||||
pool.query('SELECT now()')
|
|
||||||
}
|
|
||||||
setTimeout(function () {
|
|
||||||
expect(acquireCount).to.be(20)
|
|
||||||
pool.end(done)
|
|
||||||
}, 100)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits release every time a client is released', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
let releaseCount = 0
|
|
||||||
pool.on('release', function (err, client) {
|
|
||||||
expect(err instanceof Error).not.to.be(true)
|
|
||||||
expect(client).to.be.ok()
|
|
||||||
releaseCount++
|
|
||||||
})
|
|
||||||
const promises = []
|
|
||||||
for (let i = 0; i < 10; i++) {
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
if (err) return done(err)
|
|
||||||
release()
|
|
||||||
})
|
|
||||||
promises.push(pool.query('SELECT now()'))
|
|
||||||
}
|
|
||||||
Promise.all(promises).then(() => {
|
|
||||||
pool.end(() => {
|
|
||||||
expect(releaseCount).to.be(20)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits release with an error if client is released due to an error', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
expect(err).to.equal(undefined)
|
|
||||||
const releaseError = new Error('problem')
|
|
||||||
pool.once('release', function (err, errClient) {
|
|
||||||
expect(err).to.equal(releaseError)
|
|
||||||
expect(errClient).to.equal(client)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
release(releaseError)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('emits error and client if an idle client in the pool hits an error', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.connect(function (err, client) {
|
|
||||||
expect(err).to.equal(undefined)
|
|
||||||
client.release()
|
|
||||||
setImmediate(function () {
|
|
||||||
client.emit('error', new Error('problem'))
|
|
||||||
})
|
|
||||||
pool.once('error', function (err, errClient) {
|
|
||||||
expect(err.message).to.equal('problem')
|
|
||||||
expect(errClient).to.equal(client)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
function mockClient(methods) {
|
|
||||||
return function () {
|
|
||||||
const client = new EventEmitter()
|
|
||||||
Object.assign(client, methods)
|
|
||||||
return client
|
|
||||||
}
|
|
||||||
}
|
|
||||||
20
lib/node/node_modules/pg-pool/test/idle-timeout-exit.js
generated
vendored
20
lib/node/node_modules/pg-pool/test/idle-timeout-exit.js
generated
vendored
|
|
@ -1,20 +0,0 @@
|
||||||
// This test is meant to be spawned from idle-timeout.js
|
|
||||||
if (module === require.main) {
|
|
||||||
const allowExitOnIdle = process.env.ALLOW_EXIT_ON_IDLE === '1'
|
|
||||||
const Pool = require('../index')
|
|
||||||
|
|
||||||
const pool = new Pool({
|
|
||||||
maxLifetimeSeconds: 2,
|
|
||||||
idleTimeoutMillis: 200,
|
|
||||||
...(allowExitOnIdle ? { allowExitOnIdle: true } : {}),
|
|
||||||
})
|
|
||||||
pool.query('SELECT NOW()', (err, res) => console.log('completed first'))
|
|
||||||
pool.on('remove', () => {
|
|
||||||
console.log('removed')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
pool.query('SELECT * from generate_series(0, 1000)', (err, res) => console.log('completed second'))
|
|
||||||
}, 50)
|
|
||||||
}
|
|
||||||
118
lib/node/node_modules/pg-pool/test/idle-timeout.js
generated
vendored
118
lib/node/node_modules/pg-pool/test/idle-timeout.js
generated
vendored
|
|
@ -1,118 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const co = require('co')
|
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
const { fork } = require('child_process')
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
const wait = (time) => new Promise((resolve) => setTimeout(resolve, time))
|
|
||||||
|
|
||||||
describe('idle timeout', () => {
|
|
||||||
it('should timeout and remove the client', (done) => {
|
|
||||||
const pool = new Pool({ idleTimeoutMillis: 10 })
|
|
||||||
pool.query('SELECT NOW()')
|
|
||||||
pool.on('remove', () => {
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it(
|
|
||||||
'times out and removes clients when others are also removed',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ idleTimeoutMillis: 10 })
|
|
||||||
const clientA = yield pool.connect()
|
|
||||||
const clientB = yield pool.connect()
|
|
||||||
clientA.release()
|
|
||||||
clientB.release(new Error())
|
|
||||||
|
|
||||||
const removal = new Promise((resolve) => {
|
|
||||||
pool.on('remove', () => {
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
resolve()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
const timeout = wait(100).then(() => Promise.reject(new Error('Idle timeout failed to occur')))
|
|
||||||
|
|
||||||
try {
|
|
||||||
yield Promise.race([removal, timeout])
|
|
||||||
} finally {
|
|
||||||
pool.end()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'can remove idle clients and recreate them',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ idleTimeoutMillis: 1 })
|
|
||||||
const results = []
|
|
||||||
for (var i = 0; i < 20; i++) {
|
|
||||||
let query = pool.query('SELECT NOW()')
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
results.push(yield query)
|
|
||||||
yield wait(2)
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
}
|
|
||||||
expect(results).to.have.length(20)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'does not time out clients which are used',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ idleTimeoutMillis: 1 })
|
|
||||||
const results = []
|
|
||||||
for (var i = 0; i < 20; i++) {
|
|
||||||
let client = yield pool.connect()
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
yield wait(10)
|
|
||||||
results.push(yield client.query('SELECT NOW()'))
|
|
||||||
client.release()
|
|
||||||
expect(pool.idleCount).to.equal(1)
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
}
|
|
||||||
expect(results).to.have.length(20)
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it('unrefs the connections and timeouts so the program can exit when idle when the allowExitOnIdle option is set', function (done) {
|
|
||||||
const child = fork(path.join(__dirname, 'idle-timeout-exit.js'), [], {
|
|
||||||
silent: true,
|
|
||||||
env: { ...process.env, ALLOW_EXIT_ON_IDLE: '1' },
|
|
||||||
})
|
|
||||||
let result = ''
|
|
||||||
child.stdout.setEncoding('utf8')
|
|
||||||
child.stdout.on('data', (chunk) => (result += chunk))
|
|
||||||
child.on('error', (err) => done(err))
|
|
||||||
child.on('close', () => {
|
|
||||||
expect(result).to.equal('completed first\ncompleted second\n')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('keeps old behavior when allowExitOnIdle option is not set', function (done) {
|
|
||||||
const child = fork(path.join(__dirname, 'idle-timeout-exit.js'), [], {
|
|
||||||
silent: true,
|
|
||||||
})
|
|
||||||
let result = ''
|
|
||||||
child.stdout.setEncoding('utf8')
|
|
||||||
child.stdout.on('data', (chunk) => (result += chunk))
|
|
||||||
child.on('error', (err) => done(err))
|
|
||||||
child.on('close', () => {
|
|
||||||
expect(result).to.equal('completed first\ncompleted second\nremoved\n')
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
226
lib/node/node_modules/pg-pool/test/index.js
generated
vendored
226
lib/node/node_modules/pg-pool/test/index.js
generated
vendored
|
|
@ -1,226 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const expect = require('expect.js')
|
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('pool', function () {
|
|
||||||
describe('with callbacks', function () {
|
|
||||||
it('works totally unconfigured', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
if (err) return done(err)
|
|
||||||
client.query('SELECT NOW()', function (err, res) {
|
|
||||||
release()
|
|
||||||
if (err) return done(err)
|
|
||||||
expect(res.rows).to.have.length(1)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('passes props to clients', function (done) {
|
|
||||||
const pool = new Pool({ binary: true })
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
release()
|
|
||||||
if (err) return done(err)
|
|
||||||
expect(client.binary).to.eql(true)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can run a query with a callback without parameters', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.query('SELECT 1 as num', function (err, res) {
|
|
||||||
expect(res.rows[0]).to.eql({ num: 1 })
|
|
||||||
pool.end(function () {
|
|
||||||
done(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('can run a query with a callback', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
|
|
||||||
expect(res.rows[0]).to.eql({ name: 'brianc' })
|
|
||||||
pool.end(function () {
|
|
||||||
done(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('passes connection errors to callback', function (done) {
|
|
||||||
const pool = new Pool({ port: 53922 })
|
|
||||||
pool.query('SELECT $1::text as name', ['brianc'], function (err, res) {
|
|
||||||
expect(res).to.be(undefined)
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
// a connection error should not polute the pool with a dead client
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
pool.end(function (err) {
|
|
||||||
done(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not pass client to error callback', function (done) {
|
|
||||||
const pool = new Pool({ port: 58242 })
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(client).to.be(undefined)
|
|
||||||
expect(release).to.be.a(Function)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('removes client if it errors in background', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
release()
|
|
||||||
if (err) return done(err)
|
|
||||||
client.testString = 'foo'
|
|
||||||
setTimeout(function () {
|
|
||||||
client.emit('error', new Error('on purpose'))
|
|
||||||
}, 10)
|
|
||||||
})
|
|
||||||
pool.on('error', function (err) {
|
|
||||||
expect(err.message).to.be('on purpose')
|
|
||||||
expect(err.client).to.not.be(undefined)
|
|
||||||
expect(err.client.testString).to.be('foo')
|
|
||||||
err.client.connection.stream.on('end', function () {
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not change given options', function (done) {
|
|
||||||
const options = { max: 10 }
|
|
||||||
const pool = new Pool(options)
|
|
||||||
pool.connect(function (err, client, release) {
|
|
||||||
release()
|
|
||||||
if (err) return done(err)
|
|
||||||
expect(options).to.eql({ max: 10 })
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not create promises when connecting', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
const returnValue = pool.connect(function (err, client, release) {
|
|
||||||
release()
|
|
||||||
if (err) return done(err)
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
expect(returnValue).to.be(undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not create promises when querying', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
const returnValue = pool.query('SELECT 1 as num', function (err) {
|
|
||||||
pool.end(function () {
|
|
||||||
done(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
expect(returnValue).to.be(undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('does not create promises when ending', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
const returnValue = pool.end(done)
|
|
||||||
expect(returnValue).to.be(undefined)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('never calls callback syncronously', function (done) {
|
|
||||||
const pool = new Pool()
|
|
||||||
pool.connect((err, client) => {
|
|
||||||
if (err) throw err
|
|
||||||
client.release()
|
|
||||||
setImmediate(() => {
|
|
||||||
let called = false
|
|
||||||
pool.connect((err, client) => {
|
|
||||||
if (err) throw err
|
|
||||||
called = true
|
|
||||||
client.release()
|
|
||||||
setImmediate(() => {
|
|
||||||
pool.end(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
expect(called).to.equal(false)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('with promises', function () {
|
|
||||||
it('connects, queries, and disconnects', function () {
|
|
||||||
const pool = new Pool()
|
|
||||||
return pool.connect().then(function (client) {
|
|
||||||
return client.query('select $1::text as name', ['hi']).then(function (res) {
|
|
||||||
expect(res.rows).to.eql([{ name: 'hi' }])
|
|
||||||
client.release()
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('executes a query directly', () => {
|
|
||||||
const pool = new Pool()
|
|
||||||
return pool.query('SELECT $1::text as name', ['hi']).then((res) => {
|
|
||||||
expect(res.rows).to.have.length(1)
|
|
||||||
expect(res.rows[0].name).to.equal('hi')
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('properly pools clients', function () {
|
|
||||||
const pool = new Pool({ poolSize: 9 })
|
|
||||||
const promises = _.times(30, function () {
|
|
||||||
return pool.connect().then(function (client) {
|
|
||||||
return client.query('select $1::text as name', ['hi']).then(function (res) {
|
|
||||||
client.release()
|
|
||||||
return res
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return Promise.all(promises).then(function (res) {
|
|
||||||
expect(res).to.have.length(30)
|
|
||||||
expect(pool.totalCount).to.be(9)
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('supports just running queries', function () {
|
|
||||||
const pool = new Pool({ poolSize: 9 })
|
|
||||||
const text = 'select $1::text as name'
|
|
||||||
const values = ['hi']
|
|
||||||
const query = { text: text, values: values }
|
|
||||||
const promises = _.times(30, () => pool.query(query))
|
|
||||||
return Promise.all(promises).then(function (queries) {
|
|
||||||
expect(queries).to.have.length(30)
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('recovers from query errors', function () {
|
|
||||||
const pool = new Pool()
|
|
||||||
|
|
||||||
const errors = []
|
|
||||||
const promises = _.times(30, () => {
|
|
||||||
return pool.query('SELECT asldkfjasldkf').catch(function (e) {
|
|
||||||
errors.push(e)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
return Promise.all(promises).then(() => {
|
|
||||||
expect(errors).to.have.length(30)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
expect(pool.idleCount).to.equal(0)
|
|
||||||
return pool.query('SELECT $1::text as name', ['hi']).then(function (res) {
|
|
||||||
expect(res.rows).to.eql([{ name: 'hi' }])
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
48
lib/node/node_modules/pg-pool/test/lifetime-timeout.js
generated
vendored
48
lib/node/node_modules/pg-pool/test/lifetime-timeout.js
generated
vendored
|
|
@ -1,48 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const co = require('co')
|
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
const path = require('path')
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('lifetime timeout', () => {
|
|
||||||
it('connection lifetime should expire and remove the client', (done) => {
|
|
||||||
const pool = new Pool({ maxLifetimeSeconds: 1 })
|
|
||||||
pool.query('SELECT NOW()')
|
|
||||||
pool.on('remove', () => {
|
|
||||||
console.log('expired while idle - on-remove event')
|
|
||||||
expect(pool.expiredCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it('connection lifetime should expire and remove the client after the client is done working', (done) => {
|
|
||||||
const pool = new Pool({ maxLifetimeSeconds: 1 })
|
|
||||||
pool.query('SELECT pg_sleep(1.4)')
|
|
||||||
pool.on('remove', () => {
|
|
||||||
console.log('expired while busy - on-remove event')
|
|
||||||
expect(pool.expiredCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
it(
|
|
||||||
'can remove expired clients and recreate them',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ maxLifetimeSeconds: 1 })
|
|
||||||
let query = pool.query('SELECT pg_sleep(1.4)')
|
|
||||||
expect(pool.expiredCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
yield query
|
|
||||||
yield new Promise((resolve) => setTimeout(resolve, 100))
|
|
||||||
expect(pool.expiredCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(0)
|
|
||||||
yield pool.query('SELECT NOW()')
|
|
||||||
expect(pool.expiredCount).to.equal(0)
|
|
||||||
expect(pool.totalCount).to.equal(1)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
20
lib/node/node_modules/pg-pool/test/logging.js
generated
vendored
20
lib/node/node_modules/pg-pool/test/logging.js
generated
vendored
|
|
@ -1,20 +0,0 @@
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('logging', function () {
|
|
||||||
it('logs to supplied log function if given', function () {
|
|
||||||
const messages = []
|
|
||||||
const log = function (msg) {
|
|
||||||
messages.push(msg)
|
|
||||||
}
|
|
||||||
const pool = new Pool({ log: log })
|
|
||||||
return pool.query('SELECT NOW()').then(function () {
|
|
||||||
expect(messages.length).to.be.greaterThan(0)
|
|
||||||
return pool.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
98
lib/node/node_modules/pg-pool/test/max-uses.js
generated
vendored
98
lib/node/node_modules/pg-pool/test/max-uses.js
generated
vendored
|
|
@ -1,98 +0,0 @@
|
||||||
const expect = require('expect.js')
|
|
||||||
const co = require('co')
|
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('maxUses', () => {
|
|
||||||
it(
|
|
||||||
'can create a single client and use it once',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ maxUses: 2 })
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
const client = yield pool.connect()
|
|
||||||
const res = yield client.query('SELECT $1::text as name', ['hi'])
|
|
||||||
expect(res.rows[0].name).to.equal('hi')
|
|
||||||
client.release()
|
|
||||||
pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'getting a connection a second time returns the same connection and releasing it also closes it',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ maxUses: 2 })
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
const client = yield pool.connect()
|
|
||||||
client.release()
|
|
||||||
const client2 = yield pool.connect()
|
|
||||||
expect(client).to.equal(client2)
|
|
||||||
expect(client2._ending).to.equal(false)
|
|
||||||
client2.release()
|
|
||||||
expect(client2._ending).to.equal(true)
|
|
||||||
return yield pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'getting a connection a third time returns a new connection',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ maxUses: 2 })
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
const client = yield pool.connect()
|
|
||||||
client.release()
|
|
||||||
const client2 = yield pool.connect()
|
|
||||||
expect(client).to.equal(client2)
|
|
||||||
client2.release()
|
|
||||||
const client3 = yield pool.connect()
|
|
||||||
expect(client3).not.to.equal(client2)
|
|
||||||
client3.release()
|
|
||||||
return yield pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'getting a connection from a pending request gets a fresh client when the released candidate is expended',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ max: 1, maxUses: 2 })
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
const client1 = yield pool.connect()
|
|
||||||
pool.connect().then((client2) => {
|
|
||||||
expect(client2).to.equal(client1)
|
|
||||||
expect(pool.waitingCount).to.equal(1)
|
|
||||||
// Releasing the client this time should also expend it since maxUses is 2, causing client3 to be a fresh client
|
|
||||||
client2.release()
|
|
||||||
})
|
|
||||||
const client3Promise = pool.connect().then((client3) => {
|
|
||||||
// client3 should be a fresh client since client2's release caused the first client to be expended
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
expect(client3).not.to.equal(client1)
|
|
||||||
return client3.release()
|
|
||||||
})
|
|
||||||
// There should be two pending requests since we have 3 connect requests but a max size of 1
|
|
||||||
expect(pool.waitingCount).to.equal(2)
|
|
||||||
// Releasing the client should not yet expend it since maxUses is 2
|
|
||||||
client1.release()
|
|
||||||
yield client3Promise
|
|
||||||
return yield pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'logs when removing an expended client',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const messages = []
|
|
||||||
const log = function (msg) {
|
|
||||||
messages.push(msg)
|
|
||||||
}
|
|
||||||
const pool = new Pool({ maxUses: 1, log })
|
|
||||||
const client = yield pool.connect()
|
|
||||||
client.release()
|
|
||||||
expect(messages).to.contain('remove expended client')
|
|
||||||
return yield pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
54
lib/node/node_modules/pg-pool/test/releasing-clients.js
generated
vendored
54
lib/node/node_modules/pg-pool/test/releasing-clients.js
generated
vendored
|
|
@ -1,54 +0,0 @@
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
const expect = require('expect.js')
|
|
||||||
const net = require('net')
|
|
||||||
|
|
||||||
describe('releasing clients', () => {
|
|
||||||
it('removes a client which cannot be queried', async () => {
|
|
||||||
// make a pool w/ only 1 client
|
|
||||||
const pool = new Pool({ max: 1 })
|
|
||||||
expect(pool.totalCount).to.eql(0)
|
|
||||||
const client = await pool.connect()
|
|
||||||
expect(pool.totalCount).to.eql(1)
|
|
||||||
expect(pool.idleCount).to.eql(0)
|
|
||||||
// reach into the client and sever its connection
|
|
||||||
client.connection.end()
|
|
||||||
|
|
||||||
// wait for the client to error out
|
|
||||||
const err = await new Promise((resolve) => client.once('error', resolve))
|
|
||||||
expect(err).to.be.ok()
|
|
||||||
expect(pool.totalCount).to.eql(1)
|
|
||||||
expect(pool.idleCount).to.eql(0)
|
|
||||||
|
|
||||||
// try to return it to the pool - this removes it because its broken
|
|
||||||
client.release()
|
|
||||||
expect(pool.totalCount).to.eql(0)
|
|
||||||
expect(pool.idleCount).to.eql(0)
|
|
||||||
|
|
||||||
// make sure pool still works
|
|
||||||
const { rows } = await pool.query('SELECT NOW()')
|
|
||||||
expect(rows).to.have.length(1)
|
|
||||||
await pool.end()
|
|
||||||
})
|
|
||||||
|
|
||||||
it('removes a client which is ending', async () => {
|
|
||||||
// make a pool w/ only 1 client
|
|
||||||
const pool = new Pool({ max: 1 })
|
|
||||||
expect(pool.totalCount).to.eql(0)
|
|
||||||
const client = await pool.connect()
|
|
||||||
expect(pool.totalCount).to.eql(1)
|
|
||||||
expect(pool.idleCount).to.eql(0)
|
|
||||||
// end the client gracefully (but you shouldn't do this with pooled clients)
|
|
||||||
client.end()
|
|
||||||
|
|
||||||
// try to return it to the pool
|
|
||||||
client.release()
|
|
||||||
expect(pool.totalCount).to.eql(0)
|
|
||||||
expect(pool.idleCount).to.eql(0)
|
|
||||||
|
|
||||||
// make sure pool still works
|
|
||||||
const { rows } = await pool.query('SELECT NOW()')
|
|
||||||
expect(rows).to.have.length(1)
|
|
||||||
await pool.end()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
10
lib/node/node_modules/pg-pool/test/setup.js
generated
vendored
10
lib/node/node_modules/pg-pool/test/setup.js
generated
vendored
|
|
@ -1,10 +0,0 @@
|
||||||
const crash = (reason) => {
|
|
||||||
process.on(reason, (err) => {
|
|
||||||
console.error(reason, err.stack)
|
|
||||||
process.exit(-1)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
crash('unhandledRejection')
|
|
||||||
crash('uncaughtError')
|
|
||||||
crash('warning')
|
|
||||||
58
lib/node/node_modules/pg-pool/test/sizing.js
generated
vendored
58
lib/node/node_modules/pg-pool/test/sizing.js
generated
vendored
|
|
@ -1,58 +0,0 @@
|
||||||
const expect = require('expect.js')
|
|
||||||
const co = require('co')
|
|
||||||
const _ = require('lodash')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('pool size of 1', () => {
|
|
||||||
it(
|
|
||||||
'can create a single client and use it once',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ max: 1 })
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
const client = yield pool.connect()
|
|
||||||
const res = yield client.query('SELECT $1::text as name', ['hi'])
|
|
||||||
expect(res.rows[0].name).to.equal('hi')
|
|
||||||
client.release()
|
|
||||||
pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'can create a single client and use it multiple times',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ max: 1 })
|
|
||||||
expect(pool.waitingCount).to.equal(0)
|
|
||||||
const client = yield pool.connect()
|
|
||||||
const wait = pool.connect()
|
|
||||||
expect(pool.waitingCount).to.equal(1)
|
|
||||||
client.release()
|
|
||||||
const client2 = yield wait
|
|
||||||
expect(client).to.equal(client2)
|
|
||||||
client2.release()
|
|
||||||
return yield pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
it(
|
|
||||||
'can only send 1 query at a time',
|
|
||||||
co.wrap(function* () {
|
|
||||||
const pool = new Pool({ max: 1 })
|
|
||||||
|
|
||||||
// the query text column name changed in PostgreSQL 9.2
|
|
||||||
const versionResult = yield pool.query('SHOW server_version_num')
|
|
||||||
const version = parseInt(versionResult.rows[0].server_version_num, 10)
|
|
||||||
const queryColumn = version < 90200 ? 'current_query' : 'query'
|
|
||||||
|
|
||||||
const queryText = 'SELECT COUNT(*) as counts FROM pg_stat_activity WHERE ' + queryColumn + ' = $1'
|
|
||||||
const queries = _.times(20, () => pool.query(queryText, [queryText]))
|
|
||||||
const results = yield Promise.all(queries)
|
|
||||||
const counts = results.map((res) => parseInt(res.rows[0].counts, 10))
|
|
||||||
expect(counts).to.eql(_.times(20, (i) => 1))
|
|
||||||
return yield pool.end()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
})
|
|
||||||
19
lib/node/node_modules/pg-pool/test/submittable.js
generated
vendored
19
lib/node/node_modules/pg-pool/test/submittable.js
generated
vendored
|
|
@ -1,19 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const Cursor = require('pg-cursor')
|
|
||||||
const expect = require('expect.js')
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('submittle', () => {
|
|
||||||
it('is returned from the query method', false, (done) => {
|
|
||||||
const pool = new Pool()
|
|
||||||
const cursor = pool.query(new Cursor('SELECT * from generate_series(0, 1000)'))
|
|
||||||
cursor.read((err, rows) => {
|
|
||||||
expect(err).to.be(undefined)
|
|
||||||
expect(!!rows).to.be.ok()
|
|
||||||
cursor.close(done)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
0
lib/node/node_modules/pg-pool/test/timeout.js
generated
vendored
0
lib/node/node_modules/pg-pool/test/timeout.js
generated
vendored
24
lib/node/node_modules/pg-pool/test/verify.js
generated
vendored
24
lib/node/node_modules/pg-pool/test/verify.js
generated
vendored
|
|
@ -1,24 +0,0 @@
|
||||||
'use strict'
|
|
||||||
const expect = require('expect.js')
|
|
||||||
|
|
||||||
const describe = require('mocha').describe
|
|
||||||
const it = require('mocha').it
|
|
||||||
|
|
||||||
const Pool = require('../')
|
|
||||||
|
|
||||||
describe('verify', () => {
|
|
||||||
it('verifies a client with a callback', (done) => {
|
|
||||||
const pool = new Pool({
|
|
||||||
verify: (client, cb) => {
|
|
||||||
cb(new Error('nope'))
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
pool.connect((err, client) => {
|
|
||||||
expect(err).to.be.an(Error)
|
|
||||||
expect(err.message).to.be('nope')
|
|
||||||
pool.end()
|
|
||||||
done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
21
lib/node/node_modules/pg-protocol/LICENSE
generated
vendored
21
lib/node/node_modules/pg-protocol/LICENSE
generated
vendored
|
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2010 - 2021 Brian Carlson
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
3
lib/node/node_modules/pg-protocol/README.md
generated
vendored
3
lib/node/node_modules/pg-protocol/README.md
generated
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
# pg-protocol
|
|
||||||
|
|
||||||
Low level postgres wire protocol parser and serializer written in Typescript. Used by node-postgres. Needs more documentation. :smile:
|
|
||||||
1
lib/node/node_modules/pg-protocol/dist/b.d.ts
generated
vendored
1
lib/node/node_modules/pg-protocol/dist/b.d.ts
generated
vendored
|
|
@ -1 +0,0 @@
|
||||||
export {};
|
|
||||||
25
lib/node/node_modules/pg-protocol/dist/b.js
generated
vendored
25
lib/node/node_modules/pg-protocol/dist/b.js
generated
vendored
|
|
@ -1,25 +0,0 @@
|
||||||
"use strict";
|
|
||||||
// file for microbenchmarking
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
const buffer_writer_1 = require("./buffer-writer");
|
|
||||||
const buffer_reader_1 = require("./buffer-reader");
|
|
||||||
const LOOPS = 1000;
|
|
||||||
let count = 0;
|
|
||||||
let start = Date.now();
|
|
||||||
const writer = new buffer_writer_1.Writer();
|
|
||||||
const reader = new buffer_reader_1.BufferReader();
|
|
||||||
const buffer = Buffer.from([33, 33, 33, 33, 33, 33, 33, 0]);
|
|
||||||
const run = () => {
|
|
||||||
if (count > LOOPS) {
|
|
||||||
console.log(Date.now() - start);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
count++;
|
|
||||||
for (let i = 0; i < LOOPS; i++) {
|
|
||||||
reader.setBuffer(0, buffer);
|
|
||||||
reader.cstring();
|
|
||||||
}
|
|
||||||
setImmediate(run);
|
|
||||||
};
|
|
||||||
run();
|
|
||||||
//# sourceMappingURL=b.js.map
|
|
||||||
1
lib/node/node_modules/pg-protocol/dist/b.js.map
generated
vendored
1
lib/node/node_modules/pg-protocol/dist/b.js.map
generated
vendored
|
|
@ -1 +0,0 @@
|
||||||
{"version":3,"file":"b.js","sourceRoot":"","sources":["../src/b.ts"],"names":[],"mappings":";AAAA,6BAA6B;;AAE7B,mDAAwC;AAExC,mDAA8C;AAE9C,MAAM,KAAK,GAAG,IAAI,CAAA;AAClB,IAAI,KAAK,GAAG,CAAC,CAAA;AACb,IAAI,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;AACtB,MAAM,MAAM,GAAG,IAAI,sBAAM,EAAE,CAAA;AAE3B,MAAM,MAAM,GAAG,IAAI,4BAAY,EAAE,CAAA;AACjC,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,CAAA;AAE3D,MAAM,GAAG,GAAG,GAAG,EAAE;IACf,IAAI,KAAK,GAAG,KAAK,EAAE;QACjB,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,CAAA;QAC/B,OAAM;KACP;IACD,KAAK,EAAE,CAAA;IACP,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE;QAC9B,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,CAAC,CAAA;QAC3B,MAAM,CAAC,OAAO,EAAE,CAAA;KACjB;IACD,YAAY,CAAC,GAAG,CAAC,CAAA;AACnB,CAAC,CAAA;AAED,GAAG,EAAE,CAAA"}
|
|
||||||
14
lib/node/node_modules/pg-protocol/dist/buffer-reader.d.ts
generated
vendored
14
lib/node/node_modules/pg-protocol/dist/buffer-reader.d.ts
generated
vendored
|
|
@ -1,14 +0,0 @@
|
||||||
/// <reference types="node" />
|
|
||||||
export declare class BufferReader {
|
|
||||||
private offset;
|
|
||||||
private buffer;
|
|
||||||
private encoding;
|
|
||||||
constructor(offset?: number);
|
|
||||||
setBuffer(offset: number, buffer: Buffer): void;
|
|
||||||
int16(): number;
|
|
||||||
byte(): number;
|
|
||||||
int32(): number;
|
|
||||||
string(length: number): string;
|
|
||||||
cstring(): string;
|
|
||||||
bytes(length: number): Buffer;
|
|
||||||
}
|
|
||||||
50
lib/node/node_modules/pg-protocol/dist/buffer-reader.js
generated
vendored
50
lib/node/node_modules/pg-protocol/dist/buffer-reader.js
generated
vendored
|
|
@ -1,50 +0,0 @@
|
||||||
"use strict";
|
|
||||||
Object.defineProperty(exports, "__esModule", { value: true });
|
|
||||||
exports.BufferReader = void 0;
|
|
||||||
const emptyBuffer = Buffer.allocUnsafe(0);
|
|
||||||
class BufferReader {
|
|
||||||
constructor(offset = 0) {
|
|
||||||
this.offset = offset;
|
|
||||||
this.buffer = emptyBuffer;
|
|
||||||
// TODO(bmc): support non-utf8 encoding?
|
|
||||||
this.encoding = 'utf-8';
|
|
||||||
}
|
|
||||||
setBuffer(offset, buffer) {
|
|
||||||
this.offset = offset;
|
|
||||||
this.buffer = buffer;
|
|
||||||
}
|
|
||||||
int16() {
|
|
||||||
const result = this.buffer.readInt16BE(this.offset);
|
|
||||||
this.offset += 2;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
byte() {
|
|
||||||
const result = this.buffer[this.offset];
|
|
||||||
this.offset++;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
int32() {
|
|
||||||
const result = this.buffer.readInt32BE(this.offset);
|
|
||||||
this.offset += 4;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
string(length) {
|
|
||||||
const result = this.buffer.toString(this.encoding, this.offset, this.offset + length);
|
|
||||||
this.offset += length;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
cstring() {
|
|
||||||
const start = this.offset;
|
|
||||||
let end = start;
|
|
||||||
while (this.buffer[end++] !== 0) { }
|
|
||||||
this.offset = end;
|
|
||||||
return this.buffer.toString(this.encoding, start, end - 1);
|
|
||||||
}
|
|
||||||
bytes(length) {
|
|
||||||
const result = this.buffer.slice(this.offset, this.offset + length);
|
|
||||||
this.offset += length;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
exports.BufferReader = BufferReader;
|
|
||||||
//# sourceMappingURL=buffer-reader.js.map
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue