@push.rocks/smartlinuxrepo 0.0.0-stage → 0.1.0

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 (131) hide show
  1. package/.smartconfig.json +64 -0
  2. package/dist_ts/00_commitinfo_data.d.ts +8 -0
  3. package/dist_ts/00_commitinfo_data.js +9 -0
  4. package/dist_ts/alpine/apkindex.d.ts +39 -0
  5. package/dist_ts/alpine/apkindex.js +211 -0
  6. package/dist_ts/alpine/pkginfo.d.ts +45 -0
  7. package/dist_ts/alpine/pkginfo.js +159 -0
  8. package/dist_ts/alpine/reader.d.ts +20 -0
  9. package/dist_ts/alpine/reader.js +89 -0
  10. package/dist_ts/arch/builder.d.ts +41 -0
  11. package/dist_ts/arch/builder.js +234 -0
  12. package/dist_ts/arch/reader.d.ts +16 -0
  13. package/dist_ts/arch/reader.js +271 -0
  14. package/dist_ts/arch/signer.d.ts +21 -0
  15. package/dist_ts/arch/signer.js +66 -0
  16. package/dist_ts/arch/vercmp.d.ts +12 -0
  17. package/dist_ts/arch/vercmp.js +170 -0
  18. package/dist_ts/debian/control.d.ts +22 -0
  19. package/dist_ts/debian/control.js +137 -0
  20. package/dist_ts/debian/gostring.d.ts +26 -0
  21. package/dist_ts/debian/gostring.js +145 -0
  22. package/dist_ts/debian/indexbuilder.d.ts +47 -0
  23. package/dist_ts/debian/indexbuilder.js +299 -0
  24. package/dist_ts/debian/mailaddress.d.ts +12 -0
  25. package/dist_ts/debian/mailaddress.js +736 -0
  26. package/dist_ts/debian/reader.d.ts +30 -0
  27. package/dist_ts/debian/reader.js +110 -0
  28. package/dist_ts/index.d.ts +23 -0
  29. package/dist_ts/index.js +18 -0
  30. package/dist_ts/plugins.d.ts +8 -0
  31. package/dist_ts/plugins.js +11 -0
  32. package/dist_ts/readers/ar.d.ts +31 -0
  33. package/dist_ts/readers/ar.js +87 -0
  34. package/dist_ts/readers/bytes.d.ts +58 -0
  35. package/dist_ts/readers/bytes.js +147 -0
  36. package/dist_ts/readers/bytesource.d.ts +40 -0
  37. package/dist_ts/readers/bytesource.js +154 -0
  38. package/dist_ts/readers/decompress.d.ts +43 -0
  39. package/dist_ts/readers/decompress.js +269 -0
  40. package/dist_ts/readers/gzipmember.d.ts +32 -0
  41. package/dist_ts/readers/gzipmember.js +176 -0
  42. package/dist_ts/readers/tar.d.ts +85 -0
  43. package/dist_ts/readers/tar.js +731 -0
  44. package/dist_ts/readers/text.d.ts +16 -0
  45. package/dist_ts/readers/text.js +96 -0
  46. package/dist_ts/rpm/builder.d.ts +28 -0
  47. package/dist_ts/rpm/builder.js +208 -0
  48. package/dist_ts/rpm/header.d.ts +138 -0
  49. package/dist_ts/rpm/header.js +546 -0
  50. package/dist_ts/rpm/reader.d.ts +20 -0
  51. package/dist_ts/rpm/reader.js +306 -0
  52. package/dist_ts/rpm/repoconfig.d.ts +15 -0
  53. package/dist_ts/rpm/repoconfig.js +38 -0
  54. package/dist_ts/rpm/repodata.d.ts +44 -0
  55. package/dist_ts/rpm/repodata.js +148 -0
  56. package/dist_ts/rpm/sign.d.ts +30 -0
  57. package/dist_ts/rpm/sign.js +183 -0
  58. package/dist_ts/rpm/updates.d.ts +38 -0
  59. package/dist_ts/rpm/updates.js +265 -0
  60. package/dist_ts/rpm/xml.d.ts +18 -0
  61. package/dist_ts/rpm/xml.js +90 -0
  62. package/dist_ts/shared/architecture.d.ts +8 -0
  63. package/dist_ts/shared/architecture.js +17 -0
  64. package/dist_ts/shared/errors.d.ts +38 -0
  65. package/dist_ts/shared/errors.js +22 -0
  66. package/dist_ts/shared/gopath.d.ts +13 -0
  67. package/dist_ts/shared/gopath.js +53 -0
  68. package/dist_ts/shared/gostrings.d.ts +14 -0
  69. package/dist_ts/shared/gostrings.js +29 -0
  70. package/dist_ts/shared/limits.d.ts +47 -0
  71. package/dist_ts/shared/limits.js +57 -0
  72. package/dist_ts/shared/types.d.ts +213 -0
  73. package/dist_ts/shared/types.js +13 -0
  74. package/dist_ts/shared/url.d.ts +18 -0
  75. package/dist_ts/shared/url.js +371 -0
  76. package/dist_ts/shared/validation.d.ts +21 -0
  77. package/dist_ts/shared/validation.js +58 -0
  78. package/dist_ts/signing/keys.d.ts +36 -0
  79. package/dist_ts/signing/keys.js +89 -0
  80. package/dist_ts/signing/openpgpframing.d.ts +33 -0
  81. package/dist_ts/signing/openpgpframing.js +93 -0
  82. package/dist_ts/signing/signers.d.ts +63 -0
  83. package/dist_ts/signing/signers.js +248 -0
  84. package/dist_ts/writers/gotar.d.ts +43 -0
  85. package/dist_ts/writers/gotar.js +218 -0
  86. package/license.md +19 -0
  87. package/package.json +56 -3
  88. package/readme.md +176 -0
  89. package/ts/00_commitinfo_data.ts +8 -0
  90. package/ts/alpine/apkindex.ts +241 -0
  91. package/ts/alpine/pkginfo.ts +183 -0
  92. package/ts/alpine/reader.ts +96 -0
  93. package/ts/arch/builder.ts +283 -0
  94. package/ts/arch/reader.ts +307 -0
  95. package/ts/arch/signer.ts +70 -0
  96. package/ts/arch/vercmp.ts +188 -0
  97. package/ts/debian/control.ts +150 -0
  98. package/ts/debian/gostring.ts +150 -0
  99. package/ts/debian/indexbuilder.ts +355 -0
  100. package/ts/debian/mailaddress.ts +754 -0
  101. package/ts/debian/reader.ts +126 -0
  102. package/ts/index.ts +46 -0
  103. package/ts/plugins.ts +13 -0
  104. package/ts/readers/ar.ts +102 -0
  105. package/ts/readers/bytes.ts +182 -0
  106. package/ts/readers/bytesource.ts +164 -0
  107. package/ts/readers/decompress.ts +332 -0
  108. package/ts/readers/gzipmember.ts +209 -0
  109. package/ts/readers/tar.ts +835 -0
  110. package/ts/readers/text.ts +91 -0
  111. package/ts/rpm/builder.ts +253 -0
  112. package/ts/rpm/header.ts +628 -0
  113. package/ts/rpm/reader.ts +317 -0
  114. package/ts/rpm/repoconfig.ts +45 -0
  115. package/ts/rpm/repodata.ts +219 -0
  116. package/ts/rpm/sign.ts +200 -0
  117. package/ts/rpm/updates.ts +314 -0
  118. package/ts/rpm/xml.ts +101 -0
  119. package/ts/shared/architecture.ts +16 -0
  120. package/ts/shared/errors.ts +78 -0
  121. package/ts/shared/gopath.ts +54 -0
  122. package/ts/shared/gostrings.ts +31 -0
  123. package/ts/shared/limits.ts +93 -0
  124. package/ts/shared/types.ts +251 -0
  125. package/ts/shared/url.ts +387 -0
  126. package/ts/shared/validation.ts +71 -0
  127. package/ts/signing/keys.ts +110 -0
  128. package/ts/signing/openpgpframing.ts +115 -0
  129. package/ts/signing/signers.ts +315 -0
  130. package/ts/writers/gotar.ts +256 -0
  131. package/README.md +0 -3
@@ -0,0 +1,754 @@
1
+ import { decodeRune, RUNE_ERROR, utf8ByteString } from './gostring.js';
2
+
3
+ /**
4
+ * A port of Go 1.27.2 `net/mail.ParseAddress` (Gitea 28.2.0's toolchain,
5
+ * `go.mod`), used for the Debian `Maintainer` name. Inputs and outputs are
6
+ * byte strings (see `gostring.ts`). Only success and the parsed name matter
7
+ * to the caller, so error texts are not ported.
8
+ */
9
+
10
+ export interface IMailAddress {
11
+ name: string;
12
+ address: string;
13
+ }
14
+
15
+ class MailError extends Error {}
16
+
17
+ /** Go `mail.ParseAddress`; `null` where Go returns an error. */
18
+ export function parseMailAddress(addressArg: string): IMailAddress | null {
19
+ try {
20
+ return new AddrParser(addressArg).parseSingleAddress();
21
+ } catch (error) {
22
+ if (error instanceof MailError) {
23
+ return null;
24
+ }
25
+ throw error;
26
+ }
27
+ }
28
+
29
+ function fail(messageArg: string): never {
30
+ throw new MailError(messageArg);
31
+ }
32
+
33
+ class AddrParser {
34
+ constructor(public s: string) {}
35
+
36
+ public parseSingleAddress(): IMailAddress {
37
+ const addrs = this.parseAddress(true);
38
+ if (!this.skipCFWS()) {
39
+ fail('misformatted parenthetical comment');
40
+ }
41
+ if (!this.empty()) {
42
+ fail('expected single address');
43
+ }
44
+ if (addrs.length === 0) {
45
+ fail('empty group');
46
+ }
47
+ if (addrs.length > 1) {
48
+ fail('group with multiple addresses');
49
+ }
50
+ return addrs[0];
51
+ }
52
+
53
+ private parseAddress(handleGroupArg: boolean): IMailAddress[] {
54
+ this.skipSpace();
55
+ if (this.empty()) {
56
+ fail('no address');
57
+ }
58
+ const spec = this.tryConsumeAddrSpec();
59
+ if (spec !== null) {
60
+ let displayName = '';
61
+ this.skipSpace();
62
+ if (!this.empty() && this.peek() === '(') {
63
+ displayName = this.consumeDisplayNameComment();
64
+ }
65
+ return [{ name: displayName, address: spec }];
66
+ }
67
+
68
+ let displayName = '';
69
+ if (this.peek() !== '<') {
70
+ displayName = this.consumePhrase();
71
+ }
72
+ this.skipSpace();
73
+ if (handleGroupArg && this.consume(':')) {
74
+ return this.consumeGroupList();
75
+ }
76
+ if (!this.consume('<')) {
77
+ fail('no angle-addr');
78
+ }
79
+ const angleSpec = this.tryConsumeAddrSpec();
80
+ if (angleSpec === null) {
81
+ // Go returns the addr-spec error here
82
+ fail('invalid addr-spec');
83
+ }
84
+ if (!this.consume('>')) {
85
+ fail('unclosed angle-addr');
86
+ }
87
+ return [{ name: displayName, address: angleSpec }];
88
+ }
89
+
90
+ private consumeGroupList(): IMailAddress[] {
91
+ const group: IMailAddress[] = [];
92
+ this.skipSpace();
93
+ if (this.consume(';')) {
94
+ if (!this.skipCFWS()) {
95
+ fail('misformatted parenthetical comment');
96
+ }
97
+ return group;
98
+ }
99
+ for (;;) {
100
+ this.skipSpace();
101
+ group.push(...this.parseAddress(false));
102
+ if (!this.skipCFWS()) {
103
+ fail('misformatted parenthetical comment');
104
+ }
105
+ if (this.consume(';')) {
106
+ if (!this.skipCFWS()) {
107
+ fail('misformatted parenthetical comment');
108
+ }
109
+ break;
110
+ }
111
+ if (!this.consume(',')) {
112
+ fail('expected comma');
113
+ }
114
+ }
115
+ return group;
116
+ }
117
+
118
+ /** `consumeAddrSpec`, restoring the parser state on failure; `null` for Go's error. */
119
+ private tryConsumeAddrSpec(): string | null {
120
+ const orig = this.s;
121
+ try {
122
+ return this.consumeAddrSpec();
123
+ } catch (error) {
124
+ if (error instanceof MailError) {
125
+ this.s = orig;
126
+ return null;
127
+ }
128
+ throw error;
129
+ }
130
+ }
131
+
132
+ private consumeAddrSpec(): string {
133
+ let localPart: string;
134
+ this.skipSpace();
135
+ if (this.empty()) {
136
+ fail('no addr-spec');
137
+ }
138
+ if (this.peek() === '"') {
139
+ localPart = this.consumeQuotedString();
140
+ if (localPart === '') {
141
+ fail('empty quoted string in addr-spec');
142
+ }
143
+ } else {
144
+ localPart = this.consumeAtom(true, false);
145
+ }
146
+ if (!this.consume('@')) {
147
+ fail('missing @ in addr-spec');
148
+ }
149
+ let domain: string;
150
+ this.skipSpace();
151
+ if (this.empty()) {
152
+ fail('no domain in addr-spec');
153
+ }
154
+ if (this.peek() === '[') {
155
+ domain = this.consumeDomainLiteral();
156
+ } else {
157
+ domain = this.consumeAtom(true, false);
158
+ }
159
+ return `${localPart}@${domain}`;
160
+ }
161
+
162
+ private consumePhrase(): string {
163
+ const words: string[] = [];
164
+ let sb = '';
165
+ let failed = false;
166
+ for (;;) {
167
+ if (words.length > 0) {
168
+ if (!this.skipCFWS()) {
169
+ fail('misformatted parenthetical comment');
170
+ }
171
+ }
172
+ let word: string;
173
+ let isEncoded = false;
174
+ this.skipSpace();
175
+ if (this.empty()) {
176
+ break;
177
+ }
178
+ try {
179
+ if (this.peek() === '"') {
180
+ word = this.consumeQuotedString();
181
+ } else {
182
+ word = this.consumeAtom(true, true);
183
+ [word, isEncoded] = decodeRfc2047Word(word);
184
+ }
185
+ } catch (error) {
186
+ if (!(error instanceof MailError)) {
187
+ throw error;
188
+ }
189
+ failed = true;
190
+ break;
191
+ }
192
+ if (isEncoded) {
193
+ sb += word;
194
+ } else if (sb.length > 0) {
195
+ words.push(sb);
196
+ sb = '';
197
+ words.push(word);
198
+ } else {
199
+ words.push(word);
200
+ }
201
+ }
202
+ if (sb.length > 0) {
203
+ words.push(sb);
204
+ }
205
+ // Go ignores the error once at least one word was read
206
+ if (failed && words.length === 0) {
207
+ fail('missing word in phrase');
208
+ }
209
+ return words.join(' ');
210
+ }
211
+
212
+ private consumeQuotedString(): string {
213
+ let i = 1;
214
+ let qs = '';
215
+ let escaped = false;
216
+ for (;;) {
217
+ const [rune, size] = decodeRune(this.s, i);
218
+ if (size === 0) {
219
+ fail('unclosed quoted-string');
220
+ }
221
+ if (size === 1 && rune === RUNE_ERROR) {
222
+ fail('invalid utf-8 in quoted-string');
223
+ }
224
+ if (escaped) {
225
+ if (!isVchar(rune) && !isWsp(rune)) {
226
+ fail('bad character in quoted-string');
227
+ }
228
+ qs += this.s.slice(i, i + size);
229
+ escaped = false;
230
+ } else if (isQtext(rune) || isWsp(rune)) {
231
+ qs += this.s.slice(i, i + size);
232
+ } else if (rune === 0x22) {
233
+ break;
234
+ } else if (rune === 0x5c) {
235
+ escaped = true;
236
+ } else {
237
+ fail('bad character in quoted-string');
238
+ }
239
+ i += size;
240
+ }
241
+ this.s = this.s.slice(i + 1);
242
+ return qs;
243
+ }
244
+
245
+ private consumeAtom(dotArg: boolean, permissiveArg: boolean): string {
246
+ let i = 0;
247
+ for (;;) {
248
+ const [rune, size] = decodeRune(this.s, i);
249
+ if (size === 1 && rune === RUNE_ERROR) {
250
+ fail('invalid utf-8 in address');
251
+ }
252
+ if (size === 0 || !isAtext(rune, dotArg)) {
253
+ break;
254
+ }
255
+ i += size;
256
+ }
257
+ if (i === 0) {
258
+ fail('invalid string');
259
+ }
260
+ const atom = this.s.slice(0, i);
261
+ this.s = this.s.slice(i);
262
+ if (!permissiveArg) {
263
+ if (atom.startsWith('.')) {
264
+ fail('leading dot in atom');
265
+ }
266
+ if (atom.includes('..')) {
267
+ fail('double dot in atom');
268
+ }
269
+ if (atom.endsWith('.')) {
270
+ fail('trailing dot in atom');
271
+ }
272
+ }
273
+ return atom;
274
+ }
275
+
276
+ private consumeDomainLiteral(): string {
277
+ if (!this.consume('[')) {
278
+ fail('missing "[" in domain-literal');
279
+ }
280
+ const dtextStart = this.s;
281
+ let dtextLength = 0;
282
+ for (;;) {
283
+ if (this.empty()) {
284
+ fail('unclosed domain-literal');
285
+ }
286
+ if (this.peek() === ']') {
287
+ break;
288
+ }
289
+ const [rune, size] = decodeRune(this.s, 0);
290
+ if (size === 1 && rune === RUNE_ERROR) {
291
+ fail('invalid utf-8 in domain-literal');
292
+ }
293
+ if (!isDtext(rune)) {
294
+ fail('bad character in domain-literal');
295
+ }
296
+ dtextLength += size;
297
+ this.s = this.s.slice(size);
298
+ }
299
+ const dtext = dtextStart.slice(0, dtextLength);
300
+ if (!this.consume(']')) {
301
+ fail('unclosed domain-literal');
302
+ }
303
+ if (dtext.startsWith('IPv6:')) {
304
+ if (parseNetIp(dtext.slice(5)) === null) {
305
+ fail('invalid IPv6 address in domain-literal');
306
+ }
307
+ } else {
308
+ const ip = parseNetIp(dtext);
309
+ if (ip === null || !isIpv4Mapped(ip)) {
310
+ fail('invalid IP address in domain-literal');
311
+ }
312
+ }
313
+ return `[${dtext}]`;
314
+ }
315
+
316
+ private consumeDisplayNameComment(): string {
317
+ if (!this.consume('(')) {
318
+ fail('comment does not start with (');
319
+ }
320
+ const [comment, ok] = this.consumeComment();
321
+ if (!ok) {
322
+ fail('misformatted parenthetical comment');
323
+ }
324
+ const words = comment.split(/[ \t]+/).filter((word) => word.length > 0);
325
+ for (let index = 0; index < words.length; index++) {
326
+ const [decoded, isEncoded] = decodeRfc2047Word(words[index]);
327
+ if (isEncoded) {
328
+ words[index] = decoded;
329
+ }
330
+ }
331
+ return words.join(' ');
332
+ }
333
+
334
+ private consume(charArg: string): boolean {
335
+ if (this.empty() || this.peek() !== charArg) {
336
+ return false;
337
+ }
338
+ this.s = this.s.slice(1);
339
+ return true;
340
+ }
341
+
342
+ private skipSpace(): void {
343
+ this.s = this.s.replace(/^[ \t]+/, '');
344
+ }
345
+
346
+ private peek(): string {
347
+ return this.s[0];
348
+ }
349
+
350
+ private empty(): boolean {
351
+ return this.s.length === 0;
352
+ }
353
+
354
+ private skipCFWS(): boolean {
355
+ this.skipSpace();
356
+ for (;;) {
357
+ if (!this.consume('(')) {
358
+ break;
359
+ }
360
+ const [, ok] = this.consumeComment();
361
+ if (!ok) {
362
+ return false;
363
+ }
364
+ this.skipSpace();
365
+ }
366
+ return true;
367
+ }
368
+
369
+ private consumeComment(): [string, boolean] {
370
+ let depth = 1;
371
+ let comment = '';
372
+ for (;;) {
373
+ if (this.empty() || depth === 0) {
374
+ break;
375
+ }
376
+ if (this.peek() === '\\' && this.s.length > 1) {
377
+ this.s = this.s.slice(1);
378
+ } else if (this.peek() === '(') {
379
+ depth += 1;
380
+ } else if (this.peek() === ')') {
381
+ depth -= 1;
382
+ }
383
+ if (depth > 0) {
384
+ comment += this.s[0];
385
+ }
386
+ this.s = this.s.slice(1);
387
+ }
388
+ return [comment, depth === 0];
389
+ }
390
+ }
391
+
392
+ /**
393
+ * Go `addrParser.decodeRFC2047Word` with the default decoder: `[word, true]`
394
+ * for a decoded encoded-word; `[s, false]` for anything that is not a valid
395
+ * encoded-word; a charset other than UTF-8, ISO-8859-1 and US-ASCII throws
396
+ * (Go returns that error with `isEncoded` true).
397
+ */
398
+ function decodeRfc2047Word(wordArg: string): [string, boolean] {
399
+ if (
400
+ wordArg.length < 8 ||
401
+ !wordArg.startsWith('=?') ||
402
+ !wordArg.endsWith('?=') ||
403
+ wordArg.split('?').length - 1 !== 4
404
+ ) {
405
+ return [wordArg, false];
406
+ }
407
+ const inner = wordArg.slice(2, -2);
408
+ const firstMark = inner.indexOf('?');
409
+ const charset = inner.slice(0, firstMark);
410
+ if (charset === '') {
411
+ return [wordArg, false];
412
+ }
413
+ const rest = inner.slice(firstMark + 1);
414
+ const secondMark = rest.indexOf('?');
415
+ const encoding = rest.slice(0, secondMark);
416
+ const text = rest.slice(secondMark + 1);
417
+ if (encoding.length !== 1) {
418
+ return [wordArg, false];
419
+ }
420
+ let content: string | null;
421
+ if (encoding === 'B' || encoding === 'b') {
422
+ content = decodeStdBase64(text);
423
+ } else if (encoding === 'Q' || encoding === 'q') {
424
+ content = decodeQ(text);
425
+ } else {
426
+ content = null;
427
+ }
428
+ if (content === null) {
429
+ return [wordArg, false];
430
+ }
431
+ if (equalFoldAscii(charset, 'utf-8')) {
432
+ return [content, true];
433
+ }
434
+ if (equalFoldAscii(charset, 'iso-8859-1')) {
435
+ // each byte becomes the rune of the same value (Go `buf.WriteRune(rune(c))`)
436
+ return [utf8ByteString(content), true];
437
+ }
438
+ if (equalFoldAscii(charset, 'us-ascii')) {
439
+ let converted = '';
440
+ for (let i = 0; i < content.length; i++) {
441
+ const byte = content.charCodeAt(i);
442
+ converted += byte >= 0x80 ? '\xef\xbf\xbd' : content[i];
443
+ }
444
+ return [converted, true];
445
+ }
446
+ fail('charset not supported');
447
+ }
448
+
449
+ /**
450
+ * Go `strings.EqualFold(target, s)` for an ASCII lowercase target: each rune
451
+ * of `s` must fold to the target letter. Among the letters of the three
452
+ * charset names only `s` has a non-ASCII fold partner, U+017F.
453
+ */
454
+ function equalFoldAscii(valueArg: string, targetArg: string): boolean {
455
+ let i = 0;
456
+ for (const target of targetArg) {
457
+ const [rune, size] = decodeRune(valueArg, i);
458
+ if (size === 0) {
459
+ return false;
460
+ }
461
+ i += size;
462
+ const code = target.charCodeAt(0);
463
+ if (rune === code) {
464
+ continue;
465
+ }
466
+ if (code >= 0x61 && code <= 0x7a && rune === code - 0x20) {
467
+ continue;
468
+ }
469
+ if (target === 's' && rune === 0x17f) {
470
+ continue;
471
+ }
472
+ return false;
473
+ }
474
+ return i === valueArg.length;
475
+ }
476
+
477
+ const BASE64_ALPHABET = /^[A-Za-z0-9+/]*$/;
478
+
479
+ /** Go `base64.StdEncoding.DecodeString` (padding required, trailing bits ignored); `null` on error. */
480
+ function decodeStdBase64(textArg: string): string | null {
481
+ // Go skips CR and LF; an atom holds neither
482
+ if (textArg.length % 4 !== 0) {
483
+ return null;
484
+ }
485
+ let body = textArg;
486
+ if (body.endsWith('==')) {
487
+ body = body.slice(0, -2);
488
+ if (body.length % 4 !== 2) {
489
+ return null;
490
+ }
491
+ } else if (body.endsWith('=')) {
492
+ body = body.slice(0, -1);
493
+ if (body.length % 4 !== 3) {
494
+ return null;
495
+ }
496
+ }
497
+ if (!BASE64_ALPHABET.test(body)) {
498
+ return null;
499
+ }
500
+ return Buffer.from(body, 'base64').toString('latin1');
501
+ }
502
+
503
+ /** Go `mime.qDecode`; `null` on error. */
504
+ function decodeQ(textArg: string): string | null {
505
+ let decoded = '';
506
+ for (let i = 0; i < textArg.length; i++) {
507
+ const c = textArg.charCodeAt(i);
508
+ if (c === 0x5f) {
509
+ decoded += ' ';
510
+ } else if (c === 0x3d) {
511
+ if (i + 2 >= textArg.length) {
512
+ return null;
513
+ }
514
+ const high = hexValue(textArg.charCodeAt(i + 1));
515
+ const low = hexValue(textArg.charCodeAt(i + 2));
516
+ if (high < 0 || low < 0) {
517
+ return null;
518
+ }
519
+ decoded += String.fromCharCode((high << 4) | low);
520
+ i += 2;
521
+ } else if ((c >= 0x20 && c <= 0x7e) || c === 0x0a || c === 0x0d || c === 0x09) {
522
+ decoded += textArg[i];
523
+ } else {
524
+ return null;
525
+ }
526
+ }
527
+ return decoded;
528
+ }
529
+
530
+ function hexValue(codeArg: number): number {
531
+ if (codeArg >= 0x30 && codeArg <= 0x39) {
532
+ return codeArg - 0x30;
533
+ }
534
+ if (codeArg >= 0x41 && codeArg <= 0x46) {
535
+ return codeArg - 0x41 + 10;
536
+ }
537
+ if (codeArg >= 0x61 && codeArg <= 0x66) {
538
+ return codeArg - 0x61 + 10;
539
+ }
540
+ return -1;
541
+ }
542
+
543
+ function isAtext(runeArg: number, dotArg: boolean): boolean {
544
+ if (runeArg === 0x2e) {
545
+ return dotArg;
546
+ }
547
+ // RFC 5322 3.2.3 specials: ( ) < > [ ] : ; @ \ , "
548
+ if ('()<>[]:;@\\,"'.includes(String.fromCharCode(runeArg)) && runeArg < 0x80) {
549
+ return false;
550
+ }
551
+ return isVchar(runeArg);
552
+ }
553
+
554
+ function isQtext(runeArg: number): boolean {
555
+ if (runeArg === 0x5c || runeArg === 0x22) {
556
+ return false;
557
+ }
558
+ return isVchar(runeArg);
559
+ }
560
+
561
+ function isVchar(runeArg: number): boolean {
562
+ return (runeArg >= 0x21 && runeArg <= 0x7e) || runeArg >= 0x80;
563
+ }
564
+
565
+ function isWsp(runeArg: number): boolean {
566
+ return runeArg === 0x20 || runeArg === 0x09;
567
+ }
568
+
569
+ function isDtext(runeArg: number): boolean {
570
+ if (runeArg === 0x5b || runeArg === 0x5d || runeArg === 0x5c) {
571
+ return false;
572
+ }
573
+ return isVchar(runeArg);
574
+ }
575
+
576
+ // ---------------------------------------------------------------- net.ParseIP
577
+
578
+ /**
579
+ * Go `net.ParseIP` (through `netip.ParseAddr`, zones refused): the 16-byte
580
+ * form, or `null`.
581
+ */
582
+ function parseNetIp(valueArg: string): Uint8Array | null {
583
+ for (let i = 0; i < valueArg.length; i++) {
584
+ const c = valueArg[i];
585
+ if (c === '.') {
586
+ const fields = parseIpv4Fields(valueArg, 0, valueArg.length);
587
+ if (fields === null) {
588
+ return null;
589
+ }
590
+ const ip = new Uint8Array(16);
591
+ ip[10] = 0xff;
592
+ ip[11] = 0xff;
593
+ ip.set(fields, 12);
594
+ return ip;
595
+ }
596
+ if (c === ':') {
597
+ return parseIpv6(valueArg);
598
+ }
599
+ if (c === '%') {
600
+ return null;
601
+ }
602
+ }
603
+ return null;
604
+ }
605
+
606
+ function isIpv4Mapped(ipArg: Uint8Array): boolean {
607
+ for (let i = 0; i < 10; i++) {
608
+ if (ipArg[i] !== 0) {
609
+ return false;
610
+ }
611
+ }
612
+ return ipArg[10] === 0xff && ipArg[11] === 0xff;
613
+ }
614
+
615
+ /** Go `netip.parseIPv4Fields`. */
616
+ function parseIpv4Fields(inArg: string, offArg: number, endArg: number): Uint8Array | null {
617
+ const fields = new Uint8Array(4);
618
+ let value = 0;
619
+ let pos = 0;
620
+ let digits = 0;
621
+ const s = inArg.slice(offArg, endArg);
622
+ for (let i = 0; i < s.length; i++) {
623
+ const c = s.charCodeAt(i);
624
+ if (c >= 0x30 && c <= 0x39) {
625
+ if (digits === 1 && value === 0) {
626
+ return null;
627
+ }
628
+ value = value * 10 + (c - 0x30);
629
+ digits += 1;
630
+ if (value > 255) {
631
+ return null;
632
+ }
633
+ } else if (c === 0x2e) {
634
+ if (i === 0 || i === s.length - 1 || s[i - 1] === '.') {
635
+ return null;
636
+ }
637
+ if (pos === 3) {
638
+ return null;
639
+ }
640
+ fields[pos] = value;
641
+ pos += 1;
642
+ value = 0;
643
+ digits = 0;
644
+ } else {
645
+ return null;
646
+ }
647
+ }
648
+ if (pos < 3) {
649
+ return null;
650
+ }
651
+ fields[3] = value;
652
+ return fields;
653
+ }
654
+
655
+ /** Go `netip.parseIPv6`; a zone makes `net.ParseIP` fail, so any zone is `null`. */
656
+ function parseIpv6(inArg: string): Uint8Array | null {
657
+ let s = inArg;
658
+ const percent = s.indexOf('%');
659
+ if (percent !== -1) {
660
+ // an empty zone is an error, a non-empty zone is refused by net.ParseIP
661
+ return null;
662
+ }
663
+ const ip = new Uint8Array(16);
664
+ let ellipsis = -1;
665
+ if (s.length >= 2 && s[0] === ':' && s[1] === ':') {
666
+ ellipsis = 0;
667
+ s = s.slice(2);
668
+ if (s.length === 0) {
669
+ return ip;
670
+ }
671
+ }
672
+ let i = 0;
673
+ while (i < 16) {
674
+ let off = 0;
675
+ let acc = 0;
676
+ for (; off < s.length; off++) {
677
+ const c = s.charCodeAt(off);
678
+ if (c >= 0x30 && c <= 0x39) {
679
+ acc = acc * 16 + (c - 0x30);
680
+ } else if (c >= 0x61 && c <= 0x66) {
681
+ acc = acc * 16 + (c - 0x61 + 10);
682
+ } else if (c >= 0x41 && c <= 0x46) {
683
+ acc = acc * 16 + (c - 0x41 + 10);
684
+ } else {
685
+ break;
686
+ }
687
+ if (off > 3) {
688
+ return null;
689
+ }
690
+ if (acc > 0xffff) {
691
+ return null;
692
+ }
693
+ }
694
+ if (off === 0) {
695
+ return null;
696
+ }
697
+ if (off < s.length && s[off] === '.') {
698
+ if (ellipsis < 0 && i !== 12) {
699
+ return null;
700
+ }
701
+ if (i + 4 > 16) {
702
+ return null;
703
+ }
704
+ const end = inArg.length;
705
+ const fields = parseIpv4Fields(inArg, end - s.length, end);
706
+ if (fields === null) {
707
+ return null;
708
+ }
709
+ ip.set(fields, i);
710
+ s = '';
711
+ i += 4;
712
+ break;
713
+ }
714
+ ip[i] = acc >> 8;
715
+ ip[i + 1] = acc & 0xff;
716
+ i += 2;
717
+ s = s.slice(off);
718
+ if (s.length === 0) {
719
+ break;
720
+ }
721
+ if (s[0] !== ':') {
722
+ return null;
723
+ } else if (s.length === 1) {
724
+ return null;
725
+ }
726
+ s = s.slice(1);
727
+ if (s[0] === ':') {
728
+ if (ellipsis >= 0) {
729
+ return null;
730
+ }
731
+ ellipsis = i;
732
+ s = s.slice(1);
733
+ if (s.length === 0) {
734
+ break;
735
+ }
736
+ }
737
+ }
738
+ if (s.length !== 0) {
739
+ return null;
740
+ }
741
+ if (i < 16) {
742
+ if (ellipsis < 0) {
743
+ return null;
744
+ }
745
+ const n = 16 - i;
746
+ for (let j = i - 1; j >= ellipsis; j--) {
747
+ ip[j + n] = ip[j];
748
+ }
749
+ ip.fill(0, ellipsis, ellipsis + n);
750
+ } else if (ellipsis >= 0) {
751
+ return null;
752
+ }
753
+ return ip;
754
+ }