Repository: joke2k/django-environ
Pull request #461 (2023-03-30 15:25:58)

Added support for parsing connection strings with special characters in the password and handling invalid IPv6 URLs.
Added try-except block to handle ValueError when parsing invalid IPv6 URLs.
Added support for parsing connection strings with special characters in the password.
Added tests to verify that connection strings with special characters in the password are correctly parsed into their individual components.
Added tests to verify that an invalid IPv6 password causes the connection to fail.
All test pass in my local Ubuntu 20.04 with Python 3.10

coveralls: 
[![Coverage Status](https://coveralls.io/builds/58428115/badge)](https://coveralls.io/builds/58428115)

Coverage: 91.37% (+0.1%) from 91.255% when pulling **2b81d0ac111353a2fb78f78e255eb076572d2386 on MayGrass:main** into **4294fb56d4a13850527ede0089573584b641c624 on joke2k:develop**.


Patch:
diff --git a/environ/environ.py b/environ/environ.py
index 67b7f7d..cbbf107 100644
--- a/environ/environ.py
+++ b/environ/environ.py
@@ -21,6 +21,7 @@ import warnings
 from urllib.parse import (
     parse_qs,
     ParseResult,
+    quote,
     unquote,
     unquote_plus,
     urlparse,
@@ -65,6 +66,10 @@ def _cast_urlstr(v):
     return unquote(v) if isinstance(v, str) else v
 
 
+def _urlparse_quote(url):
+    return urlparse(quote(url, safe=':/?&=@'))
+
+
 class NoValue:
     """Represent of no value object."""
 
@@ -474,7 +479,9 @@ class Env:
         return value
 
     @classmethod
+    # pylint: disable=too-many-statements
     def db_url_config(cls, url, engine=None):
+        # pylint: enable-msg=too-many-statements
         """Parse an arbitrary database URL.
 
         Supports the following URL schemas:
@@ -509,10 +516,17 @@ class Env:
                     'NAME': ':memory:'
                 }
                 # note: no other settings are required for sqlite
-            url = urlparse(url)
+            try:
+                url = urlparse(url)
+            # handle Invalid IPv6 URL
+            except ValueError:
+                url = _urlparse_quote(url)
 
         config = {}
 
+        # handle unexpected URL schemes with special characters
+        if not url.path:
+            url = _urlparse_quote(urlunparse(url))
         # Remove query strings.
         path = url.path[1:]
         path = unquote_plus(path.split('?', 2)[0])


Test patch:
diff --git a/tests/test_db.py b/tests/test_db.py
index fe50284..8101a4a 100644
--- a/tests/test_db.py
+++ b/tests/test_db.py
@@ -133,6 +133,22 @@ from environ.compat import DJANGO_POSTGRES
          'enigma',
          'secret',
          12345),
+         # mysql://user:password@host:port/dbname
+        ('mysql://enigma:><{~!@#$%^&*}[]@example.com:1234/dbname',
+         'django.db.backends.mysql',
+         'dbname',
+         'example.com',
+         'enigma',
+         '><{~!@#$%^&*}[]',
+         1234),
+        # mysql://user:password@host/dbname
+        ('mysql://enigma:]password]@example.com/dbname',
+         'django.db.backends.mysql',
+         'dbname',
+         'example.com',
+         'enigma',
+         ']password]',
+         ''),
     ],
     ids=[
         'postgres',
@@ -149,6 +165,8 @@ from environ.compat import DJANGO_POSTGRES
         'ldap',
         'mssql',
         'mssql_port',
+        'mysql_password_special_chars',
+        'mysql_invalid_ipv6_password',
     ],
 )
 def test_db_parsing(url, engine, name, host, user, passwd, port):