nodemailer 10.0.13 → 10.0.15

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cjs/addressparser/index.d.ts +0 -1
  3. package/dist/cjs/addressparser/index.js +55 -17
  4. package/dist/cjs/dkim/message-parser.js +28 -4
  5. package/dist/cjs/dkim/sign.d.ts +1 -0
  6. package/dist/cjs/dkim/sign.js +1 -0
  7. package/dist/cjs/errors.d.ts +3 -3
  8. package/dist/cjs/mailer/index.d.ts +7 -1
  9. package/dist/cjs/mime-funcs/index.d.ts +2 -1
  10. package/dist/cjs/mime-funcs/index.js +197 -83
  11. package/dist/cjs/mime-node/index.js +2 -1
  12. package/dist/cjs/nodemailer.d.ts +13 -2
  13. package/dist/cjs/package-info.d.ts +1 -1
  14. package/dist/cjs/package-info.js +1 -1
  15. package/dist/cjs/punycode/index.js +1 -0
  16. package/dist/cjs/ses-transport/index.d.ts +6 -1
  17. package/dist/cjs/shared/index.d.ts +25 -1
  18. package/dist/cjs/shared/index.js +1 -0
  19. package/dist/cjs/smtp-connection/http-proxy-client.d.ts +11 -3
  20. package/dist/cjs/smtp-connection/http-proxy-client.js +17 -7
  21. package/dist/cjs/smtp-connection/index.d.ts +1 -1
  22. package/dist/cjs/smtp-connection/index.js +15 -1
  23. package/dist/cjs/smtp-pool/index.d.ts +6 -1
  24. package/dist/cjs/smtp-transport/index.d.ts +6 -1
  25. package/dist/cjs/well-known/services.js +6 -0
  26. package/dist/esm/addressparser/index.d.ts +0 -1
  27. package/dist/esm/addressparser/index.js +55 -17
  28. package/dist/esm/dkim/message-parser.js +28 -4
  29. package/dist/esm/dkim/sign.d.ts +1 -0
  30. package/dist/esm/dkim/sign.js +1 -0
  31. package/dist/esm/errors.d.ts +3 -3
  32. package/dist/esm/mailer/index.d.ts +7 -1
  33. package/dist/esm/mime-funcs/index.d.ts +2 -1
  34. package/dist/esm/mime-funcs/index.js +197 -83
  35. package/dist/esm/mime-node/index.js +2 -1
  36. package/dist/esm/nodemailer.d.ts +13 -2
  37. package/dist/esm/package-info.d.ts +1 -1
  38. package/dist/esm/package-info.js +1 -1
  39. package/dist/esm/punycode/index.js +1 -0
  40. package/dist/esm/ses-transport/index.d.ts +6 -1
  41. package/dist/esm/shared/index.d.ts +25 -1
  42. package/dist/esm/shared/index.js +1 -0
  43. package/dist/esm/smtp-connection/http-proxy-client.d.ts +11 -3
  44. package/dist/esm/smtp-connection/http-proxy-client.js +17 -7
  45. package/dist/esm/smtp-connection/index.d.ts +1 -1
  46. package/dist/esm/smtp-connection/index.js +15 -1
  47. package/dist/esm/smtp-pool/index.d.ts +6 -1
  48. package/dist/esm/smtp-transport/index.d.ts +6 -1
  49. package/dist/esm/well-known/services.js +6 -0
  50. package/dist/well-known/services.json +7 -0
  51. package/package.json +8 -8
@@ -88,6 +88,7 @@ function quoteString(value) {
88
88
  * no need to encode the values in any way. If the value is plaintext but has
89
89
  * longer lines then allowed, then use format=flowed
90
90
  *
91
+ * @param str Multi line string to check
91
92
  * @param lineLength Max line length to check for
92
93
  * @returns Returns true if there is at least one line longer than lineLength chars
93
94
  */
@@ -234,7 +235,10 @@ function buildHeaderValue(structured) {
234
235
  }
235
236
  });
236
237
  }
237
- else if (/[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
238
+ else if (!value.length || /[\s'"\\;:/=(),<>@[\]?]|^-/.test(value)) {
239
+ // a parameter value is a token or a quoted-string and a token is never empty, so
240
+ // a valueless parameter such as the 'flag' of 'multipart/mixed; flag; boundary=b'
241
+ // goes out as 'flag=""' rather than as the 'flag=' that parses as neither
238
242
  paramsArray.push(param + '=' + JSON.stringify(value));
239
243
  }
240
244
  else {
@@ -256,9 +260,9 @@ function buildHeaderValue(structured) {
256
260
  * title*0*=utf-8''unicode
257
261
  * title*1*=%20string
258
262
  *
263
+ * @param key Parameter name the generated keys are built from, for example title
259
264
  * @param data String to be encoded
260
265
  * @param [maxLength=50] Max length for generated chunks
261
- * @param [fromCharset='UTF-8'] Source sharacter set
262
266
  * @return A list of encoded keys and headers
263
267
  */
264
268
  function buildHeaderParam(key, data, maxLength) {
@@ -385,6 +389,26 @@ function buildHeaderParam(key, data, maxLength) {
385
389
  value: item.line
386
390
  }));
387
391
  }
392
+ /**
393
+ * An RFC 2045 token: printable ASCII without SPACE, the control characters, DEL and the
394
+ * tspecials. A charset name is one, and the name a continuation carries is written into
395
+ * the encoded word the parameter value becomes, so it is checked against this before it
396
+ * goes in. Whitespace used to come off it only because the value was trimmed first.
397
+ */
398
+ const TOKEN = /^[^\x00-\x20\x7f()<>@,;:\\"/[\]?=]+$/;
399
+ /**
400
+ * Whether a string can be a header parameter name.
401
+ *
402
+ * A parameter name is a token, so it is never empty. A "__proto__" name would target the
403
+ * prototype chain of the params object instead of an own property of it and read back as
404
+ * Object.prototype, so it is no name either.
405
+ *
406
+ * @param name Candidate parameter name, already lowercased
407
+ * @return true when the name can be used
408
+ */
409
+ function _isParamName(name) {
410
+ return !!name && !(0, objects_js_1.isProtoKey)(name);
411
+ }
388
412
  /**
389
413
  * Parses a header value with key=value arguments into a structured
390
414
  * object.
@@ -405,134 +429,224 @@ function parseHeaderValue(str) {
405
429
  value: '',
406
430
  params: {}
407
431
  };
408
- // Parameter names come from a caller supplied contentType/contentDisposition. A
409
- // "__proto__" name would target the prototype chain of the params object instead of
410
- // an own property of it, and read back as Object.prototype, so it is dropped.
432
+ // A duplicated parameter resolves to its first occurrence, the way a duplicated header
433
+ // does. Letting the last one win disagrees with the receivers that take the first, and
434
+ // the two readings of 'boundary="b"; boundary="c"' name different delimiters. The
435
+ // continuation join below is the only writer of the name it builds, so it tests the
436
+ // name with _isParamName directly rather than taking that rule along from here.
411
437
  const setParam = (name, value) => {
412
- if (!(0, objects_js_1.isProtoKey)(name)) {
438
+ name = name.toLowerCase();
439
+ if (_isParamName(name) && !Object.prototype.hasOwnProperty.call(response.params, name)) {
413
440
  response.params[name] = value;
414
441
  }
415
442
  };
416
443
  let key = false;
417
444
  let value = '';
418
- let type = 'value';
445
+ let stage = 'value';
419
446
  let quote = false;
420
447
  let escaped = false;
421
448
  let chr;
449
+ // Whitespace seen outside a quoted string is held back until a significant character
450
+ // follows it, so the whitespace around a value is dropped without trimming spaces the
451
+ // sender quoted on purpose. Trimming the stored value instead loses the trailing space
452
+ // of 'filename*0="Annual Report "', which the next continuation section is appended to.
453
+ let pendingSpace = '';
454
+ let quoteClosed = false;
455
+ // Whitespace ahead of the first character of a value is padding and is dropped, the
456
+ // whitespace between two characters of it is content
457
+ const flushSpace = () => {
458
+ if (value.length) {
459
+ value += pendingSpace;
460
+ }
461
+ pendingSpace = '';
462
+ };
463
+ const addChr = (c) => {
464
+ flushSpace();
465
+ value += c;
466
+ };
467
+ const takeValue = () => {
468
+ const taken = value;
469
+ value = '';
470
+ pendingSpace = '';
471
+ quoteClosed = false;
472
+ return taken;
473
+ };
474
+ const storeValue = () => {
475
+ const taken = takeValue();
476
+ if (key === false) {
477
+ response.value = taken;
478
+ }
479
+ else {
480
+ setParam(key, taken);
481
+ }
482
+ };
483
+ // A parameter name with no '=' is a valueless parameter, not the start of the next one.
484
+ // Without this the name keeps growing across the ';' and swallows whatever follows, which
485
+ // is how 'multipart/mixed; flag; boundary="AAA"' lost its boundary to a parameter named
486
+ // 'flag; boundary' and left the node declaring the generated boundary beside the asked
487
+ // for one, so a receiver reading the first of the two found no delimiter it matched.
488
+ const storeEmptyKey = () => {
489
+ setParam(takeValue().trim(), '');
490
+ };
422
491
  for (let i = 0, len = str.length; i < len; i++) {
423
492
  chr = str.charAt(i);
424
- if (type === 'key') {
493
+ if (stage === 'key') {
425
494
  if (chr === '=') {
426
- key = value.trim().toLowerCase();
427
- type = 'value';
428
- value = '';
495
+ key = takeValue().trim();
496
+ stage = 'value';
497
+ continue;
498
+ }
499
+ if (chr === ';') {
500
+ storeEmptyKey();
429
501
  continue;
430
502
  }
431
503
  value += chr;
432
504
  }
433
505
  else {
506
+ if (quoteClosed && chr !== ';') {
507
+ // Nothing behind a closed quoted string reaches the value. RFC 2045 says a
508
+ // parameter value is a token or a quoted string and not both, so what follows
509
+ // one is junk and only the ';' that ends the parameter still counts. Tested
510
+ // ahead of the branches rather than beside the append at the foot of them,
511
+ // where each branch above was a way around it: a second '"' reopened quoting
512
+ // and swallowed the rest of the header, so 'boundary="AAA" "; boundary=BBB"'
513
+ // read as a single boundary of 'AAA ', and the junk of
514
+ // 'boundary="AAA" (unterminated comment' still joined the declared boundary
515
+ // through the escape branch. quoteClosed is only ever set while no quote is
516
+ // open, and this is what keeps one from being opened afterwards.
517
+ escaped = false;
518
+ continue;
519
+ }
434
520
  if (escaped) {
435
- value += chr;
521
+ addChr(chr);
436
522
  }
437
- else if (chr === '\\') {
523
+ else if (quote && chr === '\\') {
524
+ // a backslash only escapes inside a quoted string, everywhere else it is an
525
+ // ordinary character. Treating it as an escape turns the parameter value
526
+ // 'C:\Users\me\report.txt' into 'C:Usersmereport.txt'
438
527
  escaped = true;
439
528
  continue;
440
529
  }
441
530
  else if (quote && chr === quote) {
442
531
  quote = false;
532
+ quoteClosed = true;
443
533
  }
444
534
  else if (!quote && chr === '"') {
445
535
  quote = chr;
536
+ flushSpace();
446
537
  }
447
538
  else if (!quote && chr === ';') {
448
- if (key === false) {
449
- response.value = value.trim();
450
- }
451
- else {
452
- setParam(key, value.trim());
453
- }
454
- type = 'key';
455
- value = '';
539
+ storeValue();
540
+ stage = 'key';
541
+ }
542
+ else if (!quote && (chr === ' ' || chr === '\t')) {
543
+ pendingSpace += chr;
456
544
  }
457
545
  else {
458
- value += chr;
546
+ addChr(chr);
459
547
  }
460
548
  escaped = false;
461
549
  }
462
550
  }
463
- if (type === 'value') {
464
- if (key === false) {
465
- response.value = value.trim();
466
- }
467
- else {
468
- setParam(key, value.trim());
469
- }
551
+ if (stage === 'value') {
552
+ storeValue();
470
553
  }
471
- else if (value.trim()) {
472
- setParam(value.trim().toLowerCase(), '');
554
+ else {
555
+ // a key with no value, as in 'Header-Key: somevalue; key=value; emptykey'
556
+ storeEmptyKey();
473
557
  }
474
558
  // handle parameter value continuations
475
559
  // https://tools.ietf.org/html/rfc2231#section-3
476
- // preprocess values
560
+ // Sections are collected in a list and ordered below rather than written into an array
561
+ // at their own section number. An index write makes the array as long as the number the
562
+ // header asked for, and the join that follows walks all of it, so the 55 byte value
563
+ // "attachment; filename*0*=utf-8''a; filename*4000000000=b" held a core for over two
564
+ // minutes.
565
+ const continuations = new Map();
477
566
  Object.keys(response.params).forEach(key => {
478
- let actualKey, nr, match, value;
479
- if ((match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/))) {
480
- actualKey = key.substr(0, match.index);
481
- nr = Number(match[2] || match[3]) || 0;
482
- if ((0, objects_js_1.isProtoKey)(actualKey)) {
483
- // see setParam. Reading it back would yield Object.prototype, which is
484
- // an object, so the initializer below would be skipped and the write
485
- // that follows would throw out of a header build the caller can not catch
486
- delete response.params[key];
487
- return;
488
- }
489
- if (!response.params[actualKey] || typeof response.params[actualKey] !== 'object') {
490
- response.params[actualKey] = {
491
- charset: false,
492
- values: []
493
- };
494
- }
495
- value = response.params[key];
496
- if (nr === 0 && match[0].substr(-1) === '*' && (match = value.match(/^([^']*)'[^']*'(.*)$/))) {
497
- response.params[actualKey].charset = match[1] || 'iso-8859-1';
498
- value = match[2];
499
- }
500
- response.params[actualKey].values[nr] = value;
501
- // remove the old reference
502
- delete response.params[key];
567
+ const match = key.match(/(\*(\d+)|\*(\d+)\*|\*)$/);
568
+ if (!match) {
569
+ // not a continuation parameter, there is nothing to join
570
+ return;
571
+ }
572
+ const actualKey = key.substr(0, match.index);
573
+ const nr = Number(match[2] || match[3]) || 0;
574
+ // RFC 2231 section 4.1: only a section whose name ends in '*' is percent encoded
575
+ const encoded = match[0].substr(-1) === '*';
576
+ // remove the old reference
577
+ let value = response.params[key];
578
+ delete response.params[key];
579
+ if (!_isParamName(actualKey)) {
580
+ // the joined value can not be written back under this name. It is empty when the
581
+ // continuation suffix was all there was of it, as in the bare '*' of 'text/plain; a;*'
582
+ return;
583
+ }
584
+ let continuation = continuations.get(actualKey);
585
+ if (!continuation) {
586
+ continuation = { charset: false, sections: [] };
587
+ continuations.set(actualKey, continuation);
588
+ }
589
+ const charsetMatch = nr === 0 && encoded ? value.match(/^([^']*)'[^']*'(.*)$/) : null;
590
+ if (charsetMatch) {
591
+ // the charset is a token, and anything else named as one is no charset a consumer
592
+ // could resolve, so it reads as the unnamed case rather than being carried into
593
+ // the encoded word below. A "\r\n" of a prefix would otherwise reach a consumer
594
+ // of the parsed value as the charset of a word that no decoder can act on
595
+ continuation.charset = TOKEN.test(charsetMatch[1]) ? charsetMatch[1] : 'iso-8859-1';
596
+ value = charsetMatch[2];
503
597
  }
598
+ continuation.sections.push({ nr, value, encoded });
504
599
  });
505
600
  // concatenate split rfc2231 strings and convert encoded strings to mime encoded words
506
- Object.keys(response.params).forEach(key => {
507
- let value;
508
- if (response.params[key] && Array.isArray(response.params[key].values)) {
509
- value = response.params[key].values.map((val) => val || '').join('');
510
- if (response.params[key].charset) {
511
- // convert "%AB" to "=?charset?Q?=AB?="
512
- response.params[key] =
513
- '=?' +
514
- response.params[key].charset +
515
- '?Q?' +
516
- value
517
- // fix invalidly encoded chars
518
- .replace(/[=?_\s]/g, s => {
519
- const c = s.charCodeAt(0).toString(16);
520
- if (s === ' ') {
521
- return '_';
522
- }
523
- return '%' + (c.length < 2 ? '0' : '') + c;
524
- })
525
- // change from urlencoding to percent encoding
526
- .replace(/%/g, '=') +
527
- '?=';
528
- }
529
- else {
530
- response.params[key] = value;
531
- }
601
+ continuations.forEach((continuation, key) => {
602
+ if (Object.prototype.hasOwnProperty.call(response.params, key)) {
603
+ // The same name was also given as a plain parameter, which the starred keys were
604
+ // just deleted from around, so this write would be the only one in the function
605
+ // to override a name already taken. 'filename=plain.txt; filename*0=evil.txt'
606
+ // resolves to the plain parameter either way round, so the reading does not come
607
+ // down to which of the two spellings the sender put first
608
+ return;
609
+ }
610
+ continuation.sections.sort((a, b) => a.nr - b.nr);
611
+ if (!continuation.charset) {
612
+ // nothing said which charset the percent escapes of an encoded section are in,
613
+ // so every section is passed on as the text it already is
614
+ response.params[key] = continuation.sections.map(section => section.value).join('');
615
+ return;
532
616
  }
617
+ // convert "%AB" to "=?charset?Q?=AB?="
618
+ response.params[key] = '=?' + continuation.charset + '?Q?' + continuation.sections.map(_encodeContinuationSection).join('') + '?=';
533
619
  });
534
620
  return response;
535
621
  }
622
+ /**
623
+ * Renders one parameter value continuation section as the payload of a Q encoded word.
624
+ *
625
+ * A section whose name ends in '*' is percent encoded and its escapes carry the bytes of
626
+ * the value, so they only have to be rewritten into the "=AB" spelling a Q encoded word
627
+ * uses. A section without the '*' is literal text (RFC 2231 section 4.1), so its '%' is a
628
+ * '%' and is escaped along with the characters a Q encoded word can not carry bare.
629
+ * Decoding a literal section invents bytes that never appeared on the wire: it is how the
630
+ * value 'filename*0*=utf-8''safe; filename*1=%2F..%2F..%2Fetc%2Fpasswd' was emitted as a
631
+ * filename every receiving client reads back as 'safe/../../etc/passwd'.
632
+ *
633
+ * @param section One collected continuation section
634
+ * @return The section as Q encoded word payload
635
+ */
636
+ function _encodeContinuationSection(section) {
637
+ const specials = section.encoded ? /[=?_\s]/g : /[=?_\s%]/g;
638
+ return (section.value
639
+ // fix invalidly encoded chars
640
+ .replace(specials, s => {
641
+ const c = s.charCodeAt(0).toString(16);
642
+ if (s === ' ') {
643
+ return '_';
644
+ }
645
+ return '%' + (c.length < 2 ? '0' : '') + c;
646
+ })
647
+ // change from urlencoding to percent encoding
648
+ .replace(/%/g, '='));
649
+ }
536
650
  /**
537
651
  * Returns file extension for a content type string. If no suitable extensions
538
652
  * are found, 'bin' is used as the default extension
@@ -1294,6 +1294,7 @@ class MimeNode {
1294
1294
  *
1295
1295
  * @param addresses An array of address objects
1296
1296
  * @param [uniqueList] An array to be populated with addresses
1297
+ * @param [seenAddresses] Addresses already added to uniqueList, shared with recursive calls to keep deduplication linear
1297
1298
  * @return address string
1298
1299
  * @internal
1299
1300
  */
@@ -1431,7 +1432,7 @@ class MimeNode {
1431
1432
  /**
1432
1433
  * If needed, mime encodes the name part
1433
1434
  *
1434
- * @param name Name part of an address
1435
+ * @param value Name part of an address
1435
1436
  * @returns Mime word encoded string if needed
1436
1437
  * @internal
1437
1438
  */
@@ -75,11 +75,22 @@ export declare function createTransport(transporter?: TransportConfig | Transpor
75
75
  /**
76
76
  * Creates a test account from the Ethereal service (https://ethereal.email)
77
77
  *
78
- * @param apiUrl Optional API endpoint, defaults to https://api.nodemailer.com
79
- * @param callback Callback function to run with the account object. If not set, a Promise is returned
78
+ * @param callback Callback function to run with the account object
80
79
  */
81
80
  export declare function createTestAccount(callback: TestAccountCallback): void;
81
+ /**
82
+ * Creates a test account from the Ethereal service (https://ethereal.email)
83
+ *
84
+ * @param apiUrl API endpoint, defaults to https://api.nodemailer.com
85
+ * @param callback Callback function to run with the account object
86
+ */
82
87
  export declare function createTestAccount(apiUrl: string | false | null | undefined, callback: TestAccountCallback): void;
88
+ /**
89
+ * Creates a test account from the Ethereal service (https://ethereal.email)
90
+ *
91
+ * @param [apiUrl] API endpoint, defaults to https://api.nodemailer.com
92
+ * @returns Promise that resolves with the account object
93
+ */
83
94
  export declare function createTestAccount(apiUrl?: string | false | null): Promise<TestAccount>;
84
95
  /**
85
96
  * Resolves the Ethereal web URL for a message sent through an Ethereal test account
@@ -1,3 +1,3 @@
1
1
  export declare const name = "nodemailer";
2
- export declare const version = "10.0.13";
2
+ export declare const version = "10.0.15";
3
3
  export declare const homepage = "https://nodemailer.com/";
@@ -3,5 +3,5 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.homepage = exports.version = exports.name = void 0;
5
5
  exports.name = 'nodemailer';
6
- exports.version = '10.0.13';
6
+ exports.version = '10.0.15';
7
7
  exports.homepage = 'https://nodemailer.com/';
@@ -178,6 +178,7 @@ const basicToDigit = function (codePoint) {
178
178
  * @see `basicToDigit()`
179
179
  * @private
180
180
  * @param digit The numeric value of a basic code point.
181
+ * @param flag Non-zero to use the uppercase form of the code point.
181
182
  * @returns The basic code point whose value (when used for
182
183
  * representing integers) is `digit`, which needs to be in the range
183
184
  * `0` to `base - 1`. If `flag` is non-zero, the uppercase form is
@@ -85,9 +85,14 @@ declare class SESTransport extends EventEmitter {
85
85
  /**
86
86
  * Verifies SES configuration
87
87
  *
88
- * @param callback Callback function
88
+ * @returns Promise that resolves to true if the configuration is usable
89
89
  */
90
90
  verify(): Promise<true>;
91
+ /**
92
+ * Verifies SES configuration
93
+ *
94
+ * @param callback Callback function
95
+ */
91
96
  verify(callback: VerifyCallback): void;
92
97
  }
93
98
  /**
@@ -187,6 +187,7 @@ export declare const parseConnectionUrl: (str?: string | null) => ConnectionUrlO
187
187
  * creates a default console logger
188
188
  *
189
189
  * @param [options] Options object that might include 'logger' value
190
+ * @param [defaults] Fields merged into every log entry, overridden by the fields of the entry itself
190
191
  * @return bunyan compatible logger
191
192
  */
192
193
  export declare const getLogger: (options?: GetLoggerOptions, defaults?: LogEntry) => Logger;
@@ -210,18 +211,41 @@ export declare const parseDataURI: (uri: unknown) => ParsedDataURI | null;
210
211
  *
211
212
  * @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
212
213
  * @param key Property name or an Array index
213
- * @param [options] Optional access policy: { disableFileAccess, disableUrlAccess }
214
214
  * @param callback Callback function with (err, value)
215
215
  */
216
216
  export declare function resolveContent(data: {
217
217
  [key: string]: any;
218
218
  }, key: string | number, callback: ResolveContentCallback): void;
219
+ /**
220
+ * Resolves a String or a Buffer value for content value
221
+ *
222
+ * @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
223
+ * @param key Property name or an Array index
224
+ * @param options Access policy: { disableFileAccess, disableUrlAccess }
225
+ * @param callback Callback function with (err, value)
226
+ */
219
227
  export declare function resolveContent(data: {
220
228
  [key: string]: any;
221
229
  }, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback): void;
230
+ /**
231
+ * Resolves a String or a Buffer value for content value
232
+ *
233
+ * @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
234
+ * @param key Property name or an Array index
235
+ * @param [options] Optional access policy: { disableFileAccess, disableUrlAccess }
236
+ * @returns Promise that resolves with the value
237
+ */
222
238
  export declare function resolveContent(data: {
223
239
  [key: string]: any;
224
240
  }, key: string | number, options?: ResolveContentOptions | false): Promise<any>;
241
+ /**
242
+ * Resolves a String or a Buffer value for content value
243
+ *
244
+ * @param data An object or an Array you want to resolve an element for, see ContentDescriptor for the values it understands
245
+ * @param key Property name or an Array index
246
+ * @param options Access policy: { disableFileAccess, disableUrlAccess }
247
+ * @param callback Callback function with (err, value), a Promise is returned if not set
248
+ */
225
249
  export declare function resolveContent(data: {
226
250
  [key: string]: any;
227
251
  }, key: string | number, options: ResolveContentOptions | false | undefined, callback: ResolveContentCallback | undefined): Promise<any> | void;
@@ -362,6 +362,7 @@ exports._logFunc = _logFunc;
362
362
  * creates a default console logger
363
363
  *
364
364
  * @param [options] Options object that might include 'logger' value
365
+ * @param [defaults] Fields merged into every log entry, overridden by the fields of the entry itself
365
366
  * @return bunyan compatible logger
366
367
  */
367
368
  const getLogger = (options, defaults) => {
@@ -21,13 +21,21 @@ export type HttpProxyClientCallback = Callback<net.Socket>;
21
21
  * socket.write("GET / HTTP/1.0\r\n\r\n");
22
22
  * });
23
23
  *
24
- * @param proxyUrl proxy configuration, etg "http://proxy.host:3128/"
24
+ * @param proxyUrl proxy configuration, e.g. "http://proxy.host:3128/"
25
25
  * @param destinationPort Port to open in destination host
26
26
  * @param destinationHost Destination hostname
27
- * @param [tlsOptions] Optional TLS options for an HTTPS proxy (e.g. { rejectUnauthorized: false })
28
- * @param callback Callback to run with the rocket object once connection is established
27
+ * @param callback Callback to run with the socket object once connection is established
29
28
  */
30
29
  declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, callback: HttpProxyClientCallback): void;
30
+ /**
31
+ * Establishes proxied connection to destinationPort through an HTTPS proxy
32
+ *
33
+ * @param proxyUrl proxy configuration, e.g. "https://proxy.host:3128/"
34
+ * @param destinationPort Port to open in destination host
35
+ * @param destinationHost Destination hostname
36
+ * @param tlsOptions TLS options for the proxy connection (e.g. { rejectUnauthorized: false })
37
+ * @param callback Callback to run with the socket object once connection is established
38
+ */
31
39
  declare function httpProxyClient(proxyUrl: string, destinationPort: number | string, destinationHost: string, tlsOptions: HttpProxyClientOptions | undefined, callback: HttpProxyClientCallback): void;
32
40
  /**
33
41
  * Socket timeout in milliseconds while the CONNECT handshake is in progress, defaults to 30 seconds.
@@ -126,18 +126,28 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
126
126
  .join('\r\n') +
127
127
  // End request
128
128
  '\r\n\r\n');
129
- let headers = '';
129
+ // The response is collected as chunks and only the bytes that just arrived, together
130
+ // with the three before them, are searched for the end of the headers. Appending to a
131
+ // string and searching all of it again re-read the whole response on every chunk.
132
+ const chunks = [];
133
+ let received = 0;
134
+ let tail = '';
130
135
  const onSocketData = (chunk) => {
131
136
  let match;
132
- let remainder;
133
137
  if (finished) {
134
138
  return;
135
139
  }
136
- headers += chunk.toString('binary');
137
- if ((match = headers.match(/\r\n\r\n/))) {
140
+ const window = tail + chunk.toString('binary');
141
+ const windowEnd = window.indexOf('\r\n\r\n');
142
+ chunks.push(chunk);
143
+ received += chunk.length;
144
+ tail = window.slice(-3);
145
+ if (windowEnd >= 0) {
138
146
  socket.removeListener('data', onSocketData);
139
- remainder = headers.substr(match.index + match[0].length);
140
- headers = headers.substr(0, match.index);
147
+ const headerEnd = received - window.length + windowEnd;
148
+ const response = Buffer.concat(chunks, received).toString('binary');
149
+ const headers = response.substr(0, headerEnd);
150
+ const remainder = response.substr(headerEnd + 4);
141
151
  if (remainder) {
142
152
  socket.unshift(Buffer.from(remainder, 'binary'));
143
153
  }
@@ -161,7 +171,7 @@ function httpProxyClient(proxyUrl, destinationPort, destinationHost, tlsOptions,
161
171
  socket.setTimeout(0);
162
172
  return done(null, socket);
163
173
  }
164
- if (headers.length > MAX_RESPONSE_HEADER_BYTES) {
174
+ if (received > MAX_RESPONSE_HEADER_BYTES) {
165
175
  socket.removeListener('data', onSocketData);
166
176
  const err = new Error('Proxy response headers too large');
167
177
  err.code = errors.EPROXY;
@@ -379,7 +379,7 @@ declare class SMTPConnection extends EventEmitter {
379
379
  *
380
380
  * @param envelope Envelope object, {from: addr, to: [addr]}
381
381
  * @param message String, Buffer or a Stream
382
- * @param callback Callback to return once sending is completed
382
+ * @param done Callback to return once sending is completed
383
383
  */
384
384
  send(envelope: SMTPEnvelope, message: string | Buffer | Readable, done: SMTPConnectionSendCallback): void;
385
385
  /**