Repository: marshmallow-code/marshmallow

Issue:
Incorrect Email Validation
https://github.com/marshmallow-code/marshmallow/blob/fbe22eb47db5df64b2c4133f9a5cb6c6920e8dd2/src/marshmallow/validate.py#L136-L151

The email validation regex will match `email@domain.com\n`, `email\n@domain.com`, and `email\n@domain.com\n`.

The issue is that `$` is used to match until the end of a string. Instead, `\Z` should be used. - https://stackoverflow.com/a/48730645

It is possible that other validators might suffer from the same bug, so it would be good if other regexes were also checked.

It is unclear, but this may lead to a security vulnerability in some projects that use marshmallow (depending on how the validator is used), so a quick fix here might be helpful. In my quick look around I didn't notice anything critical, however, so I figured it would be fine to open this issue.

Discussion:

Patch:
diff --git a/AUTHORS.rst b/AUTHORS.rst
index 02678948..cabe5ad9 100644
--- a/AUTHORS.rst
+++ b/AUTHORS.rst
@@ -92,3 +92,4 @@ Contributors (chronological)
 - Kim Gustyr `@khvn26 <https://github.com/khvn26>`_
 - Bryce Drennan `@brycedrennan <https://github.com/brycedrennan>`_
 - Cristi Scoarta `@cristi23 <https://github.com/cristi23>`_
+- Nathan `@nbanmp <https://github.com/nbanmp>`_
diff --git a/src/marshmallow/validate.py b/src/marshmallow/validate.py
index 02e09024..fdc51aef 100644
--- a/src/marshmallow/validate.py
+++ b/src/marshmallow/validate.py
@@ -65,7 +65,7 @@ class URL(Validator):
                 r'\[?[A-F0-9]*:[A-F0-9:]+\]?)',  # ...or ipv6
                 r'(?::\d+)?',  # optional port
                 r')?' if relative else r'',  # host is optional, allow for relative URLs
-                r'(?:/?|[/?]\S+)$',
+                r'(?:/?|[/?]\S+)\Z',
             )), re.IGNORECASE)
 
         def __call__(self, relative, require_tld):
@@ -120,18 +120,18 @@ class Email(Validator):
     """
 
     USER_REGEX = re.compile(
-        r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*$"  # dot-atom
+        r"(^[-!#$%&'*+/=?^`{}|~\w]+(\.[-!#$%&'*+/=?^`{}|~\w]+)*\Z"  # dot-atom
         # quoted-string
         r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]'
-        r'|\\[\001-\011\013\014\016-\177])*"$)', re.IGNORECASE | re.UNICODE)
+        r'|\\[\001-\011\013\014\016-\177])*"\Z)', re.IGNORECASE | re.UNICODE)
 
     DOMAIN_REGEX = re.compile(
         # domain
         r'(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+'
-        r'(?:[A-Z]{2,6}|[A-Z0-9-]{2,})$'
+        r'(?:[A-Z]{2,6}|[A-Z0-9-]{2,})\Z'
         # literal form, ipv4 address (SMTP 4.1.3)
         r'|^\[(25[0-5]|2[0-4]\d|[0-1]?\d?\d)'
-        r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]$', re.IGNORECASE | re.UNICODE)
+        r'(\.(25[0-5]|2[0-4]\d|[0-1]?\d?\d)){3}\]\Z', re.IGNORECASE | re.UNICODE)
 
     DOMAIN_WHITELIST = ('localhost',)
 


Test patch:
diff --git a/tests/test_validate.py b/tests/test_validate.py
index cafd1e98..109319b7 100644
--- a/tests/test_validate.py
+++ b/tests/test_validate.py
@@ -41,6 +41,7 @@ def test_url_absolute_valid(valid_url):
     'http:/example.org',
     'foo://example.org',
     '../icons/logo.gif',
+    'https://example.org\n',
     'abc',
     '..',
     '/',
@@ -71,6 +72,7 @@ def test_url_relative_valid(valid_url):
     'suppliers.html',
     '../icons/logo.gif',
     'icons/logo.gif',
+    'http://example.org\n',
     '../.../g',
     '...',
     '\\',
@@ -98,6 +100,7 @@ def test_url_dont_require_tld_valid(valid_url):
 
 @pytest.mark.parametrize('invalid_url', [
     'http//example',
+    'http://example\n',
     'http://.example.org',
     'http:///foo/bar',
     'http:// /foo/bar',
@@ -165,6 +168,8 @@ def test_email_valid(valid_email):
     assert validator(valid_email) == valid_email
 
 @pytest.mark.parametrize('invalid_email', [
+    'niceandsimple\n@example.com',
+    'NiCeAnDsImPlE@eXaMpLe.CoM\n',
     'a"b(c)d,e:f;g<h>i[j\\k]l@example.com',
     'just"not"right@example.com',
     'this is"not\allowed@example.com',