@email-utils/validator-syntax 0.0.1-9 → 1.0.0-rc.2

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.
@@ -0,0 +1,2229 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ let _email_utils_validator_syntax = require("./index.cjs");
3
+ //#region src/fixtures/types.ts
4
+ /**
5
+ * Every preset, in the order the support matrix's columns take.
6
+ *
7
+ * @example
8
+ * ```ts
9
+ * import { isValidSyntax } from '@email-utils/validator-syntax';
10
+ * import { presets } from '@email-utils/validator-syntax/fixtures';
11
+ *
12
+ * presets.filter((preset) => isValidSyntax('ada@localhost', { preset }));
13
+ * // => ['html5']
14
+ * ```
15
+ */
16
+ const presets = [
17
+ "practical",
18
+ "rfc5321",
19
+ "rfc5322",
20
+ "html5"
21
+ ];
22
+ /**
23
+ * The support matrix's rows, in order, with each feature's label.
24
+ *
25
+ * @example
26
+ * ```ts
27
+ * import { syntaxFeatures } from '@email-utils/validator-syntax/fixtures';
28
+ *
29
+ * syntaxFeatures[0];
30
+ * // => { feature: 'dot-atom', label: 'Letters, digits, and single dots' }
31
+ * ```
32
+ */
33
+ const syntaxFeatures = [
34
+ {
35
+ feature: "dot-atom",
36
+ label: "Letters, digits, and single dots"
37
+ },
38
+ {
39
+ feature: "atext-specials",
40
+ label: "Printable specials like `+`, `/`, `=`, and `_`"
41
+ },
42
+ {
43
+ feature: "route-chars",
44
+ label: "Route characters (`%`, `!`)"
45
+ },
46
+ {
47
+ feature: "misplaced-dots",
48
+ label: "Leading, trailing, or consecutive dots"
49
+ },
50
+ {
51
+ feature: "long-local",
52
+ label: "Local part over 64 characters"
53
+ },
54
+ {
55
+ feature: "quoted-local",
56
+ label: "Quoted local part"
57
+ },
58
+ {
59
+ feature: "quoted-pair",
60
+ label: "Backslash escapes in a quoted string"
61
+ },
62
+ {
63
+ feature: "obs-local",
64
+ label: "Dot-separated atoms and quoted strings mixed"
65
+ },
66
+ {
67
+ feature: "comments",
68
+ label: "Comments"
69
+ },
70
+ {
71
+ feature: "fws",
72
+ label: "Whitespace and line folding between tokens"
73
+ },
74
+ {
75
+ feature: "obs-control",
76
+ label: "Control characters (obsolete syntax)"
77
+ },
78
+ {
79
+ feature: "ipv4-literal",
80
+ label: "IPv4 address literal"
81
+ },
82
+ {
83
+ feature: "ipv6-literal",
84
+ label: "IPv6 address literal"
85
+ },
86
+ {
87
+ feature: "general-literal",
88
+ label: "Other domain literals"
89
+ },
90
+ {
91
+ feature: "dotless-domain",
92
+ label: "Domain with no dot"
93
+ },
94
+ {
95
+ feature: "unknown-tld",
96
+ label: "TLD outside the IANA set"
97
+ }
98
+ ];
99
+ const valid = { ok: true };
100
+ function fail(reason, index) {
101
+ return index === void 0 ? {
102
+ ok: false,
103
+ reason
104
+ } : {
105
+ ok: false,
106
+ reason,
107
+ index
108
+ };
109
+ }
110
+ /** The same result under every preset. */
111
+ function everywhere(expected) {
112
+ return {
113
+ practical: expected,
114
+ rfc5321: expected,
115
+ rfc5322: expected,
116
+ html5: expected
117
+ };
118
+ }
119
+ /** Valid under both RFC presets; `practical` and `html5` both give `rejected`. */
120
+ function rfcOnly(rejected) {
121
+ return {
122
+ practical: rejected,
123
+ rfc5321: valid,
124
+ rfc5322: valid,
125
+ html5: rejected
126
+ };
127
+ }
128
+ /** Fails the same way under every preset but rfc5322, which gives `rfc5322`. */
129
+ function allBut5322(others, rfc5322) {
130
+ return {
131
+ ...everywhere(others),
132
+ rfc5322
133
+ };
134
+ }
135
+ /** Valid only under `rfc5322`; every other preset gives `rejected`. */
136
+ function rfc5322Only(rejected) {
137
+ return {
138
+ ...everywhere(rejected),
139
+ rfc5322: valid
140
+ };
141
+ }
142
+ //#endregion
143
+ //#region src/fixtures/isemail.ts
144
+ /** A domain literal that only rfc5322's dtext accepts. */
145
+ const rfc5322Literal = {
146
+ practical: fail("syntax.domain.invalid_char", 5),
147
+ rfc5321: fail("syntax.domain.literal_invalid", 5),
148
+ rfc5322: valid,
149
+ html5: fail("syntax.domain.invalid_char", 5)
150
+ };
151
+ /** Quote-unaware presets fail at the opening quote; the RFC presets give `rfc`. */
152
+ function quotedLocal(rfc) {
153
+ return {
154
+ practical: fail("syntax.local.invalid_char", 0),
155
+ rfc5321: rfc,
156
+ rfc5322: rfc,
157
+ html5: fail("syntax.local.invalid_char", 0)
158
+ };
159
+ }
160
+ const longLabel = "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghikl";
161
+ /**
162
+ * Dominic Sayers' is_email tests, each tagged with its `id` in tests.xml.
163
+ *
164
+ * @example
165
+ * ```ts
166
+ * import { isemailFixtures } from '@email-utils/validator-syntax/fixtures';
167
+ *
168
+ * isemailFixtures.find((fixture) => fixture.isemail === 5);
169
+ * // => {
170
+ * // address: 'test@io',
171
+ * // expected: { practical: { ok: false, reason: 'syntax.domain.no_dot' } },
172
+ * // }
173
+ * ```
174
+ */
175
+ const isemailFixtures = [
176
+ {
177
+ isemail: 1,
178
+ address: "",
179
+ description: "Empty string",
180
+ expected: everywhere(fail("syntax.address.empty"))
181
+ },
182
+ {
183
+ isemail: 2,
184
+ address: "test",
185
+ description: "No @",
186
+ expected: everywhere(fail("syntax.address.no_at"))
187
+ },
188
+ {
189
+ isemail: 3,
190
+ address: "@",
191
+ description: "Only an @",
192
+ expected: everywhere(fail("syntax.local.empty"))
193
+ },
194
+ {
195
+ isemail: 4,
196
+ address: "test@",
197
+ description: "Nothing after the @",
198
+ expected: everywhere(fail("syntax.domain.empty"))
199
+ },
200
+ {
201
+ isemail: 5,
202
+ address: "test@io",
203
+ description: "Domain with no dot, a real TLD",
204
+ feature: "dotless-domain",
205
+ expected: {
206
+ ...everywhere(fail("syntax.domain.no_dot")),
207
+ html5: valid
208
+ }
209
+ },
210
+ {
211
+ isemail: 6,
212
+ address: "@io",
213
+ description: "Nothing before the @, dotless domain",
214
+ expected: everywhere(fail("syntax.local.empty"))
215
+ },
216
+ {
217
+ isemail: 7,
218
+ address: "@iana.org",
219
+ description: "Nothing before the @",
220
+ expected: everywhere(fail("syntax.local.empty"))
221
+ },
222
+ {
223
+ isemail: 8,
224
+ address: "test@iana.org",
225
+ description: "Simple address",
226
+ feature: "dot-atom",
227
+ expected: everywhere(valid)
228
+ },
229
+ {
230
+ isemail: 9,
231
+ address: "test@nominet.org.uk",
232
+ description: "Three-label domain",
233
+ feature: "dot-atom",
234
+ expected: everywhere(valid)
235
+ },
236
+ {
237
+ isemail: 10,
238
+ address: "test@about.museum",
239
+ description: "Six-letter TLD",
240
+ feature: "dot-atom",
241
+ expected: everywhere(valid)
242
+ },
243
+ {
244
+ isemail: 11,
245
+ address: "a@iana.org",
246
+ description: "Single-letter local part",
247
+ feature: "dot-atom",
248
+ expected: everywhere(valid)
249
+ },
250
+ {
251
+ isemail: 12,
252
+ address: "test@e.com",
253
+ description: "Single-letter domain label",
254
+ feature: "dot-atom",
255
+ expected: everywhere(valid)
256
+ },
257
+ {
258
+ isemail: 13,
259
+ address: "test@iana.a",
260
+ description: "Single-letter TLD",
261
+ feature: "unknown-tld",
262
+ expected: {
263
+ ...everywhere(valid),
264
+ practical: fail("syntax.tld.unknown")
265
+ }
266
+ },
267
+ {
268
+ isemail: 14,
269
+ address: "test.test@iana.org",
270
+ description: "Dot in the local part",
271
+ feature: "dot-atom",
272
+ expected: everywhere(valid)
273
+ },
274
+ {
275
+ isemail: 15,
276
+ address: ".test@iana.org",
277
+ description: "Local part starting with a dot",
278
+ feature: "misplaced-dots",
279
+ expected: {
280
+ ...everywhere(fail("syntax.local.invalid_char", 0)),
281
+ html5: valid
282
+ }
283
+ },
284
+ {
285
+ isemail: 16,
286
+ address: "test.@iana.org",
287
+ description: "Local part ending with a dot",
288
+ feature: "misplaced-dots",
289
+ expected: {
290
+ ...everywhere(fail("syntax.local.invalid_char", 4)),
291
+ html5: valid
292
+ }
293
+ },
294
+ {
295
+ isemail: 17,
296
+ address: "test..iana.org",
297
+ description: "No @, consecutive dots",
298
+ expected: everywhere(fail("syntax.address.no_at"))
299
+ },
300
+ {
301
+ isemail: 18,
302
+ address: "test_exa-mple.com",
303
+ description: "No @, underscore and hyphen",
304
+ expected: everywhere(fail("syntax.address.no_at"))
305
+ },
306
+ {
307
+ isemail: 19,
308
+ address: "!#$%&`*+/=?^`{|}~@iana.org",
309
+ description: "Local part of printable specials only",
310
+ feature: "route-chars",
311
+ expected: {
312
+ ...everywhere(valid),
313
+ practical: fail("syntax.local.invalid_char", 0)
314
+ }
315
+ },
316
+ {
317
+ isemail: 20,
318
+ address: "test\\@test@iana.org",
319
+ description: "Backslash-escaped @ outside a quoted string",
320
+ expected: everywhere(fail("syntax.local.invalid_char", 4))
321
+ },
322
+ {
323
+ isemail: 21,
324
+ address: "123@iana.org",
325
+ description: "All-digit local part",
326
+ feature: "dot-atom",
327
+ expected: everywhere(valid)
328
+ },
329
+ {
330
+ isemail: 22,
331
+ address: "test@123.com",
332
+ description: "All-digit domain label",
333
+ feature: "dot-atom",
334
+ expected: everywhere(valid)
335
+ },
336
+ {
337
+ isemail: 23,
338
+ address: "test@iana.123",
339
+ description: "All-digit TLD",
340
+ feature: "unknown-tld",
341
+ expected: {
342
+ ...everywhere(valid),
343
+ practical: fail("syntax.tld.unknown")
344
+ }
345
+ },
346
+ {
347
+ isemail: 24,
348
+ address: "test@255.255.255.255",
349
+ description: "Dotted quad without brackets",
350
+ feature: "unknown-tld",
351
+ expected: {
352
+ ...everywhere(valid),
353
+ practical: fail("syntax.tld.unknown")
354
+ }
355
+ },
356
+ {
357
+ isemail: 25,
358
+ address: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org",
359
+ description: "Local part of exactly 64 characters",
360
+ feature: "dot-atom",
361
+ expected: everywhere(valid)
362
+ },
363
+ {
364
+ isemail: 26,
365
+ address: "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklmn@iana.org",
366
+ description: "Local part of 65 characters",
367
+ feature: "long-local",
368
+ expected: {
369
+ practical: fail("syntax.local.too_long"),
370
+ rfc5321: fail("syntax.local.too_long"),
371
+ rfc5322: valid,
372
+ html5: valid
373
+ }
374
+ },
375
+ {
376
+ isemail: 27,
377
+ address: `test@${longLabel}.com`,
378
+ description: "Domain label of exactly 63 characters",
379
+ feature: "dot-atom",
380
+ expected: everywhere(valid)
381
+ },
382
+ {
383
+ isemail: 28,
384
+ address: `test@${longLabel}m.com`,
385
+ description: "Domain label of 64 characters",
386
+ expected: everywhere(fail("syntax.domain.label_invalid", 5))
387
+ },
388
+ {
389
+ isemail: 29,
390
+ address: "test@mason-dixon.com",
391
+ description: "Hyphen inside a domain label",
392
+ feature: "dot-atom",
393
+ expected: everywhere(valid)
394
+ },
395
+ {
396
+ isemail: 30,
397
+ address: "test@-iana.org",
398
+ description: "Domain label starting with a hyphen",
399
+ expected: everywhere(fail("syntax.domain.label_invalid", 5))
400
+ },
401
+ {
402
+ isemail: 31,
403
+ address: "test@iana-.com",
404
+ description: "Domain label ending with a hyphen",
405
+ expected: everywhere(fail("syntax.domain.label_invalid", 9))
406
+ },
407
+ {
408
+ isemail: 32,
409
+ address: "test@c--n.com",
410
+ description: "Consecutive hyphens inside a domain label",
411
+ feature: "dot-atom",
412
+ expected: everywhere(valid)
413
+ },
414
+ {
415
+ isemail: 33,
416
+ address: "test@iana.co-uk",
417
+ description: "Hyphenated TLD",
418
+ feature: "unknown-tld",
419
+ expected: {
420
+ ...everywhere(valid),
421
+ practical: fail("syntax.tld.unknown")
422
+ }
423
+ },
424
+ {
425
+ isemail: 34,
426
+ address: "test@.iana.org",
427
+ description: "Domain starting with a dot",
428
+ expected: everywhere(fail("syntax.domain.label_invalid", 5))
429
+ },
430
+ {
431
+ isemail: 35,
432
+ address: "test@iana.org.",
433
+ description: "Domain ending with a dot",
434
+ expected: everywhere(fail("syntax.domain.label_invalid", 13))
435
+ },
436
+ {
437
+ isemail: 36,
438
+ address: "test@iana..com",
439
+ description: "Consecutive dots in the domain",
440
+ expected: everywhere(fail("syntax.domain.label_invalid", 10))
441
+ },
442
+ {
443
+ isemail: 37,
444
+ address: "a@a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v.w.x.y.z.a.b.c.d.e.f.g.h.i.j.k.l.m.n.o.p.q.r.s.t.u.v",
445
+ description: "126 single-letter domain labels",
446
+ expected: {
447
+ ...everywhere(valid),
448
+ practical: fail("syntax.tld.unknown")
449
+ }
450
+ },
451
+ {
452
+ isemail: 38,
453
+ address: `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@${longLabel}.${longLabel}.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghi`,
454
+ description: "Address of exactly 254 characters",
455
+ expected: {
456
+ ...everywhere(valid),
457
+ practical: fail("syntax.tld.unknown")
458
+ }
459
+ },
460
+ {
461
+ isemail: 39,
462
+ address: `abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@${longLabel}.${longLabel}.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghij`,
463
+ description: "Address of 255 characters",
464
+ expected: everywhere(fail("syntax.address.too_long"))
465
+ },
466
+ {
467
+ isemail: 40,
468
+ address: `a@${longLabel}.${longLabel}.${longLabel}.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hij`,
469
+ description: "Domain of 255 characters",
470
+ expected: {
471
+ ...everywhere(fail("syntax.domain.too_long")),
472
+ html5: fail("syntax.address.too_long")
473
+ }
474
+ },
475
+ {
476
+ isemail: 41,
477
+ address: `a@${longLabel}.${longLabel}.${longLabel}.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefg.hijk`,
478
+ description: "Domain of 256 characters",
479
+ expected: {
480
+ ...everywhere(fail("syntax.domain.too_long")),
481
+ html5: fail("syntax.address.too_long")
482
+ }
483
+ },
484
+ {
485
+ isemail: 42,
486
+ address: "\"test\"@iana.org",
487
+ description: "Quoted local part",
488
+ feature: "quoted-local",
489
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
490
+ },
491
+ {
492
+ isemail: 43,
493
+ address: "\"\"@iana.org",
494
+ description: "Empty quoted local part",
495
+ feature: "quoted-local",
496
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
497
+ },
498
+ {
499
+ isemail: 44,
500
+ address: "\"\"\"@iana.org",
501
+ description: "Stray quote after an empty quoted string",
502
+ expected: quotedLocal(fail("syntax.local.invalid_char", 2))
503
+ },
504
+ {
505
+ isemail: 45,
506
+ address: "\"\\a\"@iana.org",
507
+ description: "Escaped letter in a quoted string",
508
+ feature: "quoted-pair",
509
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
510
+ },
511
+ {
512
+ isemail: 46,
513
+ address: "\"\\\"\"@iana.org",
514
+ description: "Escaped quote in a quoted string",
515
+ feature: "quoted-pair",
516
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
517
+ },
518
+ {
519
+ isemail: 47,
520
+ address: "\"\\\"@iana.org",
521
+ description: "Quoted string whose closing quote is escaped",
522
+ expected: quotedLocal(fail("syntax.address.no_at"))
523
+ },
524
+ {
525
+ isemail: 48,
526
+ address: "\"\\\\\"@iana.org",
527
+ description: "Escaped backslash in a quoted string",
528
+ feature: "quoted-pair",
529
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
530
+ },
531
+ {
532
+ isemail: 49,
533
+ address: "test\"@iana.org",
534
+ description: "Stray quote at the end of an atom",
535
+ expected: everywhere(fail("syntax.local.invalid_char", 4))
536
+ },
537
+ {
538
+ isemail: 50,
539
+ address: "\"test@iana.org",
540
+ description: "Unterminated quoted string",
541
+ expected: quotedLocal(fail("syntax.address.no_at"))
542
+ },
543
+ {
544
+ isemail: 51,
545
+ address: "\"test\"test@iana.org",
546
+ description: "Atom straight after a quoted string",
547
+ expected: quotedLocal(fail("syntax.local.invalid_char", 6))
548
+ },
549
+ {
550
+ isemail: 52,
551
+ address: "test\"text\"@iana.org",
552
+ description: "Quoted string straight after an atom",
553
+ expected: everywhere(fail("syntax.local.invalid_char", 4))
554
+ },
555
+ {
556
+ isemail: 53,
557
+ address: "\"test\"\"test\"@iana.org",
558
+ description: "Two quoted strings with no dot between",
559
+ expected: quotedLocal(fail("syntax.local.invalid_char", 6))
560
+ },
561
+ {
562
+ isemail: 54,
563
+ address: "\"test\".\"test\"@iana.org",
564
+ description: "Dot-separated quoted strings",
565
+ feature: "obs-local",
566
+ expected: {
567
+ ...quotedLocal(valid),
568
+ rfc5321: fail("syntax.local.invalid_char", 6)
569
+ }
570
+ },
571
+ {
572
+ isemail: 55,
573
+ address: "\"test\\ test\"@iana.org",
574
+ description: "Escaped space in a quoted string",
575
+ feature: "quoted-pair",
576
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
577
+ },
578
+ {
579
+ isemail: 56,
580
+ address: "\"test\".test@iana.org",
581
+ description: "Quoted string then a dot-separated atom",
582
+ feature: "obs-local",
583
+ expected: {
584
+ ...quotedLocal(valid),
585
+ rfc5321: fail("syntax.local.invalid_char", 6)
586
+ }
587
+ },
588
+ {
589
+ isemail: 57,
590
+ address: "\"test\0\"@iana.org",
591
+ description: "NUL in a quoted string",
592
+ expected: quotedLocal(fail("syntax.local.invalid_char", 5))
593
+ },
594
+ {
595
+ isemail: 58,
596
+ address: "\"test\\\0\"@iana.org",
597
+ description: "Escaped NUL in a quoted string",
598
+ feature: "obs-control",
599
+ expected: {
600
+ ...quotedLocal(valid),
601
+ rfc5321: fail("syntax.local.invalid_char", 6)
602
+ }
603
+ },
604
+ {
605
+ isemail: 59,
606
+ address: "\"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefghj\"@iana.org",
607
+ description: "Quoted local part of 65 characters",
608
+ expected: {
609
+ ...quotedLocal(valid),
610
+ rfc5321: fail("syntax.local.too_long")
611
+ }
612
+ },
613
+ {
614
+ isemail: 60,
615
+ address: "\"abcdefghijklmnopqrstuvwxyz abcdefghijklmnopqrstuvwxyz abcdefg\\h\"@iana.org",
616
+ description: "Quoted local part of 65 characters with an escape",
617
+ expected: {
618
+ ...quotedLocal(valid),
619
+ rfc5321: fail("syntax.local.too_long")
620
+ }
621
+ },
622
+ {
623
+ isemail: 61,
624
+ address: "test@[255.255.255.255]",
625
+ description: "IPv4 address literal",
626
+ feature: "ipv4-literal",
627
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 5))
628
+ },
629
+ {
630
+ isemail: 62,
631
+ address: "test@a[255.255.255.255]",
632
+ description: "Domain literal after a label",
633
+ expected: everywhere(fail("syntax.domain.invalid_char", 6))
634
+ },
635
+ {
636
+ isemail: 63,
637
+ address: "test@[255.255.255]",
638
+ description: "IPv4 literal with three octets",
639
+ feature: "general-literal",
640
+ expected: rfc5322Literal
641
+ },
642
+ {
643
+ isemail: 64,
644
+ address: "test@[255.255.255.255.255]",
645
+ description: "IPv4 literal with five octets",
646
+ feature: "general-literal",
647
+ expected: rfc5322Literal
648
+ },
649
+ {
650
+ isemail: 65,
651
+ address: "test@[255.255.255.256]",
652
+ description: "IPv4 literal with an octet over 255",
653
+ feature: "general-literal",
654
+ expected: rfc5322Literal
655
+ },
656
+ {
657
+ isemail: 66,
658
+ address: "test@[1111:2222:3333:4444:5555:6666:7777:8888]",
659
+ description: "IPv6 literal without the IPv6 tag",
660
+ feature: "general-literal",
661
+ expected: rfc5322Literal
662
+ },
663
+ {
664
+ isemail: 67,
665
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666:7777]",
666
+ description: "IPv6 literal with seven groups",
667
+ feature: "general-literal",
668
+ expected: rfc5322Literal
669
+ },
670
+ {
671
+ isemail: 68,
672
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888]",
673
+ description: "IPv6 literal with eight groups",
674
+ feature: "ipv6-literal",
675
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 5))
676
+ },
677
+ {
678
+ isemail: 69,
679
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666:7777:8888:9999]",
680
+ description: "IPv6 literal with nine groups",
681
+ feature: "general-literal",
682
+ expected: rfc5322Literal
683
+ },
684
+ {
685
+ isemail: 70,
686
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666:7777:888G]",
687
+ description: "IPv6 literal with a non-hex digit",
688
+ feature: "general-literal",
689
+ expected: rfc5322Literal
690
+ },
691
+ {
692
+ isemail: 71,
693
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666::8888]",
694
+ description: "IPv6 literal with :: standing for one group",
695
+ feature: "general-literal",
696
+ expected: rfc5322Literal
697
+ },
698
+ {
699
+ isemail: 72,
700
+ address: "test@[IPv6:1111:2222:3333:4444:5555::8888]",
701
+ description: "Compressed IPv6 literal",
702
+ feature: "ipv6-literal",
703
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 5))
704
+ },
705
+ {
706
+ isemail: 73,
707
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666::7777:8888]",
708
+ description: "Compressed IPv6 literal with eight groups",
709
+ feature: "general-literal",
710
+ expected: rfc5322Literal
711
+ },
712
+ {
713
+ isemail: 74,
714
+ address: "test@[IPv6::3333:4444:5555:6666:7777:8888]",
715
+ description: "IPv6 literal starting with a single colon",
716
+ feature: "general-literal",
717
+ expected: rfc5322Literal
718
+ },
719
+ {
720
+ isemail: 75,
721
+ address: "test@[IPv6:::3333:4444:5555:6666:7777:8888]",
722
+ description: "IPv6 literal starting with ::",
723
+ feature: "ipv6-literal",
724
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 5))
725
+ },
726
+ {
727
+ isemail: 76,
728
+ address: "test@[IPv6:1111::4444:5555::8888]",
729
+ description: "IPv6 literal with two ::",
730
+ feature: "general-literal",
731
+ expected: rfc5322Literal
732
+ },
733
+ {
734
+ isemail: 77,
735
+ address: "test@[IPv6:::]",
736
+ description: "IPv6 unspecified address literal",
737
+ feature: "ipv6-literal",
738
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 5))
739
+ },
740
+ {
741
+ isemail: 78,
742
+ address: "test@[IPv6:1111:2222:3333:4444:5555:255.255.255.255]",
743
+ description: "IPv4-in-IPv6 literal with five groups",
744
+ feature: "general-literal",
745
+ expected: rfc5322Literal
746
+ },
747
+ {
748
+ isemail: 79,
749
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666:255.255.255.255]",
750
+ description: "IPv4-in-IPv6 literal with six groups",
751
+ feature: "ipv6-literal",
752
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 5))
753
+ },
754
+ {
755
+ isemail: 80,
756
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666:7777:255.255.255.255]",
757
+ description: "IPv4-in-IPv6 literal with seven groups",
758
+ feature: "general-literal",
759
+ expected: rfc5322Literal
760
+ },
761
+ {
762
+ isemail: 81,
763
+ address: "test@[IPv6:1111:2222:3333:4444::255.255.255.255]",
764
+ description: "Compressed IPv4-in-IPv6 literal",
765
+ feature: "ipv6-literal",
766
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 5))
767
+ },
768
+ {
769
+ isemail: 82,
770
+ address: "test@[IPv6:1111:2222:3333:4444:5555:6666::255.255.255.255]",
771
+ description: "Compressed IPv4-in-IPv6 literal with six groups",
772
+ feature: "general-literal",
773
+ expected: rfc5322Literal
774
+ },
775
+ {
776
+ isemail: 83,
777
+ address: "test@[IPv6:1111:2222:3333:4444:::255.255.255.255]",
778
+ description: "IPv4-in-IPv6 literal with :::",
779
+ feature: "general-literal",
780
+ expected: rfc5322Literal
781
+ },
782
+ {
783
+ isemail: 84,
784
+ address: "test@[IPv6::255.255.255.255]",
785
+ description: "IPv4-in-IPv6 literal starting with a single colon",
786
+ feature: "general-literal",
787
+ expected: rfc5322Literal
788
+ },
789
+ {
790
+ isemail: 85,
791
+ address: " test @iana.org",
792
+ description: "Spaces around the local part",
793
+ feature: "fws",
794
+ expected: allBut5322(fail("syntax.local.unquoted_space", 0), valid)
795
+ },
796
+ {
797
+ isemail: 86,
798
+ address: "test@ iana .com",
799
+ description: "Spaces around a domain label",
800
+ feature: "fws",
801
+ expected: allBut5322(fail("syntax.domain.invalid_char", 5), valid)
802
+ },
803
+ {
804
+ isemail: 87,
805
+ address: "test . test@iana.org",
806
+ description: "Spaces around a dot in the local part",
807
+ feature: "fws",
808
+ expected: allBut5322(fail("syntax.local.unquoted_space", 4), valid)
809
+ },
810
+ {
811
+ isemail: 88,
812
+ address: "\r\n test@iana.org",
813
+ description: "Folded whitespace before the local part",
814
+ feature: "fws",
815
+ expected: allBut5322(fail("syntax.local.invalid_char", 0), valid)
816
+ },
817
+ {
818
+ isemail: 89,
819
+ address: "\r\n \r\n test@iana.org",
820
+ description: "Whitespace folded twice before the local part",
821
+ feature: "fws",
822
+ expected: allBut5322(fail("syntax.local.invalid_char", 0), valid)
823
+ },
824
+ {
825
+ isemail: 90,
826
+ address: "(comment)test@iana.org",
827
+ description: "Comment before the local part",
828
+ feature: "comments",
829
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 0))
830
+ },
831
+ {
832
+ isemail: 91,
833
+ address: "((comment)test@iana.org",
834
+ description: "Unterminated comment around a nested one",
835
+ expected: allBut5322(fail("syntax.comment.not_allowed", 0), fail("syntax.address.no_at"))
836
+ },
837
+ {
838
+ isemail: 92,
839
+ address: "(comment(comment))test@iana.org",
840
+ description: "Nested comment",
841
+ feature: "comments",
842
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 0))
843
+ },
844
+ {
845
+ isemail: 93,
846
+ address: "test@(comment)iana.org",
847
+ description: "Comment before the domain",
848
+ feature: "comments",
849
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 5))
850
+ },
851
+ {
852
+ isemail: 94,
853
+ address: "test(comment)test@iana.org",
854
+ description: "Comment between two atoms with no dot",
855
+ expected: allBut5322(fail("syntax.comment.not_allowed", 4), fail("syntax.local.invalid_char", 13))
856
+ },
857
+ {
858
+ isemail: 95,
859
+ address: "test@(comment)[255.255.255.255]",
860
+ description: "Comment before a domain literal",
861
+ feature: "comments",
862
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 5))
863
+ },
864
+ {
865
+ isemail: 96,
866
+ address: "(comment)abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghiklm@iana.org",
867
+ description: "Comment before a 64-character local part",
868
+ feature: "comments",
869
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 0))
870
+ },
871
+ {
872
+ isemail: 97,
873
+ address: `test@(comment)${longLabel}.com`,
874
+ description: "Comment before a 63-character domain label",
875
+ feature: "comments",
876
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 5))
877
+ },
878
+ {
879
+ isemail: 98,
880
+ address: "(comment)test@abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghik.abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstuvwxyzabcdefghijk.abcdefghijklmnopqrstu",
881
+ description: "Comment before a 254-character address",
882
+ feature: "comments",
883
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 0))
884
+ },
885
+ {
886
+ isemail: 99,
887
+ address: "test@iana.org\n",
888
+ description: "Trailing line feed",
889
+ expected: everywhere(fail("syntax.domain.invalid_char", 13))
890
+ },
891
+ {
892
+ isemail: 100,
893
+ address: "test@xn--hxajbheg2az3al.xn--jxalpdlp",
894
+ description: "Punycode domain with a retired test TLD",
895
+ feature: "unknown-tld",
896
+ expected: {
897
+ ...everywhere(valid),
898
+ practical: fail("syntax.tld.unknown")
899
+ }
900
+ },
901
+ {
902
+ isemail: 101,
903
+ address: "xn--test@iana.org",
904
+ description: "Punycode prefix in the local part",
905
+ feature: "dot-atom",
906
+ expected: everywhere(valid)
907
+ },
908
+ {
909
+ isemail: 102,
910
+ address: "test@iana.org-",
911
+ description: "TLD ending with a hyphen",
912
+ expected: everywhere(fail("syntax.domain.label_invalid", 13))
913
+ },
914
+ {
915
+ isemail: 104,
916
+ address: "(test@iana.org",
917
+ description: "Unterminated comment before the @",
918
+ expected: allBut5322(fail("syntax.comment.not_allowed", 0), fail("syntax.address.no_at"))
919
+ },
920
+ {
921
+ isemail: 105,
922
+ address: "test@(iana.org",
923
+ description: "Unterminated comment in the domain",
924
+ expected: allBut5322(fail("syntax.comment.not_allowed", 5), fail("syntax.comment.unterminated", 5))
925
+ },
926
+ {
927
+ isemail: 106,
928
+ address: "test@[1.2.3.4",
929
+ description: "Unterminated domain literal",
930
+ expected: {
931
+ practical: fail("syntax.domain.invalid_char", 5),
932
+ rfc5321: fail("syntax.domain.literal_invalid", 5),
933
+ rfc5322: fail("syntax.domain.literal_invalid", 5),
934
+ html5: fail("syntax.domain.invalid_char", 5)
935
+ }
936
+ },
937
+ {
938
+ isemail: 107,
939
+ address: "\"test\\\"@iana.org",
940
+ description: "Quoted string whose closing quote is escaped",
941
+ expected: quotedLocal(fail("syntax.address.no_at"))
942
+ },
943
+ {
944
+ isemail: 108,
945
+ address: "(comment\\)test@iana.org",
946
+ description: "Comment whose closing parenthesis is escaped",
947
+ expected: allBut5322(fail("syntax.comment.not_allowed", 0), fail("syntax.address.no_at"))
948
+ },
949
+ {
950
+ isemail: 109,
951
+ address: "test@iana.org(comment\\)",
952
+ description: "Trailing comment whose closing parenthesis is escaped",
953
+ expected: allBut5322(fail("syntax.comment.not_allowed", 13), fail("syntax.comment.unterminated", 13))
954
+ },
955
+ {
956
+ isemail: 110,
957
+ address: "test@iana.org(comment\\",
958
+ description: "Trailing comment ending in a backslash",
959
+ expected: allBut5322(fail("syntax.comment.not_allowed", 13), fail("syntax.comment.unterminated", 13))
960
+ },
961
+ {
962
+ isemail: 112,
963
+ address: "test@[RFC-5322-domain-literal]",
964
+ description: "Domain literal that isn't an address",
965
+ feature: "general-literal",
966
+ expected: rfc5322Literal
967
+ },
968
+ {
969
+ isemail: 113,
970
+ address: "test@[RFC-5322]-domain-literal]",
971
+ description: "Text after a domain literal",
972
+ expected: {
973
+ ...rfc5322Literal,
974
+ rfc5322: fail("syntax.domain.invalid_char", 15)
975
+ }
976
+ },
977
+ {
978
+ isemail: 114,
979
+ address: "test@[RFC-5322-[domain-literal]",
980
+ description: "Unescaped [ inside a domain literal",
981
+ expected: {
982
+ ...rfc5322Literal,
983
+ rfc5322: fail("syntax.domain.literal_invalid", 5)
984
+ }
985
+ },
986
+ {
987
+ isemail: 115,
988
+ address: "test@[RFC-5322-\\\x07-domain-literal]",
989
+ description: "Escaped BEL in a domain literal",
990
+ feature: "obs-control",
991
+ expected: rfc5322Literal
992
+ },
993
+ {
994
+ isemail: 116,
995
+ address: "test@[RFC-5322-\\ -domain-literal]",
996
+ description: "Escaped tab in a domain literal",
997
+ feature: "general-literal",
998
+ expected: rfc5322Literal
999
+ },
1000
+ {
1001
+ isemail: 117,
1002
+ address: "test@[RFC-5322-\\]-domain-literal]",
1003
+ description: "Escaped ] in a domain literal",
1004
+ feature: "general-literal",
1005
+ expected: rfc5322Literal
1006
+ },
1007
+ {
1008
+ isemail: 118,
1009
+ address: "test@[RFC-5322-domain-literal\\]",
1010
+ description: "Domain literal whose closing bracket is escaped",
1011
+ expected: {
1012
+ ...rfc5322Literal,
1013
+ rfc5322: fail("syntax.domain.literal_invalid", 5)
1014
+ }
1015
+ },
1016
+ {
1017
+ isemail: 119,
1018
+ address: "test@[RFC-5322-domain-literal\\",
1019
+ description: "Domain literal ending in a backslash",
1020
+ expected: {
1021
+ ...rfc5322Literal,
1022
+ rfc5322: fail("syntax.domain.literal_invalid", 5)
1023
+ }
1024
+ },
1025
+ {
1026
+ isemail: 120,
1027
+ address: "test@[RFC 5322 domain literal]",
1028
+ description: "Spaces in a domain literal",
1029
+ feature: "general-literal",
1030
+ expected: rfc5322Literal
1031
+ },
1032
+ {
1033
+ isemail: 121,
1034
+ address: "test@[RFC-5322-domain-literal] (comment)",
1035
+ description: "Comment after a domain literal",
1036
+ expected: rfc5322Literal
1037
+ },
1038
+ {
1039
+ isemail: 123,
1040
+ address: "test@.org",
1041
+ description: "Domain starting with a dot before the TLD",
1042
+ expected: everywhere(fail("syntax.domain.label_invalid", 5))
1043
+ },
1044
+ {
1045
+ isemail: 124,
1046
+ address: "\"\"@iana.org",
1047
+ description: "DEL in a quoted string",
1048
+ feature: "obs-control",
1049
+ expected: {
1050
+ ...quotedLocal(valid),
1051
+ rfc5321: fail("syntax.local.invalid_char", 1)
1052
+ }
1053
+ },
1054
+ {
1055
+ isemail: 125,
1056
+ address: "\"\\\"@iana.org",
1057
+ description: "Escaped DEL in a quoted string",
1058
+ feature: "obs-control",
1059
+ expected: {
1060
+ ...quotedLocal(valid),
1061
+ rfc5321: fail("syntax.local.invalid_char", 2)
1062
+ }
1063
+ },
1064
+ {
1065
+ isemail: 126,
1066
+ address: "()test@iana.org",
1067
+ description: "DEL in a comment",
1068
+ feature: "obs-control",
1069
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 0))
1070
+ },
1071
+ {
1072
+ isemail: 127,
1073
+ address: "test@iana.org\r",
1074
+ description: "Trailing CR",
1075
+ expected: everywhere(fail("syntax.domain.invalid_char", 13))
1076
+ },
1077
+ {
1078
+ isemail: 128,
1079
+ address: "\rtest@iana.org",
1080
+ description: "Leading CR",
1081
+ expected: everywhere(fail("syntax.local.invalid_char", 0))
1082
+ },
1083
+ {
1084
+ isemail: 129,
1085
+ address: "\"\rtest\"@iana.org",
1086
+ description: "CR in a quoted string",
1087
+ expected: quotedLocal(fail("syntax.local.invalid_char", 1))
1088
+ },
1089
+ {
1090
+ isemail: 130,
1091
+ address: "(\r)test@iana.org",
1092
+ description: "CR in a comment",
1093
+ expected: allBut5322(fail("syntax.comment.not_allowed", 0), fail("syntax.local.invalid_char", 1))
1094
+ },
1095
+ {
1096
+ isemail: 131,
1097
+ address: "test@iana.org(\r)",
1098
+ description: "CR in a trailing comment",
1099
+ expected: allBut5322(fail("syntax.comment.not_allowed", 13), fail("syntax.domain.invalid_char", 14))
1100
+ },
1101
+ {
1102
+ isemail: 132,
1103
+ address: "\ntest@iana.org",
1104
+ description: "Leading LF",
1105
+ expected: everywhere(fail("syntax.local.invalid_char", 0))
1106
+ },
1107
+ {
1108
+ isemail: 133,
1109
+ address: "\"\n\"@iana.org",
1110
+ description: "LF in a quoted string",
1111
+ expected: quotedLocal(fail("syntax.local.invalid_char", 1))
1112
+ },
1113
+ {
1114
+ isemail: 134,
1115
+ address: "\"\\\n\"@iana.org",
1116
+ description: "Escaped LF in a quoted string",
1117
+ feature: "obs-control",
1118
+ expected: {
1119
+ ...quotedLocal(valid),
1120
+ rfc5321: fail("syntax.local.invalid_char", 2)
1121
+ }
1122
+ },
1123
+ {
1124
+ isemail: 135,
1125
+ address: "(\n)test@iana.org",
1126
+ description: "LF in a comment",
1127
+ expected: allBut5322(fail("syntax.comment.not_allowed", 0), fail("syntax.local.invalid_char", 1))
1128
+ },
1129
+ {
1130
+ isemail: 136,
1131
+ address: "\x07@iana.org",
1132
+ description: "BEL as the local part",
1133
+ expected: everywhere(fail("syntax.local.invalid_char", 0))
1134
+ },
1135
+ {
1136
+ isemail: 137,
1137
+ address: "test@\x07.org",
1138
+ description: "BEL as a domain label",
1139
+ expected: everywhere(fail("syntax.domain.invalid_char", 5))
1140
+ },
1141
+ {
1142
+ isemail: 138,
1143
+ address: "\"\x07\"@iana.org",
1144
+ description: "BEL in a quoted string",
1145
+ feature: "obs-control",
1146
+ expected: {
1147
+ ...quotedLocal(valid),
1148
+ rfc5321: fail("syntax.local.invalid_char", 1)
1149
+ }
1150
+ },
1151
+ {
1152
+ isemail: 139,
1153
+ address: "\"\\\x07\"@iana.org",
1154
+ description: "Escaped BEL in a quoted string",
1155
+ feature: "obs-control",
1156
+ expected: {
1157
+ ...quotedLocal(valid),
1158
+ rfc5321: fail("syntax.local.invalid_char", 2)
1159
+ }
1160
+ },
1161
+ {
1162
+ isemail: 140,
1163
+ address: "(\x07)test@iana.org",
1164
+ description: "BEL in a comment",
1165
+ feature: "obs-control",
1166
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 0))
1167
+ },
1168
+ {
1169
+ isemail: 141,
1170
+ address: "\r\ntest@iana.org",
1171
+ description: "Leading CRLF with no whitespace after it",
1172
+ expected: everywhere(fail("syntax.local.invalid_char", 0))
1173
+ },
1174
+ {
1175
+ isemail: 142,
1176
+ address: "\r\n \r\ntest@iana.org",
1177
+ description: "Folded whitespace ending in a CRLF",
1178
+ expected: allBut5322(fail("syntax.local.invalid_char", 0), fail("syntax.local.invalid_char", 3))
1179
+ },
1180
+ {
1181
+ isemail: 143,
1182
+ address: " \r\ntest@iana.org",
1183
+ description: "Space then a CRLF with no whitespace after it",
1184
+ expected: allBut5322(fail("syntax.local.unquoted_space", 0), fail("syntax.local.invalid_char", 1))
1185
+ },
1186
+ {
1187
+ isemail: 144,
1188
+ address: " \r\n test@iana.org",
1189
+ description: "Folded whitespace before the local part, starting with a space",
1190
+ feature: "fws",
1191
+ expected: allBut5322(fail("syntax.local.unquoted_space", 0), valid)
1192
+ },
1193
+ {
1194
+ isemail: 145,
1195
+ address: " \r\n \r\ntest@iana.org",
1196
+ description: "Folded whitespace ending in a CRLF, starting with a space",
1197
+ expected: allBut5322(fail("syntax.local.unquoted_space", 0), fail("syntax.local.invalid_char", 4))
1198
+ },
1199
+ {
1200
+ isemail: 146,
1201
+ address: " \r\n\r\ntest@iana.org",
1202
+ description: "Two CRLFs in a row",
1203
+ expected: allBut5322(fail("syntax.local.unquoted_space", 0), fail("syntax.local.invalid_char", 1))
1204
+ },
1205
+ {
1206
+ isemail: 147,
1207
+ address: " \r\n\r\n test@iana.org",
1208
+ description: "Two CRLFs in a row, then a space",
1209
+ expected: allBut5322(fail("syntax.local.unquoted_space", 0), fail("syntax.local.invalid_char", 1))
1210
+ },
1211
+ {
1212
+ isemail: 148,
1213
+ address: "test@iana.org\r\n ",
1214
+ description: "Folded whitespace after the domain",
1215
+ feature: "fws",
1216
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), valid)
1217
+ },
1218
+ {
1219
+ isemail: 149,
1220
+ address: "test@iana.org\r\n \r\n ",
1221
+ description: "Whitespace folded twice after the domain",
1222
+ feature: "fws",
1223
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), valid)
1224
+ },
1225
+ {
1226
+ isemail: 150,
1227
+ address: "test@iana.org\r\n",
1228
+ description: "Trailing CRLF",
1229
+ expected: everywhere(fail("syntax.domain.invalid_char", 13))
1230
+ },
1231
+ {
1232
+ isemail: 151,
1233
+ address: "test@iana.org\r\n \r\n",
1234
+ description: "Folded whitespace after the domain, ending in a CRLF",
1235
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), fail("syntax.domain.invalid_char", 16))
1236
+ },
1237
+ {
1238
+ isemail: 152,
1239
+ address: "test@iana.org \r\n",
1240
+ description: "Trailing space then a CRLF",
1241
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), fail("syntax.domain.invalid_char", 14))
1242
+ },
1243
+ {
1244
+ isemail: 153,
1245
+ address: "test@iana.org \r\n ",
1246
+ description: "Folded whitespace after the domain, starting with a space",
1247
+ feature: "fws",
1248
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), valid)
1249
+ },
1250
+ {
1251
+ isemail: 154,
1252
+ address: "test@iana.org \r\n \r\n",
1253
+ description: "Folded whitespace after the domain, starting with a space and ending in a CRLF",
1254
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), fail("syntax.domain.invalid_char", 17))
1255
+ },
1256
+ {
1257
+ isemail: 155,
1258
+ address: "test@iana.org \r\n\r\n",
1259
+ description: "Trailing space then two CRLFs",
1260
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), fail("syntax.domain.invalid_char", 14))
1261
+ },
1262
+ {
1263
+ isemail: 156,
1264
+ address: "test@iana.org \r\n\r\n ",
1265
+ description: "Trailing space then two CRLFs and a space",
1266
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), fail("syntax.domain.invalid_char", 14))
1267
+ },
1268
+ {
1269
+ isemail: 157,
1270
+ address: " test@iana.org",
1271
+ description: "Leading space",
1272
+ feature: "fws",
1273
+ expected: allBut5322(fail("syntax.local.unquoted_space", 0), valid)
1274
+ },
1275
+ {
1276
+ isemail: 158,
1277
+ address: "test@iana.org ",
1278
+ description: "Trailing space",
1279
+ feature: "fws",
1280
+ expected: allBut5322(fail("syntax.domain.invalid_char", 13), valid)
1281
+ },
1282
+ {
1283
+ isemail: 159,
1284
+ address: "test@[IPv6:1::2:]",
1285
+ description: "IPv6 literal ending with a single colon",
1286
+ feature: "general-literal",
1287
+ expected: rfc5322Literal
1288
+ },
1289
+ {
1290
+ isemail: 160,
1291
+ address: "\"test\\©\"@iana.org",
1292
+ description: "Escaped non-ASCII character in a quoted string",
1293
+ expected: quotedLocal(fail("syntax.local.invalid_char", 6))
1294
+ },
1295
+ {
1296
+ isemail: 161,
1297
+ address: "test@iana/icann.org",
1298
+ description: "Slash in the domain",
1299
+ expected: allBut5322(fail("syntax.domain.invalid_char", 9), valid)
1300
+ },
1301
+ {
1302
+ isemail: 165,
1303
+ address: "test.(comment)test@iana.org",
1304
+ description: "Comment after a dot in the local part",
1305
+ feature: "comments",
1306
+ expected: rfc5322Only(fail("syntax.comment.not_allowed", 5))
1307
+ },
1308
+ {
1309
+ isemail: 166,
1310
+ address: "test@org",
1311
+ description: "TLD alone as the domain",
1312
+ feature: "dotless-domain",
1313
+ expected: {
1314
+ ...everywhere(fail("syntax.domain.no_dot")),
1315
+ html5: valid
1316
+ }
1317
+ },
1318
+ {
1319
+ isemail: 167,
1320
+ address: "test@test.com",
1321
+ description: "Common test domain",
1322
+ feature: "dot-atom",
1323
+ expected: everywhere(valid)
1324
+ },
1325
+ {
1326
+ isemail: 168,
1327
+ address: "test@nic.no",
1328
+ description: "Country-code TLD",
1329
+ feature: "dot-atom",
1330
+ expected: everywhere(valid)
1331
+ }
1332
+ ];
1333
+ //#endregion
1334
+ //#region src/fixtures/legacy.ts
1335
+ const quotedLocalRejected = "practical rejects quoted local parts, which are legal but never seen on real mailboxes";
1336
+ const routeRejected = "practical rejects `%` and `!`, the source-route and bang-path characters, which are legal but never seen on real mailboxes";
1337
+ /** The 0.0.1 suite reran these cases uppercased, under `local.alphaLower: false`. */
1338
+ function withUppercase(fixture) {
1339
+ return [fixture, {
1340
+ ...fixture,
1341
+ address: fixture.address.toUpperCase(),
1342
+ description: `${fixture.description}, uppercased`
1343
+ }];
1344
+ }
1345
+ /**
1346
+ * Every address the 0.0.1 test suite checked, with what 0.0.1 returned.
1347
+ *
1348
+ * @example
1349
+ * ```ts
1350
+ * import { legacyFixtures } from '@email-utils/validator-syntax/fixtures';
1351
+ *
1352
+ * // The addresses v1's default preset judges differently from 0.0.1.
1353
+ * const flipped = legacyFixtures.filter(
1354
+ * (fixture) => fixture.flipped !== undefined,
1355
+ * );
1356
+ * flipped.every((fixture) => fixture.legacy !== fixture.expected.practical.ok);
1357
+ * // => true
1358
+ * ```
1359
+ */
1360
+ const legacyFixtures = [
1361
+ ...withUppercase({
1362
+ address: "simple@example.com",
1363
+ description: "Simple address",
1364
+ feature: "dot-atom",
1365
+ expected: everywhere(valid),
1366
+ legacy: true
1367
+ }),
1368
+ ...withUppercase({
1369
+ address: "x@example.com",
1370
+ description: "Single-letter local part",
1371
+ feature: "dot-atom",
1372
+ expected: everywhere(valid),
1373
+ legacy: true
1374
+ }),
1375
+ ...withUppercase({
1376
+ address: "admin@test.co",
1377
+ description: "Two-letter TLD",
1378
+ feature: "dot-atom",
1379
+ expected: everywhere(valid),
1380
+ legacy: true
1381
+ }),
1382
+ ...withUppercase({
1383
+ address: "test@gmail.com",
1384
+ description: "Gmail address",
1385
+ feature: "dot-atom",
1386
+ expected: everywhere(valid),
1387
+ legacy: true
1388
+ }),
1389
+ ...withUppercase({
1390
+ address: "tesT@example.com",
1391
+ description: "Uppercase letter in the local part",
1392
+ feature: "dot-atom",
1393
+ expected: everywhere(valid),
1394
+ legacy: true
1395
+ }),
1396
+ ...withUppercase({
1397
+ address: "test1@example.com",
1398
+ description: "Digit in the local part",
1399
+ feature: "dot-atom",
1400
+ expected: everywhere(valid),
1401
+ legacy: true
1402
+ }),
1403
+ {
1404
+ address: "simple1@example.com",
1405
+ description: "Digit at the end of the local part",
1406
+ feature: "dot-atom",
1407
+ expected: everywhere(valid),
1408
+ legacy: true
1409
+ },
1410
+ ...withUppercase({
1411
+ address: "very.common@example.com",
1412
+ description: "Dot in the local part",
1413
+ feature: "dot-atom",
1414
+ expected: everywhere(valid),
1415
+ legacy: true
1416
+ }),
1417
+ {
1418
+ address: "disposable.style.email.with+symbol@example.com",
1419
+ description: "Dots and a plus sign in the local part",
1420
+ feature: "atext-specials",
1421
+ expected: everywhere(valid),
1422
+ legacy: true
1423
+ },
1424
+ {
1425
+ address: "other.email-with-hyphen@example.com",
1426
+ description: "Dots and hyphens in the local part",
1427
+ feature: "atext-specials",
1428
+ expected: everywhere(valid),
1429
+ legacy: true
1430
+ },
1431
+ {
1432
+ address: "user.name+tag+sorting@example.com",
1433
+ description: "Dot and plus-sign tags in the local part",
1434
+ feature: "atext-specials",
1435
+ expected: everywhere(valid),
1436
+ legacy: true
1437
+ },
1438
+ ...withUppercase({
1439
+ address: "disposablestyleemailwith+symbol@example.com",
1440
+ description: "Plus sign in the local part",
1441
+ feature: "atext-specials",
1442
+ expected: everywhere(valid),
1443
+ legacy: true
1444
+ }),
1445
+ ...withUppercase({
1446
+ address: "username+tag+sorting@example.com",
1447
+ description: "Plus-sign tags in the local part",
1448
+ feature: "atext-specials",
1449
+ expected: everywhere(valid),
1450
+ legacy: true
1451
+ }),
1452
+ ...withUppercase({
1453
+ address: "test/test@test.com",
1454
+ description: "Slash in the local part",
1455
+ feature: "atext-specials",
1456
+ expected: everywhere(valid),
1457
+ legacy: true
1458
+ }),
1459
+ ...withUppercase({
1460
+ address: "mailhost.com!username@example.org",
1461
+ description: "Bang path in the local part, dotted relay host",
1462
+ feature: "route-chars",
1463
+ expected: {
1464
+ ...everywhere(valid),
1465
+ practical: fail("syntax.local.invalid_char", 12)
1466
+ },
1467
+ legacy: true,
1468
+ flipped: routeRejected
1469
+ }),
1470
+ {
1471
+ address: "mailhost!username@example.org",
1472
+ description: "Bang path in the local part, dotless relay host",
1473
+ feature: "route-chars",
1474
+ expected: {
1475
+ ...everywhere(valid),
1476
+ practical: fail("syntax.local.invalid_char", 8)
1477
+ },
1478
+ legacy: false
1479
+ },
1480
+ ...withUppercase({
1481
+ address: "user%example.com@example.org",
1482
+ description: "Percent route in the local part, dotted relay host",
1483
+ feature: "route-chars",
1484
+ expected: {
1485
+ ...everywhere(valid),
1486
+ practical: fail("syntax.local.invalid_char", 4)
1487
+ },
1488
+ legacy: true,
1489
+ flipped: routeRejected
1490
+ }),
1491
+ {
1492
+ address: "user%example@example.org",
1493
+ description: "Percent route in the local part, dotless relay host",
1494
+ feature: "route-chars",
1495
+ expected: {
1496
+ ...everywhere(valid),
1497
+ practical: fail("syntax.local.invalid_char", 4)
1498
+ },
1499
+ legacy: false
1500
+ },
1501
+ ...withUppercase({
1502
+ address: "fully-qualified-domain@example.com",
1503
+ description: "Hyphens in the local part",
1504
+ feature: "atext-specials",
1505
+ expected: everywhere(valid),
1506
+ legacy: true
1507
+ }),
1508
+ ...withUppercase({
1509
+ address: "otheremail-with-hyphen@example.com",
1510
+ description: "Hyphens in the local part",
1511
+ feature: "atext-specials",
1512
+ expected: everywhere(valid),
1513
+ legacy: true
1514
+ }),
1515
+ ...withUppercase({
1516
+ address: "user-@example.org",
1517
+ description: "Local part ending in a hyphen",
1518
+ feature: "atext-specials",
1519
+ expected: everywhere(valid),
1520
+ legacy: true
1521
+ }),
1522
+ ...withUppercase({
1523
+ address: "exampleindeed@strange-example.com",
1524
+ description: "Hyphenated domain",
1525
+ feature: "dot-atom",
1526
+ expected: everywhere(valid),
1527
+ legacy: true
1528
+ }),
1529
+ ...withUppercase({
1530
+ address: "example-indeed@strange-example.com",
1531
+ description: "Hyphens in the local part and the domain",
1532
+ feature: "atext-specials",
1533
+ expected: everywhere(valid),
1534
+ legacy: true
1535
+ }),
1536
+ ...withUppercase({
1537
+ address: "\" \"@example.org",
1538
+ description: "Quoted space as the whole local part",
1539
+ feature: "quoted-local",
1540
+ expected: {
1541
+ practical: fail("syntax.local.invalid_char", 0),
1542
+ rfc5321: valid,
1543
+ rfc5322: valid,
1544
+ html5: fail("syntax.local.invalid_char", 0)
1545
+ },
1546
+ legacy: true,
1547
+ flipped: quotedLocalRejected
1548
+ }),
1549
+ ...withUppercase({
1550
+ address: "\"john..doe\"@example.com",
1551
+ description: "Consecutive dots inside a quoted local part",
1552
+ feature: "quoted-local",
1553
+ expected: {
1554
+ practical: fail("syntax.local.invalid_char", 0),
1555
+ rfc5321: valid,
1556
+ rfc5322: valid,
1557
+ html5: fail("syntax.local.invalid_char", 0)
1558
+ },
1559
+ legacy: true,
1560
+ flipped: quotedLocalRejected
1561
+ }),
1562
+ ...withUppercase({
1563
+ address: "\"justactually\"@example.com",
1564
+ description: "Quoted local part",
1565
+ feature: "quoted-local",
1566
+ expected: {
1567
+ practical: fail("syntax.local.invalid_char", 0),
1568
+ rfc5321: valid,
1569
+ rfc5322: valid,
1570
+ html5: fail("syntax.local.invalid_char", 0)
1571
+ },
1572
+ legacy: true,
1573
+ flipped: quotedLocalRejected
1574
+ }),
1575
+ ...withUppercase({
1576
+ address: "just.\"actually\".right@example.com",
1577
+ description: "Quoted string between dot-separated atoms",
1578
+ feature: "obs-local",
1579
+ expected: {
1580
+ practical: fail("syntax.local.invalid_char", 5),
1581
+ rfc5321: fail("syntax.local.invalid_char", 5),
1582
+ rfc5322: valid,
1583
+ html5: fail("syntax.local.invalid_char", 5)
1584
+ },
1585
+ legacy: true,
1586
+ flipped: quotedLocalRejected
1587
+ }),
1588
+ {
1589
+ address: "just.\"not\".right@example.com",
1590
+ description: "Quoted string between dot-separated atoms",
1591
+ feature: "obs-local",
1592
+ expected: {
1593
+ practical: fail("syntax.local.invalid_char", 5),
1594
+ rfc5321: fail("syntax.local.invalid_char", 5),
1595
+ rfc5322: valid,
1596
+ html5: fail("syntax.local.invalid_char", 5)
1597
+ },
1598
+ legacy: true,
1599
+ flipped: quotedLocalRejected
1600
+ },
1601
+ {
1602
+ address: "\"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual\"@strange.example.com",
1603
+ description: "Quoted strings with specials, missing their escapes",
1604
+ expected: {
1605
+ practical: fail("syntax.local.invalid_char", 0),
1606
+ rfc5321: fail("syntax.local.invalid_char", 16),
1607
+ rfc5322: fail("syntax.local.invalid_char", 31),
1608
+ html5: fail("syntax.local.invalid_char", 0)
1609
+ },
1610
+ legacy: false
1611
+ },
1612
+ ...withUppercase({
1613
+ address: "just\"not\"right@example.com",
1614
+ description: "Quotes inside an atom",
1615
+ expected: everywhere(fail("syntax.local.invalid_char", 4)),
1616
+ legacy: false
1617
+ }),
1618
+ ...withUppercase({
1619
+ address: "just.\"not\"right@example.com",
1620
+ description: "Quoted string with no dot after it",
1621
+ expected: {
1622
+ practical: fail("syntax.local.invalid_char", 5),
1623
+ rfc5321: fail("syntax.local.invalid_char", 5),
1624
+ rfc5322: fail("syntax.local.invalid_char", 10),
1625
+ html5: fail("syntax.local.invalid_char", 5)
1626
+ },
1627
+ legacy: false
1628
+ }),
1629
+ ...withUppercase({
1630
+ address: "just.\"not.\"right@example.com",
1631
+ description: "Quoted string ending in a dot, with no dot after it",
1632
+ expected: {
1633
+ practical: fail("syntax.local.invalid_char", 5),
1634
+ rfc5321: fail("syntax.local.invalid_char", 5),
1635
+ rfc5322: fail("syntax.local.invalid_char", 11),
1636
+ html5: fail("syntax.local.invalid_char", 5)
1637
+ },
1638
+ legacy: false
1639
+ }),
1640
+ ...withUppercase({
1641
+ address: "\"john..doe@example.com",
1642
+ description: "Unterminated quote",
1643
+ expected: {
1644
+ practical: fail("syntax.local.invalid_char", 0),
1645
+ rfc5321: fail("syntax.address.no_at"),
1646
+ rfc5322: fail("syntax.address.no_at"),
1647
+ html5: fail("syntax.local.invalid_char", 0)
1648
+ },
1649
+ legacy: false
1650
+ }),
1651
+ ...withUppercase({
1652
+ address: "\"john\"..doe@example.com",
1653
+ description: "Consecutive dots after a quoted string",
1654
+ expected: {
1655
+ practical: fail("syntax.local.invalid_char", 0),
1656
+ rfc5321: fail("syntax.local.invalid_char", 6),
1657
+ rfc5322: fail("syntax.local.consecutive_dots", 7),
1658
+ html5: fail("syntax.local.invalid_char", 0)
1659
+ },
1660
+ legacy: false
1661
+ }),
1662
+ ...withUppercase({
1663
+ address: "a\"b(c)d,e:f;g<h>i[j\\k]l@example.com",
1664
+ description: "Unquoted specials",
1665
+ expected: everywhere(fail("syntax.local.invalid_char", 1)),
1666
+ legacy: false
1667
+ }),
1668
+ ...withUppercase({
1669
+ address: "james richards@google.com",
1670
+ description: "Unquoted space",
1671
+ expected: everywhere(fail("syntax.local.unquoted_space", 5)),
1672
+ legacy: false
1673
+ }),
1674
+ ...withUppercase({
1675
+ address: "james\" richards@google.com",
1676
+ description: "Space after a stray quote",
1677
+ expected: everywhere(fail("syntax.local.invalid_char", 5)),
1678
+ legacy: false
1679
+ }),
1680
+ ...withUppercase({
1681
+ address: "this is\"notallowed@example.com",
1682
+ description: "Unquoted space and a stray quote",
1683
+ expected: everywhere(fail("syntax.local.unquoted_space", 4)),
1684
+ legacy: false
1685
+ }),
1686
+ ...withUppercase({
1687
+ address: "this still\"not\\allowed@example.com",
1688
+ description: "Unquoted space, a stray quote, and a backslash",
1689
+ expected: everywhere(fail("syntax.local.unquoted_space", 4)),
1690
+ legacy: false
1691
+ }),
1692
+ ...withUppercase({
1693
+ address: "john..doe@example.com",
1694
+ description: "Consecutive dots in the local part",
1695
+ feature: "misplaced-dots",
1696
+ expected: {
1697
+ practical: fail("syntax.local.consecutive_dots", 5),
1698
+ rfc5321: fail("syntax.local.consecutive_dots", 5),
1699
+ rfc5322: fail("syntax.local.consecutive_dots", 5),
1700
+ html5: valid
1701
+ },
1702
+ legacy: false
1703
+ }),
1704
+ ...withUppercase({
1705
+ address: "john.doe@example..com",
1706
+ description: "Consecutive dots in the domain",
1707
+ expected: everywhere(fail("syntax.domain.label_invalid", 17)),
1708
+ legacy: false
1709
+ }),
1710
+ ...withUppercase({
1711
+ address: "Abc.example.com",
1712
+ description: "No @",
1713
+ expected: everywhere(fail("syntax.address.no_at")),
1714
+ legacy: false
1715
+ }),
1716
+ {
1717
+ address: "Abc+example.com",
1718
+ description: "No @",
1719
+ expected: everywhere(fail("syntax.address.no_at")),
1720
+ legacy: false
1721
+ },
1722
+ ...withUppercase({
1723
+ address: "@Abc.example.com",
1724
+ description: "Nothing before the @",
1725
+ expected: everywhere(fail("syntax.local.empty")),
1726
+ legacy: false
1727
+ }),
1728
+ ...withUppercase({
1729
+ address: "A@b@c@example.com",
1730
+ description: "More than one @",
1731
+ expected: everywhere(fail("syntax.local.invalid_char", 1)),
1732
+ legacy: false
1733
+ }),
1734
+ ...withUppercase({
1735
+ address: "QA☕CHOCOLATE☕@test.com",
1736
+ description: "Non-ASCII characters in the local part",
1737
+ expected: everywhere(fail("syntax.local.invalid_char", 2)),
1738
+ legacy: false
1739
+ }),
1740
+ ...withUppercase({
1741
+ address: "i_like_underscore@but_its_not_allowed_in_this_part.example.com",
1742
+ description: "Underscores in the domain",
1743
+ expected: allBut5322(fail("syntax.domain.invalid_char", 21), valid),
1744
+ legacy: false
1745
+ }),
1746
+ ...withUppercase({
1747
+ address: "james@cb$.com",
1748
+ description: "Dollar sign in the domain",
1749
+ expected: allBut5322(fail("syntax.domain.invalid_char", 8), valid),
1750
+ legacy: false
1751
+ }),
1752
+ ...withUppercase({
1753
+ address: "1234567890123456789012345678901234567890123456789012345678901234+x@example.com",
1754
+ description: "Local part over 64 characters",
1755
+ feature: "long-local",
1756
+ expected: {
1757
+ practical: fail("syntax.local.too_long"),
1758
+ rfc5321: fail("syntax.local.too_long"),
1759
+ rfc5322: valid,
1760
+ html5: valid
1761
+ },
1762
+ legacy: false
1763
+ }),
1764
+ {
1765
+ address: "uayuYfBgRktoqVPCAJLhkcyVHFsZygReesttpNWTTiETtbQFfDUMZjyCdeaEhcotf+x@example.com",
1766
+ description: "Local part over 64 characters, letters only",
1767
+ feature: "long-local",
1768
+ expected: {
1769
+ practical: fail("syntax.local.too_long"),
1770
+ rfc5321: fail("syntax.local.too_long"),
1771
+ rfc5322: valid,
1772
+ html5: valid
1773
+ },
1774
+ legacy: false
1775
+ },
1776
+ ...withUppercase({
1777
+ address: "james@google",
1778
+ description: "Domain with no dot",
1779
+ feature: "dotless-domain",
1780
+ expected: {
1781
+ practical: fail("syntax.domain.no_dot"),
1782
+ rfc5321: fail("syntax.domain.no_dot"),
1783
+ rfc5322: fail("syntax.domain.no_dot"),
1784
+ html5: valid
1785
+ },
1786
+ legacy: false
1787
+ }),
1788
+ {
1789
+ address: "admin@mailserver1",
1790
+ description: "Domain with no dot, ending in a digit",
1791
+ feature: "dotless-domain",
1792
+ expected: {
1793
+ practical: fail("syntax.domain.no_dot"),
1794
+ rfc5321: fail("syntax.domain.no_dot"),
1795
+ rfc5322: fail("syntax.domain.no_dot"),
1796
+ html5: valid
1797
+ },
1798
+ legacy: false
1799
+ },
1800
+ {
1801
+ address: "james@g.com",
1802
+ description: "Single-character domain label",
1803
+ feature: "dot-atom",
1804
+ expected: everywhere(valid),
1805
+ legacy: false,
1806
+ flipped: "0.0.1 required two characters before the last dot (`charsBeforeDot`), rejecting real single-letter domains (validator-syntax#12)"
1807
+ },
1808
+ {
1809
+ address: "james@google.c",
1810
+ description: "Single-character TLD",
1811
+ feature: "unknown-tld",
1812
+ expected: {
1813
+ practical: fail("syntax.tld.unknown"),
1814
+ rfc5321: valid,
1815
+ rfc5322: valid,
1816
+ html5: valid
1817
+ },
1818
+ legacy: false
1819
+ },
1820
+ {
1821
+ address: "test@example.thisisnotavalidtld",
1822
+ description: "TLD outside the IANA set",
1823
+ feature: "unknown-tld",
1824
+ expected: {
1825
+ practical: fail("syntax.tld.unknown"),
1826
+ rfc5321: valid,
1827
+ rfc5322: valid,
1828
+ html5: valid
1829
+ },
1830
+ legacy: false
1831
+ }
1832
+ ];
1833
+ //#endregion
1834
+ //#region src/fixtures/rfc3696.ts
1835
+ /**
1836
+ * RFC 3696's examples, as corrected by its erratum 246.
1837
+ *
1838
+ * @example
1839
+ * ```ts
1840
+ * import { rfc3696Fixtures } from '@email-utils/validator-syntax/fixtures';
1841
+ *
1842
+ * // Printed as valid, but a backslash escape needs a quoted string.
1843
+ * rfc3696Fixtures
1844
+ * .filter((fixture) => !fixture.expected.rfc5322.ok)
1845
+ * .map((fixture) => fixture.address);
1846
+ * // => [
1847
+ * // 'Abc\\@def@example.com',
1848
+ * // 'Fred\\ Bloggs@example.com',
1849
+ * // 'Joe.\\\\Blow@example.com',
1850
+ * // ]
1851
+ * ```
1852
+ */
1853
+ const rfc3696Fixtures = [
1854
+ {
1855
+ address: "Abc\\@def@example.com",
1856
+ description: "Backslash-escaped @ outside a quoted string",
1857
+ expected: everywhere(fail("syntax.local.invalid_char", 3))
1858
+ },
1859
+ {
1860
+ address: "Fred\\ Bloggs@example.com",
1861
+ description: "Backslash-escaped space outside a quoted string",
1862
+ expected: everywhere(fail("syntax.local.invalid_char", 4))
1863
+ },
1864
+ {
1865
+ address: "Joe.\\\\Blow@example.com",
1866
+ description: "Backslash-escaped backslash outside a quoted string",
1867
+ expected: everywhere(fail("syntax.local.invalid_char", 4))
1868
+ },
1869
+ {
1870
+ address: "\"Abc\\@def\"@example.com",
1871
+ description: "Escaped @ in a quoted string",
1872
+ feature: "quoted-pair",
1873
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
1874
+ },
1875
+ {
1876
+ address: "\"Fred\\ Bloggs\"@example.com",
1877
+ description: "Escaped space in a quoted string",
1878
+ feature: "quoted-pair",
1879
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
1880
+ },
1881
+ {
1882
+ address: "\"Joe.\\\\Blow\"@example.com",
1883
+ description: "Escaped backslash in a quoted string",
1884
+ feature: "quoted-pair",
1885
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
1886
+ },
1887
+ {
1888
+ address: "\"Abc@def\"@example.com",
1889
+ description: "Quoted @",
1890
+ feature: "quoted-local",
1891
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
1892
+ },
1893
+ {
1894
+ address: "\"Fred Bloggs\"@example.com",
1895
+ description: "Quoted space",
1896
+ feature: "quoted-local",
1897
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
1898
+ },
1899
+ {
1900
+ address: "user+mailbox@example.com",
1901
+ description: "Plus sign",
1902
+ feature: "atext-specials",
1903
+ expected: everywhere(valid)
1904
+ },
1905
+ {
1906
+ address: "customer/department=shipping@example.com",
1907
+ description: "Slash and equals sign",
1908
+ feature: "atext-specials",
1909
+ expected: everywhere(valid)
1910
+ },
1911
+ {
1912
+ address: "$A12345@example.com",
1913
+ description: "Dollar sign first",
1914
+ feature: "atext-specials",
1915
+ expected: everywhere(valid)
1916
+ },
1917
+ {
1918
+ address: "!def!xyz%abc@example.com",
1919
+ description: "Bang path and percent route",
1920
+ feature: "route-chars",
1921
+ expected: {
1922
+ ...everywhere(valid),
1923
+ practical: fail("syntax.local.invalid_char", 0)
1924
+ }
1925
+ },
1926
+ {
1927
+ address: "_somename@example.com",
1928
+ description: "Underscore first",
1929
+ feature: "atext-specials",
1930
+ expected: everywhere(valid)
1931
+ }
1932
+ ];
1933
+ //#endregion
1934
+ //#region src/fixtures/wikipedia.ts
1935
+ /**
1936
+ * The examples from Wikipedia's "Email address" article.
1937
+ *
1938
+ * @example
1939
+ * ```ts
1940
+ * import { wikipediaFixtures } from '@email-utils/validator-syntax/fixtures';
1941
+ *
1942
+ * wikipediaFixtures.find((fixture) => fixture.address === 'admin@example');
1943
+ * // => {
1944
+ * // expected: {
1945
+ * // practical: { ok: false, reason: 'syntax.domain.no_dot' },
1946
+ * // html5: { ok: true },
1947
+ * // },
1948
+ * // }
1949
+ * ```
1950
+ */
1951
+ const wikipediaFixtures = [
1952
+ {
1953
+ address: "FirstName.LastName@EasierReading.org",
1954
+ description: "Mixed case",
1955
+ feature: "dot-atom",
1956
+ expected: everywhere(valid)
1957
+ },
1958
+ {
1959
+ address: "long.email-address-with-hyphens@and.subdomains.example.com",
1960
+ description: "Hyphens in the local part, subdomains",
1961
+ feature: "atext-specials",
1962
+ expected: everywhere(valid)
1963
+ },
1964
+ {
1965
+ address: "name/surname@example.com",
1966
+ description: "Slash in the local part",
1967
+ feature: "atext-specials",
1968
+ expected: everywhere(valid)
1969
+ },
1970
+ {
1971
+ address: "admin@example",
1972
+ description: "Domain with no dot",
1973
+ feature: "dotless-domain",
1974
+ expected: {
1975
+ ...everywhere(fail("syntax.domain.no_dot")),
1976
+ html5: valid
1977
+ }
1978
+ },
1979
+ {
1980
+ address: "example@s.example",
1981
+ description: "Reserved TLD",
1982
+ feature: "unknown-tld",
1983
+ expected: {
1984
+ ...everywhere(valid),
1985
+ practical: fail("syntax.tld.unknown")
1986
+ }
1987
+ },
1988
+ {
1989
+ address: "\"jane..doe\"@example.org",
1990
+ description: "Consecutive dots inside a quoted local part",
1991
+ feature: "quoted-local",
1992
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
1993
+ },
1994
+ {
1995
+ address: "\"very.(),:;<>[]\\\".VERY.\\\"very@\\\\ \\\"very\\\".unusual\"@strange.example.com",
1996
+ description: "Quoted string with specials, an @, and escapes",
1997
+ feature: "quoted-pair",
1998
+ expected: rfcOnly(fail("syntax.local.invalid_char", 0))
1999
+ },
2000
+ {
2001
+ address: "postmaster@[123.123.123.123]",
2002
+ description: "IPv4 address literal",
2003
+ feature: "ipv4-literal",
2004
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 11))
2005
+ },
2006
+ {
2007
+ address: "postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
2008
+ description: "IPv6 address literal",
2009
+ feature: "ipv6-literal",
2010
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 11))
2011
+ },
2012
+ {
2013
+ address: "_test@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]",
2014
+ description: "Underscore first, IPv6 address literal",
2015
+ feature: "ipv6-literal",
2016
+ expected: rfcOnly(fail("syntax.domain.invalid_char", 6))
2017
+ },
2018
+ {
2019
+ address: "I❤️CHOCOLATE@example.com",
2020
+ description: "Emoji in the local part",
2021
+ expected: everywhere(fail("syntax.local.invalid_char", 1))
2022
+ },
2023
+ {
2024
+ address: "abc.example.com",
2025
+ description: "No @",
2026
+ expected: everywhere(fail("syntax.address.no_at"))
2027
+ },
2028
+ {
2029
+ address: "a@b@c@example.com",
2030
+ description: "More than one @",
2031
+ expected: everywhere(fail("syntax.local.invalid_char", 1))
2032
+ },
2033
+ {
2034
+ address: "this is\"not\\allowed@example.com",
2035
+ description: "Unquoted space, quote, and backslash",
2036
+ expected: everywhere(fail("syntax.local.unquoted_space", 4))
2037
+ },
2038
+ {
2039
+ address: "this\\ still\\\"not\\\\allowed@example.com",
2040
+ description: "Backslash escapes outside a quoted string",
2041
+ expected: everywhere(fail("syntax.local.invalid_char", 4))
2042
+ },
2043
+ {
2044
+ address: "i.like.underscores@but_they_are_not_allowed_in_this_part",
2045
+ description: "Underscores in a dotless domain",
2046
+ expected: allBut5322(fail("syntax.domain.invalid_char", 22), fail("syntax.domain.no_dot"))
2047
+ }
2048
+ ];
2049
+ //#endregion
2050
+ //#region src/fixtures/index.ts
2051
+ /**
2052
+ * The syntax corpus, published as `@email-utils/validator-syntax/fixtures`
2053
+ * so dependents can check they split and judge addresses exactly as
2054
+ * `parseAddress` does, and so the docs can build the support matrix.
2055
+ *
2056
+ * @remarks
2057
+ * Every fixture's `expected` is the result under each preset's default
2058
+ * options. The rules behind them:
2059
+ *
2060
+ * - `practical`: a dot-atom local part (letters, digits, and
2061
+ * ``#$&'*+/=?^_`{|}~-`` with single inner dots), so no quoted strings, no
2062
+ * `%` or `!` routes, and no comments or whitespace; hostname labels with at
2063
+ * least one dot, and no domain literals; the TLD in the IANA set.
2064
+ * - `rfc5321`: an RFC 5321 Dot-string or Quoted-string local part, never
2065
+ * both mixed, capped at 64 characters; hostname labels with at least one
2066
+ * dot, or an IPv4 or `IPv6:` address literal (IPv6 is the only registered
2067
+ * tag, and no more than six groups may sit beside a `::`); no comments or
2068
+ * whitespace.
2069
+ * - `rfc5322`: RFC 5322's addr-spec with its obsolete syntax: dot-separated
2070
+ * atoms and quoted strings mixed, comments and folding whitespace around
2071
+ * every word and label, control characters in quoted strings, comments,
2072
+ * and escapes, and any dtext in a domain literal. Domain labels are
2073
+ * atext, since RFC 5322's domain is a dot-atom, with the hostname rules
2074
+ * for hyphens and length on top. A CRLF must be followed by a space or
2075
+ * tab. No 64-character local cap, but a dotted domain is still required.
2076
+ * - `html5`: exactly the WHATWG `input[type=email]` regex.
2077
+ *
2078
+ * Every preset caps a domain label at 63 characters and the address at 254;
2079
+ * all but `html5` cap the domain at 253. Comments and folding whitespace
2080
+ * don't count toward the caps.
2081
+ *
2082
+ * The address splits on the last `@` outside a quoted string, comment, or
2083
+ * domain literal (validator-syntax#9). A quote opens a quoted string only at
2084
+ * the start of a word; anywhere else it's an invalid character. A quoted
2085
+ * string, comment, or literal left open runs to the end, taking any `@`
2086
+ * with it.
2087
+ *
2088
+ * The first failure wins, checked in this order: empty input; input longer
2089
+ * than `maxLength` (512 by default, which no fixture reaches); the split; the
2090
+ * local part, left to right, then its length; the domain, left to right,
2091
+ * then its length; the address length; a dotless domain; the TLD. Where a
2092
+ * failure has a position, `index` points at the first offending character
2093
+ * in the whole address: the `(` of a disallowed or unterminated comment,
2094
+ * the `[` of a bad domain literal, the start of an overlong label, and the
2095
+ * CR of a CRLF that isn't followed by whitespace.
2096
+ *
2097
+ * @packageDocumentation
2098
+ */
2099
+ /**
2100
+ * Every fixture from every source, each address once.
2101
+ *
2102
+ * @example
2103
+ * ```ts
2104
+ * import { isValidSyntax } from '@email-utils/validator-syntax';
2105
+ * import { syntaxFixtures } from '@email-utils/validator-syntax/fixtures';
2106
+ *
2107
+ * syntaxFixtures.every(
2108
+ * ({ address, expected }) =>
2109
+ * isValidSyntax(address, { preset: 'rfc5321' }) === expected.rfc5321.ok,
2110
+ * ); // => true
2111
+ * ```
2112
+ */
2113
+ const syntaxFixtures = [
2114
+ ...legacyFixtures,
2115
+ ...isemailFixtures,
2116
+ ...wikipediaFixtures,
2117
+ ...rfc3696Fixtures
2118
+ ];
2119
+ /**
2120
+ * The docs' support matrix: one row per feature, one column per preset.
2121
+ *
2122
+ * @example
2123
+ * ```ts
2124
+ * import { supportMatrix } from '@email-utils/validator-syntax/fixtures';
2125
+ *
2126
+ * const row = supportMatrix().find(({ feature }) => feature === 'quoted-local');
2127
+ * row?.support;
2128
+ * // => { practical: 'no', rfc5321: 'yes', rfc5322: 'yes', html5: 'no' }
2129
+ * ```
2130
+ */
2131
+ function supportMatrix() {
2132
+ return syntaxFeatures.map(({ feature, label }) => {
2133
+ const fixtures = syntaxFixtures.filter((fixture) => fixture.feature === feature);
2134
+ const supportIn = (preset) => {
2135
+ const accepted = fixtures.filter((fixture) => fixture.expected[preset].ok).length;
2136
+ if (accepted === fixtures.length) return "yes";
2137
+ return accepted === 0 ? "no" : "partial";
2138
+ };
2139
+ return {
2140
+ feature,
2141
+ label,
2142
+ support: {
2143
+ practical: supportIn("practical"),
2144
+ rfc5321: supportIn("rfc5321"),
2145
+ rfc5322: supportIn("rfc5322"),
2146
+ html5: supportIn("html5")
2147
+ },
2148
+ fixtures
2149
+ };
2150
+ });
2151
+ }
2152
+ /**
2153
+ * Runs `addresses` through `createSyntaxValidator(options)` and splits them
2154
+ * into the ones it accepts and the ones it rejects, each list in input
2155
+ * order.
2156
+ *
2157
+ * @remarks
2158
+ * `changed` marks the addresses the overrides move: those the preset alone
2159
+ * would judge the other way. With no overrides, nothing is changed.
2160
+ *
2161
+ * @example
2162
+ * ```ts
2163
+ * import { previewSyntaxOptions } from '@email-utils/validator-syntax/fixtures';
2164
+ *
2165
+ * const { valid } = previewSyntaxOptions({ checkTld: false });
2166
+ * valid.find((entry) => entry.address === 'example@s.example');
2167
+ * // => { changed: true }
2168
+ * previewSyntaxOptions({ preset: 'html5' }, ['ada@localhost', 'ada@example..com']);
2169
+ * // => {
2170
+ * // valid: [{ address: 'ada@localhost', changed: false }],
2171
+ * // invalid: [{ address: 'ada@example..com', reason: 'syntax.domain.label_invalid' }],
2172
+ * // }
2173
+ * ```
2174
+ *
2175
+ * @param addresses - The addresses to judge; `syntaxFixtures` by default.
2176
+ * @throws TypeError when `options` are malformed, or `addresses` isn't an
2177
+ * array of strings.
2178
+ */
2179
+ function previewSyntaxOptions(options, addresses) {
2180
+ const validator = (0, _email_utils_validator_syntax.createSyntaxValidator)(options);
2181
+ const preset = (0, _email_utils_validator_syntax.createSyntaxValidator)({ preset: options?.preset });
2182
+ const entries = addresses === void 0 ? syntaxFixtures : own(addresses);
2183
+ const valid = [];
2184
+ const invalid = [];
2185
+ for (const { address, description } of entries) {
2186
+ const result = validator.parse(address);
2187
+ const described = description === void 0 ? {} : { description };
2188
+ if (result.ok) {
2189
+ const changed = !preset.isValid(address);
2190
+ valid.push({
2191
+ address,
2192
+ ...described,
2193
+ changed
2194
+ });
2195
+ } else {
2196
+ const { ok: _, ...failure } = result;
2197
+ invalid.push({
2198
+ address,
2199
+ ...described,
2200
+ ...failure,
2201
+ changed: preset.isValid(address)
2202
+ });
2203
+ }
2204
+ }
2205
+ return {
2206
+ valid,
2207
+ invalid
2208
+ };
2209
+ }
2210
+ /** Checks the caller's addresses, before any of them is parsed. */
2211
+ function own(addresses) {
2212
+ if (!Array.isArray(addresses)) throw new TypeError("Expected the addresses to be an array");
2213
+ return addresses.map((address, i) => {
2214
+ if (typeof address !== "string") throw new TypeError(`Expected addresses[${i}] to be a string`);
2215
+ return { address };
2216
+ });
2217
+ }
2218
+ //#endregion
2219
+ exports.isemailFixtures = isemailFixtures;
2220
+ exports.legacyFixtures = legacyFixtures;
2221
+ exports.presets = presets;
2222
+ exports.previewSyntaxOptions = previewSyntaxOptions;
2223
+ exports.rfc3696Fixtures = rfc3696Fixtures;
2224
+ exports.supportMatrix = supportMatrix;
2225
+ exports.syntaxFeatures = syntaxFeatures;
2226
+ exports.syntaxFixtures = syntaxFixtures;
2227
+ exports.wikipediaFixtures = wikipediaFixtures;
2228
+
2229
+ //# sourceMappingURL=fixtures.cjs.map