@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.
package/lib/punycode.js CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  Punycode.js
3
- 主要功能包括:
4
- 1.将Unicode字符串转换为Punycode字符串(仅包含ASCII字符)。
5
- 2.将Punycode字符串转换回Unicode字符串。
6
- 3.处理域名或电子邮件地址的转换,其中只有非ASCII部分会被转换。
7
- 4.将Unicode字符串转换为Punycode字符串,并将结果附加到域名或电子邮件地址的非ASCII部分之前。
3
+ 主要功能包括:
4
+ 1.将Unicode字符串转换为Punycode字符串(仅包含ASCII字符);
5
+ 2.将Punycode字符串转换回Unicode字符串;
6
+ 3.处理域名或电子邮件地址的转换,其中只有非ASCII部分会被转换;
7
+ 4.将Unicode字符串转换为Punycode字符串,并将结果附加到域名或电子邮件地址的非ASCII部分之前;
8
8
  */
9
9
  import { regexs, resetRegex, } from './regexs.js';
10
10
 
@@ -14,7 +14,8 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
14
14
  base = 36, tMin = 1, tMax = 26, skew = 38, damp = 700, initialBias = 72, initialN = 128, delimiter = '-',
15
15
  /** 错误消息 */
16
16
  errors = {
17
- overflow: '溢出:输入需要更宽的整数来处理', 'not-basic': '非法输入 >= 0x80(不是基本码点)', 'invalid-input': '无效输入'
17
+ overflow: '溢出:输入需要更宽的整数来处理', 'not-basic': '非法输入 >= 0x80(不是基本码点)',
18
+ 'invalid-input': '无效输入'
18
19
  },
19
20
  /** 便捷缩写 */
20
21
  baseMinusTMin = base - tMin, floor = Math.floor, strFromCharCode = String.fromCharCode, // 字符代码转字符串函数
@@ -46,7 +47,7 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
46
47
  },
47
48
 
48
49
  /**
49
- * 一个简单的类似 `Array#map` 的包装器,用于处理域名字符串或电子邮件地址
50
+ * 一个简单的类似 `Array#map` 的包装器,用于处理域名字符串或电子邮件地址
50
51
  * @private
51
52
  * @param {String} domain 域名或电子邮件地址
52
53
  * @param {Function} callback 为每个字符调用的函数
@@ -55,9 +56,9 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
55
56
  mapDomain = (domain, callback) => {
56
57
  const parts = domain.split('@');
57
58
  let result = '';
58
- // 在电子邮件地址中,只有域名部分应进行punycode编码。保持本地部分(即`@`之前的所有内容)不变
59
+ // 在电子邮件地址中,只有域名部分应进行punycode编码;保持本地部分(即`@`之前的所有内容)不变
59
60
  if (parts.length > 1) result = `${parts[0]}@`, domain = parts[1];
60
- // 为了IE8兼容性,避免使用`split(regex)`;参见 #17
61
+ // 为了IE8兼容性,避免使用`split(regex)`;参见 #17
61
62
  const rS = resetRegex(regexs.SEPARATORS);
62
63
  domain = domain.replace(rS, '\x2E');
63
64
  const encoded = map(domain.split('.'), callback).join('.');
@@ -65,13 +66,13 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
65
66
  },
66
67
 
67
68
  /**
68
- * 创建一个包含字符串中每个Unicode字符的数字码点的数组。
69
- * 虽然JavaScript内部使用UCS-2,但此函数会将一对代理半部(UCS-2将每个半部公开为单独的字符)
70
- * 转换为单个码点,以匹配UTF-16。
69
+ * 创建一个包含字符串中每个Unicode字符的数字码点的数组;
70
+ * 虽然JavaScript内部使用UCS-2,但此函数会将一对代理半部(UCS-2将每个半部公开为单独的字符)
71
+ * 转换为单个码点,以匹配UTF-16;
71
72
  * @see `punycode.ucs2.encode`
72
73
  * @memberOf punycode.ucs2
73
74
  * @name decode
74
- * @param {String} string Unicode输入字符串(UCS-2)
75
+ * @param {String} string Unicode输入字符串(UCS-2)
75
76
  * @returns {Array} 新的码点数组
76
77
  */
77
78
  ucs2decode = string => {
@@ -79,7 +80,7 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
79
80
  let counter = 0;
80
81
  while (counter < length) {
81
82
  const value = string.charCodeAt(counter++); // 获取当前字符的UTF-16代码单元
82
- // 如果是高代理项(范围:0xD800-0xDBFF)并且不是最后一个字符
83
+ // 如果是高代理项(范围:0xD800-0xDBFF)并且不是最后一个字符
83
84
  if (value >= 0xd800 && value <= 0xdbff && counter < length) {
84
85
  const extra = string.charCodeAt(counter++); // 获取下一个字符
85
86
  // 如果下一个字符是低代理项,则添加代理项对;否则,仅添加高代理项并回退计数器
@@ -92,53 +93,56 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
92
93
  },
93
94
 
94
95
  /**
95
- * 基于数字码点数组创建字符串。
96
+ * 基于数字码点数组创建字符串;
96
97
  * @see `punycode.ucs2.decode`
97
98
  * @memberOf punycode.ucs2
98
99
  * @name encode
99
100
  * @param {Array} codePoints 数字码点数组
100
- * @returns {String} 新的Unicode字符串(UCS-2)
101
+ * @returns {String} 新的Unicode字符串(UCS-2)
101
102
  */
102
103
  ucs2encode = codePoints => {
103
104
  return String.fromCodePoint(...codePoints);
104
105
  },
105
106
 
106
107
  /**
107
- * 将基本码点转换为数字/整数。
108
+ * 将基本码点转换为数字/整数;
108
109
  * @see `digitToBasic()`
109
110
  * @private
110
111
  * @param {Number} codePoint 基本数字码点值
111
- * @returns {Number} 基本码点的数值(用于表示整数),范围从`0`到`base - 1`,如果码点不表示值,则返回`base`
112
+ * @returns {Number} 基本码点的数值(用于表示整数),范围从`0`到`base - 1`,如果码点不表示值,则返回`base`
112
113
  */
113
114
  basicToDigit = codePoint => {
114
115
 
115
- if (codePoint >= 0x30 && codePoint < 0x3a) return 26 + (codePoint - 0x30);//如码点在`0x30`到`0x39`范围内(`0'-`9`),则返回其数值;
116
- if (codePoint >= 0x41 && codePoint < 0x5b) return codePoint - 0x41; // 如码点在`0x41`到`0x5a`范围内(`A`-`Z`),则返回其数值;
117
- if (codePoint >= 0x61 && codePoint < 0x7b) return codePoint - 0x61; // 如码点在`0x61`到`0x7a`范围内(`a`-`z`),则返回其数值;
116
+ //如码点在`0x30`到`0x39`范围内(`0'-`9`),则返回其数值;
117
+ if (codePoint >= 0x30 && codePoint < 0x3a) return 26 + (codePoint - 0x30);
118
+ // 如码点在`0x41`到`0x5a`范围内(`A`-`Z`),则返回其数值;
119
+ if (codePoint >= 0x41 && codePoint < 0x5b) return codePoint - 0x41;
120
+ // 如码点在`0x61`到`0x7a`范围内(`a`-`z`),则返回其数值;
121
+ if (codePoint >= 0x61 && codePoint < 0x7b) return codePoint - 0x61;
118
122
  return base; // 不在基本字符范围内
119
123
  },
120
124
 
121
125
  /**
122
- * 将数字/整数转换为基本码点。
126
+ * 将数字/整数转换为基本码点;
123
127
  * @see `basicToDigit()`
124
128
  * @private
125
129
  * @param {Number} digit 基本码点的数值
126
- * @returns {Number} 其值(用于表示整数)为`digit`的基本码点,需要在`0`到`base - 1`范围内。
127
- * 如果`flag`非零,则使用大写形式;否则使用小写形式。
128
- * 如果`flag`非零且`digit`没有大写形式,则行为未定义。
130
+ * @returns {Number} 其值(用于表示整数)为`digit`的基本码点,需要在`0`到`base - 1`范围内;
131
+ * 如果`flag`非零,则使用大写形式;否则使用小写形式;
132
+ * 如果`flag`非零且`digit`没有大写形式,则行为未定义;
129
133
  */
130
134
  digitToBasic = (digit, flag) => {
131
135
  return digit + 22 + 75 * (digit < 26) - ((flag != 0) << 5);
132
136
  },
133
137
 
134
138
  /**
135
- * 根据 RFC 3492 第 3.4 节的偏差适应函数。
139
+ * 根据 RFC 3492 第 3.4 节的偏差适应函数;
136
140
  * https://tools.ietf.org/html/rfc3492#section-3.4
137
141
  * @private
138
142
  */
139
143
  adapt = (delta, numPoints, firstTime) => {
140
144
  let k = 0;
141
- delta = firstTime ? floor(delta / damp) : delta >> 1; // 首次处理时除以阻尼因子,否则右移一位
145
+ delta = firstTime ? floor(delta / damp) : delta >> 1; // 首次处理时除以阻尼因子,否则右移一位
142
146
  delta += floor(delta / numPoints);
143
147
  for (; /* 无初始化 */ delta > (baseMinusTMin * tMax) >> 1; k += base) delta = floor(delta / baseMinusTMin);
144
148
 
@@ -146,14 +150,14 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
146
150
  },
147
151
 
148
152
  /**
149
- * 将仅包含ASCII符号的Punycode字符串转换为Unicode符号字符串。
153
+ * 将仅包含ASCII符号的Punycode字符串转换为Unicode符号字符串;
150
154
  * @memberOf punycode
151
155
  * @param {String} input 仅包含ASCII符号的Punycode字符串
152
156
  * @returns {String} 生成的Unicode符号字符串
153
157
  */
154
158
  decode = input => {
155
159
  const output = [], inputLen = input.length;
156
- // 当前码点值,初始码点,初始偏差,处理基本ASCII字符(分隔符前的部分)
160
+ // 当前码点值,初始码点,初始偏差,处理基本ASCII字符(分隔符前的部分)
157
161
  let i = 0, n = initialN, bias = initialBias, basic = input.lastIndexOf(delimiter);
158
162
  if (basic < 0) basic = 0;
159
163
 
@@ -190,24 +194,25 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
190
194
  const out = output.length + 1;
191
195
  bias = adapt(i - oldi, out, oldi === 0);
192
196
  if (Math.floor(i / out) > maxInt - n) throw new Error(overflow); // 更新码点值并插入到输出
193
- n += Math.floor(i / out), i %= out, output.splice(i, 0, n), i++; // 在指定位置插入解码后的字符,并移动到下一个插入位置
197
+ // 在指定位置插入解码后的字符,并移动到下一个插入位置
198
+ n += Math.floor(i / out), i %= out, output.splice(i, 0, n), i++;
194
199
  }
195
200
 
196
201
  return String.fromCodePoint(...output);
197
202
  },
198
203
 
199
204
  /**
200
- * 将Unicode符号字符串(例如域名标签)转换为仅包含ASCII符号的Punycode字符串。
205
+ * 将Unicode符号字符串(例如域名标签)转换为仅包含ASCII符号的Punycode字符串;
201
206
  * @memberOf punycode
202
207
  * @param {String} input Unicode符号字符串
203
208
  * @returns {String} 生成的仅包含ASCII符号的Punycode字符串
204
209
  */
205
210
  encode = input => {
206
- input = ucs2decode(input); // 将UCS-2输入转换为Unicode码点数组。
211
+ input = ucs2decode(input); // 将UCS-2输入转换为Unicode码点数组;
207
212
  const output = [], inputLen = input.length; // 缓存长度
208
213
  let n = initialN, delta = 0, bias = initialBias; // 初始化状态
209
214
 
210
- // 处理基本码点。
215
+ // 处理基本码点;
211
216
  for (const currentValue of input)
212
217
  if (currentValue < 0x80) output.push(strFromCharCode(currentValue));
213
218
 
@@ -215,14 +220,14 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
215
220
  let handledCPCount = basicLen; // `handledCPCount`是已处理的码点数量;`basicLength`是基本码点的数量;
216
221
  if (basicLen) output.push(delimiter); // 用分隔符结束基本字符串,除非它是空的;
217
222
 
218
- // 主编码循环:
223
+ // 主编码循环:
219
224
  while (handledCPCount < inputLen) {
220
- // 所有小于 n 的非基本码点都已处理。找到下一个更大的:
225
+ // 所有小于 n 的非基本码点都已处理;找到下一个更大的:
221
226
  let m = maxInt;
222
227
  for (const currentValue of input)
223
228
  if (currentValue >= n && currentValue < m) m = currentValue;
224
229
 
225
- // 增加 `delta` 足够多,以将解码器的 <n,i> 状态推进到 <m,0>,但要防止溢出。
230
+ // 增加 `delta` 足够多,以将解码器的 <n,i> 状态推进到 <m,0>,但要防止溢出;
226
231
  const handledCPCountPlusOne = handledCPCount + 1;
227
232
  if (m - n > floor((maxInt - delta) / handledCPCountPlusOne)) error('overflow');
228
233
 
@@ -230,14 +235,15 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
230
235
  for (const currentValue of input) {
231
236
  if (currentValue < n && ++delta > maxInt) error('overflow');
232
237
  if (currentValue === n) {
233
- // 将 delta 表示为广义可变长度整数。
238
+ // 将 delta 表示为广义可变长度整数;
234
239
  let q = delta;
235
240
  for (let k = base /* 无条件 */; ; k += base) {
236
241
  const t = k <= bias ? tMin : k >= bias + tMax ? tMax : k - bias;
237
242
  if (q < t) break;
238
243
 
239
244
  const qMinusT = q - t, baseMinusT = base - t;
240
- output.push(strFromCharCode(digitToBasic(t + (qMinusT % baseMinusT), 0))), q = floor(qMinusT / baseMinusT);
245
+ output.push(strFromCharCode(digitToBasic(t + (qMinusT % baseMinusT), 0))),
246
+ q = floor(qMinusT / baseMinusT);
241
247
  }
242
248
 
243
249
  output.push(strFromCharCode(digitToBasic(q, 0)));
@@ -250,8 +256,8 @@ const maxInt = 2147483647, // 即 0x7FFFFFFF 或 2^31-1
250
256
  },
251
257
 
252
258
  /**
253
- * 将表示域名或电子邮件地址的Punycode字符串转换为Unicode。
254
- * 只有输入的Punycode部分会被转换,即如果你在已经转换为Unicode的字符串上调用它也没有关系。
259
+ * 将表示域名或电子邮件地址的Punycode字符串转换为Unicode;
260
+ * 只有输入的Punycode部分会被转换,即如果你在已经转换为Unicode的字符串上调用它也没有关系;
255
261
  * @memberOf punycode
256
262
  * @param {String} input 要转换为Unicode的Punycode编码的域名或电子邮件地址
257
263
  * @returns {String} 给定Punycode字符串的Unicode表示
package/lib/qp.js CHANGED
@@ -10,7 +10,7 @@ import { regexs, resetRegex } from './regexs.js';
10
10
  */
11
11
  const qpWrap = (str = '', lineLength = 76) => {
12
12
  str = str.toString();
13
- if (str.length <= lineLength) return str; // 如果字符串长度不超过行限制,直接返回
13
+ if (str.length <= lineLength) return str; // 如果字符串长度不超过行限制,直接返回
14
14
  let pos = 0, match, code, line, result = '';
15
15
  const len = str.length, lineMargin = Math.floor(lineLength / 3),
16
16
  // 将行添加到结果中并更新位置
@@ -25,7 +25,7 @@ const qpWrap = (str = '', lineLength = 76) => {
25
25
  continue;
26
26
  }
27
27
 
28
- // 如果以换行符结尾,保持不变
28
+ // 如果以换行符结尾,保持不变
29
29
  if (line.endsWith('\n')) {
30
30
  addLineAndUpdatePos(line);
31
31
  continue;
@@ -34,15 +34,18 @@ const qpWrap = (str = '', lineLength = 76) => {
34
34
  line = line.slice(0, line.length - (match[0].length - 1)), addLineAndUpdatePos(line); // 截断到最近的换行符
35
35
  continue;
36
36
  }
37
- else if (line.length > lineLength - lineMargin && (match = line.slice(-lineMargin).match(regexs.TRAILING_SPACE_OR_PUNCTUATION)))
37
+ else if (line.length > lineLength - lineMargin &&
38
+ (match = line.slice(-lineMargin).match(regexs.TRAILING_SPACE_OR_PUNCTUATION)))
38
39
  line = line.slice(0, line.length - (match[0].length - 1)); // 截断到最近的空格
39
40
  else if (line.match(regexs.INCOMPLETE_ENCODING)) {
40
41
  // 将不完整的编码序列推到下一行
41
- if ((match = line.match(regexs.INCOMPLETE_ENCODING_SHORT))) line = line.slice(0, line.length - match[0].length);
42
+ if ((match = line.match(regexs.INCOMPLETE_ENCODING_SHORT)))
43
+ line = line.slice(0, line.length - match[0].length);
42
44
 
43
45
  const rCE = resetRegex(regexs.COMPLETE_ENCODING);
44
46
  // 确保UTF-8序列不被分割
45
- while (line.length > 3 && line.length < len - pos && !line.match(regexs.UTF8_ENCODING) && (match = line.match(rCE))) {
47
+ while (line.length > 3 && line.length < len - pos &&
48
+ !line.match(regexs.UTF8_ENCODING) && (match = line.match(rCE))) {
46
49
  code = parseInt(match[0].slice(1, 3), 16);
47
50
  if (code < 128) break;
48
51
 
@@ -68,17 +71,18 @@ const qpWrap = (str = '', lineLength = 76) => {
68
71
  },
69
72
 
70
73
  /**
71
- * 辅助函数:检查数字是否在指定范围内
74
+ * 辅助函数:检查数字是否在指定范围内
72
75
  *
73
76
  * @param {Number} nr 要检查的数字
74
77
  * @param {Array} ranges 允许的值范围数组
75
- * @returns {Boolean} 如果在允许范围内返回true,否则返回false
78
+ * @returns {Boolean} 如果在允许范围内返回true,否则返回false
76
79
  */
77
80
  checkRanges = (nr, ranges) => {
78
81
  for (let i = ranges.length - 1; i >= 0; i--) {
79
- if (!ranges[i].length) continue; // 如果范围为空,则跳过
80
- if (ranges[i].length === 1 && nr === ranges[i][0]) return true; // 如果范围只有一个值,则直接比较
81
- if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) return true;// 如果范围有两个值,则比较是否在范围内
82
+ if (!ranges[i].length) continue; // 如果范围为空,则跳过
83
+ if (ranges[i].length === 1 && nr === ranges[i][0]) return true; // 如果范围只有一个值,则直接比较
84
+ // 如果范围有两个值,则比较是否在范围内
85
+ if (ranges[i].length === 2 && nr >= ranges[i][0] && nr <= ranges[i][1]) return true;
82
86
  }
83
87
  return false;
84
88
  };
@@ -90,7 +94,7 @@ const qpWrap = (str = '', lineLength = 76) => {
90
94
  * @returns {String} Quoted-Printable编码的字符串
91
95
  */
92
96
  const qpEncode = buffer => {
93
- if (typeof buffer === 'string') buffer = Buffer.from(buffer, 'utf-8'); // 如果输入是字符串,先转换为Buffer
97
+ if (typeof buffer === 'string') buffer = Buffer.from(buffer, 'utf-8'); // 如果输入是字符串,先转换为Buffer
94
98
 
95
99
  // 不需要编码的可用字符范围
96
100
  const ranges = [
@@ -105,13 +109,13 @@ const qpEncode = buffer => {
105
109
  for (let i = 0, len = buffer.length; i < len; i++) {
106
110
  const ord = buffer[i], nextByte = buffer[i + 1];
107
111
 
108
- // 如果字符在允许范围内,并且不是行尾的空格或制表符,则直接使用原字符
112
+ // 如果字符在允许范围内,并且不是行尾的空格或制表符,则直接使用原字符
109
113
  if (checkRanges(ord, ranges) && !((ord === 0x20 || ord === 0x09) && (i === len - 1 || nextByte === 0x0a
110
114
  || nextByte === 0x0d))) {
111
115
  result += String.fromCharCode(ord);
112
116
  continue;
113
117
  }
114
- result += `=${(ord < 0x10 ? '0' : '')}${ord.toString(16).toUpperCase()}`; // 否则进行编码:=后跟两位十六进制数
118
+ result += `=${(ord < 0x10 ? '0' : '')}${ord.toString(16).toUpperCase()}`; // 否则进行编码:=后跟两位十六进制数
115
119
  }
116
120
 
117
121
  return result;
@@ -154,13 +158,13 @@ class QpEncoder extends Transform {
154
158
 
155
159
  if (qp) this.outputBytes += qp.length, this.push(qp);
156
160
  }
157
- // 否则没有启用行长度限制,直接编码并输出
161
+ // 否则没有启用行长度限制,直接编码并输出
158
162
  else qp = qpEncode(chunk), this.outputBytes += qp.length, this.push(qp, 'ascii');
159
163
  done();
160
164
  }
161
165
 
162
166
  /**
163
- * 刷新流,输出剩余数据
167
+ * 刷新流,输出剩余数据
164
168
  */
165
169
  _flush(done) {
166
170
  if (this._curLine) this.outputBytes += this._curLine.length, this.push(this._curLine, 'ascii');
package/lib/regexs.js CHANGED
@@ -6,28 +6,28 @@ const regexs = {
6
6
  LINE_SEPARATOR: /\r?\n/, // 按行分割丢弃换行符
7
7
  SPLIT_KEEP_NEWLINE: /(\r?\n)/, // 按行分割并保留行结束符
8
8
  LINE_BREAK: /^[^\n\r]*(\r?\n|\r)/, // 匹配行结束符
9
- INVALID_ADDRESS_CHARS: /[\r\n<>]/, // 匹配地址中的非法字符(回车、换行、<>)
9
+ INVALID_ADDRESS_CHARS: /[\r\n<>]/, // 匹配地址中的非法字符(回车、换行、<>)
10
10
  WHITESPACE: /\s+/, // 匹配空白字符
11
- FOLDED_HEADER_LINE: /^\s/, // 匹配折叠的头信息行(以空白字符开头)
11
+ FOLDED_HEADER_LINE: /^\s/, // 匹配折叠的头信息行(以空白字符开头)
12
12
  RESPONSE_CODE: /^(\d+)(?:\s(\d+\.\d+\.\d+))?\s/, // 匹配SMTP响应代码和可选状态码
13
13
  NEXT_WORD: /^[^\s]+(\s*)/, // 匹配下一个单词及其后的空白
14
- ADDRESS_CLEAN: /^[^<]*<\s*/, // 地址令牌清理:清理地址令牌中的多余字符
15
- EMAIL_LOOSE: /\s*\b[^@\s]+@[^\s]+\b\s*/, // 邮箱地址宽松匹配:匹配文本中可能包含特殊字符的邮箱地址
14
+ ADDRESS_CLEAN: /^[^<]*<\s*/, // 地址令牌清理:清理地址令牌中的多余字符
15
+ EMAIL_LOOSE: /\s*\b[^@\s]+@[^\s]+\b\s*/, // 邮箱地址宽松匹配:匹配文本中可能包含特殊字符的邮箱地址
16
16
  UNSAFE_PARAM_VALUE: /[\s'"\\;:/=(),<>@[\]?]|^-/, // 匹配参数值中的不安全字符或以连字符开头
17
17
 
18
- TRAILING_WSP: /[ \t]+$/, // 匹配行尾空白字符(WSP)
18
+ TRAILING_WSP: /[ \t]+$/, // 匹配行尾空白字符(WSP)
19
19
  OPTIONAL_CRLF: /\r?\n$/, // 匹配可选的CRLF行结束符
20
20
  TRAILING_NEWLINE: /\n.*?$/, // 匹配行尾的换行符及其后的内容
21
- LAST_LINE: /(^|\n)([^\n]*)$/, // 匹配最后一行(用于换行处理)
21
+ LAST_LINE: /(^|\n)([^\n]*)$/, // 匹配最后一行(用于换行处理)
22
22
  EMAIL_CHECK: /^[^@]+@[^@]+$/, // 检查电子邮件格式
23
23
  TRAILING_SPACES: /(\s+)[^\s]*$/, // 匹配行尾的空白字符及其后的非空白字符
24
- EMAIL_EXACT: /^[^@\s]+@[^@\s]+$/, // 邮箱地址精确匹配:匹配简单的邮箱格式(名称@域名)
24
+ EMAIL_EXACT: /^[^@\s]+@[^@\s]+$/, // 邮箱地址精确匹配:匹配简单的邮箱格式(名称@域名)
25
25
  PRINTABLE_ASCII: /^[\x20-\x7e]*$/, // 匹配可打印ASCII字符
26
26
  CRAM_MD5_CHALLENGE: /^334\s+(.+)$/, // 匹配CRAM-MD5挑战字符串
27
27
  TRAILING_LINE_BREAK: /(?:\r\n|\r|\n)$/, // 检测字符串是否以行结束符结尾
28
- RFC2231_KEY: /(\*(\d+)|\*(\d+)\*|\*)$/, // 匹配RFC2231格式的参数键名(带星号和编号)
28
+ RFC2231_KEY: /(\*(\d+)|\*(\d+)\*|\*)$/, // 匹配RFC2231格式的参数键名(带星号和编号)
29
29
  RALPHANUMERIC_UNDERSCORE_SPACE: /^[\w ]*$/, // 匹配仅包含字母数字、下划线和空格的字符串
30
- CHARSET_LANGUAGE_VALUE: /^([^']*)'[^']*'(.*)$/, // 匹配字符集、语言和值的格式(RFC2231)
30
+ CHARSET_LANGUAGE_VALUE: /^([^']*)'[^']*'(.*)$/, // 匹配字符集、语言和值的格式(RFC2231)
31
31
  IPV4_ADDRESS: /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/, // 匹配IPv4地址格式
32
32
  UNSAFE_HEADER_PARAM: /[\s"\\;:/=(),<>@[\]?]|^[-']|'$/, // 匹配头参数值中的不安全字符
33
33
  TRAILING_SPACE_OR_PUNCTUATION: /[ \t.,!?][^ \t.,!?]*$/, // 匹配空格、制表符或标点符号以及其后的非空格、非标点符号字符
@@ -38,32 +38,32 @@ const regexs = {
38
38
  TEXT_TYPE: /^text\//i, // 匹配所有text/*内容类型
39
39
  IMAGE_TYPE: /^image\//i, // 检测图像内容类型
40
40
  HTTP_URL: /^https?:\/\//i, // 检测HTTP/HTTPS协议
41
- ERROR_SUFFIX: /Error\]$/i, // 匹配以"Error]"结尾的字符串(不区分大小写)
41
+ ERROR_SUFFIX: /Error\]$/i, // 匹配以"Error]"结尾的字符串(不区分大小写)
42
42
  MULTIPART: /^multipart\//i, // 匹配multipart/*内容类型
43
43
  HTML_TYPE: /^text\/html\b/i, // 检测HTML内容类型
44
44
  MESSAGE_TYPE: /^message\//i, // 检测消息内容类型
45
- ENCODED_SEQUENCE: /^[=]([0-9A-F]{2})/i, // 匹配编码序列(等号后跟2个十六进制数字)
45
+ ENCODED_SEQUENCE: /^[=]([0-9A-F]{2})/i, // 匹配编码序列(等号后跟2个十六进制数字)
46
46
  CONTENT_FEATURES: /^Content-Features$/i, // 匹配Content-Features头部
47
- UTF8_ENCODING: /^(?:=[\da-f]{2}){1,4}$/i, // 匹配UTF-8编码序列(1-4个连续的编码字节)
48
- INCOMPLETE_ENCODING: /[=][\da-f]{0,2}$/i, // 匹配不完整的编码序列(等号后跟0-2个十六进制数字)
47
+ UTF8_ENCODING: /^(?:=[\da-f]{2}){1,4}$/i, // 匹配UTF-8编码序列(1-4个连续的编码字节)
48
+ INCOMPLETE_ENCODING: /[=][\da-f]{0,2}$/i, // 匹配不完整的编码序列(等号后跟0-2个十六进制数字)
49
49
  INCOMPLETE_ENCODING_END: /[=][0-9A-F]?$/i, // 匹配字符串末尾的不完整编码序列
50
- TRANSPORTER_PROTOCOL: /^(smtps?|direct):/i, // 匹配传输器协议(smtp://, smtps://, direct://)
51
- INCOMPLETE_ENCODING_SHORT: /[=][\da-f]{0,1}$/i, // 匹配更短的不完整编码序列(等号后跟0-1个十六进制数字)
50
+ TRANSPORTER_PROTOCOL: /^(smtps?|direct):/i, // 匹配传输器协议(smtp://, smtps://, direct://)
51
+ INCOMPLETE_ENCODING_SHORT: /[=][\da-f]{0,1}$/i, // 匹配更短的不完整编码序列(等号后跟0-1个十六进制数字)
52
52
  MULTIPART_OR_MESSAGE: /^(multipart|message)\//i, // 匹配multipart/*或message/*内容类型
53
53
  HTTP_RESPONSE_STATUS_LINE: /^HTTP\/\d+\.\d+ (\d+)/i, // 匹配 HTTP 响应状态行
54
54
  AUTH_MECHANISM: /[ -]AUTH(?:(\s+|=)[^\n]*\s+|\s+|=)/i, // 匹配AUTH机制
55
55
  IPV6_ADDRESS: /^(?:[A-F0-9]{1,4}:){7}[A-F0-9]{1,4}$/i, // 匹配IPv6地址格式
56
56
 
57
- NON_ASCII: /\P{ASCII}/u, // 匹配非ASCII字符(Unicode字符)
58
- NON_BASIC_ASCII: /[^\x00-\x7F]/, // 检测非基本ASCII字符(编码>127)
57
+ NON_ASCII: /\P{ASCII}/u, // 匹配非ASCII字符(Unicode字符)
58
+ NON_BASIC_ASCII: /[^\x00-\x7F]/, // 检测非基本ASCII字符(编码>127)
59
59
  PROTOCOL_CHECK: /^(https?|mailto|ftp):/, // 检查URL协议
60
60
  LIST_ID_PROTOCOL_REMOVE: /^<[^:]+\/{,2}/, // 移除List-ID中的协议部分
61
- UTF16_HIGH_SURROGATE: /[\uD800-\uDBFF]/, // 匹配UTF-16高代理项(代理对的第一部分)
61
+ UTF16_HIGH_SURROGATE: /[\uD800-\uDBFF]/, // 匹配UTF-16高代理项(代理对的第一部分)
62
62
  EMOJI_OR_SURROGATE: /[\ud83c\ud83d\ud83e]/, // 匹配emoji或UTF-16代理对起始字符
63
- PLAIN_TEXT: /[^\x09\x0a\x0d\x20-\x7e]/, // 匹配非可打印7位ASCII字符(允许\t\n\r和\x20-\x7e)
64
- PARAM_PLAIN_TEXT: /[^\x09\x0a\x0d\x20-\x7e]|"/, // 匹配非可打印字符或双引号(参数校验用)
63
+ PLAIN_TEXT: /[^\x09\x0a\x0d\x20-\x7e]/, // 匹配非可打印7位ASCII字符(允许\t\n\r和\x20-\x7e)
64
+ PARAM_PLAIN_TEXT: /[^\x09\x0a\x0d\x20-\x7e]|"/, // 匹配非可打印字符或双引号(参数校验用)
65
65
  FIRST_NON_ASCII_WORD: /(?:^|\s)([^\s]*[^\x00-\x7F])/, // 匹配第一个包含非ASCII字符的单词
66
- NEED_ENCODING: /[\x00-\x20\x2B\x3D\x7F]|[^\x21-\x7E]/, // 不可打印ASCII字符(包括非ASCII字符)
66
+ NEED_ENCODING: /[\x00-\x20\x2B\x3D\x7F]|[^\x21-\x7E]/, // 不可打印ASCII字符(包括非ASCII字符)
67
67
 
68
68
  DSN: /[ -]DSN\b/im, // 匹配DSN扩展支持
69
69
  STARTTLS: /[ -]STARTTLS\b/im, // 匹配STARTTLS扩展支持
@@ -84,28 +84,28 @@ const regexs = {
84
84
  NON_DIGIT: /\D/g, // 匹配非数字字符
85
85
  CID_CLEAN: /[<>]/g, // 清理Content-ID中的尖括号
86
86
  BLANK_REGEX: /\s/g, // 匹配所有空白字符
87
- URL_SAFE_BASE64: /\W/g, // 匹配非单词字符(非字母、数字、下划线)
87
+ URL_SAFE_BASE64: /\W/g, // 匹配非单词字符(非字母、数字、下划线)
88
88
  MULTIPLE_WSP: /[ \t]+/g, // 匹配行内多个空白字符
89
- LINEBREAKS: /\r?\n|\r/g, // 匹配各种换行符(CRLF、LF、CR)
90
- QUOTED_PAIR: /([\\"])/g, // 匹配需要转义的字符(反斜杠和引号)
89
+ LINEBREAKS: /\r?\n|\r/g, // 匹配各种换行符(CRLF、LF、CR)
90
+ QUOTED_PAIR: /([\\"])/g, // 匹配需要转义的字符(反斜杠和引号)
91
91
  HYPHEN_REPLACE: /-(\w)/g, // 连字符替换为驼峰命名
92
92
  ANGLE_BRACKET: /<[^>]*>/g, // 匹配尖括号内的内容
93
93
  GLOBAL_WHITESPACE: /\s+/g, // 匹配多个连续空白字符为单个空格
94
94
  ENCODING_FORMAT: /[-_\s]/g, // 格式化编码字符串
95
- CLEAN_MESSAGE_ID: /[<>\s]/g, // 清理message-id头,移除尖括号和空白字符
95
+ CLEAN_MESSAGE_ID: /[<>\s]/g, // 清理message-id头,移除尖括号和空白字符
96
96
  GLOBAL_CRLF_OR_LF: /\r?\n/g, // 匹配CRLF或LF换行符
97
97
  URL_CLEAN: /[\s<]+|[\s>]+/g, // 清理URL中的空白和尖括号
98
- RFC2231_SPECIAL_CHARS: /[=?_\s]/g, // RFC2231编码中需要转义的特殊字符:等号、问号、下划线和空白字符
99
- FORBIDDEN_CHARS: /[\x00-\x1F<>]+/g, // 匹配控制字符和尖括号(不允许的字符)
98
+ RFC2231_SPECIAL_CHARS: /[=?_\s]/g, // RFC2231编码中需要转义的特殊字符:等号、问号、下划线和空白字符
99
+ FORBIDDEN_CHARS: /[\x00-\x1F<>]+/g, // 匹配控制字符和尖括号(不允许的字符)
100
100
  SEPARATORS: /[\x2E\u3002\uFF0E\uFF61]/g, // RFC 3490 分隔符
101
101
  NON_ALPHANUMERIC_DOT_HYPHEN: /[^a-zA-Z0-9.-]/g, // 匹配非字母数字、点号和连字符的字符
102
102
  SIGNATURE_FOLDING: /(^.{73}|.{75}(?!\r?\n|\r))/g, // 签名折行处理正则
103
- NON_LATIN: /[\x00-\x08\x0B\x0C\x0E-\x1F\P{ASCII}]/gu, // 匹配非拉丁字符(控制字符和Unicode)
103
+ NON_LATIN: /[\x00-\x08\x0B\x0C\x0E-\x1F\P{ASCII}]/gu, // 匹配非拉丁字符(控制字符和Unicode)
104
104
  NON_SAFE_URI: /[^\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]+/g, // 匹配URI中的不安全字符范围
105
105
  NON_SAFE_URI_CHAR: /[\x00-\x1F *'()<>@,;:\\"[\]?=\u007F-\uFFFF]/g, // 匹配单个URI不安全字符
106
106
  LATIN: /[a-z]/gi, // 匹配拉丁字母
107
- NON_SAFE_CHAR: /[^a-z0-9!*+\-/=]/gi, // 匹配非安全字符(用于QP编码)
108
- COMPLETE_ENCODING: /[=][\da-f]{2}$/gi, // 匹配完整的编码序列(等号后跟2个十六进制数字)
107
+ NON_SAFE_CHAR: /[^a-z0-9!*+\-/=]/gi, // 匹配非安全字符(用于QP编码)
108
+ COMPLETE_ENCODING: /[=][\da-f]{2}$/gi, // 匹配完整的编码序列(等号后跟2个十六进制数字)
109
109
  DATA_URL_IMAGE: /(]{0,1024} src\s{0,20}=[\s"']{0,20})(data:([^;]+);[^"'>\s]+)/gi, // 匹配HTML中的data URL图片
110
110
  NORMALIZE_HEADER_KEY: /^X-SMTPAPI$|^(MIME|DKIM|ARC|BIMI)\b|^[a-z]|-(SPF|FBL|ID|MD5)$|-[a-z]/gi, // 头部键规范化规则
111
111
  };
@@ -4,7 +4,7 @@ import { PK, getLogger, prepareMessageForSending, cleanup } from './shared.js';
4
4
  /**
5
5
  * 生成Sendmail传输对象
6
6
  *
7
- * 可用的选项如下:
7
+ * 可用的选项如下:
8
8
  * **path** sendmail二进制文件的可选路径
9
9
  * **newline** 换行符类型,可以是'windows'或'unix'
10
10
  * **args** sendmail二进制文件的参数数组
@@ -24,8 +24,8 @@ class SendmailTransport {
24
24
 
25
25
  // 根据选项配置参数
26
26
  if (options) {
27
- if (typeof options === 'string') this.path = options; // 如果options是字符串,直接作为sendmail路径
28
- // 否则,如果options是对象,检查path和args属性
27
+ if (typeof options === 'string') this.path = options; // 如果options是字符串,直接作为sendmail路径
28
+ // 否则,如果options是对象,检查path和args属性
29
29
  else if (typeof options === 'object') {
30
30
  const { path, args, newline = '' } = options;
31
31
  if (path) this.path = path;
@@ -23,7 +23,7 @@ class SesTransport extends EventEmitter {
23
23
  */
24
24
  getRegion(cb) {
25
25
  const regionFunc = this.ses.sesClient.config?.region;
26
- // 如果是函数,则调用该函数并返回结果,否则返回结果
26
+ // 如果是函数,则调用该函数并返回结果,否则返回结果
27
27
  return typeof regionFunc === 'function' ? regionFunc().then(r => cb(null, r)).catch(cb) : cb(null, false);
28
28
  }
29
29
 
@@ -50,14 +50,16 @@ class SesTransport extends EventEmitter {
50
50
  */
51
51
  getRawMessage = next => {
52
52
  // 在 DKIM 签名中不使用 Message-ID 和 Date; 将流数据读取到Buffer中
53
- const sF = _dkim.skipFields, dMsgId = 'date:message-id', stream = readStream.pipe(new LeWindows()); // 创建消息流
54
- _dkim.skipFields = (sF && typeof sF === 'string') ? `${sF}:${dMsgId}` : dMsgId, resolveStream(stream, next);
53
+ // 创建消息流
54
+ const sF = _dkim.skipFields, dMsgId = 'date:message-id', stream = readStream.pipe(new LeWindows());
55
+ _dkim.skipFields = (sF && typeof sF === 'string') ? `${sF}:${dMsgId}` : dMsgId,
56
+ resolveStream(stream, next);
55
57
  };
56
58
 
57
59
  // 异步执行发送过程
58
60
  setImmediate(() =>
59
61
  getRawMessage((err, raw) => {
60
- if (err) return handleError(err, '为 %s 创建消息失败。%s');
62
+ if (err) return handleError(err, '为 %s 创建消息失败;%s');
61
63
 
62
64
  // 构建 SES 消息对象(添加自定义参数: ses)
63
65
  const sesMessage = {
@@ -86,19 +88,21 @@ class SesTransport extends EventEmitter {
86
88
  * 验证 SES 配置
87
89
  *
88
90
  * @param {Function} callback 回调函数
89
- * @return {Promise} 如果没有提供回调函数,则返回 Promise
91
+ * @return {Promise} 如果没有提供回调函数,则返回 Promise
90
92
  */
91
93
  verify(callback) {
92
94
  const promise = !callback ? new Promise((resolve, reject) => callback = callbackPromise(resolve, reject)) : null,
93
- // 忽略某些特定错误,仍然认为验证成功
95
+ // 忽略某些特定错误,仍然认为验证成功
94
96
  cb = err => callback(
95
- err && !['InvalidParameterValue', 'MessageRejected'].includes(err?.code || err?.Code || err?.name) ? err : null, true
97
+ err && !['InvalidParameterValue', 'MessageRejected'].includes(err?.code || err?.Code || err?.name)
98
+ ? err : null, true
96
99
  ),
97
100
 
98
101
  iEmail = 'invalid@invalid',
99
102
  // 创建测试用的无效消息
100
103
  sesMessage = {
101
- Content: { Raw: { Data: Buffer.from(`From: <${iEmail}>\r\nTo: <${iEmail}>\r\nSubject: Invalid\r\n\r\nInvalid`) } },
104
+ Content: { Raw: { Data: Buffer.from(
105
+ `From: <${iEmail}>\r\nTo: <${iEmail}>\r\nSubject: Invalid\r\n\r\nInvalid`) } },
102
106
  FromEmailAddress: iEmail, Destination: { ToAddresses: [iEmail] }
103
107
  };
104
108