@flun/mailer 2.1.23 → 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
  */
@@ -265,9 +272,8 @@ class MimeNode {
265
272
  }
266
273
 
267
274
  this._headers.forEach(header => {
268
- let structured, param, value = header.value;
269
- const key = header.key, options = {},
270
- formattedHeaders = ['From', 'Sender', 'To', 'Cc', 'Bcc', 'Reply-To', 'Date', 'References'];
275
+ let structured, param, value = header.value, key = header.key;
276
+ const options = {}, formattedHeaders = ['From', 'Sender', 'To', 'Cc', 'Bcc', 'Reply-To', 'Date', 'References'];
271
277
 
272
278
  if (value && typeof value === 'object' && !formattedHeaders.includes(key)) {
273
279
  Object.keys(value).forEach(key => {
@@ -291,27 +297,29 @@ class MimeNode {
291
297
  break;
292
298
  case C_TYPE:
293
299
  structured = parseHeaderValue(value), this.#handleContentType(structured);
294
- 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))
295
302
  structured.params.charset = 'utf-8';
296
303
 
297
304
  value = buildHeaderValue(structured);
298
305
  if (filename) {
299
- // 为非兼容客户端(如QQ网页邮件)添加支持,不能使用buildHeaderValue构建值,因为该值是非标准的,并且不支持参数;
306
+ // 为非兼容客户端(如QQ网页邮件)添加支持,不能使用buildHeaderValue构建值,
307
+ // 因为该值是非标准的,并且不支持参数;
300
308
  param = this.#encodeWords(filename);
301
- // 如果参数与文件名匹配,则不添加参数。如果参数包含特殊字符,则添加引号。
309
+ // 如果参数与文件名匹配,则不添加参数;如果参数包含特殊字符,则添加引号;
302
310
  if (param !== filename || regexs.UNSAFE_PARAM_VALUE.test(param)) param = `"${param}"`;
303
311
  value += `;name=${param}`;
304
312
  }
305
313
  break;
306
314
 
307
315
  case 'Bcc':
308
- if (!this.keepBcc) return; // 如果不保留Bcc,则不添加头部
316
+ if (!this.keepBcc) return; // 如果不保留Bcc,则不添加头部
309
317
  break;
310
318
  }
311
319
 
312
320
  value = this.encodeHeaderValue(key, value);
313
321
  if (!value.toString().trim()) return; // 跳过空行
314
- // 如果存在自定义的normalizeHeaderKey函数,则使用它来规范化头部键
322
+ // 如果存在自定义的normalizeHeaderKey函数,则使用它来规范化头部键
315
323
  if (typeof this.normalizeHeaderKey === 'function') {
316
324
  let normalized = this.normalizeHeaderKey(key, value);
317
325
  if (normalized && typeof normalized === 'string' && normalized.length) key = normalized;
@@ -324,8 +332,8 @@ class MimeNode {
324
332
  }
325
333
 
326
334
  /**
327
- * 从当前节点流式传输rfc2822消息。如果这是根节点,
328
- * 则设置缺失的必需头部字段(Date, Message-Id, MIME-Version)
335
+ * 从当前节点流式传输rfc2822消息;如果这是根节点,
336
+ * 则设置缺失的必需头部字段(Date, Message-Id, MIME-Version)
329
337
  *
330
338
  * @return {String} 编译后的消息 */
331
339
  createReadStream(options = {}) {
@@ -352,7 +360,7 @@ class MimeNode {
352
360
  // 在可能的用户转换之后确保终止换行符
353
361
  transform = new LastNewline(), outputStream = pipeStream(outputStream, transform);
354
362
 
355
- // 循环遍历所有后处理函数,并将输出流传递给每个函数。
363
+ // 循环遍历所有后处理函数,并将输出流传递给每个函数;
356
364
  for (let i = 0; i < this._processFuncs.length; i++) {
357
365
  transform = this._processFuncs[i], outputStream = transform(outputStream);
358
366
  }
@@ -368,7 +376,7 @@ class MimeNode {
368
376
  }
369
377
 
370
378
  /**
371
- * 将转换流对象附加到转换列表。最终输出在暴露之前通过此流传递
379
+ * 将转换流对象附加到转换列表;最终输出在暴露之前通过此流传递
372
380
  *
373
381
  * @param {Object} transform 读写流
374
382
  */
@@ -377,7 +385,7 @@ class MimeNode {
377
385
  }
378
386
 
379
387
  /**
380
- * 附加后处理函数。该函数在转换之后运行,并使用以下语法
388
+ * 附加后处理函数;该函数在转换之后运行,并使用以下语法
381
389
  *
382
390
  * processFunc(input) -> outputStream
383
391
  *
@@ -394,7 +402,7 @@ class MimeNode {
394
402
  const callback = args => {
395
403
  cleanup(state), done(args);
396
404
  },
397
- // 对于多部分节点,推送子节点;对于内容节点,结束流
405
+ // 对于多部分节点,推送子节点;对于内容节点,结束流
398
406
  finalize = () => {
399
407
  let childId = 0;
400
408
  const processChildNode = () => {
@@ -417,7 +425,7 @@ class MimeNode {
417
425
  sendContent = () => {
418
426
  const content = this.content;
419
427
  if (content) {
420
- // 如果内容是错误,则直接调用回调
428
+ // 如果内容是错误,则直接调用回调
421
429
  if (Object.prototype.toString.call(content) === '[object Error]') return callback(content);
422
430
 
423
431
  if (typeof content.pipe === 'function') {
@@ -456,12 +464,13 @@ class MimeNode {
456
464
 
457
465
  if (this._raw) {
458
466
  setImmediate(() => {
459
- // 如果内容是错误,则直接调用回调;如果设置错误处理程序,则将其移除
467
+ // 如果内容是错误,则直接调用回调;如果设置错误处理程序,则将其移除
460
468
  if (Object.prototype.toString.call(this._raw) === '[object Error]') return callback(this._raw);
461
469
  if (typeof this._raw.pipe === 'function') this._raw.removeListener('error', this.contentErrorHandler);
462
470
 
463
471
  const raw = this.#getStream(this._raw);
464
- 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));
465
474
  });
466
475
  }
467
476
  else outputStream.write(`${this.buildHeaders()}\r\n\r\n`), setImmediate(sendContent);
@@ -470,7 +479,7 @@ class MimeNode {
470
479
  /**
471
480
  * 设置要使用的信封而不是生成的信封
472
481
  *
473
- * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
482
+ * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
474
483
  */
475
484
  setEnvelope(envelope) {
476
485
  this._envelope = { from: false, to: [] };
@@ -483,11 +492,13 @@ class MimeNode {
483
492
 
484
493
  const { to } = this._envelope, standardFields = ['to', 'cc', 'bcc', 'from'];
485
494
  ['to', 'cc', 'bcc'].forEach(key => {
486
- if (envelope[key]) this.#convertAddresses(this.parseAddresses(envelope[key]), to); // 如果存在,则将地址添加到信封中
495
+ // 如果存在,则将地址添加到信封中
496
+ if (envelope[key]) this.#convertAddresses(this.parseAddresses(envelope[key]), to);
487
497
  });
488
498
  this._envelope.to = to.map(to => to.address).filter(address => address);
489
499
  Object.keys(envelope).forEach(key => {
490
- if (!standardFields.includes(key)) this._envelope[key] = envelope[key]; // 如果字段不是标准字段,则将其添加到信封中
500
+ // 如果字段不是标准字段,则将其添加到信封中
501
+ if (!standardFields.includes(key)) this._envelope[key] = envelope[key];
491
502
  });
492
503
 
493
504
  return this;
@@ -514,7 +525,7 @@ class MimeNode {
514
525
  /**
515
526
  * 生成并返回包含发件人地址和收件人地址列表的SMTP信封
516
527
  *
517
- * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
528
+ * @return {Object} SMTP信封,格式为 {from: 'from@example.com', to: ['to@example.com']}
518
529
  */
519
530
  getEnvelope() {
520
531
  if (this._envelope) return this._envelope;
@@ -533,7 +544,7 @@ class MimeNode {
533
544
  }
534
545
 
535
546
  /**
536
- * 返回Message-Id值。如果不存在,则创建一个
547
+ * 返回Message-Id值;如果不存在,则创建一个
537
548
  *
538
549
  * @return {String} Message-Id值
539
550
  */
@@ -542,14 +553,14 @@ class MimeNode {
542
553
  }
543
554
 
544
555
  /**
545
- * 设置预生成的内容,该内容将用作此节点的输出
556
+ * 设置预生成的内容,该内容将用作此节点的输出
546
557
  *
547
558
  * @param {String|Buffer|Stream} 原始MIME内容
548
559
  */
549
560
  setRaw(raw) {
550
561
  this._raw = raw;
551
562
  if (this._raw && typeof this._raw.pipe === 'function') {
552
- // 预流处理程序。如果流被设置为内容,并且在此流执行任何操作之前触发'error',则可能被触发
563
+ // 预流处理程序;如果流被设置为内容,并且在此流执行任何操作之前触发'error',则可能被触发
553
564
  this.contentErrorHandler = err => {
554
565
  this._raw.removeListener('error', this.contentErrorHandler), this._raw = err;
555
566
  };
@@ -560,7 +571,7 @@ class MimeNode {
560
571
  }
561
572
 
562
573
  /**
563
- * 检测并返回与内容相关的流的句柄。
574
+ * 检测并返回与内容相关的流的句柄;
564
575
  *
565
576
  * @param {Mixed} content 节点内容
566
577
  * @returns {Object} 流对象
@@ -586,25 +597,26 @@ class MimeNode {
586
597
  return stream;
587
598
  }, { _resolvedValue, pipe, path, href, httpHeaders } = content;
588
599
  if (_resolvedValue) return createContentStream(_resolvedValue); // 处理已解析的字符串或Buffer类型内容
589
- else if (typeof pipe === 'function') return content; // 如果是流本身,则直接返回该流
590
- // 如果是文件路径:根据访问权限返回文件流或错误流
600
+ else if (typeof pipe === 'function') return content; // 如果是流本身,则直接返回该流
601
+ // 如果是文件路径:根据访问权限返回文件流或错误流
591
602
  else if (content && typeof path === 'string' && !href)
592
603
  return this.disableFileAccess ? createErrorStream(`文件访问被拒绝:${path}`) : fsReadStream(path);
593
- // 如果是URL:根据访问权限返回网络流或错误流
604
+ // 如果是URL:根据访问权限返回网络流或错误流
594
605
  else if (content && typeof href === 'string')
595
- return this.disableUrlAccess ? createErrorStream(`URL访问被拒绝:${href}`) : nmfetch(href, { headers: httpHeaders });
596
- return createContentStream(content || ''); // 兜底处理:将其他类型内容转换为流
606
+ return this.disableUrlAccess ? createErrorStream(`URL访问被拒绝:${href}`)
607
+ : nmfetch(href, { headers: httpHeaders });
608
+ return createContentStream(content || ''); // 兜底处理:将其他类型内容转换为流
597
609
  }
598
610
 
599
611
  /**
600
- * 解析地址。接受单个地址或数组或地址数组的数组(例如To: [[第一组], [第二组],...])
612
+ * 解析地址;接受单个地址或数组或地址数组的数组(例如To: [[第一组], [第二组],...])
601
613
  *
602
614
  * @param {Mixed} addresses 要解析的地址
603
615
  * @return {Array} 地址对象数组
604
616
  */
605
617
  parseAddresses(addresses) {
606
618
  return [].concat.apply([], [].concat(addresses).map(mAddress => {
607
- // 如果地址和名称都存在,则将其转换为数组
619
+ // 如果地址和名称都存在,则将其转换为数组
608
620
  if (mAddress?.address) {
609
621
  const { address, name = '' } = mAddress;
610
622
  mAddress.address = this.#normalizeAddress(address), mAddress.name = name;
@@ -615,7 +627,7 @@ class MimeNode {
615
627
  }
616
628
 
617
629
  /**
618
- * 规范化头部键,使用驼峰式,除了大写的MIME-
630
+ * 规范化头部键,使用驼峰式,除了大写的MIME-
619
631
  *
620
632
  * @param {String} key 要规范化的键
621
633
  * @return {String} 驼峰式形式的键
@@ -630,8 +642,8 @@ class MimeNode {
630
642
  }
631
643
 
632
644
  /**
633
- * 检查内容类型是否为多部分,并在需要时定义边界。
634
- * 不返回任何内容,而是修改对象参数。
645
+ * 检查内容类型是否为多部分,并在需要时定义边界;
646
+ * 不返回任何内容,而是修改对象参数;
635
647
  *
636
648
  * @param {Object} structured 为'Content-Type'键解析的头部值
637
649
  */
@@ -651,14 +663,15 @@ class MimeNode {
651
663
  }
652
664
 
653
665
  /**
654
- * 对生成的rfc2822电子邮件中使用的头部值进行编码。
666
+ * 对生成的rfc2822电子邮件中使用的头部值进行编码;
655
667
  *
656
668
  * @param {String} key 头部键
657
669
  * @param {String} value 头部值
658
670
  */
659
671
  encodeHeaderValue(key, value = '') {
660
672
  key = this.#normalizeHeaderKey(key);
661
- 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),
662
675
  cleanValue = (val = '') => {
663
676
  return val.toString().replace(rL, ' '); // 将值转换为字符串,并替换掉换行符
664
677
  },
@@ -681,7 +694,7 @@ class MimeNode {
681
694
  case 'In-Reply-To':
682
695
  case 'Content-Id':
683
696
  return ensureAngleBrackets(this.#encodeWords(cleanValue(value)));
684
- // 用<>括起来的值,以空格分隔的列表
697
+ // 用<>括起来的值,以空格分隔的列表
685
698
  case 'References':
686
699
  value = [].concat(value)
687
700
  // 将值转换为字符串,删除尖括号中的空白字符,然后按空白字符分割
@@ -692,8 +705,8 @@ class MimeNode {
692
705
  if (value instanceof Date) return value.toUTCString().replace('GMT', '+0000');
693
706
  return this.#encodeWords(cleanValue(value));
694
707
  case 'Content-Type':
695
- case 'Content-Disposition': return cleanValue(value); // 如果包含文件名,则已编码
696
- default: return this.#encodeWords(cleanValue(value)); // 默认encodeWords仅在需要时编码,否则返回原始字符串
708
+ case 'Content-Disposition': return cleanValue(value); // 如果包含文件名,则已编码
709
+ default: return this.#encodeWords(cleanValue(value)); // 默认encodeWords仅在需要时编码,否则返回原始字符串
697
710
  }
698
711
  }
699
712
 
@@ -735,7 +748,7 @@ class MimeNode {
735
748
  address = address.toString().replace(rFC, ' ').trim();
736
749
 
737
750
  const lIndex = address.lastIndexOf('@');
738
- if (lIndex < 0) return address; // 没有@符号,保持原样
751
+ if (lIndex < 0) return address; // 没有@符号,保持原样
739
752
 
740
753
  let user = address.substring(0, lIndex), encodedDomain;
741
754
  const domain = address.substring(lIndex + 1);
@@ -755,9 +768,9 @@ class MimeNode {
755
768
 
756
769
  /**
757
770
  * 对邮件地址中的名称部分进行编码处理
758
- * 如果名称包含非ASCII字符或特殊字符,则进行MIME编码或引号包裹
771
+ * 如果名称包含非ASCII字符或特殊字符,则进行MIME编码或引号包裹
759
772
  *
760
- * @param {String} name 邮件地址中的名称部分(如 "张三" in "张三<zhangsan@example.com>")
773
+ * @param {String} name 邮件地址中的名称部分(如 "张三" in "张三<zhangsan@example.com>")
761
774
  * @returns {String} 编码后的名称字符串
762
775
  */
763
776
  #encodeAddressName(name) {
@@ -789,7 +802,7 @@ class MimeNode {
789
802
  */
790
803
  #getTextEncoding(value = '') {
791
804
  value = value.toString();
792
- if (this.textEncoding) return this.textEncoding; // 如果有预设编码,直接返回
805
+ if (this.textEncoding) return this.textEncoding; // 如果有预设编码,直接返回
793
806
  const rNL = resetRegex(regexs.NON_LATIN), rL = resetRegex(regexs.LATIN),
794
807
  nonLatinLen = (value.match(rNL) || []).length, latinLen = (value.match(rL) || []).length;
795
808
  return nonLatinLen < latinLen ? 'Q' : 'B';
@@ -801,7 +814,7 @@ class MimeNode {
801
814
  * @return {String} 随机Message-ID值
802
815
  */
803
816
  #generateMessageId() {
804
- // 使用不同长度的随机字节生成类似UUID的格式,优先使用发件人地址中的域名,服务器主机名或localhost次之
817
+ // 使用不同长度的随机字节生成类似UUID的格式,优先使用发件人地址中的域名,服务器主机名或localhost次之
805
818
  const randomPart = [4, 2, 2, 2, 6].map(len => randomBytes(len).toString('hex')).join('-'),
806
819
  domain = (this.getEnvelope().from || this.hostname || 'localhost').split('@').pop();
807
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();