Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/rfc3986/parseresult.py: 51%
167 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:04 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:04 +0000
1# Copyright (c) 2015 Ian Stapleton Cordasco
2# Licensed under the Apache License, Version 2.0 (the "License");
3# you may not use this file except in compliance with the License.
4# You may obtain a copy of the License at
5#
6# http://www.apache.org/licenses/LICENSE-2.0
7#
8# Unless required by applicable law or agreed to in writing, software
9# distributed under the License is distributed on an "AS IS" BASIS,
10# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
11# implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Module containing the urlparse compatibility logic."""
15from collections import namedtuple
17from . import compat
18from . import exceptions
19from . import misc
20from . import normalizers
21from . import uri
23__all__ = ("ParseResult", "ParseResultBytes")
25PARSED_COMPONENTS = (
26 "scheme",
27 "userinfo",
28 "host",
29 "port",
30 "path",
31 "query",
32 "fragment",
33)
36class ParseResultMixin:
37 def _generate_authority(self, attributes):
38 # I swear I did not align the comparisons below. That's just how they
39 # happened to align based on pep8 and attribute lengths.
40 userinfo, host, port = (
41 attributes[p] for p in ("userinfo", "host", "port")
42 )
43 if self.userinfo != userinfo or self.host != host or self.port != port:
44 if port:
45 port = f"{port}"
46 return normalizers.normalize_authority(
47 (
48 compat.to_str(userinfo, self.encoding),
49 compat.to_str(host, self.encoding),
50 port,
51 )
52 )
53 if isinstance(self.authority, bytes):
54 return self.authority.decode("utf-8")
55 return self.authority
57 def geturl(self):
58 """Shim to match the standard library method."""
59 return self.unsplit()
61 @property
62 def hostname(self):
63 """Shim to match the standard library."""
64 return self.host
66 @property
67 def netloc(self):
68 """Shim to match the standard library."""
69 return self.authority
71 @property
72 def params(self):
73 """Shim to match the standard library."""
74 return self.query
77class ParseResult(
78 namedtuple("ParseResult", PARSED_COMPONENTS), ParseResultMixin
79):
80 """Implementation of urlparse compatibility class.
82 This uses the URIReference logic to handle compatibility with the
83 urlparse.ParseResult class.
84 """
86 slots = ()
88 def __new__(
89 cls,
90 scheme,
91 userinfo,
92 host,
93 port,
94 path,
95 query,
96 fragment,
97 uri_ref,
98 encoding="utf-8",
99 ):
100 """Create a new ParseResult."""
101 parse_result = super().__new__(
102 cls,
103 scheme or None,
104 userinfo or None,
105 host,
106 port or None,
107 path or None,
108 query,
109 fragment,
110 )
111 parse_result.encoding = encoding
112 parse_result.reference = uri_ref
113 return parse_result
115 @classmethod
116 def from_parts(
117 cls,
118 scheme=None,
119 userinfo=None,
120 host=None,
121 port=None,
122 path=None,
123 query=None,
124 fragment=None,
125 encoding="utf-8",
126 ):
127 """Create a ParseResult instance from its parts."""
128 authority = ""
129 if userinfo is not None:
130 authority += userinfo + "@"
131 if host is not None:
132 authority += host
133 if port is not None:
134 authority += f":{port}"
135 uri_ref = uri.URIReference(
136 scheme=scheme,
137 authority=authority,
138 path=path,
139 query=query,
140 fragment=fragment,
141 encoding=encoding,
142 ).normalize()
143 userinfo, host, port = authority_from(uri_ref, strict=True)
144 return cls(
145 scheme=uri_ref.scheme,
146 userinfo=userinfo,
147 host=host,
148 port=port,
149 path=uri_ref.path,
150 query=uri_ref.query,
151 fragment=uri_ref.fragment,
152 uri_ref=uri_ref,
153 encoding=encoding,
154 )
156 @classmethod
157 def from_string(
158 cls, uri_string, encoding="utf-8", strict=True, lazy_normalize=True
159 ):
160 """Parse a URI from the given unicode URI string.
162 :param str uri_string: Unicode URI to be parsed into a reference.
163 :param str encoding: The encoding of the string provided
164 :param bool strict: Parse strictly according to :rfc:`3986` if True.
165 If False, parse similarly to the standard library's urlparse
166 function.
167 :returns: :class:`ParseResult` or subclass thereof
168 """
169 reference = uri.URIReference.from_string(uri_string, encoding)
170 if not lazy_normalize:
171 reference = reference.normalize()
172 userinfo, host, port = authority_from(reference, strict)
174 return cls(
175 scheme=reference.scheme,
176 userinfo=userinfo,
177 host=host,
178 port=port,
179 path=reference.path,
180 query=reference.query,
181 fragment=reference.fragment,
182 uri_ref=reference,
183 encoding=encoding,
184 )
186 @property
187 def authority(self):
188 """Return the normalized authority."""
189 return self.reference.authority
191 def copy_with(
192 self,
193 scheme=misc.UseExisting,
194 userinfo=misc.UseExisting,
195 host=misc.UseExisting,
196 port=misc.UseExisting,
197 path=misc.UseExisting,
198 query=misc.UseExisting,
199 fragment=misc.UseExisting,
200 ):
201 """Create a copy of this instance replacing with specified parts."""
202 attributes = zip(
203 PARSED_COMPONENTS,
204 (scheme, userinfo, host, port, path, query, fragment),
205 )
206 attrs_dict = {}
207 for name, value in attributes:
208 if value is misc.UseExisting:
209 value = getattr(self, name)
210 attrs_dict[name] = value
211 authority = self._generate_authority(attrs_dict)
212 ref = self.reference.copy_with(
213 scheme=attrs_dict["scheme"],
214 authority=authority,
215 path=attrs_dict["path"],
216 query=attrs_dict["query"],
217 fragment=attrs_dict["fragment"],
218 )
219 return ParseResult(uri_ref=ref, encoding=self.encoding, **attrs_dict)
221 def encode(self, encoding=None):
222 """Convert to an instance of ParseResultBytes."""
223 encoding = encoding or self.encoding
224 attrs = dict(
225 zip(
226 PARSED_COMPONENTS,
227 (
228 attr.encode(encoding) if hasattr(attr, "encode") else attr
229 for attr in self
230 ),
231 )
232 )
233 return ParseResultBytes(
234 uri_ref=self.reference, encoding=encoding, **attrs
235 )
237 def unsplit(self, use_idna=False):
238 """Create a URI string from the components.
240 :returns: The parsed URI reconstituted as a string.
241 :rtype: str
242 """
243 parse_result = self
244 if use_idna and self.host:
245 hostbytes = self.host.encode("idna")
246 host = hostbytes.decode(self.encoding)
247 parse_result = self.copy_with(host=host)
248 return parse_result.reference.unsplit()
251class ParseResultBytes(
252 namedtuple("ParseResultBytes", PARSED_COMPONENTS), ParseResultMixin
253):
254 """Compatibility shim for the urlparse.ParseResultBytes object."""
256 def __new__(
257 cls,
258 scheme,
259 userinfo,
260 host,
261 port,
262 path,
263 query,
264 fragment,
265 uri_ref,
266 encoding="utf-8",
267 lazy_normalize=True,
268 ):
269 """Create a new ParseResultBytes instance."""
270 parse_result = super().__new__(
271 cls,
272 scheme or None,
273 userinfo or None,
274 host,
275 port or None,
276 path or None,
277 query or None,
278 fragment or None,
279 )
280 parse_result.encoding = encoding
281 parse_result.reference = uri_ref
282 parse_result.lazy_normalize = lazy_normalize
283 return parse_result
285 @classmethod
286 def from_parts(
287 cls,
288 scheme=None,
289 userinfo=None,
290 host=None,
291 port=None,
292 path=None,
293 query=None,
294 fragment=None,
295 encoding="utf-8",
296 lazy_normalize=True,
297 ):
298 """Create a ParseResult instance from its parts."""
299 authority = ""
300 if userinfo is not None:
301 authority += userinfo + "@"
302 if host is not None:
303 authority += host
304 if port is not None:
305 authority += f":{int(port)}"
306 uri_ref = uri.URIReference(
307 scheme=scheme,
308 authority=authority,
309 path=path,
310 query=query,
311 fragment=fragment,
312 encoding=encoding,
313 )
314 if not lazy_normalize:
315 uri_ref = uri_ref.normalize()
316 to_bytes = compat.to_bytes
317 userinfo, host, port = authority_from(uri_ref, strict=True)
318 return cls(
319 scheme=to_bytes(scheme, encoding),
320 userinfo=to_bytes(userinfo, encoding),
321 host=to_bytes(host, encoding),
322 port=port,
323 path=to_bytes(path, encoding),
324 query=to_bytes(query, encoding),
325 fragment=to_bytes(fragment, encoding),
326 uri_ref=uri_ref,
327 encoding=encoding,
328 lazy_normalize=lazy_normalize,
329 )
331 @classmethod
332 def from_string(
333 cls, uri_string, encoding="utf-8", strict=True, lazy_normalize=True
334 ):
335 """Parse a URI from the given unicode URI string.
337 :param str uri_string: Unicode URI to be parsed into a reference.
338 :param str encoding: The encoding of the string provided
339 :param bool strict: Parse strictly according to :rfc:`3986` if True.
340 If False, parse similarly to the standard library's urlparse
341 function.
342 :returns: :class:`ParseResultBytes` or subclass thereof
343 """
344 reference = uri.URIReference.from_string(uri_string, encoding)
345 if not lazy_normalize:
346 reference = reference.normalize()
347 userinfo, host, port = authority_from(reference, strict)
349 to_bytes = compat.to_bytes
350 return cls(
351 scheme=to_bytes(reference.scheme, encoding),
352 userinfo=to_bytes(userinfo, encoding),
353 host=to_bytes(host, encoding),
354 port=port,
355 path=to_bytes(reference.path, encoding),
356 query=to_bytes(reference.query, encoding),
357 fragment=to_bytes(reference.fragment, encoding),
358 uri_ref=reference,
359 encoding=encoding,
360 lazy_normalize=lazy_normalize,
361 )
363 @property
364 def authority(self):
365 """Return the normalized authority."""
366 return self.reference.authority.encode(self.encoding)
368 def copy_with(
369 self,
370 scheme=misc.UseExisting,
371 userinfo=misc.UseExisting,
372 host=misc.UseExisting,
373 port=misc.UseExisting,
374 path=misc.UseExisting,
375 query=misc.UseExisting,
376 fragment=misc.UseExisting,
377 lazy_normalize=True,
378 ):
379 """Create a copy of this instance replacing with specified parts."""
380 attributes = zip(
381 PARSED_COMPONENTS,
382 (scheme, userinfo, host, port, path, query, fragment),
383 )
384 attrs_dict = {}
385 for name, value in attributes:
386 if value is misc.UseExisting:
387 value = getattr(self, name)
388 if not isinstance(value, bytes) and hasattr(value, "encode"):
389 value = value.encode(self.encoding)
390 attrs_dict[name] = value
391 authority = self._generate_authority(attrs_dict)
392 to_str = compat.to_str
393 ref = self.reference.copy_with(
394 scheme=to_str(attrs_dict["scheme"], self.encoding),
395 authority=to_str(authority, self.encoding),
396 path=to_str(attrs_dict["path"], self.encoding),
397 query=to_str(attrs_dict["query"], self.encoding),
398 fragment=to_str(attrs_dict["fragment"], self.encoding),
399 )
400 if not lazy_normalize:
401 ref = ref.normalize()
402 return ParseResultBytes(
403 uri_ref=ref,
404 encoding=self.encoding,
405 lazy_normalize=lazy_normalize,
406 **attrs_dict,
407 )
409 def unsplit(self, use_idna=False):
410 """Create a URI bytes object from the components.
412 :returns: The parsed URI reconstituted as a string.
413 :rtype: bytes
414 """
415 parse_result = self
416 if use_idna and self.host:
417 # self.host is bytes, to encode to idna, we need to decode it
418 # first
419 host = self.host.decode(self.encoding)
420 hostbytes = host.encode("idna")
421 parse_result = self.copy_with(host=hostbytes)
422 if self.lazy_normalize:
423 parse_result = parse_result.copy_with(lazy_normalize=False)
424 uri = parse_result.reference.unsplit()
425 return uri.encode(self.encoding)
428def split_authority(authority):
429 # Initialize our expected return values
430 userinfo = host = port = None
431 # Initialize an extra var we may need to use
432 extra_host = None
433 # Set-up rest in case there is no userinfo portion
434 rest = authority
436 if "@" in authority:
437 userinfo, rest = authority.rsplit("@", 1)
439 # Handle IPv6 host addresses
440 if rest.startswith("["):
441 host, rest = rest.split("]", 1)
442 host += "]"
444 if ":" in rest:
445 extra_host, port = rest.split(":", 1)
446 elif not host and rest:
447 host = rest
449 if extra_host and not host:
450 host = extra_host
452 return userinfo, host, port
455def authority_from(reference, strict):
456 try:
457 subauthority = reference.authority_info()
458 except exceptions.InvalidAuthority:
459 if strict:
460 raise
461 userinfo, host, port = split_authority(reference.authority)
462 else:
463 # Thanks to Richard Barrell for this idea:
464 # https://twitter.com/0x2ba22e11/status/617338811975139328
465 userinfo, host, port = (
466 subauthority.get(p) for p in ("userinfo", "host", "port")
467 )
469 if port:
470 if port.isascii() and port.isdigit():
471 port = int(port)
472 else:
473 raise exceptions.InvalidPort(port)
474 return userinfo, host, port