@flun/mailer 2.1.24 → 2.1.25

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.
@@ -22,15 +22,15 @@ const B64 = 'base64', QP = 'quoted-printable';
22
22
  * 创建一个新的MIME树节点;如果是分支节点,假设内容类型为'multipart/*',
23
23
  * 其他任何内容类型都视为叶节点,如果选项中缺少rootNode,则假设此为根节点;
24
24
  * >查看定义:@see {@link MimeNode}
25
- * @param {String} contentType 定义节点的内容类型,对于附件可以留空(从文件名派生)
25
+ * @param {String} contentType 定义节点的内容类型,对于附件可以留空(从文件名派生)
26
26
  * @param {Object} [options] 可选选项
27
27
  * @param {Object} [options.rootNode] 此树的根节点
28
28
  * @param {Object} [options.parentNode] 此节点的直接父节点
29
29
  * @param {Object} [options.filename] 附件节点的文件名
30
30
  * @param {String} [options.baseBoundary] 唯一多部分边界值的共享部分
31
- * @param {Boolean} [options.keepBcc] 如果为true,不在生成的头部中排除Bcc
31
+ * @param {Boolean} [options.keepBcc] 如果为true,不在生成的头部中排除Bcc
32
32
  * @param {Function} [options.normalizeHeaderKey] 用于自定义头部键大小写规范化的方法
33
- * @param {String} [options.textEncoding] 编码类型:'Q'(默认)或'B'
33
+ * @param {String} [options.textEncoding] 编码类型:'Q'(默认)或'B'
34
34
  */
35
35
  class MimeNode {
36
36
  constructor(contentType, options = {}) {
@@ -50,25 +50,29 @@ class MimeNode {
50
50
  }
51
51
 
52
52
  this.newline = newline; // 如果设置为'win'则使用\r\n,如果为'linux'则使用\n;如果未设置(或使用`raw`),则换行符保持原样
53
- this.textEncoding = textEncoding.toString().trim().charAt(0).toUpperCase(); // 指示头部字符串应使用的编码:"Q"或"B"
54
- this.parentNode = parentNode; // 此节点的直接父节点(如果未设置则为undefined)
53
+ this.textEncoding = textEncoding.toString().trim().charAt(0).toUpperCase(); // 指示头部字符串应使用的编码:"Q"或"B"
54
+ // 此节点的直接父节点(如果未设置则为undefined)
55
+ this.parentNode = parentNode;
55
56
  this.hostname = hostname; // 默认message - id值使用的主机名
56
57
 
57
58
  this.date = new Date(); // 当前日期
58
59
  this.childNodes = []; // 可能的子节点数组
59
- this._headers = []; // 此节点的头部值列表,格式为[{ key: '', value: '' }]
60
- this._transforms = []; // 在通过createReadStream暴露之前,消息将被管道传输的额外转换流
61
- this._processFuncs = []; // 在通过createReadStream暴露之前,消息将被管道传输的额外处理函数
62
- this._nodeId = ++this.rootNode.nodeCounter; // 用于生成唯一边界值(前缀添加到共享基础值之前)
60
+ this._headers = []; // 此节点的头部值列表,格式为[{ key: '', value: '' }]
61
+ // 在通过createReadStream暴露之前,消息将被管道传输的额外转换流
62
+ this._transforms = [];
63
+ // 在通过createReadStream暴露之前,消息将被管道传输的额外处理函数
64
+ this._processFuncs = [];
65
+ this._nodeId = ++this.rootNode.nodeCounter; // 用于生成唯一边界值(前缀添加到共享基础值之前)
63
66
  this._isPlainText = false; // 如果内容仅使用ASCII可打印字符则为true
64
67
  this._hasLongLines = false; // 如果内容是纯文本但有超过允许长度的行则为true
65
- this._envelope = false; // 如果设置,则使用此值作为信封而不是生成一个
66
- this._raw = false; // 如果设置,则使用此值作为流内容而不是构建它
67
- if (contentType) this.setHeader('Content-Type', contentType); // 如果设置了内容类型(或从文件名派生),则将其添加到头部
68
+ this._envelope = false; // 如果设置,则使用此值作为信封而不是生成一个
69
+ this._raw = false; // 如果设置,则使用此值作为流内容而不是构建它
70
+ // 如果设置了内容类型(或从文件名派生),则将其添加到头部
71
+ if (contentType) this.setHeader('Content-Type', contentType);
68
72
  }
69
73
 
70
74
  /**
71
- * 创建并附加一个子节点。提供的参数传递给MimeNode构造函数
75
+ * 创建并附加一个子节点;提供的参数传递给MimeNode构造函数
72
76
  *
73
77
  * @param {String} [contentType] 可选内容类型
74
78
  * @param {Object} [options] 可选选项对象
@@ -127,11 +131,12 @@ class MimeNode {
127
131
  }
128
132
  }
129
133
 
130
- // 统一处理头部操作,支持多种输入格式和操作类型
134
+ // 统一处理头部操作,支持多种输入格式和操作类型
131
135
  #processHeadersOp(key, value, singleOp, isSet) {
132
- // 如果是批量操作(没有value且key是对象)
136
+ // 如果是批量操作(没有value且key是对象)
133
137
  if (!value && key && typeof key === 'object') {
134
- // 允许{key:'content-type',value:'text/plain'},[{key:'content-type',value:'text/plain'}],{'content-type':'text/plain'}
138
+ // 允许{key:'content-type',value:'text/plain'},[{key:'content-type',value:'text/plain'}],
139
+ // {'content-type':'text/plain'}
135
140
  if (key.key && 'value' in key) singleOp.call(this, key.key, key.value);
136
141
  else if (Array.isArray(key)) key.forEach(i => singleOp.call(this, i.key, i.value));
137
142
  else Object.keys(key).forEach(i => singleOp.call(this, i, key[i]));
@@ -148,29 +153,29 @@ class MimeNode {
148
153
  }
149
154
 
150
155
  /**
151
- * 设置头部值。如果选定键的值已存在,则会被覆盖。
152
- * 可以使用[{key:'', value:''}]或{key: 'value'}作为第一个参数来设置多个值。
156
+ * 设置头部值;如果选定键的值已存在,则会被覆盖;
157
+ * 可以使用[{key:'', value:''}]或{key: 'value'}作为第一个参数来设置多个值;
153
158
  *
154
159
  * @param {String|Array|Object} key 头部键或键值对列表
155
160
  * @param {String} value 头部值
156
161
  * @return {Object} 当前节点
157
162
  */
158
163
  setHeader(key, value) {
159
- // 如果未处理批量操作,说明是单个操作,已经在#processHeadersOp中处理完成
164
+ // 如果未处理批量操作,说明是单个操作,已经在#processHeadersOp中处理完成
160
165
  if (!this.#processHeadersOp(key, value, this.setHeader, true)); // true表示是set操作
161
166
  return this;
162
167
  }
163
168
 
164
169
  /**
165
- * 添加头部值。如果选定键的值已存在,则值将作为新字段附加,旧字段不被触动。
166
- * 您也可以使用[{key:'', value:''}]或{key: 'value'}作为第一个参数来设置多个值。
170
+ * 添加头部值;如果选定键的值已存在,则值将作为新字段附加,旧字段不被触动;
171
+ * 您也可以使用[{key:'', value:''}]或{key: 'value'}作为第一个参数来设置多个值;
167
172
  *
168
173
  * @param {String|Array|Object} key 头部键或键值对列表
169
174
  * @param {String} value 头部值
170
175
  * @return {Object} 当前节点
171
176
  */
172
177
  addHeader(key, value) {
173
- // 如果未处理批量操作,说明是单个操作,已经在#processHeadersOp中处理完成
178
+ // 如果未处理批量操作,说明是单个操作,已经在#processHeadersOp中处理完成
174
179
  if (!this.#processHeadersOp(key, value, this.addHeader, false)); // false表示是add操作
175
180
  return this;
176
181
  }
@@ -188,8 +193,8 @@ class MimeNode {
188
193
  }
189
194
 
190
195
  /**
191
- * 设置当前节点的正文内容。如果值是字符串,则自动将字符集添加到Content-Type(如果是text/*)。
192
- * 如果值是Buffer,则需要自行指定字符集
196
+ * 设置当前节点的正文内容;如果值是字符串,则自动将字符集添加到Content-Type(如果是text/*);
197
+ * 如果值是Buffer,则需要自行指定字符集
193
198
  *
194
199
  * @param (String|Buffer) content 正文内容
195
200
  * @return {Object} 当前节点
@@ -197,14 +202,14 @@ class MimeNode {
197
202
  setContent(content) {
198
203
  this.content = content;
199
204
  if (typeof this.content.pipe === 'function') {
200
- // 预流处理程序。如果流被设置为内容,并且在此流执行任何操作之前触发'error',则可能被触发
205
+ // 预流处理程序;如果流被设置为内容,并且在此流执行任何操作之前触发'error',则可能被触发
201
206
  this.contentErrorHandler = err => {
202
207
  this.content.removeListener('error', this.contentErrorHandler), this.content = err;
203
208
  };
204
209
  this.content.once('error', this.contentErrorHandler);
205
210
  } else if (typeof this.content === 'string') {
206
211
  this._isPlainText = isPlainText(this.content);
207
- // 如果有超过76个符号/字节的行,则不使用7bit编码,而是使用8位编码。
212
+ // 如果有超过76个符号/字节的行,则不使用7bit编码,而是使用8位编码;
208
213
  if (this._isPlainText && hasLongerLines(this.content, 76)) this._hasLongLines = true;
209
214
  }
210
215
  return this;
@@ -212,7 +217,7 @@ class MimeNode {
212
217
 
213
218
  // 构建MIME树
214
219
  build(callback) {
215
- // 如果没有回调,则返回一个承诺,该承诺将在构建完成时解析
220
+ // 如果没有回调,则返回一个承诺,该承诺将在构建完成时解析
216
221
  const promise = !callback ? new Promise((resolve, reject) => callback = callbackPromise(resolve, reject)) : null;
217
222
  resolveStream(this.createReadStream(), callback); // 将流数据读取到Buffer中
218
223
  return promise;
@@ -227,12 +232,14 @@ class MimeNode {
227
232
  encoding = (this.getHeader('Content-Transfer-Encoding') || '').toString().toLowerCase().trim();
228
233
  if (!encoding || ![B64, QP].includes(encoding)) {
229
234
  if (regexs.TEXT_TYPE.test(contentType)) {
230
- if (this._isPlainText && !this._hasLongLines) encoding = '7bit'; // 如果没有特殊符号,则无需修改文本
231
- else if (typeof content === 'string' || content instanceof Buffer) // 否则,内容是字符串或Buffer,则用QP或Base64
235
+ if (this._isPlainText && !this._hasLongLines) encoding = '7bit'; // 如果没有特殊符号,则无需修改文本
236
+ // 否则,内容是字符串或Buffer,则用QP或Base64
237
+ else if (typeof content === 'string' || content instanceof Buffer)
232
238
  encoding = this.#getTextEncoding(content) === 'Q' ? QP : B64;
233
- else encoding = this.textEncoding === 'B' ? B64 : QP; // 否则,如果对于流,因无法检查内容,则使用首选编码或回退到QP
239
+ // 否则,如果对于流,因无法检查内容,则使用首选编码或回退到QP
240
+ else encoding = this.textEncoding === 'B' ? B64 : QP;
234
241
  }
235
- // 否则,如果不是 multipart 或 message 类型,默认使用 base64 编码
242
+ // 否则,如果不是 multipart 或 message 类型,默认使用 base64 编码
236
243
  else if (!regexs.MULTIPART_OR_MESSAGE.test(contentType)) encoding = encoding || B64;
237
244
  }
238
245
  }
@@ -240,7 +247,7 @@ class MimeNode {
240
247
  }
241
248
 
242
249
  /**
243
- * 构建MIME节点的头部块。在写入内容之前追加 \r\n\r\n
250
+ * 构建MIME节点的头部块;在写入内容之前追加 \r\n\r\n
244
251
  *
245
252
  * @returns {String} 头部
246
253
  */
@@ -290,27 +297,29 @@ class MimeNode {
290
297
  break;
291
298
  case C_TYPE:
292
299
  structured = parseHeaderValue(value), this.#handleContentType(structured);
293
- if (regexs.TEXT_PLAIN.test(structured.value) && typeof content === 'string' && regexs.NON_ASCII.test(content))
300
+ if (regexs.TEXT_PLAIN.test(structured.value)
301
+ && typeof content === 'string' && regexs.NON_ASCII.test(content))
294
302
  structured.params.charset = 'utf-8';
295
303
 
296
304
  value = buildHeaderValue(structured);
297
305
  if (filename) {
298
- // 为非兼容客户端(如QQ网页邮件)添加支持,不能使用buildHeaderValue构建值,因为该值是非标准的,并且不支持参数;
306
+ // 为非兼容客户端(如QQ网页邮件)添加支持,不能使用buildHeaderValue构建值,
307
+ // 因为该值是非标准的,并且不支持参数;
299
308
  param = this.#encodeWords(filename);
300
- // 如果参数与文件名匹配,则不添加参数。如果参数包含特殊字符,则添加引号。
309
+ // 如果参数与文件名匹配,则不添加参数;如果参数包含特殊字符,则添加引号;
301
310
  if (param !== filename || regexs.UNSAFE_PARAM_VALUE.test(param)) param = `"${param}"`;
302
311
  value += `;name=${param}`;
303
312
  }
304
313
  break;
305
314
 
306
315
  case 'Bcc':
307
- if (!this.keepBcc) return; // 如果不保留Bcc,则不添加头部
316
+ if (!this.keepBcc) return; // 如果不保留Bcc,则不添加头部
308
317
  break;
309
318
  }
310
319
 
311
320
  value = this.encodeHeaderValue(key, value);
312
321
  if (!value.toString().trim()) return; // 跳过空行
313
- // 如果存在自定义的normalizeHeaderKey函数,则使用它来规范化头部键
322
+ // 如果存在自定义的normalizeHeaderKey函数,则使用它来规范化头部键
314
323
  if (typeof this.normalizeHeaderKey === 'function') {
315
324
  let normalized = this.normalizeHeaderKey(key, value);
316
325
  if (normalized && typeof normalized === 'string' && normalized.length) key = normalized;
@@ -323,8 +332,8 @@ class MimeNode {
323
332
  }
324
333
 
325
334
  /**
326
- * 从当前节点流式传输rfc2822消息。如果这是根节点,
327
- * 则设置缺失的必需头部字段(Date, Message-Id, MIME-Version)
335
+ * 从当前节点流式传输rfc2822消息;如果这是根节点,
336
+ * 则设置缺失的必需头部字段(Date, Message-Id, MIME-Version)
328
337
  *
329
338
  * @return {String} 编译后的消息 */
330
339
  createReadStream(options = {}) {
@@ -351,7 +360,7 @@ class MimeNode {
351
360
  // 在可能的用户转换之后确保终止换行符
352
361
  transform = new LastNewline(), outputStream = pipeStream(outputStream, transform);
353
362
 
354
- // 循环遍历所有后处理函数,并将输出流传递给每个函数。
363
+ // 循环遍历所有后处理函数,并将输出流传递给每个函数;
355
364
  for (let i = 0; i < this._processFuncs.length; i++) {
356
365
  transform = this._processFuncs[i], outputStream = transform(outputStream);
357
366
  }
@@ -367,7 +376,7 @@ class MimeNode {
367
376
  }
368
377
 
369
378
  /**
370
- * 将转换流对象附加到转换列表。最终输出在暴露之前通过此流传递
379
+ * 将转换流对象附加到转换列表;最终输出在暴露之前通过此流传递
371
380
  *
372
381
  * @param {Object} transform 读写流
373
382
  */
@@ -376,7 +385,7 @@ class MimeNode {
376
385
  }
377
386
 
378
387
  /**
379
- * 附加后处理函数。该函数在转换之后运行,并使用以下语法
388
+ * 附加后处理函数;该函数在转换之后运行,并使用以下语法
380
389
  *
381
390
  * processFunc(input) -> outputStream
382
391
  *
@@ -393,7 +402,7 @@ class MimeNode {
393
402
  const callback = args => {
394
403
  cleanup(state), done(args);
395
404
  },
396
- // 对于多部分节点,推送子节点;对于内容节点,结束流
405
+ // 对于多部分节点,推送子节点;对于内容节点,结束流
397
406
  finalize = () => {
398
407
  let childId = 0;
399
408
  const processChildNode = () => {
@@ -416,7 +425,7 @@ class MimeNode {
416
425
  sendContent = () => {
417
426
  const content = this.content;
418
427
  if (content) {
419
- // 如果内容是错误,则直接调用回调
428
+ // 如果内容是错误,则直接调用回调
420
429
  if (Object.prototype.toString.call(content) === '[object Error]') return callback(content);
421
430
 
422
431
  if (typeof content.pipe === 'function') {
@@ -455,12 +464,13 @@ class MimeNode {
455
464
 
456
465
  if (this._raw) {
457
466
  setImmediate(() => {
458
- // 如果内容是错误,则直接调用回调;如果设置错误处理程序,则将其移除
467
+ // 如果内容是错误,则直接调用回调;如果设置错误处理程序,则将其移除
459
468
  if (Object.prototype.toString.call(this._raw) === '[object Error]') return callback(this._raw);
460
469
  if (typeof this._raw.pipe === 'function') this._raw.removeListener('error', this.contentErrorHandler);
461
470
 
462
471
  const raw = this.#getStream(this._raw);
463
- raw.pipe(outputStream, { end: false }), raw.on('end', finalize).on('error', err => outputStream.emit('error', err));
472
+ raw.pipe(outputStream, { end: false })
473
+ , raw.on('end', finalize).on('error', err => outputStream.emit('error', err));
464
474
  });
465
475
  }
466
476
  else outputStream.write(`${this.buildHeaders()}\r\n\r\n`), setImmediate(sendContent);
@@ -469,7 +479,7 @@ class MimeNode {
469
479
  /**
470
480
  * 设置要使用的信封而不是生成的信封
471
481
  *
472
- * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
482
+ * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
473
483
  */
474
484
  setEnvelope(envelope) {
475
485
  this._envelope = { from: false, to: [] };
@@ -482,11 +492,13 @@ class MimeNode {
482
492
 
483
493
  const { to } = this._envelope, standardFields = ['to', 'cc', 'bcc', 'from'];
484
494
  ['to', 'cc', 'bcc'].forEach(key => {
485
- if (envelope[key]) this.#convertAddresses(this.parseAddresses(envelope[key]), to); // 如果存在,则将地址添加到信封中
495
+ // 如果存在,则将地址添加到信封中
496
+ if (envelope[key]) this.#convertAddresses(this.parseAddresses(envelope[key]), to);
486
497
  });
487
498
  this._envelope.to = to.map(to => to.address).filter(address => address);
488
499
  Object.keys(envelope).forEach(key => {
489
- if (!standardFields.includes(key)) this._envelope[key] = envelope[key]; // 如果字段不是标准字段,则将其添加到信封中
500
+ // 如果字段不是标准字段,则将其添加到信封中
501
+ if (!standardFields.includes(key)) this._envelope[key] = envelope[key];
490
502
  });
491
503
 
492
504
  return this;
@@ -513,7 +525,7 @@ class MimeNode {
513
525
  /**
514
526
  * 生成并返回包含发件人地址和收件人地址列表的SMTP信封
515
527
  *
516
- * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
528
+ * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
517
529
  */
518
530
  getEnvelope() {
519
531
  if (this._envelope) return this._envelope;
@@ -532,7 +544,7 @@ class MimeNode {
532
544
  }
533
545
 
534
546
  /**
535
- * 返回Message-Id值。如果不存在,则创建一个
547
+ * 返回Message-Id值;如果不存在,则创建一个
536
548
  *
537
549
  * @return {String} Message-Id值
538
550
  */
@@ -541,14 +553,14 @@ class MimeNode {
541
553
  }
542
554
 
543
555
  /**
544
- * 设置预生成的内容,该内容将用作此节点的输出
556
+ * 设置预生成的内容,该内容将用作此节点的输出
545
557
  *
546
558
  * @param {String|Buffer|Stream} 原始MIME内容
547
559
  */
548
560
  setRaw(raw) {
549
561
  this._raw = raw;
550
562
  if (this._raw && typeof this._raw.pipe === 'function') {
551
- // 预流处理程序。如果流被设置为内容,并且在此流执行任何操作之前触发'error',则可能被触发
563
+ // 预流处理程序;如果流被设置为内容,并且在此流执行任何操作之前触发'error',则可能被触发
552
564
  this.contentErrorHandler = err => {
553
565
  this._raw.removeListener('error', this.contentErrorHandler), this._raw = err;
554
566
  };
@@ -559,7 +571,7 @@ class MimeNode {
559
571
  }
560
572
 
561
573
  /**
562
- * 检测并返回与内容相关的流的句柄。
574
+ * 检测并返回与内容相关的流的句柄;
563
575
  *
564
576
  * @param {Mixed} content 节点内容
565
577
  * @returns {Object} 流对象
@@ -585,25 +597,26 @@ class MimeNode {
585
597
  return stream;
586
598
  }, { _resolvedValue, pipe, path, href, httpHeaders } = content;
587
599
  if (_resolvedValue) return createContentStream(_resolvedValue); // 处理已解析的字符串或Buffer类型内容
588
- else if (typeof pipe === 'function') return content; // 如果是流本身,则直接返回该流
589
- // 如果是文件路径:根据访问权限返回文件流或错误流
600
+ else if (typeof pipe === 'function') return content; // 如果是流本身,则直接返回该流
601
+ // 如果是文件路径:根据访问权限返回文件流或错误流
590
602
  else if (content && typeof path === 'string' && !href)
591
603
  return this.disableFileAccess ? createErrorStream(`文件访问被拒绝:${path}`) : fsReadStream(path);
592
- // 如果是URL:根据访问权限返回网络流或错误流
604
+ // 如果是URL:根据访问权限返回网络流或错误流
593
605
  else if (content && typeof href === 'string')
594
- return this.disableUrlAccess ? createErrorStream(`URL访问被拒绝:${href}`) : nmfetch(href, { headers: httpHeaders });
595
- return createContentStream(content || ''); // 兜底处理:将其他类型内容转换为流
606
+ return this.disableUrlAccess ? createErrorStream(`URL访问被拒绝:${href}`)
607
+ : nmfetch(href, { headers: httpHeaders });
608
+ return createContentStream(content || ''); // 兜底处理:将其他类型内容转换为流
596
609
  }
597
610
 
598
611
  /**
599
- * 解析地址。接受单个地址或数组或地址数组的数组(例如To: [[第一组], [第二组],...])
612
+ * 解析地址;接受单个地址或数组或地址数组的数组(例如To: [[第一组], [第二组],...])
600
613
  *
601
614
  * @param {Mixed} addresses 要解析的地址
602
615
  * @return {Array} 地址对象数组
603
616
  */
604
617
  parseAddresses(addresses) {
605
618
  return [].concat.apply([], [].concat(addresses).map(mAddress => {
606
- // 如果地址和名称都存在,则将其转换为数组
619
+ // 如果地址和名称都存在,则将其转换为数组
607
620
  if (mAddress?.address) {
608
621
  const { address, name = '' } = mAddress;
609
622
  mAddress.address = this.#normalizeAddress(address), mAddress.name = name;
@@ -614,7 +627,7 @@ class MimeNode {
614
627
  }
615
628
 
616
629
  /**
617
- * 规范化头部键,使用驼峰式,除了大写的MIME-
630
+ * 规范化头部键,使用驼峰式,除了大写的MIME-
618
631
  *
619
632
  * @param {String} key 要规范化的键
620
633
  * @return {String} 驼峰式形式的键
@@ -629,8 +642,8 @@ class MimeNode {
629
642
  }
630
643
 
631
644
  /**
632
- * 检查内容类型是否为多部分,并在需要时定义边界。
633
- * 不返回任何内容,而是修改对象参数。
645
+ * 检查内容类型是否为多部分,并在需要时定义边界;
646
+ * 不返回任何内容,而是修改对象参数;
634
647
  *
635
648
  * @param {Object} structured 为'Content-Type'键解析的头部值
636
649
  */
@@ -650,14 +663,15 @@ class MimeNode {
650
663
  }
651
664
 
652
665
  /**
653
- * 对生成的rfc2822电子邮件中使用的头部值进行编码。
666
+ * 对生成的rfc2822电子邮件中使用的头部值进行编码;
654
667
  *
655
668
  * @param {String} key 头部键
656
669
  * @param {String} value 头部值
657
670
  */
658
671
  encodeHeaderValue(key, value = '') {
659
672
  key = this.#normalizeHeaderKey(key);
660
- const rL = resetRegex(regexs.LINEBREAKS), rAB = resetRegex(regexs.ANGLE_BRACKET), rBR = resetRegex(regexs.BLANK_REGEX),
673
+ const rL = resetRegex(regexs.LINEBREAKS), rAB = resetRegex(regexs.ANGLE_BRACKET),
674
+ rBR = resetRegex(regexs.BLANK_REGEX),
661
675
  cleanValue = (val = '') => {
662
676
  return val.toString().replace(rL, ' '); // 将值转换为字符串,并替换掉换行符
663
677
  },
@@ -680,7 +694,7 @@ class MimeNode {
680
694
  case 'In-Reply-To':
681
695
  case 'Content-Id':
682
696
  return ensureAngleBrackets(this.#encodeWords(cleanValue(value)));
683
- // 用<>括起来的值,以空格分隔的列表
697
+ // 用<>括起来的值,以空格分隔的列表
684
698
  case 'References':
685
699
  value = [].concat(value)
686
700
  // 将值转换为字符串,删除尖括号中的空白字符,然后按空白字符分割
@@ -691,8 +705,8 @@ class MimeNode {
691
705
  if (value instanceof Date) return value.toUTCString().replace('GMT', '+0000');
692
706
  return this.#encodeWords(cleanValue(value));
693
707
  case 'Content-Type':
694
- case 'Content-Disposition': return cleanValue(value); // 如果包含文件名,则已编码
695
- default: return this.#encodeWords(cleanValue(value)); // 默认encodeWords仅在需要时编码,否则返回原始字符串
708
+ case 'Content-Disposition': return cleanValue(value); // 如果包含文件名,则已编码
709
+ default: return this.#encodeWords(cleanValue(value)); // 默认encodeWords仅在需要时编码,否则返回原始字符串
696
710
  }
697
711
  }
698
712
 
@@ -734,7 +748,7 @@ class MimeNode {
734
748
  address = address.toString().replace(rFC, ' ').trim();
735
749
 
736
750
  const lIndex = address.lastIndexOf('@');
737
- if (lIndex < 0) return address; // 没有@符号,保持原样
751
+ if (lIndex < 0) return address; // 没有@符号,保持原样
738
752
 
739
753
  let user = address.substring(0, lIndex), encodedDomain;
740
754
  const domain = address.substring(lIndex + 1);
@@ -754,9 +768,9 @@ class MimeNode {
754
768
 
755
769
  /**
756
770
  * 对邮件地址中的名称部分进行编码处理
757
- * 如果名称包含非ASCII字符或特殊字符,则进行MIME编码或引号包裹
771
+ * 如果名称包含非ASCII字符或特殊字符,则进行MIME编码或引号包裹
758
772
  *
759
- * @param {String} name 邮件地址中的名称部分(如 "张三" in "张三<zhangsan@example.com>")
773
+ * @param {String} name 邮件地址中的名称部分(如 "张三" in "张三<zhangsan@example.com>")
760
774
  * @returns {String} 编码后的名称字符串
761
775
  */
762
776
  #encodeAddressName(name) {
@@ -788,7 +802,7 @@ class MimeNode {
788
802
  */
789
803
  #getTextEncoding(value = '') {
790
804
  value = value.toString();
791
- if (this.textEncoding) return this.textEncoding; // 如果有预设编码,直接返回
805
+ if (this.textEncoding) return this.textEncoding; // 如果有预设编码,直接返回
792
806
  const rNL = resetRegex(regexs.NON_LATIN), rL = resetRegex(regexs.LATIN),
793
807
  nonLatinLen = (value.match(rNL) || []).length, latinLen = (value.match(rL) || []).length;
794
808
  return nonLatinLen < latinLen ? 'Q' : 'B';
@@ -800,7 +814,7 @@ class MimeNode {
800
814
  * @return {String} 随机Message-ID值
801
815
  */
802
816
  #generateMessageId() {
803
- // 使用不同长度的随机字节生成类似UUID的格式,优先使用发件人地址中的域名,服务器主机名或localhost次之
817
+ // 使用不同长度的随机字节生成类似UUID的格式,优先使用发件人地址中的域名,服务器主机名或localhost次之
804
818
  const randomPart = [4, 2, 2, 2, 6].map(len => randomBytes(len).toString('hex')).join('-'),
805
819
  domain = (this.getEnvelope().from || this.hostname || 'localhost').split('@').pop();
806
820
  return `<${randomPart}@${domain}>`; // 返回符合RFC标准的Message-ID格式
@@ -18,8 +18,8 @@ class LastNewline extends Transform {
18
18
  * @param {function} done - 完成回调
19
19
  */
20
20
  _transform(chunk, encoding, done) {
21
- if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding); // 如果是字符串,转换为Buffer
22
- if (chunk.length) this.lastByte = chunk.at(-1); // 如果数据块不为空,记录最后一个字节
21
+ if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding); // 如果是字符串,转换为Buffer
22
+ if (chunk.length) this.lastByte = chunk.at(-1); // 如果数据块不为空,记录最后一个字节
23
23
  this.push(chunk), done(); // 将数据块推送到下一个流;完成
24
24
  }
25
25
 
@@ -28,9 +28,9 @@ class LastNewline extends Transform {
28
28
  * @param {function} done - 完成回调
29
29
  */
30
30
  _flush(done) {
31
- if (this.lastByte === 0x0a) return done(); // 如果最后一个字节是换行符,直接结束
31
+ if (this.lastByte === 0x0a) return done(); // 如果最后一个字节是换行符,直接结束
32
32
 
33
- // 如果最后一个字节是回车符,添加换行符
33
+ // 如果最后一个字节是回车符,添加换行符
34
34
  if (this.lastByte === 0x0d) {
35
35
  this.push(Buffer.from('\n'));
36
36
  return done();
@@ -27,9 +27,9 @@ class LeUnix extends Transform {
27
27
  if (chunk[i] === 0x0d) buf = chunk.subarray(lastPos, i), lastPos = i + 1, this.push(buf);
28
28
  }
29
29
 
30
- // 如果剩余数据为真,则推送最后一个回车符之后的数据
30
+ // 如果剩余数据为真,则推送最后一个回车符之后的数据
31
31
  if (lastPos && lastPos < chunk.length) buf = chunk.subarray(lastPos), this.push(buf);
32
- else if (!lastPos) this.push(chunk); // 否则, 如果没有找到回车符,直接推送整个数据块
32
+ else if (!lastPos) this.push(chunk); // 否则, 如果没有找到回车符,直接推送整个数据块
33
33
  done();
34
34
  }
35
35
  }
@@ -8,17 +8,17 @@ import { Transform } from 'stream';
8
8
  class LeWindows extends Transform {
9
9
  constructor(options = {}) {
10
10
  super(options);
11
- this.options = options, this.lastByte = false; // 记录上一个字节,用于跨 chunk 的换行符检测
11
+ this.options = options, this.lastByte = false; // 记录上一个字节,用于跨 chunk 的换行符检测
12
12
  }
13
13
 
14
14
  /**
15
- * 转换函数:确保所有换行符都是 \r\n 格式
15
+ * 转换函数:确保所有换行符都是 \r\n 格式
16
16
  */
17
17
  _transform(chunk, encoding, done) {
18
18
  if (chunk.length === 0) return done();
19
19
  if (typeof chunk === 'string') chunk = Buffer.from(chunk, encoding); // 如果chunk是字符串,根据encoding转换为Buffer
20
20
 
21
- // 快速路径:无换行符直接推送
21
+ // 快速路径:无换行符直接推送
22
22
  if (!chunk.includes(0x0a)) {
23
23
  this.push(chunk), this.lastByte = chunk.at(-1);
24
24
  return done();