Skip to content

Commit 36fef58

Browse files
authored
Make email regex reasonable (#2157)
* Remove logging * Clean up email testsg * Add back 1char tld test * Final tweaks to email regex * Fix test * Update test
1 parent af08390 commit 36fef58

File tree

5 files changed

+284
-74
lines changed

5 files changed

+284
-74
lines changed

deno/lib/__tests__/string.test.ts

Lines changed: 64 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -39,70 +39,97 @@ test("failing validations", () => {
3939
});
4040

4141
test("email validations", () => {
42-
const email = z.string().email();
43-
email.parse("mojojojo@example.com");
44-
expect(() => email.parse("asdf")).toThrow();
45-
expect(() => email.parse("@lkjasdf.com")).toThrow();
46-
expect(() => email.parse("asdf@sdf.")).toThrow();
47-
expect(() => email.parse("asdf@asdf.com-")).toThrow();
48-
expect(() => email.parse("asdf@-asdf.com")).toThrow();
49-
expect(() => email.parse("asdf@-a(sdf.com")).toThrow();
50-
expect(() => email.parse("asdf@-asdf.com(")).toThrow();
51-
expect(() =>
52-
email.parse("pawan.anand@%9y83&#$%R&#$%R&%#$R%%^^%5rw3ewe.d.d.aaaa.wef.co")
53-
).toThrow();
54-
});
55-
56-
test("more email validations", () => {
5742
const validEmails = [
43+
`email@domain.com`,
44+
`firstname.lastname@domain.com`,
45+
`email@subdomain.domain.com`,
46+
`firstname+lastname@domain.com`,
47+
`1234567890@domain.com`,
48+
`email@domain-one.com`,
49+
`_______@domain.com`,
50+
`email@domain.name`,
51+
`email@domain.co.jp`,
52+
`firstname-lastname@domain.com`,
5853
`very.common@example.com`,
5954
`disposable.style.email.with+symbol@example.com`,
6055
`other.email-with-hyphen@example.com`,
6156
`fully-qualified-domain@example.com`,
6257
`user.name+tag+sorting@example.com`,
6358
`x@example.com`,
59+
`mojojojo@asdf.example.com`,
6460
`example-indeed@strange-example.com`,
65-
`test/test@test.com`,
6661
`example@s.example`,
67-
`" "@example.org`,
68-
`"john..doe"@example.org`,
69-
`mailhost!username@example.org`,
70-
`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
71-
`user%example.com@example.org`,
7262
`user-@example.org`,
73-
`postmaster@[123.123.123.123]`,
7463
`user@my-example.com`,
7564
`a@b.cd`,
7665
`work+user@mail.com`,
66+
`tom@test.te-st.com`,
67+
`something@subdomain.domain-with-hyphens.tld`,
68+
`francois@etu.inp-n7.fr`,
69+
];
70+
const invalidEmails = [
71+
// no "printable characters"
72+
// `user%example.com@example.org`,
73+
// `mailhost!username@example.org`,
74+
// `test/test@test.com`,
75+
76+
// double @
77+
`francois@@etu.inp-n7.fr`,
78+
// do not support quotes
79+
`"email"@domain.com`,
80+
`"e asdf sadf ?<>ail"@domain.com`,
81+
`" "@example.org`,
82+
`"john..doe"@example.org`,
83+
`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
84+
85+
// do not support IPv4
86+
`email@123.123.123.123`,
87+
`email@[123.123.123.123]`,
88+
`postmaster@123.123.123.123`,
7789
`user@[68.185.127.196]`,
7890
`ipv4@[85.129.96.247]`,
7991
`valid@[79.208.229.53]`,
8092
`valid@[255.255.255.255]`,
8193
`valid@[255.0.55.2]`,
8294
`valid@[255.0.55.2]`,
95+
96+
// do not support ipv6
8397
`hgrebert0@[IPv6:4dc8:ac7:ce79:8878:1290:6098:5c50:1f25]`,
8498
`bshapiro4@[IPv6:3669:c709:e981:4884:59a3:75d1:166b:9ae]`,
8599
`jsmith@[IPv6:2001:db8::1]`,
86100
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]`,
87101
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:192.168.1.1]`,
88-
`test@any.th1ng.com`,
89-
`me@y.z.com`,
90-
`me@y.z.co.jp`,
91-
`example@subdomain.hyphenated-domain.com`,
92-
`example@atlanta.k12.ga.us`,
93-
`a.b@c.d`,
94-
];
95-
const invalidEmails = [
102+
103+
// microsoft test cases
104+
`plainaddress`,
105+
`#@%^%#$@#$@#.com`,
106+
`@domain.com`,
107+
`Joe Smith &lt;email@domain.com&gt;`,
108+
`email.domain.com`,
109+
`email@domain@domain.com`,
110+
`.email@domain.com`,
111+
`email.@domain.com`,
112+
`email..email@domain.com`,
113+
`あいうえお@domain.com`,
114+
`email@domain.com (Joe Smith)`,
115+
`email@domain`,
116+
`email@-domain.com`,
117+
`email@111.222.333.44444`,
118+
`email@domain..com`,
96119
`Abc.example.com`,
97120
`A@b@c@example.com`,
121+
`colin..hacks@domain.com`,
98122
`a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
99123
`just"not"right@example.com`,
100124
`this is"not\allowed@example.com`,
101125
`this\ still\"not\\allowed@example.com`,
126+
127+
// random
102128
`i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
103129
`QA[icon]CHOCOLATE[icon]@test.com`,
104130
`invalid@-start.com`,
105131
`invalid@end.com-`,
132+
`a.b@c.d`,
106133
`invalid@[1.1.1.-1]`,
107134
`invalid@[68.185.127.196.55]`,
108135
`temp@[192.168.1]`,
@@ -121,13 +148,16 @@ test("more email validations", () => {
121148
`test@.com`,
122149
];
123150
const emailSchema = z.string().email();
151+
124152
expect(
125-
validEmails.every((email) => emailSchema.safeParse(email).success)
153+
validEmails.every((email) => {
154+
return emailSchema.safeParse(email).success;
155+
})
126156
).toBe(true);
127157
expect(
128-
invalidEmails.every(
129-
(email) => emailSchema.safeParse(email).success === false
130-
)
158+
invalidEmails.every((email) => {
159+
return emailSchema.safeParse(email).success === false;
160+
})
131161
).toBe(true);
132162
});
133163

deno/lib/types.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -551,9 +551,17 @@ const uuidRegex =
551551
//old email regex
552552
// const emailRegex = /^(([^<>()[\].,;:\s@"]+(\.[^<>()[\].,;:\s@"]+)*)|(".+"))@((?!-)([^<>()[\].,;:\s@"]+\.)+[^<>()[\].,;:\s@"]{1,})[^-<>()[\].,;:\s@"]$/i;
553553
// eslint-disable-next-line
554-
554+
// const emailRegex =
555+
// /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\.[A-Za-z]{2,})+))$/;
556+
// const emailRegex =
557+
// /^[a-zA-Z0-9\.\!\#\$\%\&\'\*\+\/\=\?\^\_\`\{\|\}\~\-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
558+
// const emailRegex =
559+
// /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\[\x01-\x09\x0b\x0c\x0e-\x7f])+)\])$/i;
555560
const emailRegex =
556-
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\])|(\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\])|([A-Za-z0-9]\.?([A-Za-z0-9-]+\.)*([A-Za-z0-9-])*[A-Za-z0-9]))$/;
561+
/^([A-Z0-9_+-]+\.?)*[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\-]*\.)+[A-Z]{2,}$/i;
562+
// const emailRegex =
563+
// /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\.[a-z0-9\-]+)*$/i;
564+
557565
// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression
558566
const emojiRegex = /^(\p{Extended_Pictographic}|\p{Emoji_Component})+$/u;
559567

@@ -4174,7 +4182,10 @@ export class ZodPromise<T extends ZodTypeAny> extends ZodType<
41744182
//////////////////////////////////////////////
41754183

41764184
export type Refinement<T> = (arg: T, ctx: RefinementCtx) => any;
4177-
export type SuperRefinement<T> = (arg: T, ctx: RefinementCtx) => void | Promise<void>;
4185+
export type SuperRefinement<T> = (
4186+
arg: T,
4187+
ctx: RefinementCtx
4188+
) => void | Promise<void>;
41784189

41794190
export type RefinementEffect<T> = {
41804191
type: "refinement";

playground.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,130 @@
11
import { z } from "./src";
22
z;
3+
4+
const validEmails = [
5+
`email@domain.com`,
6+
`firstname.lastname@domain.com`,
7+
`email@subdomain.domain.com`,
8+
`firstname+lastname@domain.com`,
9+
`1234567890@domain.com`,
10+
`email@domain-one.com`,
11+
`_______@domain.com`,
12+
`email@domain.name`,
13+
`email@domain.co.jp`,
14+
`firstname-lastname@domain.com`,
15+
`very.common@example.com`,
16+
`disposable.style.email.with+symbol@example.com`,
17+
`other.email-with-hyphen@example.com`,
18+
`fully-qualified-domain@example.com`,
19+
`user.name+tag+sorting@example.com`,
20+
`x@example.com`,
21+
`mojojojo@asdf.example.com`,
22+
`example-indeed@strange-example.com`,
23+
`example@s.example`,
24+
`user-@example.org`,
25+
`user@my-example.com`,
26+
`a@b.cd`,
27+
`work+user@mail.com`,
28+
`tom@test.te-st.com`,
29+
`something@subdomain.domain-with-hyphens.tld`,
30+
`francois@etu.inp-n7.fr`,
31+
];
32+
const invalidEmails = [
33+
// no "printable characters"
34+
// `user%example.com@example.org`,
35+
// `mailhost!username@example.org`,
36+
// `test/test@test.com`,
37+
38+
// double @
39+
`francois@@etu.inp-n7.fr`,
40+
41+
// do not support quotes
42+
`"email"@domain.com`,
43+
`"e asdf sadf ?<>ail"@domain.com`,
44+
`" "@example.org`,
45+
`"john..doe"@example.org`,
46+
`"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com`,
47+
48+
// do not support IPv4
49+
`email@123.123.123.123`,
50+
`email@[123.123.123.123]`,
51+
`postmaster@123.123.123.123`,
52+
`user@[68.185.127.196]`,
53+
`ipv4@[85.129.96.247]`,
54+
`valid@[79.208.229.53]`,
55+
`valid@[255.255.255.255]`,
56+
`valid@[255.0.55.2]`,
57+
`valid@[255.0.55.2]`,
58+
59+
// do not support ipv6
60+
`hgrebert0@[IPv6:4dc8:ac7:ce79:8878:1290:6098:5c50:1f25]`,
61+
`bshapiro4@[IPv6:3669:c709:e981:4884:59a3:75d1:166b:9ae]`,
62+
`jsmith@[IPv6:2001:db8::1]`,
63+
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:7334]`,
64+
`postmaster@[IPv6:2001:0db8:85a3:0000:0000:8a2e:0370:192.168.1.1]`,
65+
66+
// microsoft test cases
67+
`plainaddress`,
68+
`#@%^%#$@#$@#.com`,
69+
`@domain.com`,
70+
`Joe Smith &lt;email@domain.com&gt;`,
71+
`email.domain.com`,
72+
`email@domain@domain.com`,
73+
`.email@domain.com`,
74+
`email.@domain.com`,
75+
`email..email@domain.com`,
76+
`あいうえお@domain.com`,
77+
`email@domain.com (Joe Smith)`,
78+
`email@domain`,
79+
`email@-domain.com`,
80+
`email@111.222.333.44444`,
81+
`email@domain..com`,
82+
`Abc.example.com`,
83+
`A@b@c@example.com`,
84+
`colin..hacks@domain.com`,
85+
`a"b(c)d,e:f;g<h>i[j\k]l@example.com`,
86+
`just"not"right@example.com`,
87+
`this is"not\allowed@example.com`,
88+
`this\ still\"not\\allowed@example.com`,
89+
90+
// random
91+
`i_like_underscore@but_its_not_allowed_in_this_part.example.com`,
92+
`QA[icon]CHOCOLATE[icon]@test.com`,
93+
`invalid@-start.com`,
94+
`invalid@end.com-`,
95+
`a.b@c.d`,
96+
`invalid@[1.1.1.-1]`,
97+
`invalid@[68.185.127.196.55]`,
98+
`temp@[192.168.1]`,
99+
`temp@[9.18.122.]`,
100+
`double..point@test.com`,
101+
`asdad@test..com`,
102+
`asdad@hghg...sd...au`,
103+
`asdad@hghg........au`,
104+
`invalid@[256.2.2.48]`,
105+
`invalid@[256.2.2.48]`,
106+
`invalid@[999.465.265.1]`,
107+
`jkibbey4@[IPv6:82c4:19a8::70a9:2aac:557::ea69:d985:28d]`,
108+
`mlivesay3@[9952:143f:b4df:2179:49a1:5e82:b92e:6b6]`,
109+
`gbacher0@[IPv6:bc37:4d3f:5048:2e26:37cc:248e:df8e:2f7f:af]`,
110+
`invalid@[IPv6:5348:4ed3:5d38:67fb:e9b:acd2:c13:192.168.256.1]`,
111+
];
112+
113+
const emailSchema = z.string().email();
114+
console.log(
115+
validEmails.every((email) => {
116+
const val = emailSchema.safeParse(email).success;
117+
if (!val) console.log(`fail`, email);
118+
return val;
119+
})
120+
);
121+
// for (const email of validEmails) {
122+
// console.log("good", email);
123+
// emailSchema.parse(email);
124+
// }
125+
for (const email of invalidEmails) {
126+
try {
127+
emailSchema.parse(email);
128+
console.log(`PASS`, email);
129+
} catch (_) {}
130+
}

0 commit comments

Comments
 (0)