zuzu-js 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +5 -0
- package/README.md +113 -0
- package/bin/zuzu +17 -0
- package/bin/zuzu-build-browser-bundle +57 -0
- package/bin/zuzu-generate-browser-stdlib +584 -0
- package/bin/zuzu-js +23 -0
- package/bin/zuzu-js-compile +152 -0
- package/bin/zuzu-js-electron +19 -0
- package/dist/zuzu-browser-worker.js +45574 -0
- package/dist/zuzu-browser.js +45362 -0
- package/lib/browser-bundle-entry.js +160 -0
- package/lib/browser-gui-renderer.js +387 -0
- package/lib/browser-runtime.js +167 -0
- package/lib/browser-worker-entry.js +413 -0
- package/lib/browser-ztests/runner.html +103 -0
- package/lib/browser-ztests/runner.js +369 -0
- package/lib/cli.js +350 -0
- package/lib/collections.js +367 -0
- package/lib/compiler.js +303 -0
- package/lib/electron/launcher.js +70 -0
- package/lib/electron/main.js +956 -0
- package/lib/electron/preload.js +80 -0
- package/lib/electron/renderer.html +122 -0
- package/lib/electron/renderer.js +24 -0
- package/lib/execution-metadata.js +18 -0
- package/lib/gui/dom-renderer.js +778 -0
- package/lib/host/browser-host.js +278 -0
- package/lib/host/capabilities.js +47 -0
- package/lib/host/electron-host.js +15 -0
- package/lib/host/node-host.js +74 -0
- package/lib/paths.js +150 -0
- package/lib/runtime-entrypoints.js +60 -0
- package/lib/runtime-helpers.js +886 -0
- package/lib/runtime.js +3529 -0
- package/lib/tap.js +37 -0
- package/lib/transpiler-new/ast.js +23 -0
- package/lib/transpiler-new/codegen.js +2455 -0
- package/lib/transpiler-new/errors.js +28 -0
- package/lib/transpiler-new/index.js +26 -0
- package/lib/transpiler-new/lexer.js +834 -0
- package/lib/transpiler-new/parser.js +2332 -0
- package/lib/transpiler-new/validate-bindings.js +326 -0
- package/lib/transpiler-utils.js +95 -0
- package/lib/transpiler.js +33 -0
- package/lib/zuzu.js +53 -0
- package/modules/javascript.js +193 -0
- package/modules/std/archive.js +603 -0
- package/modules/std/clib.js +338 -0
- package/modules/std/data/csv.js +1331 -0
- package/modules/std/data/json.js +531 -0
- package/modules/std/data/xml.js +441 -0
- package/modules/std/data/yaml.js +256 -0
- package/modules/std/db-worker.js +250 -0
- package/modules/std/db.js +664 -0
- package/modules/std/digest/_hash.js +443 -0
- package/modules/std/digest/md5.js +26 -0
- package/modules/std/digest/sha.js +72 -0
- package/modules/std/eval.js +10 -0
- package/modules/std/gui/objects.js +1519 -0
- package/modules/std/internals.js +571 -0
- package/modules/std/io/socks-worker.js +318 -0
- package/modules/std/io/socks.js +186 -0
- package/modules/std/io.js +475 -0
- package/modules/std/marshal/cbor.js +463 -0
- package/modules/std/marshal/graph.js +1624 -0
- package/modules/std/marshal.js +87 -0
- package/modules/std/math/bignum.js +91 -0
- package/modules/std/math.js +79 -0
- package/modules/std/net/dns.js +306 -0
- package/modules/std/net/http.js +820 -0
- package/modules/std/net/smtp.js +943 -0
- package/modules/std/net/url.js +109 -0
- package/modules/std/proc.js +602 -0
- package/modules/std/secure.js +3724 -0
- package/modules/std/string/base64.js +138 -0
- package/modules/std/string.js +299 -0
- package/modules/std/task.js +914 -0
- package/modules/std/time.js +579 -0
- package/modules/std/tui.js +188 -0
- package/modules/std/worker-thread.js +246 -0
- package/modules/std/worker.js +790 -0
- package/package.json +67 -0
- package/stdlib/modules/javascript.zzm +99 -0
- package/stdlib/modules/perl.zzm +105 -0
- package/stdlib/modules/std/archive.zzm +132 -0
- package/stdlib/modules/std/cache/lru.zzm +174 -0
- package/stdlib/modules/std/clib.zzm +112 -0
- package/stdlib/modules/std/colour.zzm +220 -0
- package/stdlib/modules/std/config.zzm +818 -0
- package/stdlib/modules/std/data/cbor.zzm +497 -0
- package/stdlib/modules/std/data/csv.zzm +285 -0
- package/stdlib/modules/std/data/ini.zzm +472 -0
- package/stdlib/modules/std/data/json/schema/core.zzm +573 -0
- package/stdlib/modules/std/data/json/schema/format.zzm +581 -0
- package/stdlib/modules/std/data/json/schema/model.zzm +255 -0
- package/stdlib/modules/std/data/json/schema/output.zzm +272 -0
- package/stdlib/modules/std/data/json/schema/relative_pointer.zzm +299 -0
- package/stdlib/modules/std/data/json/schema/validation.zzm +1503 -0
- package/stdlib/modules/std/data/json/schema.zzm +306 -0
- package/stdlib/modules/std/data/json.zzm +102 -0
- package/stdlib/modules/std/data/kdl/json.zzm +460 -0
- package/stdlib/modules/std/data/kdl/xml.zzm +387 -0
- package/stdlib/modules/std/data/kdl.zzm +1631 -0
- package/stdlib/modules/std/data/toml.zzm +756 -0
- package/stdlib/modules/std/data/toon.zzm +1017 -0
- package/stdlib/modules/std/data/xml/escape.zzm +156 -0
- package/stdlib/modules/std/data/xml.zzm +276 -0
- package/stdlib/modules/std/data/yaml.zzm +94 -0
- package/stdlib/modules/std/db.zzm +173 -0
- package/stdlib/modules/std/defer.zzm +75 -0
- package/stdlib/modules/std/digest/crc32.zzm +196 -0
- package/stdlib/modules/std/digest/md5.zzm +54 -0
- package/stdlib/modules/std/digest/sha.zzm +83 -0
- package/stdlib/modules/std/dump.zzm +317 -0
- package/stdlib/modules/std/eval.zzm +63 -0
- package/stdlib/modules/std/getopt.zzm +432 -0
- package/stdlib/modules/std/gui/dialogue.zzm +592 -0
- package/stdlib/modules/std/gui/objects.zzm +123 -0
- package/stdlib/modules/std/gui.zzm +1914 -0
- package/stdlib/modules/std/internals.zzm +139 -0
- package/stdlib/modules/std/io/socks.zzm +139 -0
- package/stdlib/modules/std/io.zzm +157 -0
- package/stdlib/modules/std/lingua/en.zzm +347 -0
- package/stdlib/modules/std/log.zzm +169 -0
- package/stdlib/modules/std/mail.zzm +2726 -0
- package/stdlib/modules/std/marshal.zzm +138 -0
- package/stdlib/modules/std/math/bignum.zzm +98 -0
- package/stdlib/modules/std/math/range.zzm +116 -0
- package/stdlib/modules/std/math/roman.zzm +156 -0
- package/stdlib/modules/std/math.zzm +141 -0
- package/stdlib/modules/std/net/dns.zzm +93 -0
- package/stdlib/modules/std/net/http.zzm +278 -0
- package/stdlib/modules/std/net/smtp.zzm +257 -0
- package/stdlib/modules/std/net/url.zzm +69 -0
- package/stdlib/modules/std/path/jsonpointer.zzm +526 -0
- package/stdlib/modules/std/path/kdl.zzm +1003 -0
- package/stdlib/modules/std/path/simple.zzm +520 -0
- package/stdlib/modules/std/path/z/context.zzm +147 -0
- package/stdlib/modules/std/path/z/evaluate.zzm +549 -0
- package/stdlib/modules/std/path/z/functions.zzm +874 -0
- package/stdlib/modules/std/path/z/lexer.zzm +490 -0
- package/stdlib/modules/std/path/z/node.zzm +1455 -0
- package/stdlib/modules/std/path/z/operators.zzm +445 -0
- package/stdlib/modules/std/path/z/parser.zzm +359 -0
- package/stdlib/modules/std/path/z.zzm +403 -0
- package/stdlib/modules/std/path/zz/functions.zzm +828 -0
- package/stdlib/modules/std/path/zz/operators.zzm +1036 -0
- package/stdlib/modules/std/path/zz.zzm +100 -0
- package/stdlib/modules/std/proc.zzm +155 -0
- package/stdlib/modules/std/result.zzm +149 -0
- package/stdlib/modules/std/secure.zzm +606 -0
- package/stdlib/modules/std/string/base64.zzm +66 -0
- package/stdlib/modules/std/string/quoted_printable.zzm +485 -0
- package/stdlib/modules/std/string.zzm +179 -0
- package/stdlib/modules/std/task.zzm +221 -0
- package/stdlib/modules/std/template/z.zzm +531 -0
- package/stdlib/modules/std/template/zz.zzm +62 -0
- package/stdlib/modules/std/time.zzm +188 -0
- package/stdlib/modules/std/tui.zzm +89 -0
- package/stdlib/modules/std/uuid.zzm +223 -0
- package/stdlib/modules/std/web/session.zzm +388 -0
- package/stdlib/modules/std/web/static.zzm +329 -0
- package/stdlib/modules/std/web.zzm +1942 -0
- package/stdlib/modules/std/worker.zzm +202 -0
- package/stdlib/modules/std/zuzuzoo.zzm +3960 -0
- package/stdlib/modules/test/more.zzm +528 -0
- package/stdlib/modules/test/parser.zzm +209 -0
|
@@ -0,0 +1,943 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { Task } = require( '../task' );
|
|
4
|
+
const { BinaryString } = require( '../../../lib/runtime-helpers' );
|
|
5
|
+
const { PairList } = require( '../../../lib/collections' );
|
|
6
|
+
|
|
7
|
+
let runtimePolicy = {};
|
|
8
|
+
|
|
9
|
+
const SENDMAIL_PATHS = [
|
|
10
|
+
'/usr/sbin/sendmail',
|
|
11
|
+
'/usr/lib/sendmail',
|
|
12
|
+
'/sbin/sendmail',
|
|
13
|
+
'/usr/bin/sendmail',
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function nodeModule( name ) {
|
|
17
|
+
if ( typeof require !== 'function' ) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return require( name );
|
|
22
|
+
}
|
|
23
|
+
catch ( _err ) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isBrowser() {
|
|
29
|
+
return runtimePolicy.host_name === 'browser';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function typeName( value ) {
|
|
33
|
+
if ( value == null ) {
|
|
34
|
+
return 'Null';
|
|
35
|
+
}
|
|
36
|
+
if ( value instanceof BinaryString ) {
|
|
37
|
+
return 'BinaryString';
|
|
38
|
+
}
|
|
39
|
+
if ( value instanceof PairList ) {
|
|
40
|
+
return 'PairList';
|
|
41
|
+
}
|
|
42
|
+
if ( Array.isArray( value ) ) {
|
|
43
|
+
return 'Array';
|
|
44
|
+
}
|
|
45
|
+
if ( typeof value === 'string' ) {
|
|
46
|
+
return 'String';
|
|
47
|
+
}
|
|
48
|
+
if ( typeof value === 'boolean' ) {
|
|
49
|
+
return 'Boolean';
|
|
50
|
+
}
|
|
51
|
+
if ( typeof value === 'number' ) {
|
|
52
|
+
return 'Number';
|
|
53
|
+
}
|
|
54
|
+
if ( value.constructor && value.constructor.name ) {
|
|
55
|
+
return value.constructor.name === 'Object' ? 'Dict' : value.constructor.name;
|
|
56
|
+
}
|
|
57
|
+
return typeof value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function asDict( value ) {
|
|
61
|
+
if ( value == null ) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
if ( value instanceof PairList ) {
|
|
65
|
+
const out = {};
|
|
66
|
+
for ( const [ key, item ] of value.list ) {
|
|
67
|
+
out[String( key )] = item;
|
|
68
|
+
}
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
if ( typeof value !== 'object' || Array.isArray( value ) || value instanceof BinaryString ) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
return { ...value };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function boolOption( value, fallback = false ) {
|
|
78
|
+
if ( value == null ) {
|
|
79
|
+
return fallback;
|
|
80
|
+
}
|
|
81
|
+
return Boolean( value );
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function stringArray( value, label ) {
|
|
85
|
+
if ( value == null ) {
|
|
86
|
+
return [];
|
|
87
|
+
}
|
|
88
|
+
if ( !Array.isArray( value ) ) {
|
|
89
|
+
throw new Error( `mail.invalid_address: ${label} expects Array, got ${typeName( value )}` );
|
|
90
|
+
}
|
|
91
|
+
return value.map( (item) => String( item ?? '' ) );
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function validateSendmailArgs( args ) {
|
|
95
|
+
for ( const arg of args ) {
|
|
96
|
+
if (
|
|
97
|
+
arg === '--read-recipients'
|
|
98
|
+
|| ( /^-[^-]/u.test( arg ) && /^-[A-Za-z]*t[A-Za-z]*$/u.test( arg ) )
|
|
99
|
+
) {
|
|
100
|
+
throw new Error(
|
|
101
|
+
'mail.unsupported: sendmail_args must not enable header-derived recipients'
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return args;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function rejectUnsupportedSecurityOptions( config ) {
|
|
109
|
+
const auth = String( config.auth || '' ).toLowerCase();
|
|
110
|
+
if ( auth && ![ 'plain', 'login', 'xoauth2' ].includes( auth ) ) {
|
|
111
|
+
throw new Error( `mail.auth: unsupported SMTP auth mechanism '${auth}'` );
|
|
112
|
+
}
|
|
113
|
+
if (
|
|
114
|
+
( config.username || config.password || auth )
|
|
115
|
+
&& !config.tls
|
|
116
|
+
&& !config.starttls
|
|
117
|
+
&& !config.allow_insecure_auth
|
|
118
|
+
) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
'mail.auth: SMTP authentication without TLS requires allow_insecure_auth: true'
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function normalizeConfig( raw = {} ) {
|
|
126
|
+
const config = { ...raw };
|
|
127
|
+
config.transport = config.transport == null
|
|
128
|
+
? 'smtp'
|
|
129
|
+
: String( config.transport ).toLowerCase();
|
|
130
|
+
if ( config.transport !== 'smtp' && config.transport !== 'sendmail' ) {
|
|
131
|
+
throw new Error( "mail.unsupported: transport must be 'smtp' or 'sendmail'" );
|
|
132
|
+
}
|
|
133
|
+
const submission = boolOption( config.submission, false );
|
|
134
|
+
config.host = config.host == null ? 'localhost' : String( config.host );
|
|
135
|
+
config.port = config.port == null
|
|
136
|
+
? ( submission ? 587 : 25 )
|
|
137
|
+
: Math.trunc( Number( config.port ) );
|
|
138
|
+
config.timeout = Number( config.timeout ?? 30 );
|
|
139
|
+
if ( !Number.isFinite( config.timeout ) || config.timeout <= 0 ) {
|
|
140
|
+
config.timeout = 30;
|
|
141
|
+
}
|
|
142
|
+
config.tls = boolOption( config.tls, false );
|
|
143
|
+
config.starttls = Object.prototype.hasOwnProperty.call( config, 'starttls' )
|
|
144
|
+
? boolOption( config.starttls, false )
|
|
145
|
+
: submission;
|
|
146
|
+
config.tls_verify = boolOption( config.tls_verify, true );
|
|
147
|
+
config.smtputf8 = boolOption( config.smtputf8, false );
|
|
148
|
+
config.allow_insecure_auth = boolOption( config.allow_insecure_auth, false );
|
|
149
|
+
config.reject_partial = boolOption( config.reject_partial, false );
|
|
150
|
+
config.sendmail_path = config.sendmail_path == null
|
|
151
|
+
? null
|
|
152
|
+
: String( config.sendmail_path );
|
|
153
|
+
config.sendmail_args = validateSendmailArgs(
|
|
154
|
+
stringArray( config.sendmail_args, 'sendmail_args' )
|
|
155
|
+
);
|
|
156
|
+
for ( const key of [ 'username', 'password', 'auth', 'tls_server_name' ] ) {
|
|
157
|
+
if ( config[key] != null ) {
|
|
158
|
+
config[key] = String( config[key] );
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return config;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function mergeConfig( base, options ) {
|
|
165
|
+
return normalizeConfig( { ...( base || {} ), ...asDict( options ) } );
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function sendmailPathAvailable() {
|
|
169
|
+
if ( isBrowser() || runtimePolicy.deny_proc ) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const fs = nodeModule( 'node:fs' );
|
|
173
|
+
if ( !fs ) {
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
for ( const candidate of SENDMAIL_PATHS ) {
|
|
177
|
+
try {
|
|
178
|
+
fs.accessSync( candidate, fs.constants.X_OK );
|
|
179
|
+
return candidate;
|
|
180
|
+
}
|
|
181
|
+
catch ( _err ) {}
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function capabilities() {
|
|
187
|
+
const nodeNet = !isBrowser() && !runtimePolicy.deny_net && nodeModule( 'node:net' );
|
|
188
|
+
const nodeTls = !isBrowser() && !runtimePolicy.deny_net && nodeModule( 'node:tls' );
|
|
189
|
+
return {
|
|
190
|
+
smtp: Boolean( nodeNet ),
|
|
191
|
+
sendmail: Boolean( sendmailPathAvailable() ),
|
|
192
|
+
tls: Boolean( nodeTls ),
|
|
193
|
+
starttls: Boolean( nodeTls ),
|
|
194
|
+
auth: nodeTls ? [ 'plain', 'login', 'xoauth2' ] : [],
|
|
195
|
+
async: !isBrowser(),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function validateAddress( value ) {
|
|
200
|
+
const address = String( value ?? '' );
|
|
201
|
+
if ( address === '' ) {
|
|
202
|
+
throw new Error( 'mail.invalid_address: envelope address must not be empty' );
|
|
203
|
+
}
|
|
204
|
+
if ( /[\x00-\x1F\x7F]/u.test( address ) ) {
|
|
205
|
+
throw new Error( 'mail.invalid_address: envelope address contains a control character' );
|
|
206
|
+
}
|
|
207
|
+
if ( /[<>]/u.test( address ) ) {
|
|
208
|
+
throw new Error( 'mail.invalid_address: envelope address must not contain angle brackets' );
|
|
209
|
+
}
|
|
210
|
+
return address;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function recipientList( value ) {
|
|
214
|
+
const recipients = Array.isArray( value ) ? value : [ value ];
|
|
215
|
+
if ( recipients.length === 0 ) {
|
|
216
|
+
throw new Error( 'mail.invalid_address: at least one envelope recipient is required' );
|
|
217
|
+
}
|
|
218
|
+
return recipients.map( validateAddress );
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function validateEnvelopeAscii( config, addresses ) {
|
|
222
|
+
if ( config.smtputf8 ) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
for ( const address of addresses ) {
|
|
226
|
+
if ( /[^\x00-\x7F]/u.test( address ) ) {
|
|
227
|
+
throw new Error(
|
|
228
|
+
'mail.invalid_address: non-ASCII envelope addresses require smtputf8: true'
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function assertPairList( headers ) {
|
|
235
|
+
if ( headers instanceof PairList ) {
|
|
236
|
+
return headers.list;
|
|
237
|
+
}
|
|
238
|
+
throw new Error( `mail.invalid_headers: headers expects PairList, got ${typeName( headers )}` );
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function assertBody( body ) {
|
|
242
|
+
if ( body instanceof BinaryString ) {
|
|
243
|
+
return Buffer.from( body.bytes );
|
|
244
|
+
}
|
|
245
|
+
throw new Error( `TypeException: Mailer.send body expects BinaryString, got ${typeName( body )}` );
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function headerName( name ) {
|
|
249
|
+
const text = String( name ?? '' );
|
|
250
|
+
if ( text === '' ) {
|
|
251
|
+
throw new Error( 'mail.invalid_headers: header name must not be empty' );
|
|
252
|
+
}
|
|
253
|
+
if ( !/^[!-9;-~]+$/u.test( text ) ) {
|
|
254
|
+
throw new Error( `mail.invalid_headers: invalid header name '${text}'` );
|
|
255
|
+
}
|
|
256
|
+
return text;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function headerValueBytes( name, value ) {
|
|
260
|
+
let bytes;
|
|
261
|
+
if ( value instanceof BinaryString ) {
|
|
262
|
+
bytes = Buffer.from( value.bytes );
|
|
263
|
+
}
|
|
264
|
+
else if ( typeof value === 'string' ) {
|
|
265
|
+
bytes = Buffer.from( value, 'utf8' );
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
throw new Error(
|
|
269
|
+
`mail.invalid_headers: header '${name}' expects String or BinaryString, got ${typeName( value )}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
if ( bytes.includes( 13 ) || bytes.includes( 10 ) ) {
|
|
273
|
+
throw new Error( `mail.invalid_headers: header '${name}' value must not contain CR or LF` );
|
|
274
|
+
}
|
|
275
|
+
return bytes;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function serializeMessage( headers, body ) {
|
|
279
|
+
const pairs = assertPairList( headers );
|
|
280
|
+
const chunks = [];
|
|
281
|
+
let messageId = null;
|
|
282
|
+
for ( const pair of pairs ) {
|
|
283
|
+
const name = headerName( pair[0] );
|
|
284
|
+
const value = headerValueBytes( name, pair[1] );
|
|
285
|
+
chunks.push( Buffer.from( `${name}: `, 'ascii' ), value, Buffer.from( '\r\n', 'ascii' ) );
|
|
286
|
+
if ( messageId == null && name.toLowerCase() === 'message-id' ) {
|
|
287
|
+
messageId = value.toString( 'utf8' );
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
chunks.push( Buffer.from( '\r\n', 'ascii' ), assertBody( body ) );
|
|
291
|
+
return {
|
|
292
|
+
message: Buffer.concat( chunks ),
|
|
293
|
+
messageId,
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function dotStuff( message ) {
|
|
298
|
+
const parts = [];
|
|
299
|
+
let start = 0;
|
|
300
|
+
if ( message[0] === 46 ) {
|
|
301
|
+
parts.push( Buffer.from( '.', 'ascii' ) );
|
|
302
|
+
}
|
|
303
|
+
for ( let i = 0; i + 2 < message.length; i++ ) {
|
|
304
|
+
if ( message[i] === 13 && message[i + 1] === 10 && message[i + 2] === 46 ) {
|
|
305
|
+
parts.push( message.subarray( start, i + 2 ), Buffer.from( '.', 'ascii' ) );
|
|
306
|
+
start = i + 2;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
parts.push( message.subarray( start ) );
|
|
310
|
+
return Buffer.concat( parts );
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function mailResult( payload ) {
|
|
314
|
+
return new MailResult( payload );
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function rejectUnsupportedSmtp( config ) {
|
|
318
|
+
rejectUnsupportedSecurityOptions( config );
|
|
319
|
+
if ( runtimePolicy.deny_net || isBrowser() ) {
|
|
320
|
+
throw new Error( 'mail.unsupported: SMTP transport is unsupported in this runtime' );
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function sendmailCommand( config, from, recipients ) {
|
|
325
|
+
if ( runtimePolicy.deny_proc || isBrowser() ) {
|
|
326
|
+
throw new Error( 'mail.unsupported: sendmail transport is unsupported in this runtime' );
|
|
327
|
+
}
|
|
328
|
+
const path = config.sendmail_path || sendmailPathAvailable();
|
|
329
|
+
if ( !path ) {
|
|
330
|
+
throw new Error( 'mail.unsupported: sendmail transport is unavailable; configure sendmail_path' );
|
|
331
|
+
}
|
|
332
|
+
return {
|
|
333
|
+
path,
|
|
334
|
+
args: [
|
|
335
|
+
...config.sendmail_args,
|
|
336
|
+
'-i',
|
|
337
|
+
'-f',
|
|
338
|
+
from,
|
|
339
|
+
...recipients,
|
|
340
|
+
],
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function sendmailSync( config, from, recipients, message, messageId ) {
|
|
345
|
+
const childProcess = nodeModule( 'node:child_process' );
|
|
346
|
+
if ( !childProcess || typeof childProcess.spawnSync !== 'function' ) {
|
|
347
|
+
throw new Error( 'mail.unsupported: sendmail process support is unavailable' );
|
|
348
|
+
}
|
|
349
|
+
const command = sendmailCommand( config, from, recipients );
|
|
350
|
+
const spawned = childProcess.spawnSync( command.path, command.args, {
|
|
351
|
+
input: message,
|
|
352
|
+
maxBuffer: 1024 * 1024,
|
|
353
|
+
} );
|
|
354
|
+
if ( spawned.error ) {
|
|
355
|
+
throw new Error( `mail.process: sendmail failed to start: ${spawned.error.message}` );
|
|
356
|
+
}
|
|
357
|
+
if ( spawned.status !== 0 ) {
|
|
358
|
+
const stderr = Buffer.from( spawned.stderr || [] ).toString( 'utf8' ).slice( 0, 4096 );
|
|
359
|
+
throw new Error(
|
|
360
|
+
`mail.process: sendmail exited with status ${spawned.status}${stderr ? `: ${stderr}` : ''}`
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return {
|
|
364
|
+
transport: 'sendmail',
|
|
365
|
+
accepted: recipients.slice(),
|
|
366
|
+
rejected: [],
|
|
367
|
+
message_id: messageId,
|
|
368
|
+
response: 'sendmail exit 0',
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
function sendmailAsync( config, from, recipients, message, messageId ) {
|
|
373
|
+
const childProcess = nodeModule( 'node:child_process' );
|
|
374
|
+
if ( !childProcess || typeof childProcess.spawn !== 'function' ) {
|
|
375
|
+
return Promise.reject(
|
|
376
|
+
new Error( 'mail.unsupported: sendmail process support is unavailable' )
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
const command = sendmailCommand( config, from, recipients );
|
|
380
|
+
return new Promise( ( resolve, reject ) => {
|
|
381
|
+
const child = childProcess.spawn( command.path, command.args, {
|
|
382
|
+
stdio: [ 'pipe', 'ignore', 'pipe' ],
|
|
383
|
+
} );
|
|
384
|
+
let stderr = Buffer.alloc( 0 );
|
|
385
|
+
child.on( 'error', (err) => {
|
|
386
|
+
reject( new Error( `mail.process: sendmail failed to start: ${err.message}` ) );
|
|
387
|
+
} );
|
|
388
|
+
child.stderr.on( 'data', (chunk) => {
|
|
389
|
+
if ( stderr.length < 4096 ) {
|
|
390
|
+
stderr = Buffer.concat( [ stderr, Buffer.from( chunk ) ] ).subarray( 0, 4096 );
|
|
391
|
+
}
|
|
392
|
+
} );
|
|
393
|
+
child.on( 'close', (code) => {
|
|
394
|
+
if ( code !== 0 ) {
|
|
395
|
+
const diag = stderr.toString( 'utf8' );
|
|
396
|
+
reject( new Error(
|
|
397
|
+
`mail.process: sendmail exited with status ${code}${diag ? `: ${diag}` : ''}`
|
|
398
|
+
) );
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
resolve( {
|
|
402
|
+
transport: 'sendmail',
|
|
403
|
+
accepted: recipients.slice(),
|
|
404
|
+
rejected: [],
|
|
405
|
+
message_id: messageId,
|
|
406
|
+
response: 'sendmail exit 0',
|
|
407
|
+
} );
|
|
408
|
+
} );
|
|
409
|
+
child.stdin.end( message );
|
|
410
|
+
} );
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
class SmtpSession {
|
|
414
|
+
constructor( config ) {
|
|
415
|
+
this.config = config;
|
|
416
|
+
this.net = nodeModule( 'node:net' );
|
|
417
|
+
this.tls = nodeModule( 'node:tls' );
|
|
418
|
+
this.socket = null;
|
|
419
|
+
this.buffer = '';
|
|
420
|
+
this.waiters = [];
|
|
421
|
+
this.currentLines = [];
|
|
422
|
+
this.closedError = null;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
connect() {
|
|
426
|
+
if ( !this.net || ( this.config.tls && !this.tls ) ) {
|
|
427
|
+
return Promise.reject( new Error( 'mail.unsupported: SMTP network support is unavailable' ) );
|
|
428
|
+
}
|
|
429
|
+
return new Promise( ( resolve, reject ) => {
|
|
430
|
+
const connectOptions = {
|
|
431
|
+
host: this.config.host,
|
|
432
|
+
port: this.config.port,
|
|
433
|
+
};
|
|
434
|
+
const socket = this.config.tls
|
|
435
|
+
? this.tls.connect( {
|
|
436
|
+
...connectOptions,
|
|
437
|
+
servername: this.config.tls_server_name || this.config.host,
|
|
438
|
+
rejectUnauthorized: this.config.tls_verify !== false,
|
|
439
|
+
} )
|
|
440
|
+
: this.net.createConnection( connectOptions );
|
|
441
|
+
this._attachSocket( socket );
|
|
442
|
+
let connected = false;
|
|
443
|
+
socket.on( this.config.tls ? 'secureConnect' : 'connect', () => {
|
|
444
|
+
connected = true;
|
|
445
|
+
resolve();
|
|
446
|
+
} );
|
|
447
|
+
socket.on( 'error', (err) => {
|
|
448
|
+
if ( !connected ) {
|
|
449
|
+
reject( err );
|
|
450
|
+
}
|
|
451
|
+
this._failPending( err );
|
|
452
|
+
} );
|
|
453
|
+
} );
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
_attachSocket( socket ) {
|
|
457
|
+
this.socket = socket;
|
|
458
|
+
socket.setTimeout( Math.floor( this.config.timeout * 1000 ) );
|
|
459
|
+
socket.on( 'timeout', () => {
|
|
460
|
+
const err = new Error( 'mail.timeout: SMTP command timed out' );
|
|
461
|
+
this._failPending( err );
|
|
462
|
+
socket.destroy( err );
|
|
463
|
+
} );
|
|
464
|
+
socket.on( 'close', () => {
|
|
465
|
+
this._failPending(
|
|
466
|
+
new Error( 'mail.connection: SMTP server closed the connection' )
|
|
467
|
+
);
|
|
468
|
+
} );
|
|
469
|
+
socket.on( 'data', (chunk) => this._onData( chunk ) );
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
upgradeTls() {
|
|
473
|
+
if ( !this.tls ) {
|
|
474
|
+
return Promise.reject( new Error( 'mail.unsupported: SMTP TLS support is unavailable' ) );
|
|
475
|
+
}
|
|
476
|
+
const oldSocket = this.socket;
|
|
477
|
+
oldSocket.removeAllListeners( 'data' );
|
|
478
|
+
oldSocket.removeAllListeners( 'close' );
|
|
479
|
+
oldSocket.removeAllListeners( 'timeout' );
|
|
480
|
+
return new Promise( ( resolve, reject ) => {
|
|
481
|
+
const tlsSocket = this.tls.connect( {
|
|
482
|
+
socket: oldSocket,
|
|
483
|
+
servername: this.config.tls_server_name || this.config.host,
|
|
484
|
+
rejectUnauthorized: this.config.tls_verify !== false,
|
|
485
|
+
} );
|
|
486
|
+
this._attachSocket( tlsSocket );
|
|
487
|
+
tlsSocket.once( 'secureConnect', resolve );
|
|
488
|
+
tlsSocket.once( 'error', reject );
|
|
489
|
+
} );
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
_onData( chunk ) {
|
|
493
|
+
this.buffer += Buffer.from( chunk ).toString( 'latin1' );
|
|
494
|
+
for (;;) {
|
|
495
|
+
const match = this.buffer.match( /\r?\n/u );
|
|
496
|
+
if ( !match ) {
|
|
497
|
+
break;
|
|
498
|
+
}
|
|
499
|
+
const rawLine = this.buffer.slice( 0, match.index );
|
|
500
|
+
this.buffer = this.buffer.slice( match.index + match[0].length );
|
|
501
|
+
this.currentLines.push( rawLine );
|
|
502
|
+
if ( /^\d{3} /u.test( rawLine ) || !/^\d{3}-/u.test( rawLine ) ) {
|
|
503
|
+
const lines = this.currentLines;
|
|
504
|
+
this.currentLines = [];
|
|
505
|
+
const waiter = this.waiters.shift();
|
|
506
|
+
if ( waiter ) {
|
|
507
|
+
try {
|
|
508
|
+
waiter.resolve( parseResponse( lines ) );
|
|
509
|
+
}
|
|
510
|
+
catch ( err ) {
|
|
511
|
+
waiter.reject( err );
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
_failPending( err ) {
|
|
519
|
+
if ( this.closedError ) {
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
this.closedError = err;
|
|
523
|
+
const waiters = this.waiters.splice( 0 );
|
|
524
|
+
this.currentLines = [];
|
|
525
|
+
for ( const waiter of waiters ) {
|
|
526
|
+
waiter.reject( err );
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
readResponse() {
|
|
531
|
+
return new Promise( ( resolve, reject ) => {
|
|
532
|
+
if ( this.closedError ) {
|
|
533
|
+
reject( this.closedError );
|
|
534
|
+
return;
|
|
535
|
+
}
|
|
536
|
+
this.waiters.push( { resolve, reject } );
|
|
537
|
+
} );
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
command( line ) {
|
|
541
|
+
this.socket.write( `${line}\r\n`, 'ascii' );
|
|
542
|
+
return this.readResponse();
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
writeData( message ) {
|
|
546
|
+
this.socket.write( dotStuff( message ) );
|
|
547
|
+
if ( message.length < 2 || message[message.length - 2] !== 13 || message[message.length - 1] !== 10 ) {
|
|
548
|
+
this.socket.write( Buffer.from( '\r\n', 'ascii' ) );
|
|
549
|
+
}
|
|
550
|
+
this.socket.write( Buffer.from( '.\r\n', 'ascii' ) );
|
|
551
|
+
return this.readResponse();
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
close() {
|
|
555
|
+
if ( this.socket ) {
|
|
556
|
+
this.socket.end();
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function parseResponse( lines ) {
|
|
562
|
+
const first = lines[0] || '';
|
|
563
|
+
const match = first.match( /^(\d{3})/u );
|
|
564
|
+
if ( !match ) {
|
|
565
|
+
throw new Error( 'mail.connection: SMTP server returned an invalid response' );
|
|
566
|
+
}
|
|
567
|
+
return {
|
|
568
|
+
code: Number( match[1] ),
|
|
569
|
+
lines,
|
|
570
|
+
text: lines.join( '\n' ),
|
|
571
|
+
};
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function expectCode( category, response, codes, context ) {
|
|
575
|
+
if ( codes.includes( response.code ) ) {
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
throw new Error( `${category}: ${context} failed: ${response.text}` );
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
function extensionsFromResponse( response ) {
|
|
582
|
+
const extensions = new Set();
|
|
583
|
+
for ( const line of response.lines || [] ) {
|
|
584
|
+
const match = line.match( /^\d{3}[- ](.+)$/u );
|
|
585
|
+
if ( !match ) {
|
|
586
|
+
continue;
|
|
587
|
+
}
|
|
588
|
+
const name = match[1].trim().split( /\s+/u )[0];
|
|
589
|
+
if ( name ) {
|
|
590
|
+
extensions.add( name.toUpperCase() );
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
return extensions;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
function authMechanismsFromResponse( response ) {
|
|
597
|
+
const mechanisms = new Set();
|
|
598
|
+
for ( const line of response.lines || [] ) {
|
|
599
|
+
const match = line.match( /^\d{3}[- ]AUTH(?:\s+(.+))?$/iu );
|
|
600
|
+
if ( !match ) {
|
|
601
|
+
continue;
|
|
602
|
+
}
|
|
603
|
+
for ( const mechanism of ( match[1] || '' ).trim().split( /\s+/u ) ) {
|
|
604
|
+
if ( mechanism ) {
|
|
605
|
+
mechanisms.add( mechanism.toLowerCase() );
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
return mechanisms;
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function smtpAuthenticate( session, config, response ) {
|
|
613
|
+
if ( !config.username && !config.password && !config.auth ) {
|
|
614
|
+
return;
|
|
615
|
+
}
|
|
616
|
+
const username = config.username || '';
|
|
617
|
+
const password = config.password || '';
|
|
618
|
+
const advertised = authMechanismsFromResponse( response );
|
|
619
|
+
let mechanism = String( config.auth || '' ).toLowerCase();
|
|
620
|
+
if ( !mechanism ) {
|
|
621
|
+
for ( const candidate of [ 'plain', 'login', 'xoauth2' ] ) {
|
|
622
|
+
if ( advertised.has( candidate ) ) {
|
|
623
|
+
mechanism = candidate;
|
|
624
|
+
break;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
if ( !mechanism ) {
|
|
628
|
+
mechanism = 'plain';
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
if ( ![ 'plain', 'login', 'xoauth2' ].includes( mechanism ) ) {
|
|
632
|
+
throw new Error( `mail.auth: unsupported SMTP auth mechanism '${mechanism}'` );
|
|
633
|
+
}
|
|
634
|
+
if ( advertised.size > 0 && !advertised.has( mechanism ) ) {
|
|
635
|
+
throw new Error( `mail.auth: SMTP server did not advertise AUTH ${mechanism}` );
|
|
636
|
+
}
|
|
637
|
+
if ( mechanism === 'plain' ) {
|
|
638
|
+
const token = Buffer.from( `\0${username}\0${password}`, 'utf8' ).toString( 'base64' );
|
|
639
|
+
const authResponse = await session.command( `AUTH PLAIN ${token}` );
|
|
640
|
+
expectCode( 'mail.auth', authResponse, [ 235 ], 'AUTH PLAIN' );
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if ( mechanism === 'login' ) {
|
|
644
|
+
let authResponse = await session.command( 'AUTH LOGIN' );
|
|
645
|
+
expectCode( 'mail.auth', authResponse, [ 334 ], 'AUTH LOGIN' );
|
|
646
|
+
authResponse = await session.command( Buffer.from( username, 'utf8' ).toString( 'base64' ) );
|
|
647
|
+
expectCode( 'mail.auth', authResponse, [ 334 ], 'AUTH username' );
|
|
648
|
+
authResponse = await session.command( Buffer.from( password, 'utf8' ).toString( 'base64' ) );
|
|
649
|
+
expectCode( 'mail.auth', authResponse, [ 235 ], 'AUTH password' );
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
const xoauth2 = `user=${username}\x01auth=Bearer ${password}\x01\x01`;
|
|
653
|
+
const token = Buffer.from( xoauth2, 'utf8' ).toString( 'base64' );
|
|
654
|
+
const authResponse = await session.command( `AUTH XOAUTH2 ${token}` );
|
|
655
|
+
expectCode( 'mail.auth', authResponse, [ 235 ], 'AUTH XOAUTH2' );
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
async function sendSmtpPayload( payload ) {
|
|
659
|
+
const { config, from, recipients, messageBase64, messageId } = payload;
|
|
660
|
+
const message = Buffer.from( messageBase64, 'base64' );
|
|
661
|
+
rejectUnsupportedSmtp( config );
|
|
662
|
+
const session = new SmtpSession( config );
|
|
663
|
+
try {
|
|
664
|
+
await session.connect();
|
|
665
|
+
let response = await session.readResponse();
|
|
666
|
+
expectCode( 'mail.connection', response, [ 220 ], 'SMTP greeting' );
|
|
667
|
+
const heloName = 'localhost';
|
|
668
|
+
response = await session.command( `EHLO ${heloName}` );
|
|
669
|
+
let extensions = new Set();
|
|
670
|
+
if ( response.code >= 500 ) {
|
|
671
|
+
response = await session.command( `HELO ${heloName}` );
|
|
672
|
+
expectCode( 'mail.connection', response, [ 250 ], 'HELO' );
|
|
673
|
+
}
|
|
674
|
+
else {
|
|
675
|
+
expectCode( 'mail.connection', response, [ 250 ], 'EHLO' );
|
|
676
|
+
extensions = extensionsFromResponse( response );
|
|
677
|
+
}
|
|
678
|
+
if ( config.starttls ) {
|
|
679
|
+
if ( !extensions.has( 'STARTTLS' ) ) {
|
|
680
|
+
throw new Error( 'mail.tls: SMTP server did not advertise STARTTLS' );
|
|
681
|
+
}
|
|
682
|
+
response = await session.command( 'STARTTLS' );
|
|
683
|
+
expectCode( 'mail.tls', response, [ 220 ], 'STARTTLS' );
|
|
684
|
+
await session.upgradeTls();
|
|
685
|
+
response = await session.command( `EHLO ${heloName}` );
|
|
686
|
+
expectCode( 'mail.connection', response, [ 250 ], 'EHLO after STARTTLS' );
|
|
687
|
+
extensions = extensionsFromResponse( response );
|
|
688
|
+
}
|
|
689
|
+
await smtpAuthenticate( session, config, response );
|
|
690
|
+
if ( config.smtputf8 && !extensions.has( 'SMTPUTF8' ) ) {
|
|
691
|
+
throw new Error( 'mail.unsupported: SMTPUTF8 was requested but not advertised' );
|
|
692
|
+
}
|
|
693
|
+
let mailFrom = `MAIL FROM:<${from}>`;
|
|
694
|
+
if ( config.smtputf8 ) {
|
|
695
|
+
mailFrom += ' SMTPUTF8';
|
|
696
|
+
}
|
|
697
|
+
response = await session.command( mailFrom );
|
|
698
|
+
expectCode( 'mail.recipient', response, [ 250 ], 'MAIL FROM' );
|
|
699
|
+
const accepted = [];
|
|
700
|
+
const rejected = [];
|
|
701
|
+
for ( const recipient of recipients ) {
|
|
702
|
+
response = await session.command( `RCPT TO:<${recipient}>` );
|
|
703
|
+
if ( [ 250, 251, 252 ].includes( response.code ) ) {
|
|
704
|
+
accepted.push( recipient );
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
rejected.push( recipient );
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if ( accepted.length === 0 ) {
|
|
711
|
+
try {
|
|
712
|
+
await session.command( 'QUIT' );
|
|
713
|
+
}
|
|
714
|
+
catch ( _err ) {}
|
|
715
|
+
throw new Error( 'mail.recipient: all recipients were rejected' );
|
|
716
|
+
}
|
|
717
|
+
if ( rejected.length > 0 && config.reject_partial ) {
|
|
718
|
+
try {
|
|
719
|
+
await session.command( 'QUIT' );
|
|
720
|
+
}
|
|
721
|
+
catch ( _err ) {}
|
|
722
|
+
throw new Error( 'mail.recipient: one or more recipients were rejected' );
|
|
723
|
+
}
|
|
724
|
+
response = await session.command( 'DATA' );
|
|
725
|
+
expectCode( 'mail.data', response, [ 354 ], 'DATA' );
|
|
726
|
+
response = await session.writeData( message );
|
|
727
|
+
expectCode( 'mail.data', response, [ 250 ], 'message DATA' );
|
|
728
|
+
const dataResponse = response.text;
|
|
729
|
+
try {
|
|
730
|
+
await session.command( 'QUIT' );
|
|
731
|
+
}
|
|
732
|
+
catch ( _err ) {}
|
|
733
|
+
return {
|
|
734
|
+
transport: 'smtp',
|
|
735
|
+
accepted,
|
|
736
|
+
rejected,
|
|
737
|
+
message_id: messageId,
|
|
738
|
+
response: dataResponse,
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
catch ( err ) {
|
|
742
|
+
if ( err && /^mail\./u.test( err.message || '' ) ) {
|
|
743
|
+
throw err;
|
|
744
|
+
}
|
|
745
|
+
throw new Error( `mail.connection: ${err && err.message ? err.message : String( err )}` );
|
|
746
|
+
}
|
|
747
|
+
finally {
|
|
748
|
+
session.close();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
function sendSmtpSync( config, from, recipients, message, messageId ) {
|
|
753
|
+
const childProcess = nodeModule( 'node:child_process' );
|
|
754
|
+
if ( !childProcess || typeof childProcess.spawnSync !== 'function' ) {
|
|
755
|
+
throw new Error( 'mail.unsupported: synchronous SMTP support is unavailable' );
|
|
756
|
+
}
|
|
757
|
+
const source = `
|
|
758
|
+
const smtp = require( process.argv[1] );
|
|
759
|
+
let input = '';
|
|
760
|
+
process.stdin.setEncoding( 'utf8' );
|
|
761
|
+
process.stdin.on( 'data', ( chunk ) => { input += chunk; } );
|
|
762
|
+
process.stdin.on( 'end', async () => {
|
|
763
|
+
try {
|
|
764
|
+
const result = await smtp.__zuzu_smtp_send_payload( JSON.parse( input ) );
|
|
765
|
+
process.stdout.write( JSON.stringify( { ok: true, result } ) );
|
|
766
|
+
}
|
|
767
|
+
catch ( err ) {
|
|
768
|
+
process.stdout.write( JSON.stringify( {
|
|
769
|
+
ok: false,
|
|
770
|
+
error: err && err.message ? err.message : String( err ),
|
|
771
|
+
} ) );
|
|
772
|
+
}
|
|
773
|
+
} );
|
|
774
|
+
`;
|
|
775
|
+
const payload = {
|
|
776
|
+
config,
|
|
777
|
+
from,
|
|
778
|
+
recipients,
|
|
779
|
+
messageBase64: message.toString( 'base64' ),
|
|
780
|
+
messageId,
|
|
781
|
+
};
|
|
782
|
+
const spawned = childProcess.spawnSync(
|
|
783
|
+
process.execPath,
|
|
784
|
+
[ '-e', source, __filename ],
|
|
785
|
+
{
|
|
786
|
+
input: JSON.stringify( payload ),
|
|
787
|
+
encoding: 'utf8',
|
|
788
|
+
timeout: Math.floor( ( config.timeout + 2 ) * 1000 ),
|
|
789
|
+
maxBuffer: 1024 * 1024,
|
|
790
|
+
}
|
|
791
|
+
);
|
|
792
|
+
if ( spawned.error ) {
|
|
793
|
+
if ( spawned.error.code === 'ETIMEDOUT' ) {
|
|
794
|
+
throw new Error( 'mail.timeout: SMTP command timed out' );
|
|
795
|
+
}
|
|
796
|
+
throw new Error( `mail.connection: ${spawned.error.message}` );
|
|
797
|
+
}
|
|
798
|
+
const parsed = JSON.parse( spawned.stdout || '{"ok":false,"error":"empty SMTP worker response"}' );
|
|
799
|
+
if ( !parsed.ok ) {
|
|
800
|
+
throw new Error( parsed.error );
|
|
801
|
+
}
|
|
802
|
+
return parsed.result;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function prepareSend( baseConfig, fromValue, toValue, headers, body, options ) {
|
|
806
|
+
const config = mergeConfig( baseConfig, options );
|
|
807
|
+
rejectUnsupportedSecurityOptions( config );
|
|
808
|
+
const from = validateAddress( fromValue );
|
|
809
|
+
const recipients = recipientList( toValue );
|
|
810
|
+
validateEnvelopeAscii( config, [ from, ...recipients ] );
|
|
811
|
+
const serialized = serializeMessage( headers, body );
|
|
812
|
+
return {
|
|
813
|
+
config,
|
|
814
|
+
from,
|
|
815
|
+
recipients,
|
|
816
|
+
message: serialized.message,
|
|
817
|
+
messageId: serialized.messageId,
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
class MailResult {
|
|
822
|
+
constructor( payload = {} ) {
|
|
823
|
+
this.transport = payload.transport ?? null;
|
|
824
|
+
this.accepted = Array.isArray( payload.accepted ) ? payload.accepted.slice() : [];
|
|
825
|
+
this.rejected = Array.isArray( payload.rejected ) ? payload.rejected.slice() : [];
|
|
826
|
+
this.message_id = payload.message_id ?? null;
|
|
827
|
+
this.response = payload.response ?? null;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
to_Dict() {
|
|
831
|
+
return {
|
|
832
|
+
transport: this.transport,
|
|
833
|
+
accepted: this.accepted.slice(),
|
|
834
|
+
rejected: this.rejected.slice(),
|
|
835
|
+
message_id: this.message_id,
|
|
836
|
+
response: this.response,
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
class Mailer {
|
|
842
|
+
constructor( options = {}, named = {} ) {
|
|
843
|
+
this._config = normalizeConfig( { ...asDict( options ), ...asDict( named ) } );
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
static capabilities() {
|
|
847
|
+
return capabilities();
|
|
848
|
+
}
|
|
849
|
+
|
|
850
|
+
send( fromValue, toValue, headers, body, options = {} ) {
|
|
851
|
+
if ( isBrowser() ) {
|
|
852
|
+
throw new Error( 'mail.unsupported: std/net/smtp delivery is unsupported on JS/Browser' );
|
|
853
|
+
}
|
|
854
|
+
const prepared = prepareSend(
|
|
855
|
+
this._config,
|
|
856
|
+
fromValue,
|
|
857
|
+
toValue,
|
|
858
|
+
headers,
|
|
859
|
+
body,
|
|
860
|
+
options,
|
|
861
|
+
);
|
|
862
|
+
const result = prepared.config.transport === 'sendmail'
|
|
863
|
+
? sendmailSync(
|
|
864
|
+
prepared.config,
|
|
865
|
+
prepared.from,
|
|
866
|
+
prepared.recipients,
|
|
867
|
+
prepared.message,
|
|
868
|
+
prepared.messageId,
|
|
869
|
+
)
|
|
870
|
+
: sendSmtpSync(
|
|
871
|
+
prepared.config,
|
|
872
|
+
prepared.from,
|
|
873
|
+
prepared.recipients,
|
|
874
|
+
prepared.message,
|
|
875
|
+
prepared.messageId,
|
|
876
|
+
);
|
|
877
|
+
return mailResult( result );
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
send_async( fromValue, toValue, headers, body, options = {} ) {
|
|
881
|
+
if ( isBrowser() ) {
|
|
882
|
+
return Task.failed(
|
|
883
|
+
new Error( 'mail.unsupported: std/net/smtp delivery is unsupported on JS/Browser' )
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
let prepared;
|
|
887
|
+
try {
|
|
888
|
+
prepared = prepareSend(
|
|
889
|
+
this._config,
|
|
890
|
+
fromValue,
|
|
891
|
+
toValue,
|
|
892
|
+
headers,
|
|
893
|
+
body,
|
|
894
|
+
options,
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
catch ( err ) {
|
|
898
|
+
return Task.failed( err );
|
|
899
|
+
}
|
|
900
|
+
return new Task(
|
|
901
|
+
async () => {
|
|
902
|
+
const result = prepared.config.transport === 'sendmail'
|
|
903
|
+
? await sendmailAsync(
|
|
904
|
+
prepared.config,
|
|
905
|
+
prepared.from,
|
|
906
|
+
prepared.recipients,
|
|
907
|
+
prepared.message,
|
|
908
|
+
prepared.messageId,
|
|
909
|
+
)
|
|
910
|
+
: await sendSmtpPayload( {
|
|
911
|
+
config: prepared.config,
|
|
912
|
+
from: prepared.from,
|
|
913
|
+
recipients: prepared.recipients,
|
|
914
|
+
messageBase64: prepared.message.toString( 'base64' ),
|
|
915
|
+
messageId: prepared.messageId,
|
|
916
|
+
} );
|
|
917
|
+
return mailResult( result );
|
|
918
|
+
},
|
|
919
|
+
{ name: 'smtp.send_async' },
|
|
920
|
+
);
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
function __zuzu_set_runtime_policy( policy = {} ) {
|
|
925
|
+
runtimePolicy = policy || {};
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const api = {
|
|
929
|
+
Mailer,
|
|
930
|
+
MailResult,
|
|
931
|
+
};
|
|
932
|
+
|
|
933
|
+
Object.defineProperty( api, '__zuzu_set_runtime_policy', {
|
|
934
|
+
value: __zuzu_set_runtime_policy,
|
|
935
|
+
enumerable: false,
|
|
936
|
+
} );
|
|
937
|
+
|
|
938
|
+
Object.defineProperty( api, '__zuzu_smtp_send_payload', {
|
|
939
|
+
value: sendSmtpPayload,
|
|
940
|
+
enumerable: false,
|
|
941
|
+
} );
|
|
942
|
+
|
|
943
|
+
module.exports = api;
|