66from enum import Enum
77from pathlib import Path
88from tarfile import TarFile
9- from typing import Generator , Union
9+ from typing import Generator , NamedTuple , Optional , Union
1010from urllib .parse import ParseResult , urljoin , urlparse , urlunparse
1111from zipfile import ZipFile
1212
1313URLLike = Union ["URL" , str , Path ]
1414
1515
16+ class HTTPAuthentication (NamedTuple ):
17+ """HTTP Basic Authentication parameters."""
18+
19+ username : Optional [str ]
20+ password : Optional [str ]
21+
22+ def __repr__ (self ) -> str :
23+ """Return a representation of this HTTPAuthentication.
24+
25+ We hide username and password since both may contain sensitive values.
26+ """
27+ return "HTTPAuthentication(username='***', password='***')"
28+
29+
1630class SupportedArchives (str , Enum ):
1731 """Enumeration of supported archive formats."""
1832
@@ -333,6 +347,21 @@ def parent(self) -> "URL":
333347 """
334348 return URL (self .path .parent )
335349
350+ @property
351+ def authentication (self ) -> HTTPAuthentication :
352+ """Return this URL HTTP Authentication (if any).
353+
354+ >>> URL("/local/path/to/project.zip").authentication
355+ HTTPAuthentication(username='***', password='***')
356+ >>> URL("https://user:pw@gitlab.com").authentication # pragma: allowlist secret
357+ HTTPAuthentication(username='***', password='***')
358+ >>> URL("https://username@gitlab.com").authentication
359+ HTTPAuthentication(username='***', password='***')
360+ """
361+ return HTTPAuthentication (
362+ username = self ._parsed .username , password = self ._parsed .password
363+ )
364+
336365 def __truediv__ (self , other ) -> "URL" :
337366 """Allows concatenating a path to this URL's path.
338367
@@ -354,8 +383,18 @@ def __str__(self) -> str:
354383
355384 >>> str(URL("s3://s3-bucket/path/to/my/profiles.yml"))
356385 's3://s3-bucket/path/to/my/profiles.yml'
386+ >>> str(URL("https://hey:secret@gh.com/hey/repo")) # pragma: allowlist secret
387+ 'https://hey:***@github.com/hey/my-repo'
357388 """
358- return self ._parsed .geturl ()
389+ url_str = self ._parsed .geturl ()
390+
391+ if self ._parsed .password :
392+ url_str = url_str .replace (self ._parsed .password , "***" )
393+
394+ if self ._parsed .username :
395+ url_str = url_str .replace (self ._parsed .username , "***" )
396+
397+ return url_str
359398
360399 def __repr__ (self ) -> str :
361400 """Return a representation of this URL."""
0 commit comments