--- /dev/null
+.. change::
+ :tags: bug, engine
+ :tickets: 10662
+
+ Fixed URL-encoding of the username and password components of
+ :class:`.engine.URL` objects when converting them to string using the
+ :meth:`_engine.URL.render_as_string` method, by using Python standard
+ library ``urllib.parse.quote_plus``, rather than the legacy home-grown
+ routine from many years ago. Pull request courtesy of Xavier NUNN.
s += "@"
if self.host is not None:
if ":" in self.host:
- s += "[%s]" % self.host
+ s += f"[{self.host}]"
else:
s += self.host
if self.port is not None:
keys = list(self.query)
keys.sort()
s += "?" + "&".join(
- "%s=%s" % (quote_plus(k), quote_plus(element))
+ f"{_sqla_url_quote(k)}={_sqla_url_quote(element)}"
for k in keys
for element in util.to_list(self.query[k])
)
)
-def _sqla_url_quote(text: str) -> str:
- return re.sub(r"[:@/]", lambda m: "%%%X" % ord(m.group(0)), text)
+_sqla_url_quote = quote_plus
_sqla_url_unquote = unquote
"dbtype://username:password@hostspec/test database with@atsign",
"dbtype://username:password@hostspec?query=but_no_db",
"dbtype://username:password@hostspec:450?query=but_no_db",
+ "dbtype://user%25%26%7C:pass%25%26%7C@hostspec:499?query=but_no_db",
)
def test_rfc1738(self, text):
u = url.make_url(text)
assert u.drivername in ("dbtype", "dbtype+apitype")
- assert u.username in ("username", None)
- assert u.password in ("password", "apples/oranges", None)
+ assert u.username in ("username", "user%&|", None)
+ assert u.password in (
+ "password",
+ "apples/oranges",
+ "pass%&|",
+ None,
+ )
assert u.host in (
"hostspec",
"127.0.0.1",
eq_(str(u), "dbtype://user:***@host/dbname")
eq_(
u.render_as_string(hide_password=False),
- "dbtype://user:pass word + other%3Awords@host/dbname",
+ "dbtype://user:pass+word+%2B+other%3Awords@host/dbname",
)
u = url.make_url(