Repository: pydantic/pydantic

Issue:
url_regex doesn't correctly match all allowed characters in a URI
# Bug

```python
from pydantic.networks import url_regex
m=url_regex.match("postgresql+psycopg2://postgres:postgres@localhost:5432/hatch")
parts = m.groupdict()
parts
```
produces the following incorrect matching:

```python
{'scheme': None,
 'user': 'postgresql+psycopg2',
 'password': '//postgres:postgres',
 'ipv4': None,
 'ipv6': None,
 'domain': 'localhost',
 'port': '5432',
 'path': '/hatch',
 'query': None,
 'fragment': None}
```

This is with Python 3.7.5 on OSX, pydantic version 1.2





Comments:
Note that according to Appendix A of [rfc2396](http://www.ietf.org/rfc/rfc2396.txt) a URI scheme is defined as:

```
scheme        = alpha *( alpha | digit | "+" | "-" | "." )
```

(search the document for "Collected BNF for URI").

Edit: [RFC3986](https://tools.ietf.org/html/rfc3986) is probably more up to date. [Appendix A](https://tools.ietf.org/html/rfc3986#appendix-A) of that document describes scheme as:

```
scheme        = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
```

In any case the `+`, `-` and `.` characters are all valid in the scheme part.
Thanks for reporting, please could you submit a PR for this?
I'll look into it. Will need to get work clearance etc tho, so it might take a little while.
Thank you, let me know if it's too much work to be worth it and/or urgent, if so I can do it.
It would require legal review, so if you didn't mind doing it, that would be very much appreciated.
wow :sleeping:, there are advantages to working for a startup.

I'll do it.

Patch:
diff --git a/pydantic/networks.py b/pydantic/networks.py
--- a/pydantic/networks.py
+++ b/pydantic/networks.py
@@ -53,7 +53,7 @@ def url_regex() -> Pattern[str]:
     global _url_regex_cache
     if _url_regex_cache is None:
         _url_regex_cache = re.compile(
-            r'(?:(?P<scheme>[a-z0-9]+?)://)?'  # scheme
+            r'(?:(?P<scheme>[a-z][a-z0-9+\-.]+)://)?'  # scheme https://tools.ietf.org/html/rfc3986#appendix-A
             r'(?:(?P<user>[^\s:]+)(?::(?P<password>\S*))?@)?'  # user info
             r'(?:'
             r'(?P<ipv4>(?:\d{1,3}\.){3}\d{1,3})|'  # ipv4


Test patch:
diff --git a/tests/test_networks.py b/tests/test_networks.py
--- a/tests/test_networks.py
+++ b/tests/test_networks.py
@@ -18,6 +18,10 @@
         'https://example.org/whatever/next/',
         'postgres://user:pass@localhost:5432/app',
         'postgres://just-user@localhost:5432/app',
+        'postgresql+psycopg2://postgres:postgres@localhost:5432/hatch',
+        'foo-bar://example.org',
+        'foo.bar://example.org',
+        'foo0bar://example.org',
         'https://example.org',
         'http://localhost',
         'http://localhost/',
@@ -77,6 +81,8 @@ class Model(BaseModel):
         ('abc', 'value_error.url.scheme', 'invalid or missing URL scheme', None),
         ('..', 'value_error.url.scheme', 'invalid or missing URL scheme', None),
         ('/', 'value_error.url.scheme', 'invalid or missing URL scheme', None),
+        ('+http://example.com/', 'value_error.url.scheme', 'invalid or missing URL scheme', None),
+        ('ht*tp://example.com/', 'value_error.url.scheme', 'invalid or missing URL scheme', None),
         (' ', 'value_error.any_str.min_length', 'ensure this value has at least 1 characters', {'limit_value': 1}),
         ('', 'value_error.any_str.min_length', 'ensure this value has at least 1 characters', {'limit_value': 1}),
         (None, 'type_error.none.not_allowed', 'none is not an allowed value', None),