imapflow 1.2.4 → 1.2.5

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.
@@ -0,0 +1,541 @@
1
+ 'use strict';
2
+
3
+ const proxyquire = require('proxyquire').noCallThru();
4
+
5
+ // Mock socket object
6
+ const createMockSocket = () => ({
7
+ on: () => {},
8
+ write: () => {},
9
+ end: () => {},
10
+ destroy: () => {}
11
+ });
12
+
13
+ // Mock logger
14
+ const createMockLogger = () => {
15
+ const logs = { info: [], error: [] };
16
+ return {
17
+ info: msg => logs.info.push(msg),
18
+ error: msg => logs.error.push(msg),
19
+ _logs: logs
20
+ };
21
+ };
22
+
23
+ // ============================================
24
+ // HTTP Proxy Tests
25
+ // ============================================
26
+
27
+ module.exports['Proxy Connection: HTTP proxy success'] = async test => {
28
+ const mockSocket = createMockSocket();
29
+ const logger = createMockLogger();
30
+
31
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
32
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
33
+ cb(null, mockSocket);
34
+ },
35
+ socks: { SocksClient: {} },
36
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
37
+ net: { isIP: () => true }
38
+ });
39
+
40
+ const socket = await proxyConnection(logger, 'http://proxy.example.com:8080', '192.168.1.1', 993);
41
+
42
+ test.equal(socket, mockSocket);
43
+ test.equal(logger._logs.info.length, 1);
44
+ test.ok(logger._logs.info[0].msg.includes('HTTP proxy'));
45
+ test.done();
46
+ };
47
+
48
+ module.exports['Proxy Connection: HTTPS proxy success'] = async test => {
49
+ const mockSocket = createMockSocket();
50
+ const logger = createMockLogger();
51
+
52
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
53
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
54
+ cb(null, mockSocket);
55
+ },
56
+ socks: { SocksClient: {} },
57
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
58
+ net: { isIP: () => true }
59
+ });
60
+
61
+ const socket = await proxyConnection(logger, 'https://proxy.example.com:8080', '192.168.1.1', 993);
62
+
63
+ test.equal(socket, mockSocket);
64
+ test.equal(logger._logs.info.length, 1);
65
+ test.done();
66
+ };
67
+
68
+ module.exports['Proxy Connection: HTTP proxy with password hides it in logs'] = async test => {
69
+ const mockSocket = createMockSocket();
70
+ const logger = createMockLogger();
71
+
72
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
73
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
74
+ cb(null, mockSocket);
75
+ },
76
+ socks: { SocksClient: {} },
77
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
78
+ net: { isIP: () => true }
79
+ });
80
+
81
+ await proxyConnection(logger, 'http://user:secret123@proxy.example.com:8080', '192.168.1.1', 993);
82
+
83
+ test.equal(logger._logs.info.length, 1);
84
+ test.ok(!logger._logs.info[0].proxyUrl.includes('secret123'));
85
+ test.ok(logger._logs.info[0].proxyUrl.includes('(hidden)'));
86
+ test.done();
87
+ };
88
+
89
+ module.exports['Proxy Connection: HTTP proxy failure'] = async test => {
90
+ const logger = createMockLogger();
91
+ const testError = new Error('Connection refused');
92
+
93
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
94
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
95
+ cb(testError);
96
+ },
97
+ socks: { SocksClient: {} },
98
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
99
+ net: { isIP: () => true }
100
+ });
101
+
102
+ try {
103
+ await proxyConnection(logger, 'http://proxy.example.com:8080', '192.168.1.1', 993);
104
+ test.ok(false, 'Should have thrown');
105
+ } catch (err) {
106
+ test.equal(err, testError);
107
+ test.equal(logger._logs.error.length, 1);
108
+ test.ok(logger._logs.error[0].msg.includes('Failed'));
109
+ }
110
+
111
+ test.done();
112
+ };
113
+
114
+ module.exports['Proxy Connection: HTTP proxy failure hides password'] = async test => {
115
+ const logger = createMockLogger();
116
+
117
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
118
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
119
+ cb(new Error('Failed'));
120
+ },
121
+ socks: { SocksClient: {} },
122
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
123
+ net: { isIP: () => true }
124
+ });
125
+
126
+ try {
127
+ await proxyConnection(logger, 'http://user:secret@proxy.example.com:8080', '192.168.1.1', 993);
128
+ } catch (err) {
129
+ // Error expected - we just need to verify logging
130
+ err.expected = true;
131
+ test.ok(!logger._logs.error[0].proxyUrl.includes('secret'));
132
+ test.ok(logger._logs.error[0].proxyUrl.includes('(hidden)'));
133
+ }
134
+
135
+ test.done();
136
+ };
137
+
138
+ // ============================================
139
+ // SOCKS Proxy Tests
140
+ // ============================================
141
+
142
+ module.exports['Proxy Connection: SOCKS5 proxy success'] = async test => {
143
+ const mockSocket = createMockSocket();
144
+ const logger = createMockLogger();
145
+
146
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
147
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
148
+ socks: {
149
+ SocksClient: {
150
+ createConnection: async opts => {
151
+ test.equal(opts.proxy.type, 5);
152
+ test.equal(opts.command, 'connect');
153
+ return { socket: mockSocket };
154
+ }
155
+ }
156
+ },
157
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
158
+ net: { isIP: () => true }
159
+ });
160
+
161
+ const socket = await proxyConnection(logger, 'socks5://proxy.example.com:1080', '192.168.1.1', 993);
162
+
163
+ test.equal(socket, mockSocket);
164
+ test.equal(logger._logs.info.length, 1);
165
+ test.ok(logger._logs.info[0].msg.includes('SOCKS proxy'));
166
+ test.done();
167
+ };
168
+
169
+ module.exports['Proxy Connection: SOCKS proxy (defaults to SOCKS5)'] = async test => {
170
+ const mockSocket = createMockSocket();
171
+ const logger = createMockLogger();
172
+
173
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
174
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
175
+ socks: {
176
+ SocksClient: {
177
+ createConnection: async opts => {
178
+ test.equal(opts.proxy.type, 5); // Default to SOCKS5
179
+ return { socket: mockSocket };
180
+ }
181
+ }
182
+ },
183
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
184
+ net: { isIP: () => true }
185
+ });
186
+
187
+ await proxyConnection(logger, 'socks://proxy.example.com:1080', '192.168.1.1', 993);
188
+ test.done();
189
+ };
190
+
191
+ module.exports['Proxy Connection: SOCKS4 proxy'] = async test => {
192
+ const mockSocket = createMockSocket();
193
+ const logger = createMockLogger();
194
+
195
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
196
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
197
+ socks: {
198
+ SocksClient: {
199
+ createConnection: async opts => {
200
+ test.equal(opts.proxy.type, 4);
201
+ return { socket: mockSocket };
202
+ }
203
+ }
204
+ },
205
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
206
+ net: { isIP: () => true }
207
+ });
208
+
209
+ await proxyConnection(logger, 'socks4://proxy.example.com:1080', '192.168.1.1', 993);
210
+ test.done();
211
+ };
212
+
213
+ module.exports['Proxy Connection: SOCKS4a proxy'] = async test => {
214
+ const mockSocket = createMockSocket();
215
+ const logger = createMockLogger();
216
+
217
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
218
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
219
+ socks: {
220
+ SocksClient: {
221
+ createConnection: async opts => {
222
+ test.equal(opts.proxy.type, 4);
223
+ return { socket: mockSocket };
224
+ }
225
+ }
226
+ },
227
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
228
+ net: { isIP: () => true }
229
+ });
230
+
231
+ await proxyConnection(logger, 'socks4a://proxy.example.com:1080', '192.168.1.1', 993);
232
+ test.done();
233
+ };
234
+
235
+ module.exports['Proxy Connection: SOCKS proxy with authentication'] = async test => {
236
+ const mockSocket = createMockSocket();
237
+ const logger = createMockLogger();
238
+
239
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
240
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
241
+ socks: {
242
+ SocksClient: {
243
+ createConnection: async opts => {
244
+ test.equal(opts.proxy.userId, 'testuser');
245
+ test.equal(opts.proxy.password, 'testpass');
246
+ return { socket: mockSocket };
247
+ }
248
+ }
249
+ },
250
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
251
+ net: { isIP: () => true }
252
+ });
253
+
254
+ await proxyConnection(logger, 'socks5://testuser:testpass@proxy.example.com:1080', '192.168.1.1', 993);
255
+ test.done();
256
+ };
257
+
258
+ module.exports['Proxy Connection: SOCKS proxy hides password in logs'] = async test => {
259
+ const mockSocket = createMockSocket();
260
+ const logger = createMockLogger();
261
+
262
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
263
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
264
+ socks: {
265
+ SocksClient: {
266
+ createConnection: async () => ({ socket: mockSocket })
267
+ }
268
+ },
269
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
270
+ net: { isIP: () => true }
271
+ });
272
+
273
+ await proxyConnection(logger, 'socks5://user:secretpass@proxy.example.com:1080', '192.168.1.1', 993);
274
+
275
+ test.ok(!logger._logs.info[0].proxyUrl.includes('secretpass'));
276
+ test.ok(logger._logs.info[0].proxyUrl.includes('(hidden)'));
277
+ test.done();
278
+ };
279
+
280
+ module.exports['Proxy Connection: SOCKS proxy default port'] = async test => {
281
+ const mockSocket = createMockSocket();
282
+ const logger = createMockLogger();
283
+
284
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
285
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
286
+ socks: {
287
+ SocksClient: {
288
+ createConnection: async opts => {
289
+ test.equal(opts.proxy.port, 1080); // Default SOCKS port
290
+ return { socket: mockSocket };
291
+ }
292
+ }
293
+ },
294
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
295
+ net: { isIP: () => true }
296
+ });
297
+
298
+ await proxyConnection(logger, 'socks5://proxy.example.com', '192.168.1.1', 993);
299
+ test.done();
300
+ };
301
+
302
+ module.exports['Proxy Connection: SOCKS proxy failure'] = async test => {
303
+ const logger = createMockLogger();
304
+ const testError = new Error('SOCKS connection failed');
305
+
306
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
307
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
308
+ socks: {
309
+ SocksClient: {
310
+ createConnection: async () => {
311
+ throw testError;
312
+ }
313
+ }
314
+ },
315
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
316
+ net: { isIP: () => true }
317
+ });
318
+
319
+ try {
320
+ await proxyConnection(logger, 'socks5://proxy.example.com:1080', '192.168.1.1', 993);
321
+ test.ok(false, 'Should have thrown');
322
+ } catch (err) {
323
+ test.equal(err, testError);
324
+ test.equal(logger._logs.error.length, 1);
325
+ test.ok(logger._logs.error[0].msg.includes('Failed'));
326
+ }
327
+
328
+ test.done();
329
+ };
330
+
331
+ module.exports['Proxy Connection: SOCKS proxy failure hides password'] = async test => {
332
+ const logger = createMockLogger();
333
+
334
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
335
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
336
+ socks: {
337
+ SocksClient: {
338
+ createConnection: async () => {
339
+ throw new Error('Failed');
340
+ }
341
+ }
342
+ },
343
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
344
+ net: { isIP: () => true }
345
+ });
346
+
347
+ try {
348
+ await proxyConnection(logger, 'socks5://user:secret@proxy.example.com:1080', '192.168.1.1', 993);
349
+ } catch (err) {
350
+ // Error expected - we just need to verify logging
351
+ err.expected = true;
352
+ test.ok(!logger._logs.error[0].proxyUrl.includes('secret'));
353
+ test.ok(logger._logs.error[0].proxyUrl.includes('(hidden)'));
354
+ }
355
+
356
+ test.done();
357
+ };
358
+
359
+ // ============================================
360
+ // DNS Resolution Tests
361
+ // ============================================
362
+
363
+ module.exports['Proxy Connection: Resolves hostname to IP'] = async test => {
364
+ const mockSocket = createMockSocket();
365
+ const logger = createMockLogger();
366
+ let resolvedHost = null;
367
+
368
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
369
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
370
+ resolvedHost = host;
371
+ cb(null, mockSocket);
372
+ },
373
+ socks: { SocksClient: {} },
374
+ dns: {
375
+ promises: {
376
+ resolve: async hostname => {
377
+ if (hostname === 'mail.example.com') {
378
+ return ['93.184.216.34'];
379
+ }
380
+ return [];
381
+ }
382
+ }
383
+ },
384
+ net: { isIP: host => /^\d+\.\d+\.\d+\.\d+$/.test(host) }
385
+ });
386
+
387
+ await proxyConnection(logger, 'http://proxy.example.com:8080', 'mail.example.com', 993);
388
+
389
+ test.equal(resolvedHost, '93.184.216.34');
390
+ test.done();
391
+ };
392
+
393
+ module.exports['Proxy Connection: Skips DNS for IP addresses'] = async test => {
394
+ const mockSocket = createMockSocket();
395
+ const logger = createMockLogger();
396
+ let dnsResolveCalled = false;
397
+
398
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
399
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
400
+ cb(null, mockSocket);
401
+ },
402
+ socks: { SocksClient: {} },
403
+ dns: {
404
+ promises: {
405
+ resolve: async () => {
406
+ dnsResolveCalled = true;
407
+ return ['127.0.0.1'];
408
+ }
409
+ }
410
+ },
411
+ net: { isIP: () => true } // Pretend it's already an IP
412
+ });
413
+
414
+ await proxyConnection(logger, 'http://proxy.example.com:8080', '192.168.1.1', 993);
415
+
416
+ test.equal(dnsResolveCalled, false);
417
+ test.done();
418
+ };
419
+
420
+ module.exports['Proxy Connection: SOCKS resolves proxy hostname'] = async test => {
421
+ const mockSocket = createMockSocket();
422
+ const logger = createMockLogger();
423
+ let proxyHostResolved = null;
424
+
425
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
426
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
427
+ socks: {
428
+ SocksClient: {
429
+ createConnection: async opts => {
430
+ proxyHostResolved = opts.proxy.host;
431
+ return { socket: mockSocket };
432
+ }
433
+ }
434
+ },
435
+ dns: {
436
+ promises: {
437
+ resolve: async hostname => {
438
+ if (hostname === 'proxy.example.com') {
439
+ return ['10.0.0.1'];
440
+ }
441
+ return ['127.0.0.1'];
442
+ }
443
+ }
444
+ },
445
+ net: { isIP: host => /^\d+\.\d+\.\d+\.\d+$/.test(host) }
446
+ });
447
+
448
+ await proxyConnection(logger, 'socks5://proxy.example.com:1080', '192.168.1.1', 993);
449
+
450
+ test.equal(proxyHostResolved, '10.0.0.1');
451
+ test.done();
452
+ };
453
+
454
+ // ============================================
455
+ // Edge Cases
456
+ // ============================================
457
+
458
+ module.exports['Proxy Connection: Unknown protocol returns undefined'] = async test => {
459
+ const logger = createMockLogger();
460
+
461
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
462
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
463
+ socks: { SocksClient: {} },
464
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
465
+ net: { isIP: () => true }
466
+ });
467
+
468
+ const result = await proxyConnection(logger, 'ftp://proxy.example.com:21', '192.168.1.1', 993);
469
+
470
+ test.equal(result, undefined);
471
+ test.done();
472
+ };
473
+
474
+ module.exports['Proxy Connection: HTTP proxy with no socket returned'] = async test => {
475
+ const logger = createMockLogger();
476
+
477
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
478
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
479
+ cb(null, null); // No socket
480
+ },
481
+ socks: { SocksClient: {} },
482
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
483
+ net: { isIP: () => true }
484
+ });
485
+
486
+ const socket = await proxyConnection(logger, 'http://proxy.example.com:8080', '192.168.1.1', 993);
487
+
488
+ test.equal(socket, null);
489
+ // No log when socket is null
490
+ test.equal(logger._logs.info.length, 0);
491
+ test.done();
492
+ };
493
+
494
+ module.exports['Proxy Connection: DNS returns empty result'] = async test => {
495
+ const mockSocket = createMockSocket();
496
+ const logger = createMockLogger();
497
+ let usedHost = null;
498
+
499
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
500
+ 'nodemailer/lib/smtp-connection/http-proxy-client': (url, port, host, cb) => {
501
+ usedHost = host;
502
+ cb(null, mockSocket);
503
+ },
504
+ socks: { SocksClient: {} },
505
+ dns: {
506
+ promises: {
507
+ resolve: async () => [] // Empty result
508
+ }
509
+ },
510
+ net: { isIP: () => false }
511
+ });
512
+
513
+ await proxyConnection(logger, 'http://proxy.example.com:8080', 'mail.example.com', 993);
514
+
515
+ // Should keep original hostname when DNS returns empty
516
+ test.equal(usedHost, 'mail.example.com');
517
+ test.done();
518
+ };
519
+
520
+ module.exports['Proxy Connection: SOCKS with username only'] = async test => {
521
+ const mockSocket = createMockSocket();
522
+ const logger = createMockLogger();
523
+
524
+ const { proxyConnection } = proxyquire('../lib/proxy-connection', {
525
+ 'nodemailer/lib/smtp-connection/http-proxy-client': () => {},
526
+ socks: {
527
+ SocksClient: {
528
+ createConnection: async opts => {
529
+ test.equal(opts.proxy.userId, 'testuser');
530
+ test.equal(opts.proxy.password, ''); // Empty string from URL parsing
531
+ return { socket: mockSocket };
532
+ }
533
+ }
534
+ },
535
+ dns: { promises: { resolve: async () => ['127.0.0.1'] } },
536
+ net: { isIP: () => true }
537
+ });
538
+
539
+ await proxyConnection(logger, 'socks5://testuser@proxy.example.com:1080', '192.168.1.1', 993);
540
+ test.done();
541
+ };