email-validator 2.1.2__tar.gz → 2.2.0__tar.gz
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.
- {email_validator-2.1.2/email_validator.egg-info → email_validator-2.2.0}/PKG-INFO +44 -65
- {email_validator-2.1.2 → email_validator-2.2.0}/README.md +44 -65
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/__init__.py +9 -4
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/__main__.py +4 -3
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/deliverability.py +15 -9
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/exceptions_types.py +16 -16
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/rfc_constants.py +2 -3
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/syntax.py +306 -108
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/validate_email.py +52 -18
- email_validator-2.2.0/email_validator/version.py +1 -0
- {email_validator-2.1.2 → email_validator-2.2.0/email_validator.egg-info}/PKG-INFO +44 -65
- email_validator-2.2.0/pyproject.toml +17 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/tests/mocked_dns_response.py +29 -23
- {email_validator-2.1.2 → email_validator-2.2.0}/tests/test_deliverability.py +11 -9
- {email_validator-2.1.2 → email_validator-2.2.0}/tests/test_main.py +6 -6
- {email_validator-2.1.2 → email_validator-2.2.0}/tests/test_syntax.py +124 -41
- email_validator-2.1.2/email_validator/version.py +0 -1
- email_validator-2.1.2/pyproject.toml +0 -4
- {email_validator-2.1.2 → email_validator-2.2.0}/LICENSE +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/MANIFEST.in +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator/py.typed +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator.egg-info/SOURCES.txt +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator.egg-info/dependency_links.txt +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator.egg-info/entry_points.txt +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator.egg-info/requires.txt +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/email_validator.egg-info/top_level.txt +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/setup.cfg +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/setup.py +0 -0
- {email_validator-2.1.2 → email_validator-2.2.0}/tests/mocked-dns-answers.json +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: email_validator
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: A robust email address syntax and deliverability validation library.
|
|
5
5
|
Home-page: https://github.com/JoshData/python-email-validator
|
|
6
6
|
Author: Joshua Tauberer
|
|
@@ -32,8 +32,7 @@ Python 3.8+ by [Joshua Tauberer](https://joshdata.me).
|
|
|
32
32
|
This library validates that a string is of the form `name@example.com`
|
|
33
33
|
and optionally checks that the domain name is set up to receive email.
|
|
34
34
|
This is the sort of validation you would want when you are identifying
|
|
35
|
-
users by their email address like on a registration
|
|
36
|
-
necessarily for composing an email message, see below).
|
|
35
|
+
users by their email address like on a registration form.
|
|
37
36
|
|
|
38
37
|
Key features:
|
|
39
38
|
|
|
@@ -43,19 +42,21 @@ Key features:
|
|
|
43
42
|
can display to end-users.
|
|
44
43
|
* Checks deliverability (optional): Does the domain name resolve?
|
|
45
44
|
(You can override the default DNS resolver to add query caching.)
|
|
46
|
-
* Supports internationalized domain names
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
* Supports internationalized domain names (like `@ツ.life`),
|
|
46
|
+
internationalized local parts (like `ツ@example.com`),
|
|
47
|
+
and optionally parses display names (e.g. `"My Name" <me@example.com>`).
|
|
48
|
+
* Rejects addresses with invalid or unsafe Unicode characters,
|
|
49
|
+
obsolete email address syntax that you'd find unexpected,
|
|
50
|
+
special use domain names like `@localhost`,
|
|
51
|
+
and domains without a dot by default.
|
|
52
|
+
This is an opinionated library!
|
|
51
53
|
* Normalizes email addresses (important for internationalized
|
|
52
54
|
and quoted-string addresses! see below).
|
|
53
55
|
* Python type annotations are used.
|
|
54
56
|
|
|
55
57
|
This is an opinionated library. You should definitely also consider using
|
|
56
|
-
the less-opinionated [pyIsEmail](https://github.com/michaelherold/pyIsEmail)
|
|
57
|
-
|
|
58
|
-
use case.
|
|
58
|
+
the less-opinionated [pyIsEmail](https://github.com/michaelherold/pyIsEmail)
|
|
59
|
+
if it works better for you.
|
|
59
60
|
|
|
60
61
|
[](https://github.com/JoshData/python-email-validator/actions/workflows/test_and_build.yaml)
|
|
61
62
|
|
|
@@ -169,6 +170,8 @@ The `validate_email` function also accepts the following keyword arguments
|
|
|
169
170
|
|
|
170
171
|
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-prefixd IPv6 addresses in the domain part of the email address. No deliverability checks are performed for these addresses. In the object returned by `validate_email`, the normalized domain will use the condensed IPv6 format, if applicable. The object's `domain_address` attribute will hold the parsed `ipaddress.IPv4Address` or `ipaddress.IPv6Address` object if applicable. You can also set `email_validator.ALLOW_DOMAIN_LITERAL` to `True` to turn this on for all calls by default.
|
|
171
172
|
|
|
173
|
+
`allow_display_name=False`: Set to `True` to allow a display name and bracketed address in the input string, like `My Name <me@example.org>`. It's implemented in the spirit but not the letter of RFC 5322 3.4, so it may be stricter or more relaxed than what you want. The display name, if present, is provided in the returned object's `display_name` field after being unquoted and unescaped. You can also set `email_validator.ALLOW_DISPLAY_NAME` to `True` to turn this on for all calls by default.
|
|
174
|
+
|
|
172
175
|
`allow_empty_local=False`: Set to `True` to allow an empty local part (i.e.
|
|
173
176
|
`@example.com`), e.g. for validating Postfix aliases.
|
|
174
177
|
|
|
@@ -207,8 +210,12 @@ Internationalized email addresses
|
|
|
207
210
|
The email protocol SMTP and the domain name system DNS have historically
|
|
208
211
|
only allowed English (ASCII) characters in email addresses and domain names,
|
|
209
212
|
respectively. Each has adapted to internationalization in a separate
|
|
210
|
-
way, creating two separate aspects to email address
|
|
211
|
-
|
|
213
|
+
way, creating two separate aspects to email address internationalization.
|
|
214
|
+
|
|
215
|
+
(If your mail submission library doesn't support Unicode at all, then
|
|
216
|
+
immediately prior to mail submission you must replace the email address with
|
|
217
|
+
its ASCII-ized form. This library gives you back the ASCII-ized form in the
|
|
218
|
+
`ascii_email` field in the returned object.)
|
|
212
219
|
|
|
213
220
|
### Internationalized domain names (IDN)
|
|
214
221
|
|
|
@@ -231,6 +238,19 @@ email addresses, only English letters, numbers, and some punctuation
|
|
|
231
238
|
(`._!#$%&'^``*+-=~/?{|}`) are allowed. In internationalized email address
|
|
232
239
|
local parts, a wider range of Unicode characters are allowed.
|
|
233
240
|
|
|
241
|
+
Email addresses with these non-ASCII characters require that your mail
|
|
242
|
+
submission library and all the mail servers along the route to the destination,
|
|
243
|
+
including your own outbound mail server, all support the
|
|
244
|
+
[SMTPUTF8 (RFC 6531)](https://tools.ietf.org/html/rfc6531) extension.
|
|
245
|
+
Support for SMTPUTF8 varies. If you know ahead of time that SMTPUTF8 is not
|
|
246
|
+
supported by your mail submission stack, then you must filter out addresses that
|
|
247
|
+
require SMTPUTF8 using the `allow_smtputf8=False` keyword argument (see above).
|
|
248
|
+
This will cause the validation function to raise a `EmailSyntaxError` if
|
|
249
|
+
delivery would require SMTPUTF8. If you do not set `allow_smtputf8=False`,
|
|
250
|
+
you can also check the value of the `smtputf8` field in the returned object.
|
|
251
|
+
|
|
252
|
+
### Unsafe Unicode characters are rejected
|
|
253
|
+
|
|
234
254
|
A surprisingly large number of Unicode characters are not safe to display,
|
|
235
255
|
especially when the email address is concatenated with other text, so this
|
|
236
256
|
library tries to protect you by not permitting reserved, non-, private use,
|
|
@@ -241,48 +261,10 @@ cannot combine with something outside of the email address string or with
|
|
|
241
261
|
the @-sign). See https://qntm.org/safe and https://trojansource.codes/
|
|
242
262
|
for relevant prior work. (Other than whitespace, these are checks that
|
|
243
263
|
you should be applying to nearly all user inputs in a security-sensitive
|
|
244
|
-
context.)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
so you are only fully protected if you replace all user-provided email addresses
|
|
248
|
-
with the normalized email address string returned by this library. This does not
|
|
249
|
-
guard against the well known problem that many Unicode characters look alike
|
|
250
|
-
(or are identical), which can be used to fool humans reading displayed text.
|
|
251
|
-
|
|
252
|
-
Email addresses with these non-ASCII characters require that your mail
|
|
253
|
-
submission library and the mail servers along the route to the destination,
|
|
254
|
-
including your own outbound mail server, all support the
|
|
255
|
-
[SMTPUTF8 (RFC 6531)](https://tools.ietf.org/html/rfc6531) extension.
|
|
256
|
-
Support for SMTPUTF8 varies. See the `allow_smtputf8` parameter.
|
|
257
|
-
|
|
258
|
-
### If you know ahead of time that SMTPUTF8 is not supported by your mail submission stack
|
|
264
|
+
context.) This does not guard against the well known problem that many
|
|
265
|
+
Unicode characters look alike, which can be used to fool humans reading
|
|
266
|
+
displayed text.
|
|
259
267
|
|
|
260
|
-
By default all internationalized forms are accepted by the validator.
|
|
261
|
-
But if you know ahead of time that SMTPUTF8 is not supported by your
|
|
262
|
-
mail submission stack, then you must filter out addresses that require
|
|
263
|
-
SMTPUTF8 using the `allow_smtputf8=False` keyword argument (see above).
|
|
264
|
-
This will cause the validation function to raise a `EmailSyntaxError` if
|
|
265
|
-
delivery would require SMTPUTF8. That's just in those cases where
|
|
266
|
-
non-ASCII characters appear before the @-sign. If you do not set
|
|
267
|
-
`allow_smtputf8=False`, you can also check the value of the `smtputf8`
|
|
268
|
-
field in the returned object.
|
|
269
|
-
|
|
270
|
-
If your mail submission library doesn't support Unicode at all --- even
|
|
271
|
-
in the domain part of the address --- then immediately prior to mail
|
|
272
|
-
submission you must replace the email address with its ASCII-ized form.
|
|
273
|
-
This library gives you back the ASCII-ized form in the `ascii_email`
|
|
274
|
-
field in the returned object, which you can get like this:
|
|
275
|
-
|
|
276
|
-
```python
|
|
277
|
-
emailinfo = validate_email(email, allow_smtputf8=False)
|
|
278
|
-
email = emailinfo.ascii_email
|
|
279
|
-
```
|
|
280
|
-
|
|
281
|
-
The local part is left alone (if it has internationalized characters
|
|
282
|
-
`allow_smtputf8=False` will force validation to fail) and the domain
|
|
283
|
-
part is converted to [IDNA ASCII](https://tools.ietf.org/html/rfc5891).
|
|
284
|
-
(You probably should not do this at account creation time so you don't
|
|
285
|
-
change the user's login information without telling them.)
|
|
286
268
|
|
|
287
269
|
Normalization
|
|
288
270
|
-------------
|
|
@@ -297,7 +279,7 @@ address.
|
|
|
297
279
|
|
|
298
280
|
For example, the CJK fullwidth Latin letters are considered semantically
|
|
299
281
|
equivalent in domain names to their ASCII counterparts. This library
|
|
300
|
-
normalizes them to their ASCII counterparts:
|
|
282
|
+
normalizes them to their ASCII counterparts (as required by IDNA):
|
|
301
283
|
|
|
302
284
|
```python
|
|
303
285
|
emailinfo = validate_email("me@Domain.com")
|
|
@@ -310,9 +292,7 @@ Because an end-user might type their email address in different (but
|
|
|
310
292
|
equivalent) un-normalized forms at different times, you ought to
|
|
311
293
|
replace what they enter with the normalized form immediately prior to
|
|
312
294
|
going into your database (during account creation), querying your database
|
|
313
|
-
(during login), or sending outbound mail.
|
|
314
|
-
the length of an email address, and this may affect whether it is valid
|
|
315
|
-
and acceptable by your SMTP provider.
|
|
295
|
+
(during login), or sending outbound mail.
|
|
316
296
|
|
|
317
297
|
The normalizations include lowercasing the domain part of the email
|
|
318
298
|
address (domain names are case-insensitive), [Unicode "NFC"
|
|
@@ -326,6 +306,11 @@ in the domain part, possibly other
|
|
|
326
306
|
[UTS46](http://unicode.org/reports/tr46) mappings on the domain part,
|
|
327
307
|
and conversion from Punycode to Unicode characters.
|
|
328
308
|
|
|
309
|
+
Normalization may change the characters in the email address and the
|
|
310
|
+
length of the email address, such that a string might be a valid address
|
|
311
|
+
before normalization but invalid after, or vice versa. This library only
|
|
312
|
+
permits addresses that are valid both before and after normalization.
|
|
313
|
+
|
|
329
314
|
(See [RFC 6532 (internationalized email) section
|
|
330
315
|
3.1](https://tools.ietf.org/html/rfc6532#section-3.1) and [RFC 5895
|
|
331
316
|
(IDNA 2008) section 2](http://www.ietf.org/rfc/rfc5895.txt).)
|
|
@@ -340,13 +325,6 @@ they are unnecessary. For IPv6 domain literals, the IPv6 address is
|
|
|
340
325
|
normalized to condensed form. [RFC 2142](https://datatracker.ietf.org/doc/html/rfc2142)
|
|
341
326
|
also requires lowercase normalization for some specific mailbox names like `postmaster@`.
|
|
342
327
|
|
|
343
|
-
### Length checks
|
|
344
|
-
|
|
345
|
-
This library checks that the length of the email address is not longer than
|
|
346
|
-
the maximum length. The check is performed on the normalized form of the
|
|
347
|
-
address, which might be different from a string provided by a user. If you
|
|
348
|
-
send email to the original string and not the normalized address, the email
|
|
349
|
-
might be rejected because the original address could be too long.
|
|
350
328
|
|
|
351
329
|
Examples
|
|
352
330
|
--------
|
|
@@ -420,6 +398,7 @@ are:
|
|
|
420
398
|
| `domain` | The canonical internationalized Unicode form of the domain part of the email address. If the returned string contains non-ASCII characters, either the [SMTPUTF8](https://tools.ietf.org/html/rfc6531) feature of your mail relay will be required to transmit the message or else the email address's domain part must be converted to IDNA ASCII first: Use `ascii_domain` field instead. |
|
|
421
399
|
| `ascii_domain` | The [IDNA](https://tools.ietf.org/html/rfc5891) [Punycode](https://www.rfc-editor.org/rfc/rfc3492.txt)-encoded form of the domain part of the given email address, as it would be transmitted on the wire. |
|
|
422
400
|
| `domain_address` | If domain literals are allowed and if the email address contains one, an `ipaddress.IPv4Address` or `ipaddress.IPv6Address` object. |
|
|
401
|
+
| `display_name` | If no display name was present and angle brackets do not surround the address, this will be `None`; otherwise, it will be set to the display name, or the empty string if there were angle brackets but no display name. If the display name was quoted, it will be unquoted and unescaped. |
|
|
423
402
|
| `smtputf8` | A boolean indicating that the [SMTPUTF8](https://tools.ietf.org/html/rfc6531) feature of your mail relay will be required to transmit messages to this address because the local part of the address has non-ASCII characters (the local part cannot be IDNA-encoded). If `allow_smtputf8=False` is passed as an argument, this flag will always be false because an exception is raised if it would have been true. |
|
|
424
403
|
| `mx` | A list of (priority, domain) tuples of MX records specified in the DNS for the domain (see [RFC 5321 section 5](https://tools.ietf.org/html/rfc5321#section-5)). May be `None` if the deliverability check could not be completed because of a temporary issue like a timeout. |
|
|
425
404
|
| `mx_fallback_type` | `None` if an `MX` record is found. If no MX records are actually specified in DNS and instead are inferred, through an obsolete mechanism, from A or AAAA records, the value is the type of DNS record used instead (`A` or `AAAA`). May be `None` if the deliverability check could not be completed because of a temporary issue like a timeout. |
|
|
@@ -7,8 +7,7 @@ Python 3.8+ by [Joshua Tauberer](https://joshdata.me).
|
|
|
7
7
|
This library validates that a string is of the form `name@example.com`
|
|
8
8
|
and optionally checks that the domain name is set up to receive email.
|
|
9
9
|
This is the sort of validation you would want when you are identifying
|
|
10
|
-
users by their email address like on a registration
|
|
11
|
-
necessarily for composing an email message, see below).
|
|
10
|
+
users by their email address like on a registration form.
|
|
12
11
|
|
|
13
12
|
Key features:
|
|
14
13
|
|
|
@@ -18,19 +17,21 @@ Key features:
|
|
|
18
17
|
can display to end-users.
|
|
19
18
|
* Checks deliverability (optional): Does the domain name resolve?
|
|
20
19
|
(You can override the default DNS resolver to add query caching.)
|
|
21
|
-
* Supports internationalized domain names
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
20
|
+
* Supports internationalized domain names (like `@ツ.life`),
|
|
21
|
+
internationalized local parts (like `ツ@example.com`),
|
|
22
|
+
and optionally parses display names (e.g. `"My Name" <me@example.com>`).
|
|
23
|
+
* Rejects addresses with invalid or unsafe Unicode characters,
|
|
24
|
+
obsolete email address syntax that you'd find unexpected,
|
|
25
|
+
special use domain names like `@localhost`,
|
|
26
|
+
and domains without a dot by default.
|
|
27
|
+
This is an opinionated library!
|
|
26
28
|
* Normalizes email addresses (important for internationalized
|
|
27
29
|
and quoted-string addresses! see below).
|
|
28
30
|
* Python type annotations are used.
|
|
29
31
|
|
|
30
32
|
This is an opinionated library. You should definitely also consider using
|
|
31
|
-
the less-opinionated [pyIsEmail](https://github.com/michaelherold/pyIsEmail)
|
|
32
|
-
|
|
33
|
-
use case.
|
|
33
|
+
the less-opinionated [pyIsEmail](https://github.com/michaelherold/pyIsEmail)
|
|
34
|
+
if it works better for you.
|
|
34
35
|
|
|
35
36
|
[](https://github.com/JoshData/python-email-validator/actions/workflows/test_and_build.yaml)
|
|
36
37
|
|
|
@@ -144,6 +145,8 @@ The `validate_email` function also accepts the following keyword arguments
|
|
|
144
145
|
|
|
145
146
|
`allow_domain_literal=False`: Set to `True` to allow bracketed IPv4 and "IPv6:"-prefixd IPv6 addresses in the domain part of the email address. No deliverability checks are performed for these addresses. In the object returned by `validate_email`, the normalized domain will use the condensed IPv6 format, if applicable. The object's `domain_address` attribute will hold the parsed `ipaddress.IPv4Address` or `ipaddress.IPv6Address` object if applicable. You can also set `email_validator.ALLOW_DOMAIN_LITERAL` to `True` to turn this on for all calls by default.
|
|
146
147
|
|
|
148
|
+
`allow_display_name=False`: Set to `True` to allow a display name and bracketed address in the input string, like `My Name <me@example.org>`. It's implemented in the spirit but not the letter of RFC 5322 3.4, so it may be stricter or more relaxed than what you want. The display name, if present, is provided in the returned object's `display_name` field after being unquoted and unescaped. You can also set `email_validator.ALLOW_DISPLAY_NAME` to `True` to turn this on for all calls by default.
|
|
149
|
+
|
|
147
150
|
`allow_empty_local=False`: Set to `True` to allow an empty local part (i.e.
|
|
148
151
|
`@example.com`), e.g. for validating Postfix aliases.
|
|
149
152
|
|
|
@@ -182,8 +185,12 @@ Internationalized email addresses
|
|
|
182
185
|
The email protocol SMTP and the domain name system DNS have historically
|
|
183
186
|
only allowed English (ASCII) characters in email addresses and domain names,
|
|
184
187
|
respectively. Each has adapted to internationalization in a separate
|
|
185
|
-
way, creating two separate aspects to email address
|
|
186
|
-
|
|
188
|
+
way, creating two separate aspects to email address internationalization.
|
|
189
|
+
|
|
190
|
+
(If your mail submission library doesn't support Unicode at all, then
|
|
191
|
+
immediately prior to mail submission you must replace the email address with
|
|
192
|
+
its ASCII-ized form. This library gives you back the ASCII-ized form in the
|
|
193
|
+
`ascii_email` field in the returned object.)
|
|
187
194
|
|
|
188
195
|
### Internationalized domain names (IDN)
|
|
189
196
|
|
|
@@ -206,6 +213,19 @@ email addresses, only English letters, numbers, and some punctuation
|
|
|
206
213
|
(`._!#$%&'^``*+-=~/?{|}`) are allowed. In internationalized email address
|
|
207
214
|
local parts, a wider range of Unicode characters are allowed.
|
|
208
215
|
|
|
216
|
+
Email addresses with these non-ASCII characters require that your mail
|
|
217
|
+
submission library and all the mail servers along the route to the destination,
|
|
218
|
+
including your own outbound mail server, all support the
|
|
219
|
+
[SMTPUTF8 (RFC 6531)](https://tools.ietf.org/html/rfc6531) extension.
|
|
220
|
+
Support for SMTPUTF8 varies. If you know ahead of time that SMTPUTF8 is not
|
|
221
|
+
supported by your mail submission stack, then you must filter out addresses that
|
|
222
|
+
require SMTPUTF8 using the `allow_smtputf8=False` keyword argument (see above).
|
|
223
|
+
This will cause the validation function to raise a `EmailSyntaxError` if
|
|
224
|
+
delivery would require SMTPUTF8. If you do not set `allow_smtputf8=False`,
|
|
225
|
+
you can also check the value of the `smtputf8` field in the returned object.
|
|
226
|
+
|
|
227
|
+
### Unsafe Unicode characters are rejected
|
|
228
|
+
|
|
209
229
|
A surprisingly large number of Unicode characters are not safe to display,
|
|
210
230
|
especially when the email address is concatenated with other text, so this
|
|
211
231
|
library tries to protect you by not permitting reserved, non-, private use,
|
|
@@ -216,48 +236,10 @@ cannot combine with something outside of the email address string or with
|
|
|
216
236
|
the @-sign). See https://qntm.org/safe and https://trojansource.codes/
|
|
217
237
|
for relevant prior work. (Other than whitespace, these are checks that
|
|
218
238
|
you should be applying to nearly all user inputs in a security-sensitive
|
|
219
|
-
context.)
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
so you are only fully protected if you replace all user-provided email addresses
|
|
223
|
-
with the normalized email address string returned by this library. This does not
|
|
224
|
-
guard against the well known problem that many Unicode characters look alike
|
|
225
|
-
(or are identical), which can be used to fool humans reading displayed text.
|
|
226
|
-
|
|
227
|
-
Email addresses with these non-ASCII characters require that your mail
|
|
228
|
-
submission library and the mail servers along the route to the destination,
|
|
229
|
-
including your own outbound mail server, all support the
|
|
230
|
-
[SMTPUTF8 (RFC 6531)](https://tools.ietf.org/html/rfc6531) extension.
|
|
231
|
-
Support for SMTPUTF8 varies. See the `allow_smtputf8` parameter.
|
|
232
|
-
|
|
233
|
-
### If you know ahead of time that SMTPUTF8 is not supported by your mail submission stack
|
|
239
|
+
context.) This does not guard against the well known problem that many
|
|
240
|
+
Unicode characters look alike, which can be used to fool humans reading
|
|
241
|
+
displayed text.
|
|
234
242
|
|
|
235
|
-
By default all internationalized forms are accepted by the validator.
|
|
236
|
-
But if you know ahead of time that SMTPUTF8 is not supported by your
|
|
237
|
-
mail submission stack, then you must filter out addresses that require
|
|
238
|
-
SMTPUTF8 using the `allow_smtputf8=False` keyword argument (see above).
|
|
239
|
-
This will cause the validation function to raise a `EmailSyntaxError` if
|
|
240
|
-
delivery would require SMTPUTF8. That's just in those cases where
|
|
241
|
-
non-ASCII characters appear before the @-sign. If you do not set
|
|
242
|
-
`allow_smtputf8=False`, you can also check the value of the `smtputf8`
|
|
243
|
-
field in the returned object.
|
|
244
|
-
|
|
245
|
-
If your mail submission library doesn't support Unicode at all --- even
|
|
246
|
-
in the domain part of the address --- then immediately prior to mail
|
|
247
|
-
submission you must replace the email address with its ASCII-ized form.
|
|
248
|
-
This library gives you back the ASCII-ized form in the `ascii_email`
|
|
249
|
-
field in the returned object, which you can get like this:
|
|
250
|
-
|
|
251
|
-
```python
|
|
252
|
-
emailinfo = validate_email(email, allow_smtputf8=False)
|
|
253
|
-
email = emailinfo.ascii_email
|
|
254
|
-
```
|
|
255
|
-
|
|
256
|
-
The local part is left alone (if it has internationalized characters
|
|
257
|
-
`allow_smtputf8=False` will force validation to fail) and the domain
|
|
258
|
-
part is converted to [IDNA ASCII](https://tools.ietf.org/html/rfc5891).
|
|
259
|
-
(You probably should not do this at account creation time so you don't
|
|
260
|
-
change the user's login information without telling them.)
|
|
261
243
|
|
|
262
244
|
Normalization
|
|
263
245
|
-------------
|
|
@@ -272,7 +254,7 @@ address.
|
|
|
272
254
|
|
|
273
255
|
For example, the CJK fullwidth Latin letters are considered semantically
|
|
274
256
|
equivalent in domain names to their ASCII counterparts. This library
|
|
275
|
-
normalizes them to their ASCII counterparts:
|
|
257
|
+
normalizes them to their ASCII counterparts (as required by IDNA):
|
|
276
258
|
|
|
277
259
|
```python
|
|
278
260
|
emailinfo = validate_email("me@Domain.com")
|
|
@@ -285,9 +267,7 @@ Because an end-user might type their email address in different (but
|
|
|
285
267
|
equivalent) un-normalized forms at different times, you ought to
|
|
286
268
|
replace what they enter with the normalized form immediately prior to
|
|
287
269
|
going into your database (during account creation), querying your database
|
|
288
|
-
(during login), or sending outbound mail.
|
|
289
|
-
the length of an email address, and this may affect whether it is valid
|
|
290
|
-
and acceptable by your SMTP provider.
|
|
270
|
+
(during login), or sending outbound mail.
|
|
291
271
|
|
|
292
272
|
The normalizations include lowercasing the domain part of the email
|
|
293
273
|
address (domain names are case-insensitive), [Unicode "NFC"
|
|
@@ -301,6 +281,11 @@ in the domain part, possibly other
|
|
|
301
281
|
[UTS46](http://unicode.org/reports/tr46) mappings on the domain part,
|
|
302
282
|
and conversion from Punycode to Unicode characters.
|
|
303
283
|
|
|
284
|
+
Normalization may change the characters in the email address and the
|
|
285
|
+
length of the email address, such that a string might be a valid address
|
|
286
|
+
before normalization but invalid after, or vice versa. This library only
|
|
287
|
+
permits addresses that are valid both before and after normalization.
|
|
288
|
+
|
|
304
289
|
(See [RFC 6532 (internationalized email) section
|
|
305
290
|
3.1](https://tools.ietf.org/html/rfc6532#section-3.1) and [RFC 5895
|
|
306
291
|
(IDNA 2008) section 2](http://www.ietf.org/rfc/rfc5895.txt).)
|
|
@@ -315,13 +300,6 @@ they are unnecessary. For IPv6 domain literals, the IPv6 address is
|
|
|
315
300
|
normalized to condensed form. [RFC 2142](https://datatracker.ietf.org/doc/html/rfc2142)
|
|
316
301
|
also requires lowercase normalization for some specific mailbox names like `postmaster@`.
|
|
317
302
|
|
|
318
|
-
### Length checks
|
|
319
|
-
|
|
320
|
-
This library checks that the length of the email address is not longer than
|
|
321
|
-
the maximum length. The check is performed on the normalized form of the
|
|
322
|
-
address, which might be different from a string provided by a user. If you
|
|
323
|
-
send email to the original string and not the normalized address, the email
|
|
324
|
-
might be rejected because the original address could be too long.
|
|
325
303
|
|
|
326
304
|
Examples
|
|
327
305
|
--------
|
|
@@ -395,6 +373,7 @@ are:
|
|
|
395
373
|
| `domain` | The canonical internationalized Unicode form of the domain part of the email address. If the returned string contains non-ASCII characters, either the [SMTPUTF8](https://tools.ietf.org/html/rfc6531) feature of your mail relay will be required to transmit the message or else the email address's domain part must be converted to IDNA ASCII first: Use `ascii_domain` field instead. |
|
|
396
374
|
| `ascii_domain` | The [IDNA](https://tools.ietf.org/html/rfc5891) [Punycode](https://www.rfc-editor.org/rfc/rfc3492.txt)-encoded form of the domain part of the given email address, as it would be transmitted on the wire. |
|
|
397
375
|
| `domain_address` | If domain literals are allowed and if the email address contains one, an `ipaddress.IPv4Address` or `ipaddress.IPv6Address` object. |
|
|
376
|
+
| `display_name` | If no display name was present and angle brackets do not surround the address, this will be `None`; otherwise, it will be set to the display name, or the empty string if there were angle brackets but no display name. If the display name was quoted, it will be unquoted and unescaped. |
|
|
398
377
|
| `smtputf8` | A boolean indicating that the [SMTPUTF8](https://tools.ietf.org/html/rfc6531) feature of your mail relay will be required to transmit messages to this address because the local part of the address has non-ASCII characters (the local part cannot be IDNA-encoded). If `allow_smtputf8=False` is passed as an argument, this flag will always be false because an exception is raised if it would have been true. |
|
|
399
378
|
| `mx` | A list of (priority, domain) tuples of MX records specified in the DNS for the domain (see [RFC 5321 section 5](https://tools.ietf.org/html/rfc5321#section-5)). May be `None` if the deliverability check could not be completed because of a temporary issue like a timeout. |
|
|
400
379
|
| `mx_fallback_type` | `None` if an `MX` record is found. If no MX records are actually specified in DNS and instead are inferred, through an obsolete mechanism, from A or AAAA records, the value is the type of DNS record used instead (`A` or `AAAA`). May be `None` if the deliverability check could not be completed because of a temporary issue like a timeout. |
|
|
@@ -458,4 +437,4 @@ git push --tags
|
|
|
458
437
|
License
|
|
459
438
|
-------
|
|
460
439
|
|
|
461
|
-
This project is free of any copyright restrictions per the [Unlicense](https://unlicense.org/). (Prior to Feb. 4, 2024, the project was made available under the terms of the [CC0 1.0 Universal public domain dedication](http://creativecommons.org/publicdomain/zero/1.0/).) See [LICENSE](LICENSE) and [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
440
|
+
This project is free of any copyright restrictions per the [Unlicense](https://unlicense.org/). (Prior to Feb. 4, 2024, the project was made available under the terms of the [CC0 1.0 Universal public domain dedication](http://creativecommons.org/publicdomain/zero/1.0/).) See [LICENSE](LICENSE) and [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
1
3
|
# Export the main method, helper methods, and the public data types.
|
|
2
4
|
from .exceptions_types import ValidatedEmail, EmailNotValidError, \
|
|
3
5
|
EmailSyntaxError, EmailUndeliverableError
|
|
@@ -9,12 +11,14 @@ __all__ = ["validate_email",
|
|
|
9
11
|
"EmailSyntaxError", "EmailUndeliverableError",
|
|
10
12
|
"caching_resolver", "__version__"]
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
def caching_resolver(*args, **kwargs):
|
|
14
|
-
# Lazy load `deliverability` as it is slow to import (due to dns.resolver)
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
15
|
from .deliverability import caching_resolver
|
|
16
|
+
else:
|
|
17
|
+
def caching_resolver(*args, **kwargs):
|
|
18
|
+
# Lazy load `deliverability` as it is slow to import (due to dns.resolver)
|
|
19
|
+
from .deliverability import caching_resolver
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
return caching_resolver(*args, **kwargs)
|
|
18
22
|
|
|
19
23
|
|
|
20
24
|
# These global attributes are a part of the library's API and can be
|
|
@@ -25,6 +29,7 @@ def caching_resolver(*args, **kwargs):
|
|
|
25
29
|
ALLOW_SMTPUTF8 = True
|
|
26
30
|
ALLOW_QUOTED_LOCAL = False
|
|
27
31
|
ALLOW_DOMAIN_LITERAL = False
|
|
32
|
+
ALLOW_DISPLAY_NAME = False
|
|
28
33
|
GLOBALLY_DELIVERABLE = True
|
|
29
34
|
CHECK_DELIVERABILITY = True
|
|
30
35
|
TEST_ENVIRONMENT = False
|
|
@@ -17,17 +17,18 @@
|
|
|
17
17
|
import json
|
|
18
18
|
import os
|
|
19
19
|
import sys
|
|
20
|
+
from typing import Any, Dict, Optional
|
|
20
21
|
|
|
21
|
-
from .validate_email import validate_email
|
|
22
|
+
from .validate_email import validate_email, _Resolver
|
|
22
23
|
from .deliverability import caching_resolver
|
|
23
24
|
from .exceptions_types import EmailNotValidError
|
|
24
25
|
|
|
25
26
|
|
|
26
|
-
def main(dns_resolver=None):
|
|
27
|
+
def main(dns_resolver: Optional[_Resolver] = None) -> None:
|
|
27
28
|
# The dns_resolver argument is for tests.
|
|
28
29
|
|
|
29
30
|
# Set options from environment variables.
|
|
30
|
-
options = {}
|
|
31
|
+
options: Dict[str, Any] = {}
|
|
31
32
|
for varname in ('ALLOW_SMTPUTF8', 'ALLOW_QUOTED_LOCAL', 'ALLOW_DOMAIN_LITERAL',
|
|
32
33
|
'GLOBALLY_DELIVERABLE', 'CHECK_DELIVERABILITY', 'TEST_ENVIRONMENT'):
|
|
33
34
|
if varname in os.environ:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Optional,
|
|
1
|
+
from typing import Any, List, Optional, Tuple, TypedDict
|
|
2
2
|
|
|
3
3
|
import ipaddress
|
|
4
4
|
|
|
@@ -8,17 +8,24 @@ import dns.resolver
|
|
|
8
8
|
import dns.exception
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
def caching_resolver(*, timeout: Optional[int] = None, cache=None, dns_resolver=None):
|
|
11
|
+
def caching_resolver(*, timeout: Optional[int] = None, cache: Any = None, dns_resolver: Optional[dns.resolver.Resolver] = None) -> dns.resolver.Resolver:
|
|
12
12
|
if timeout is None:
|
|
13
13
|
from . import DEFAULT_TIMEOUT
|
|
14
14
|
timeout = DEFAULT_TIMEOUT
|
|
15
15
|
resolver = dns_resolver or dns.resolver.Resolver()
|
|
16
|
-
resolver.cache = cache or dns.resolver.LRUCache()
|
|
17
|
-
resolver.lifetime = timeout #
|
|
16
|
+
resolver.cache = cache or dns.resolver.LRUCache()
|
|
17
|
+
resolver.lifetime = timeout # timeout, in seconds
|
|
18
18
|
return resolver
|
|
19
19
|
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
DeliverabilityInfo = TypedDict("DeliverabilityInfo", {
|
|
22
|
+
"mx": List[Tuple[int, str]],
|
|
23
|
+
"mx_fallback_type": Optional[str],
|
|
24
|
+
"unknown-deliverability": str,
|
|
25
|
+
}, total=False)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Optional[int] = None, dns_resolver: Optional[dns.resolver.Resolver] = None) -> DeliverabilityInfo:
|
|
22
29
|
# Check that the domain resolves to an MX record. If there is no MX record,
|
|
23
30
|
# try an A or AAAA record which is a deprecated fallback for deliverability.
|
|
24
31
|
# Raises an EmailUndeliverableError on failure. On success, returns a dict
|
|
@@ -36,7 +43,7 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
|
|
|
36
43
|
elif timeout is not None:
|
|
37
44
|
raise ValueError("It's not valid to pass both timeout and dns_resolver.")
|
|
38
45
|
|
|
39
|
-
deliverability_info:
|
|
46
|
+
deliverability_info: DeliverabilityInfo = {}
|
|
40
47
|
|
|
41
48
|
try:
|
|
42
49
|
try:
|
|
@@ -69,9 +76,9 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
|
|
|
69
76
|
# https://www.iana.org/assignments/iana-ipv4-special-registry/iana-ipv4-special-registry.xhtml
|
|
70
77
|
# https://www.iana.org/assignments/iana-ipv6-special-registry/iana-ipv6-special-registry.xhtml
|
|
71
78
|
# (Issue #134.)
|
|
72
|
-
def is_global_addr(
|
|
79
|
+
def is_global_addr(address: Any) -> bool:
|
|
73
80
|
try:
|
|
74
|
-
ipaddr = ipaddress.ip_address(
|
|
81
|
+
ipaddr = ipaddress.ip_address(address)
|
|
75
82
|
except ValueError:
|
|
76
83
|
return False
|
|
77
84
|
return ipaddr.is_global
|
|
@@ -115,7 +122,6 @@ def validate_email_deliverability(domain: str, domain_i18n: str, timeout: Option
|
|
|
115
122
|
for rec in response:
|
|
116
123
|
value = b"".join(rec.strings)
|
|
117
124
|
if value.startswith(b"v=spf1 "):
|
|
118
|
-
deliverability_info["spf"] = value.decode("ascii", errors='replace')
|
|
119
125
|
if value == b"v=spf1 -all":
|
|
120
126
|
raise EmailUndeliverableError(f"The domain name {domain_i18n} does not send email.")
|
|
121
127
|
except dns.resolver.NoAnswer:
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import warnings
|
|
2
|
-
from typing import Optional
|
|
2
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
class EmailNotValidError(ValueError):
|
|
@@ -24,7 +24,7 @@ class ValidatedEmail:
|
|
|
24
24
|
"""The email address that was passed to validate_email. (If passed as bytes, this will be a string.)"""
|
|
25
25
|
original: str
|
|
26
26
|
|
|
27
|
-
"""The normalized email address, which should always be used in
|
|
27
|
+
"""The normalized email address, which should always be used in preference to the original address.
|
|
28
28
|
The normalized address converts an IDNA ASCII domain name to Unicode, if possible, and performs
|
|
29
29
|
Unicode normalization on the local part and on the domain (if originally Unicode). It is the
|
|
30
30
|
concatenation of the local_part and domain attributes, separated by an @-sign."""
|
|
@@ -56,22 +56,20 @@ class ValidatedEmail:
|
|
|
56
56
|
|
|
57
57
|
"""If a deliverability check is performed and if it succeeds, a list of (priority, domain)
|
|
58
58
|
tuples of MX records specified in the DNS for the domain."""
|
|
59
|
-
mx:
|
|
59
|
+
mx: List[Tuple[int, str]]
|
|
60
60
|
|
|
61
61
|
"""If no MX records are actually specified in DNS and instead are inferred, through an obsolete
|
|
62
62
|
mechanism, from A or AAAA records, the value is the type of DNS record used instead (`A` or `AAAA`)."""
|
|
63
|
-
mx_fallback_type: str
|
|
63
|
+
mx_fallback_type: Optional[str]
|
|
64
64
|
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
for k, v in kwargs.items():
|
|
68
|
-
setattr(self, k, v)
|
|
65
|
+
"""The display name in the original input text, unquoted and unescaped, or None."""
|
|
66
|
+
display_name: Optional[str]
|
|
69
67
|
|
|
70
|
-
def __repr__(self):
|
|
68
|
+
def __repr__(self) -> str:
|
|
71
69
|
return f"<ValidatedEmail {self.normalized}>"
|
|
72
70
|
|
|
73
71
|
"""For backwards compatibility, support old field names."""
|
|
74
|
-
def __getattr__(self, key):
|
|
72
|
+
def __getattr__(self, key: str) -> str:
|
|
75
73
|
if key == "original_email":
|
|
76
74
|
return self.original
|
|
77
75
|
if key == "email":
|
|
@@ -79,13 +77,13 @@ class ValidatedEmail:
|
|
|
79
77
|
raise AttributeError(key)
|
|
80
78
|
|
|
81
79
|
@property
|
|
82
|
-
def email(self):
|
|
80
|
+
def email(self) -> str:
|
|
83
81
|
warnings.warn("ValidatedEmail.email is deprecated and will be removed, use ValidatedEmail.normalized instead", DeprecationWarning)
|
|
84
82
|
return self.normalized
|
|
85
83
|
|
|
86
84
|
"""For backwards compatibility, some fields are also exposed through a dict-like interface. Note
|
|
87
85
|
that some of the names changed when they became attributes."""
|
|
88
|
-
def __getitem__(self, key):
|
|
86
|
+
def __getitem__(self, key: str) -> Union[Optional[str], bool, List[Tuple[int, str]]]:
|
|
89
87
|
warnings.warn("dict-like access to the return value of validate_email is deprecated and may not be supported in the future.", DeprecationWarning, stacklevel=2)
|
|
90
88
|
if key == "email":
|
|
91
89
|
return self.normalized
|
|
@@ -106,7 +104,7 @@ class ValidatedEmail:
|
|
|
106
104
|
raise KeyError()
|
|
107
105
|
|
|
108
106
|
"""Tests use this."""
|
|
109
|
-
def __eq__(self, other):
|
|
107
|
+
def __eq__(self, other: object) -> bool:
|
|
110
108
|
if not isinstance(other, ValidatedEmail):
|
|
111
109
|
return False
|
|
112
110
|
return (
|
|
@@ -120,21 +118,23 @@ class ValidatedEmail:
|
|
|
120
118
|
and repr(sorted(self.mx) if getattr(self, 'mx', None) else None)
|
|
121
119
|
== repr(sorted(other.mx) if getattr(other, 'mx', None) else None)
|
|
122
120
|
and getattr(self, 'mx_fallback_type', None) == getattr(other, 'mx_fallback_type', None)
|
|
121
|
+
and getattr(self, 'display_name', None) == getattr(other, 'display_name', None)
|
|
123
122
|
)
|
|
124
123
|
|
|
125
124
|
"""This helps producing the README."""
|
|
126
|
-
def as_constructor(self):
|
|
125
|
+
def as_constructor(self) -> str:
|
|
127
126
|
return "ValidatedEmail(" \
|
|
128
127
|
+ ",".join(f"\n {key}={repr(getattr(self, key))}"
|
|
129
128
|
for key in ('normalized', 'local_part', 'domain',
|
|
130
129
|
'ascii_email', 'ascii_local_part', 'ascii_domain',
|
|
131
|
-
'smtputf8', 'mx', 'mx_fallback_type'
|
|
130
|
+
'smtputf8', 'mx', 'mx_fallback_type',
|
|
131
|
+
'display_name')
|
|
132
132
|
if hasattr(self, key)
|
|
133
133
|
) \
|
|
134
134
|
+ ")"
|
|
135
135
|
|
|
136
136
|
"""Convenience method for accessing ValidatedEmail as a dict"""
|
|
137
|
-
def as_dict(self):
|
|
137
|
+
def as_dict(self) -> Dict[str, Any]:
|
|
138
138
|
d = self.__dict__
|
|
139
139
|
if d.get('domain_address'):
|
|
140
140
|
d['domain_address'] = repr(d['domain_address'])
|
|
@@ -13,7 +13,7 @@ DOT_ATOM_TEXT = re.compile('[' + ATEXT + ']+(?:\\.[' + ATEXT + r']+)*\Z')
|
|
|
13
13
|
# RFC 3629 section 4, which appear to be the Unicode code points from
|
|
14
14
|
# U+0080 to U+10FFFF.
|
|
15
15
|
ATEXT_INTL = ATEXT + "\u0080-\U0010FFFF"
|
|
16
|
-
|
|
16
|
+
ATEXT_INTL_DOT_RE = re.compile('[.' + ATEXT_INTL + ']') # ATEXT_INTL plus dots
|
|
17
17
|
DOT_ATOM_TEXT_INTL = re.compile('[' + ATEXT_INTL + ']+(?:\\.[' + ATEXT_INTL + r']+)*\Z')
|
|
18
18
|
|
|
19
19
|
# The domain part of the email address, after IDNA (ASCII) encoding,
|
|
@@ -30,10 +30,9 @@ DOMAIN_LITERAL_CHARS = re.compile(r"[\u0021-\u00FA\u005E-\u007E]")
|
|
|
30
30
|
# Quoted-string local part (RFC 5321 4.1.2, internationalized by RFC 6531 3.3)
|
|
31
31
|
# The permitted characters in a quoted string are the characters in the range
|
|
32
32
|
# 32-126, except that quotes and (literal) backslashes can only appear when escaped
|
|
33
|
-
# by a backslash. When internationalized,
|
|
33
|
+
# by a backslash. When internationalized, UTF-8 strings are also permitted except
|
|
34
34
|
# the ASCII characters that are not previously permitted (see above).
|
|
35
35
|
# QUOTED_LOCAL_PART_ADDR = re.compile(r"^\"((?:[\u0020-\u0021\u0023-\u005B\u005D-\u007E]|\\[\u0020-\u007E])*)\"@(.*)")
|
|
36
|
-
QUOTED_LOCAL_PART_ADDR = re.compile(r"^\"((?:[^\"\\]|\\.)*)\"@(.*)")
|
|
37
36
|
QTEXT_INTL = re.compile(r"[\u0020-\u007E\u0080-\U0010FFFF]")
|
|
38
37
|
|
|
39
38
|
# Length constants
|