5
5
from zope .schema .interfaces import INativeStringLine
6
6
from zope .schema .interfaces import ValidationError
7
7
8
- import re
9
-
10
8
11
9
_ = MessageFactory ("plone" )
12
10
13
- # Taken from http://www.regular-expressions.info/email.html
14
- _isemail = r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}"
15
- _isemail = re .compile (_isemail ).match
16
-
17
11
18
12
class IEmail (INativeStringLine ):
19
13
"""A field containing an email address"""
@@ -23,6 +17,78 @@ class InvalidEmail(ValidationError):
23
17
__doc__ = _ ("""The specified email is not valid.""" )
24
18
25
19
20
+ def _isemail (value ):
21
+ r"""Is this a valid email?
22
+
23
+ https://www.regular-expressions.info/email.html has some hints on how to
24
+ check for a valid email with regular expressions. It also gives reasons
25
+ for why you may *not* want to use them. A too simple regex will work for
26
+ most cases, but may give false negatives: it will treat a rare but valid
27
+ email address as invalid. A complex regex may still allow some invalid
28
+ email addresses, and is hard to debug in case of errors.
29
+
30
+ Originally we had this regex, unchanged between 2013 and 2024:
31
+
32
+ import re
33
+ _isemail = r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}"
34
+ _isemail = re.compile(_isemail).match
35
+
36
+ Problems:
37
+
38
+ 1. It does not allow apostrophes.
39
+
40
+ See: https://github.com/plone/plone.schema/issues/6
41
+
42
+ 2. It does not allow accented characters.
43
+ Example: jens@österreich.example.com
44
+ See: https://github.com/plone/plone.schema/issues/9
45
+
46
+ 3. It does not allow ampersand in the user part.
47
+
48
+ See: https://github.com/plone/plone.schema/issues/15
49
+
50
+ 4. It allows spaces.
51
+ Example: "[email protected] hello"
52
+ See: https://github.com/plone/plone.schema/issues/30
53
+
54
+ 5. It correctly accepts TLDs with more than 4 characters,
55
+ but this seems by accident, so it could get lost in an update.
56
+
57
+ See: https://github.com/plone/plone.schema/issues/30
58
+ """
59
+ # We only accept a string.
60
+ if not isinstance (value , str ):
61
+ return False
62
+
63
+ # It is up to the caller to strip spaces, newlines, etc.
64
+ if value != value .strip ():
65
+ return False
66
+ if len (value .split ()) != 1 :
67
+ return False
68
+
69
+ # only one @ sign
70
+ if value .count ("@" ) != 1 :
71
+ return False
72
+
73
+ # At least one dot in the domain. And when split on dot,
74
+ # each part must not be empty.
75
+ user , domain = value .split ("@" )
76
+ if not all (domain .partition ("." )):
77
+ return False
78
+
79
+ # user part must not be empty
80
+ if not user :
81
+ return False
82
+
83
+ # The maximum length of an email address that can be handled by SMTP
84
+ # is 254 characters.
85
+ if len (value ) > 254 :
86
+ return False
87
+
88
+ # We have found no problems.
89
+ return True
90
+
91
+
26
92
@implementer (IEmail , IFromUnicode )
27
93
class Email (NativeStringLine ):
28
94
"""Email schema field"""
@@ -34,6 +100,14 @@ def _validate(self, value):
34
100
35
101
raise InvalidEmail (value )
36
102
103
+ def fromBytes (self , value ):
104
+ # Originally, fromBytes was not defined.
105
+ # Upstream NativeStringLine had it, but without the 'strip'
106
+ # that we added in fromUnicode.
107
+ value = value .strip ().decode ("utf-8" )
108
+ self .validate (value )
109
+ return value
110
+
37
111
def fromUnicode (self , value ):
38
112
v = str (value .strip ())
39
113
self .validate (v )
0 commit comments