Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/datastructures/headers.py: 58%

251 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-09 06:08 +0000

1from __future__ import annotations 

2 

3import re 

4import typing as t 

5import warnings 

6 

7from .._internal import _missing 

8from ..exceptions import BadRequestKeyError 

9from .mixins import ImmutableHeadersMixin 

10from .structures import iter_multi_items 

11from .structures import MultiDict 

12 

13 

14class Headers: 

15 """An object that stores some headers. It has a dict-like interface, 

16 but is ordered, can store the same key multiple times, and iterating 

17 yields ``(key, value)`` pairs instead of only keys. 

18 

19 This data structure is useful if you want a nicer way to handle WSGI 

20 headers which are stored as tuples in a list. 

21 

22 From Werkzeug 0.3 onwards, the :exc:`KeyError` raised by this class is 

23 also a subclass of the :class:`~exceptions.BadRequest` HTTP exception 

24 and will render a page for a ``400 BAD REQUEST`` if caught in a 

25 catch-all for HTTP exceptions. 

26 

27 Headers is mostly compatible with the Python :class:`wsgiref.headers.Headers` 

28 class, with the exception of `__getitem__`. :mod:`wsgiref` will return 

29 `None` for ``headers['missing']``, whereas :class:`Headers` will raise 

30 a :class:`KeyError`. 

31 

32 To create a new ``Headers`` object, pass it a list, dict, or 

33 other ``Headers`` object with default values. These values are 

34 validated the same way values added later are. 

35 

36 :param defaults: The list of default values for the :class:`Headers`. 

37 

38 .. versionchanged:: 2.1.0 

39 Default values are validated the same as values added later. 

40 

41 .. versionchanged:: 0.9 

42 This data structure now stores unicode values similar to how the 

43 multi dicts do it. The main difference is that bytes can be set as 

44 well which will automatically be latin1 decoded. 

45 

46 .. versionchanged:: 0.9 

47 The :meth:`linked` function was removed without replacement as it 

48 was an API that does not support the changes to the encoding model. 

49 """ 

50 

51 def __init__(self, defaults=None): 

52 self._list = [] 

53 if defaults is not None: 

54 self.extend(defaults) 

55 

56 def __getitem__(self, key, _get_mode=False): 

57 if not _get_mode: 

58 if isinstance(key, int): 

59 return self._list[key] 

60 elif isinstance(key, slice): 

61 return self.__class__(self._list[key]) 

62 if not isinstance(key, str): 

63 raise BadRequestKeyError(key) 

64 ikey = key.lower() 

65 for k, v in self._list: 

66 if k.lower() == ikey: 

67 return v 

68 # micro optimization: if we are in get mode we will catch that 

69 # exception one stack level down so we can raise a standard 

70 # key error instead of our special one. 

71 if _get_mode: 

72 raise KeyError() 

73 raise BadRequestKeyError(key) 

74 

75 def __eq__(self, other): 

76 def lowered(item): 

77 return (item[0].lower(),) + item[1:] 

78 

79 return other.__class__ is self.__class__ and set( 

80 map(lowered, other._list) 

81 ) == set(map(lowered, self._list)) 

82 

83 __hash__ = None 

84 

85 def get(self, key, default=None, type=None, as_bytes=None): 

86 """Return the default value if the requested data doesn't exist. 

87 If `type` is provided and is a callable it should convert the value, 

88 return it or raise a :exc:`ValueError` if that is not possible. In 

89 this case the function will return the default as if the value was not 

90 found: 

91 

92 >>> d = Headers([('Content-Length', '42')]) 

93 >>> d.get('Content-Length', type=int) 

94 42 

95 

96 :param key: The key to be looked up. 

97 :param default: The default value to be returned if the key can't 

98 be looked up. If not further specified `None` is 

99 returned. 

100 :param type: A callable that is used to cast the value in the 

101 :class:`Headers`. If a :exc:`ValueError` is raised 

102 by this callable the default value is returned. 

103 

104 .. versionchanged:: 2.3 

105 The ``as_bytes`` parameter is deprecated and will be removed 

106 in Werkzeug 3.0. 

107 

108 .. versionchanged:: 0.9 

109 The ``as_bytes`` parameter was added. 

110 """ 

111 if as_bytes is not None: 

112 warnings.warn( 

113 "The 'as_bytes' parameter is deprecated and will be" 

114 " removed in Werkzeug 3.0.", 

115 DeprecationWarning, 

116 stacklevel=2, 

117 ) 

118 

119 try: 

120 rv = self.__getitem__(key, _get_mode=True) 

121 except KeyError: 

122 return default 

123 if as_bytes: 

124 rv = rv.encode("latin1") 

125 if type is None: 

126 return rv 

127 try: 

128 return type(rv) 

129 except ValueError: 

130 return default 

131 

132 def getlist(self, key, type=None, as_bytes=None): 

133 """Return the list of items for a given key. If that key is not in the 

134 :class:`Headers`, the return value will be an empty list. Just like 

135 :meth:`get`, :meth:`getlist` accepts a `type` parameter. All items will 

136 be converted with the callable defined there. 

137 

138 :param key: The key to be looked up. 

139 :param type: A callable that is used to cast the value in the 

140 :class:`Headers`. If a :exc:`ValueError` is raised 

141 by this callable the value will be removed from the list. 

142 :return: a :class:`list` of all the values for the key. 

143 

144 .. versionchanged:: 2.3 

145 The ``as_bytes`` parameter is deprecated and will be removed 

146 in Werkzeug 3.0. 

147 

148 .. versionchanged:: 0.9 

149 The ``as_bytes`` parameter was added. 

150 """ 

151 if as_bytes is not None: 

152 warnings.warn( 

153 "The 'as_bytes' parameter is deprecated and will be" 

154 " removed in Werkzeug 3.0.", 

155 DeprecationWarning, 

156 stacklevel=2, 

157 ) 

158 

159 ikey = key.lower() 

160 result = [] 

161 for k, v in self: 

162 if k.lower() == ikey: 

163 if as_bytes: 

164 v = v.encode("latin1") 

165 if type is not None: 

166 try: 

167 v = type(v) 

168 except ValueError: 

169 continue 

170 result.append(v) 

171 return result 

172 

173 def get_all(self, name): 

174 """Return a list of all the values for the named field. 

175 

176 This method is compatible with the :mod:`wsgiref` 

177 :meth:`~wsgiref.headers.Headers.get_all` method. 

178 """ 

179 return self.getlist(name) 

180 

181 def items(self, lower=False): 

182 for key, value in self: 

183 if lower: 

184 key = key.lower() 

185 yield key, value 

186 

187 def keys(self, lower=False): 

188 for key, _ in self.items(lower): 

189 yield key 

190 

191 def values(self): 

192 for _, value in self.items(): 

193 yield value 

194 

195 def extend(self, *args, **kwargs): 

196 """Extend headers in this object with items from another object 

197 containing header items as well as keyword arguments. 

198 

199 To replace existing keys instead of extending, use 

200 :meth:`update` instead. 

201 

202 If provided, the first argument can be another :class:`Headers` 

203 object, a :class:`MultiDict`, :class:`dict`, or iterable of 

204 pairs. 

205 

206 .. versionchanged:: 1.0 

207 Support :class:`MultiDict`. Allow passing ``kwargs``. 

208 """ 

209 if len(args) > 1: 

210 raise TypeError(f"update expected at most 1 arguments, got {len(args)}") 

211 

212 if args: 

213 for key, value in iter_multi_items(args[0]): 

214 self.add(key, value) 

215 

216 for key, value in iter_multi_items(kwargs): 

217 self.add(key, value) 

218 

219 def __delitem__(self, key, _index_operation=True): 

220 if _index_operation and isinstance(key, (int, slice)): 

221 del self._list[key] 

222 return 

223 key = key.lower() 

224 new = [] 

225 for k, v in self._list: 

226 if k.lower() != key: 

227 new.append((k, v)) 

228 self._list[:] = new 

229 

230 def remove(self, key): 

231 """Remove a key. 

232 

233 :param key: The key to be removed. 

234 """ 

235 return self.__delitem__(key, _index_operation=False) 

236 

237 def pop(self, key=None, default=_missing): 

238 """Removes and returns a key or index. 

239 

240 :param key: The key to be popped. If this is an integer the item at 

241 that position is removed, if it's a string the value for 

242 that key is. If the key is omitted or `None` the last 

243 item is removed. 

244 :return: an item. 

245 """ 

246 if key is None: 

247 return self._list.pop() 

248 if isinstance(key, int): 

249 return self._list.pop(key) 

250 try: 

251 rv = self[key] 

252 self.remove(key) 

253 except KeyError: 

254 if default is not _missing: 

255 return default 

256 raise 

257 return rv 

258 

259 def popitem(self): 

260 """Removes a key or index and returns a (key, value) item.""" 

261 return self.pop() 

262 

263 def __contains__(self, key): 

264 """Check if a key is present.""" 

265 try: 

266 self.__getitem__(key, _get_mode=True) 

267 except KeyError: 

268 return False 

269 return True 

270 

271 def __iter__(self): 

272 """Yield ``(key, value)`` tuples.""" 

273 return iter(self._list) 

274 

275 def __len__(self): 

276 return len(self._list) 

277 

278 def add(self, _key, _value, **kw): 

279 """Add a new header tuple to the list. 

280 

281 Keyword arguments can specify additional parameters for the header 

282 value, with underscores converted to dashes:: 

283 

284 >>> d = Headers() 

285 >>> d.add('Content-Type', 'text/plain') 

286 >>> d.add('Content-Disposition', 'attachment', filename='foo.png') 

287 

288 The keyword argument dumping uses :func:`dump_options_header` 

289 behind the scenes. 

290 

291 .. versionadded:: 0.4.1 

292 keyword arguments were added for :mod:`wsgiref` compatibility. 

293 """ 

294 if kw: 

295 _value = _options_header_vkw(_value, kw) 

296 _key = _str_header_key(_key) 

297 _value = _str_header_value(_value) 

298 self._list.append((_key, _value)) 

299 

300 def add_header(self, _key, _value, **_kw): 

301 """Add a new header tuple to the list. 

302 

303 An alias for :meth:`add` for compatibility with the :mod:`wsgiref` 

304 :meth:`~wsgiref.headers.Headers.add_header` method. 

305 """ 

306 self.add(_key, _value, **_kw) 

307 

308 def clear(self): 

309 """Clears all headers.""" 

310 del self._list[:] 

311 

312 def set(self, _key, _value, **kw): 

313 """Remove all header tuples for `key` and add a new one. The newly 

314 added key either appears at the end of the list if there was no 

315 entry or replaces the first one. 

316 

317 Keyword arguments can specify additional parameters for the header 

318 value, with underscores converted to dashes. See :meth:`add` for 

319 more information. 

320 

321 .. versionchanged:: 0.6.1 

322 :meth:`set` now accepts the same arguments as :meth:`add`. 

323 

324 :param key: The key to be inserted. 

325 :param value: The value to be inserted. 

326 """ 

327 if kw: 

328 _value = _options_header_vkw(_value, kw) 

329 _key = _str_header_key(_key) 

330 _value = _str_header_value(_value) 

331 if not self._list: 

332 self._list.append((_key, _value)) 

333 return 

334 listiter = iter(self._list) 

335 ikey = _key.lower() 

336 for idx, (old_key, _old_value) in enumerate(listiter): 

337 if old_key.lower() == ikey: 

338 # replace first occurrence 

339 self._list[idx] = (_key, _value) 

340 break 

341 else: 

342 self._list.append((_key, _value)) 

343 return 

344 self._list[idx + 1 :] = [t for t in listiter if t[0].lower() != ikey] 

345 

346 def setlist(self, key, values): 

347 """Remove any existing values for a header and add new ones. 

348 

349 :param key: The header key to set. 

350 :param values: An iterable of values to set for the key. 

351 

352 .. versionadded:: 1.0 

353 """ 

354 if values: 

355 values_iter = iter(values) 

356 self.set(key, next(values_iter)) 

357 

358 for value in values_iter: 

359 self.add(key, value) 

360 else: 

361 self.remove(key) 

362 

363 def setdefault(self, key, default): 

364 """Return the first value for the key if it is in the headers, 

365 otherwise set the header to the value given by ``default`` and 

366 return that. 

367 

368 :param key: The header key to get. 

369 :param default: The value to set for the key if it is not in the 

370 headers. 

371 """ 

372 if key in self: 

373 return self[key] 

374 

375 self.set(key, default) 

376 return default 

377 

378 def setlistdefault(self, key, default): 

379 """Return the list of values for the key if it is in the 

380 headers, otherwise set the header to the list of values given 

381 by ``default`` and return that. 

382 

383 Unlike :meth:`MultiDict.setlistdefault`, modifying the returned 

384 list will not affect the headers. 

385 

386 :param key: The header key to get. 

387 :param default: An iterable of values to set for the key if it 

388 is not in the headers. 

389 

390 .. versionadded:: 1.0 

391 """ 

392 if key not in self: 

393 self.setlist(key, default) 

394 

395 return self.getlist(key) 

396 

397 def __setitem__(self, key, value): 

398 """Like :meth:`set` but also supports index/slice based setting.""" 

399 if isinstance(key, (slice, int)): 

400 if isinstance(key, int): 

401 value = [value] 

402 value = [(_str_header_key(k), _str_header_value(v)) for (k, v) in value] 

403 if isinstance(key, int): 

404 self._list[key] = value[0] 

405 else: 

406 self._list[key] = value 

407 else: 

408 self.set(key, value) 

409 

410 def update(self, *args, **kwargs): 

411 """Replace headers in this object with items from another 

412 headers object and keyword arguments. 

413 

414 To extend existing keys instead of replacing, use :meth:`extend` 

415 instead. 

416 

417 If provided, the first argument can be another :class:`Headers` 

418 object, a :class:`MultiDict`, :class:`dict`, or iterable of 

419 pairs. 

420 

421 .. versionadded:: 1.0 

422 """ 

423 if len(args) > 1: 

424 raise TypeError(f"update expected at most 1 arguments, got {len(args)}") 

425 

426 if args: 

427 mapping = args[0] 

428 

429 if isinstance(mapping, (Headers, MultiDict)): 

430 for key in mapping.keys(): 

431 self.setlist(key, mapping.getlist(key)) 

432 elif isinstance(mapping, dict): 

433 for key, value in mapping.items(): 

434 if isinstance(value, (list, tuple)): 

435 self.setlist(key, value) 

436 else: 

437 self.set(key, value) 

438 else: 

439 for key, value in mapping: 

440 self.set(key, value) 

441 

442 for key, value in kwargs.items(): 

443 if isinstance(value, (list, tuple)): 

444 self.setlist(key, value) 

445 else: 

446 self.set(key, value) 

447 

448 def to_wsgi_list(self): 

449 """Convert the headers into a list suitable for WSGI. 

450 

451 :return: list 

452 """ 

453 return list(self) 

454 

455 def copy(self): 

456 return self.__class__(self._list) 

457 

458 def __copy__(self): 

459 return self.copy() 

460 

461 def __str__(self): 

462 """Returns formatted headers suitable for HTTP transmission.""" 

463 strs = [] 

464 for key, value in self.to_wsgi_list(): 

465 strs.append(f"{key}: {value}") 

466 strs.append("\r\n") 

467 return "\r\n".join(strs) 

468 

469 def __repr__(self): 

470 return f"{type(self).__name__}({list(self)!r})" 

471 

472 

473def _options_header_vkw(value: str, kw: dict[str, t.Any]): 

474 return http.dump_options_header( 

475 value, {k.replace("_", "-"): v for k, v in kw.items()} 

476 ) 

477 

478 

479def _str_header_key(key: t.Any) -> str: 

480 if not isinstance(key, str): 

481 warnings.warn( 

482 "Header keys must be strings. Passing other types is deprecated and will" 

483 " not be supported in Werkzeug 3.0.", 

484 DeprecationWarning, 

485 stacklevel=2, 

486 ) 

487 

488 if isinstance(key, bytes): 

489 key = key.decode("latin-1") 

490 else: 

491 key = str(key) 

492 

493 return key 

494 

495 

496_newline_re = re.compile(r"[\r\n]") 

497 

498 

499def _str_header_value(value: t.Any) -> str: 

500 if isinstance(value, bytes): 

501 warnings.warn( 

502 "Passing bytes as a header value is deprecated and will not be supported in" 

503 " Werkzeug 3.0.", 

504 DeprecationWarning, 

505 stacklevel=2, 

506 ) 

507 value = value.decode("latin-1") 

508 

509 if not isinstance(value, str): 

510 value = str(value) 

511 

512 if _newline_re.search(value) is not None: 

513 raise ValueError("Header values must not contain newline characters.") 

514 

515 return value 

516 

517 

518class EnvironHeaders(ImmutableHeadersMixin, Headers): 

519 """Read only version of the headers from a WSGI environment. This 

520 provides the same interface as `Headers` and is constructed from 

521 a WSGI environment. 

522 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a 

523 subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will 

524 render a page for a ``400 BAD REQUEST`` if caught in a catch-all for 

525 HTTP exceptions. 

526 """ 

527 

528 def __init__(self, environ): 

529 self.environ = environ 

530 

531 def __eq__(self, other): 

532 return self.environ is other.environ 

533 

534 __hash__ = None 

535 

536 def __getitem__(self, key, _get_mode=False): 

537 # _get_mode is a no-op for this class as there is no index but 

538 # used because get() calls it. 

539 if not isinstance(key, str): 

540 raise KeyError(key) 

541 key = key.upper().replace("-", "_") 

542 if key in {"CONTENT_TYPE", "CONTENT_LENGTH"}: 

543 return self.environ[key] 

544 return self.environ[f"HTTP_{key}"] 

545 

546 def __len__(self): 

547 # the iter is necessary because otherwise list calls our 

548 # len which would call list again and so forth. 

549 return len(list(iter(self))) 

550 

551 def __iter__(self): 

552 for key, value in self.environ.items(): 

553 if key.startswith("HTTP_") and key not in { 

554 "HTTP_CONTENT_TYPE", 

555 "HTTP_CONTENT_LENGTH", 

556 }: 

557 yield key[5:].replace("_", "-").title(), value 

558 elif key in {"CONTENT_TYPE", "CONTENT_LENGTH"} and value: 

559 yield key.replace("_", "-").title(), value 

560 

561 def copy(self): 

562 raise TypeError(f"cannot create {type(self).__name__!r} copies") 

563 

564 

565# circular dependencies 

566from .. import http