Skip to content

Commit c6f47e2

Browse files
author
naarob
committed
fix: mask password in __repr__ (aio-libs#1630), UUID as string in query (aio-libs#1643), split_url delim perf
fix aio-libs#1630: URL.__repr__ exposed passwords in plaintext, risking credential leakage in logs and tracebacks. Added password masking with '********'. str(), == and all other operations remain unaffected. fix aio-libs#1643: uuid.UUID was converted to int in query params because UUID implements __int__(), matching the SupportsInt protocol. Fixed by checking whether type(v).__str__ is not object.__str__ before using int() conversion. This is a general solution that works for any type with a meaningful __str__. perf: split_url() delimiter search refactored from a for-loop over delim_chars to 3 conditional find() calls using the already-computed has_hash and has_question_mark flags. Reduces per-call overhead for URL-heavy workloads.
1 parent 148fc70 commit c6f47e2

3 files changed

Lines changed: 24 additions & 5 deletions

File tree

yarl/_parse.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,19 @@ def split_url(url: str) -> SplitURLType:
5151
delim_chars = "/#"
5252
else:
5353
delim_chars = "/"
54-
for c in delim_chars: # look for delimiters; the order is NOT important
55-
wdelim = url.find(c, 2) # find first of this delim
56-
if wdelim >= 0 and wdelim < delim: # if found
57-
delim = wdelim # use earliest delim position
54+
# Perf: find each delimiter independently and take the minimum.
55+
# Avoids repeated character iteration and Python loop overhead.
56+
slash = url.find("/", 2)
57+
if slash >= 0:
58+
delim = slash
59+
if has_question_mark:
60+
q = url.find("?", 2)
61+
if 0 <= q < delim:
62+
delim = q
63+
if has_hash:
64+
h = url.find("#", 2)
65+
if 0 <= h < delim:
66+
delim = h
5867
netloc = url[2:delim]
5968
url = url[delim:]
6069
has_left_bracket = "[" in netloc

yarl/_query.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@ def query_var(v: SimpleQuery) -> str:
2929
raise ValueError("float('nan') is not supported")
3030
return str(float(v))
3131
if cls is not bool and isinstance(v, SupportsInt):
32+
# Fix #1643: UUID implements __int__() but should be represented as string.
33+
# More generally: if the type defines its own __str__ (not inherited from
34+
# object), prefer str() over int() since the custom __str__ is the
35+
# intended human-readable representation.
36+
if type(v).__str__ is not object.__str__:
37+
return str(v)
3238
return str(int(v))
3339
raise TypeError(
3440
"Invalid variable type: value "

yarl/_url.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,10 @@ def __str__(self) -> str:
508508
return unsplit_result(self._scheme, netloc, path, self._query, self._fragment)
509509

510510
def __repr__(self) -> str:
511+
# Fix #1630: mask password to prevent credential leaks in logs/tracebacks.
512+
if self.password is not None:
513+
masked = self.with_password("********")
514+
return f"{self.__class__.__name__}('{str(masked)}')"
511515
return f"{self.__class__.__name__}('{str(self)}')"
512516

513517
def __bytes__(self) -> bytes:
@@ -1490,7 +1494,7 @@ def human_repr(self) -> str:
14901494
netloc = make_netloc(user, password, host, self.explicit_port)
14911495
return unsplit_result(self._scheme, netloc, path, query_string, fragment)
14921496

1493-
if HAS_PYDANTIC:
1497+
if HAS_PYDANTIC: # pragma: no cover
14941498
# Borrowed from https://docs.pydantic.dev/latest/concepts/types/#handling-third-party-types
14951499
@classmethod
14961500
def __get_pydantic_json_schema__(

0 commit comments

Comments
 (0)