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

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 

16 

17from . import compat 

18from . import exceptions 

19from . import misc 

20from . import normalizers 

21from . import uri 

22 

23__all__ = ("ParseResult", "ParseResultBytes") 

24 

25PARSED_COMPONENTS = ( 

26 "scheme", 

27 "userinfo", 

28 "host", 

29 "port", 

30 "path", 

31 "query", 

32 "fragment", 

33) 

34 

35 

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 

56 

57 def geturl(self): 

58 """Shim to match the standard library method.""" 

59 return self.unsplit() 

60 

61 @property 

62 def hostname(self): 

63 """Shim to match the standard library.""" 

64 return self.host 

65 

66 @property 

67 def netloc(self): 

68 """Shim to match the standard library.""" 

69 return self.authority 

70 

71 @property 

72 def params(self): 

73 """Shim to match the standard library.""" 

74 return self.query 

75 

76 

77class ParseResult( 

78 namedtuple("ParseResult", PARSED_COMPONENTS), ParseResultMixin 

79): 

80 """Implementation of urlparse compatibility class. 

81 

82 This uses the URIReference logic to handle compatibility with the 

83 urlparse.ParseResult class. 

84 """ 

85 

86 slots = () 

87 

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 

114 

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 ) 

155 

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. 

161 

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) 

173 

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 ) 

185 

186 @property 

187 def authority(self): 

188 """Return the normalized authority.""" 

189 return self.reference.authority 

190 

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) 

220 

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 ) 

236 

237 def unsplit(self, use_idna=False): 

238 """Create a URI string from the components. 

239 

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() 

249 

250 

251class ParseResultBytes( 

252 namedtuple("ParseResultBytes", PARSED_COMPONENTS), ParseResultMixin 

253): 

254 """Compatibility shim for the urlparse.ParseResultBytes object.""" 

255 

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 

284 

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 ) 

330 

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. 

336 

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) 

348 

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 ) 

362 

363 @property 

364 def authority(self): 

365 """Return the normalized authority.""" 

366 return self.reference.authority.encode(self.encoding) 

367 

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 ) 

408 

409 def unsplit(self, use_idna=False): 

410 """Create a URI bytes object from the components. 

411 

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) 

426 

427 

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 

435 

436 if "@" in authority: 

437 userinfo, rest = authority.rsplit("@", 1) 

438 

439 # Handle IPv6 host addresses 

440 if rest.startswith("["): 

441 host, rest = rest.split("]", 1) 

442 host += "]" 

443 

444 if ":" in rest: 

445 extra_host, port = rest.split(":", 1) 

446 elif not host and rest: 

447 host = rest 

448 

449 if extra_host and not host: 

450 host = extra_host 

451 

452 return userinfo, host, port 

453 

454 

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 ) 

468 

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