Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/sansio/request.py: 60%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

181 statements  

1from __future__ import annotations 

2 

3import collections.abc as cabc 

4import typing as t 

5from datetime import datetime 

6from urllib.parse import parse_qsl 

7 

8from ..datastructures import Accept 

9from ..datastructures import Authorization 

10from ..datastructures import ETags 

11from ..datastructures import Headers 

12from ..datastructures import HeaderSet 

13from ..datastructures import IfRange 

14from ..datastructures import ImmutableMultiDict 

15from ..datastructures import LanguageAccept 

16from ..datastructures import MIMEAccept 

17from ..datastructures import Range 

18from ..datastructures import RequestCacheControl 

19from ..http import parse_date 

20from ..http import parse_list_header 

21from ..http import parse_options_header 

22from ..http import SecFetchDest 

23from ..http import SecFetchMode 

24from ..http import SecFetchSite 

25from ..user_agent import UserAgent 

26from ..utils import cached_property 

27from ..utils import header_property 

28from .http import parse_cookie 

29from .utils import get_content_length 

30from .utils import get_current_url 

31from .utils import get_host 

32 

33 

34class Request: 

35 """Represents the non-IO parts of a HTTP request, including the 

36 method, URL info, and headers. 

37 

38 This class is not meant for general use. It should only be used when 

39 implementing WSGI, ASGI, or another HTTP application spec. Werkzeug 

40 provides a WSGI implementation at :cls:`werkzeug.wrappers.Request`. 

41 

42 :param method: The method the request was made with, such as 

43 ``GET``. 

44 :param scheme: The URL scheme of the protocol the request used, such 

45 as ``https`` or ``wss``. 

46 :param server: The address of the server. ``(host, port)``, 

47 ``(path, None)`` for unix sockets, or ``None`` if not known. 

48 :param root_path: The prefix that the application is mounted under. 

49 This is prepended to generated URLs, but is not part of route 

50 matching. 

51 :param path: The path part of the URL after ``root_path``. 

52 :param query_string: The part of the URL after the "?". 

53 :param headers: The headers received with the request. 

54 :param remote_addr: The address of the client sending the request. 

55 

56 .. versionchanged:: 3.0 

57 The ``charset``, ``url_charset``, and ``encoding_errors`` attributes 

58 were removed. 

59 

60 .. versionadded:: 2.0 

61 """ 

62 

63 #: The class to use for :attr:`args`, :attr:`form`, and :attr:`files`. 

64 #: 

65 #: .. deprecated:: 3.2 

66 #: Will be removed in Werkzeug 3.3. It will always be ``ImmutableMultiDict``. 

67 #: 

68 #: .. versionadded:: 0.6 

69 parameter_storage_class: None = None 

70 

71 #: The class to use for parsed dict values, such as :attr:`cookies`. 

72 #: 

73 #: .. deprecated:: 3.2 

74 #: Will be removed in Werkzeug 3.3. It will always be ``ImmutableMultiDict``. 

75 #: 

76 #: .. versionchanged:: 1.0.0 

77 #: Changed to ``ImmutableMultiDict`` to support multiple values. 

78 #: 

79 #: .. versionadded:: 0.6 

80 dict_storage_class: None = None 

81 

82 #: The class to use for parsed list values, such as :attr:`access_route`. 

83 #: 

84 #: .. deprecated:: 3.2 

85 #: Will be removed in Werkzeug 3.3. It will always be ``Sequence``. 

86 #: 

87 #: .. versionadded:: 0.6 

88 list_storage_class: None = None 

89 

90 user_agent_class: type[UserAgent] = UserAgent 

91 """The class used and returned by the :attr:`user_agent` property to 

92 parse the header. Defaults to 

93 :class:`~werkzeug.user_agent.UserAgent`, which does no parsing. An 

94 extension can provide a subclass that uses a parser to provide other 

95 data. 

96 

97 .. versionadded:: 2.0 

98 """ 

99 

100 #: Valid host names when handling requests. By default all hosts are 

101 #: trusted, which means that whatever the client says the host is 

102 #: will be accepted. 

103 #: 

104 #: Because ``Host`` and ``X-Forwarded-Host`` headers can be set to 

105 #: any value by a malicious client, it is recommended to either set 

106 #: this property or implement similar validation in the proxy (if 

107 #: the application is being run behind one). 

108 #: 

109 #: .. versionadded:: 0.9 

110 trusted_hosts: list[str] | None = None 

111 

112 def __init__( 

113 self, 

114 method: str, 

115 scheme: str, 

116 server: tuple[str, int | None] | None, 

117 root_path: str, 

118 path: str, 

119 query_string: bytes, 

120 headers: Headers, 

121 remote_addr: str | None, 

122 ) -> None: 

123 #: The method the request was made with, such as ``GET``. 

124 self.method = method.upper() 

125 #: The URL scheme of the protocol the request used, such as 

126 #: ``https`` or ``wss``. 

127 self.scheme = scheme 

128 #: The address of the server. ``(host, port)``, ``(path, None)`` 

129 #: for unix sockets, or ``None`` if not known. 

130 self.server = server 

131 #: The prefix that the application is mounted under, without a 

132 #: trailing slash. :attr:`path` comes after this. 

133 self.root_path = root_path.rstrip("/") 

134 #: The path part of the URL after :attr:`root_path`. This is the 

135 #: path used for routing within the application. 

136 self.path = "/" + path.lstrip("/") 

137 #: The part of the URL after the "?". This is the raw value, use 

138 #: :attr:`args` for the parsed values. 

139 self.query_string = query_string 

140 #: The headers received with the request. 

141 self.headers = headers 

142 #: The address of the client sending the request. 

143 self.remote_addr = remote_addr 

144 

145 def __repr__(self) -> str: 

146 try: 

147 url = self.url 

148 except Exception as e: 

149 url = f"(invalid URL: {e})" 

150 

151 return f"<{type(self).__name__} {url!r} [{self.method}]>" 

152 

153 @cached_property 

154 def args(self) -> ImmutableMultiDict[str, str]: 

155 """The parsed URL query parameters (the ``?key=value&a=b`` part of a 

156 URL) as an :class:`ImmutableMultiDict`. 

157 

158 .. versionchanged:: 2.3 

159 Invalid bytes remain percent encoded. 

160 """ 

161 items = parse_qsl( 

162 self.query_string.decode(), 

163 keep_blank_values=True, 

164 errors="werkzeug.url_quote", 

165 ) 

166 

167 if self.parameter_storage_class is not None: 

168 import warnings 

169 

170 warnings.warn( 

171 "Setting 'Request.parameter_storage_class' is deprecated and will be" 

172 " removed in Werkzeug 3.3. It will always be 'ImmutableMultiDict'.", 

173 DeprecationWarning, 

174 stacklevel=2, 

175 ) 

176 return self.parameter_storage_class(items) 

177 

178 return ImmutableMultiDict(items) 

179 

180 @cached_property 

181 def access_route(self) -> cabc.Sequence[str]: 

182 """The route taken from the client to the application. 

183 

184 This is ``X-Forwarded-For`` if it is set. Remember to only trust the 

185 last N values, where N is the number of servers setting this header in 

186 front of the application. 

187 

188 Otherwise, this only contains :attr:`remote_addr`, or is empty. 

189 """ 

190 if "X-Forwarded-For" in self.headers: 

191 items = parse_list_header(self.headers["X-Forwarded-For"]) 

192 elif self.remote_addr is not None: 

193 items = [self.remote_addr] 

194 else: 

195 items = [] 

196 

197 if self.list_storage_class is not None: 

198 import warnings 

199 

200 warnings.warn( 

201 "Setting 'Request.list_storage_class' is deprecated and will be" 

202 " removed in Werkzeug 3.3. It will always be 'Sequence'.", 

203 DeprecationWarning, 

204 stacklevel=2, 

205 ) 

206 return self.list_storage_class(items) 

207 

208 return items 

209 

210 @cached_property 

211 def full_path(self) -> str: 

212 """Requested path, including the query string.""" 

213 return f"{self.path}?{self.query_string.decode()}" 

214 

215 @property 

216 def is_secure(self) -> bool: 

217 """``True`` if the request was made with a secure protocol 

218 (HTTPS or WSS). 

219 """ 

220 return self.scheme in {"https", "wss"} 

221 

222 @cached_property 

223 def url(self) -> str: 

224 """The full request URL with the scheme, host, root path, path, 

225 and query string.""" 

226 return get_current_url( 

227 self.scheme, self.host, self.root_path, self.path, self.query_string 

228 ) 

229 

230 @cached_property 

231 def base_url(self) -> str: 

232 """Like :attr:`url` but without the query string.""" 

233 return get_current_url(self.scheme, self.host, self.root_path, self.path) 

234 

235 @cached_property 

236 def root_url(self) -> str: 

237 """The request URL scheme, host, and root path. This is the root 

238 that the application is accessed from. 

239 """ 

240 return get_current_url(self.scheme, self.host, self.root_path) 

241 

242 @cached_property 

243 def host_url(self) -> str: 

244 """The request URL scheme and host only.""" 

245 return get_current_url(self.scheme, self.host) 

246 

247 @cached_property 

248 def host(self) -> str: 

249 """The host name the request was made to, including the port if 

250 it's non-standard. Validated with :attr:`trusted_hosts`. 

251 

252 See :func:`.get_host` for a detailed explanation. 

253 """ 

254 return get_host( 

255 self.scheme, self.headers.get("Host"), self.server, self.trusted_hosts 

256 ) 

257 

258 @cached_property 

259 def cookies(self) -> ImmutableMultiDict[str, str]: 

260 """A :class:`dict` with the contents of all cookies transmitted with 

261 the request.""" 

262 wsgi_combined_cookie = ";".join(self.headers.getlist("Cookie")) 

263 kwargs: dict[str, t.Any] = {} 

264 

265 if self.dict_storage_class is not None: 

266 kwargs["cls"] = self.dict_storage_class 

267 

268 return parse_cookie(wsgi_combined_cookie, **kwargs) 

269 

270 # Common Descriptors 

271 

272 content_type = header_property[str]( 

273 "Content-Type", 

274 doc="""The Content-Type entity-header field indicates the media 

275 type of the entity-body sent to the recipient or, in the case of 

276 the HEAD method, the media type that would have been sent had 

277 the request been a GET.""", 

278 read_only=True, 

279 ) 

280 

281 @cached_property 

282 def content_length(self) -> int | None: 

283 """The Content-Length entity-header field indicates the size of the 

284 entity-body in bytes or, in the case of the HEAD method, the size of 

285 the entity-body that would have been sent had the request been a 

286 GET. 

287 """ 

288 return get_content_length( 

289 http_content_length=self.headers.get("Content-Length"), 

290 http_transfer_encoding=self.headers.get("Transfer-Encoding"), 

291 ) 

292 

293 content_encoding = header_property[str]( 

294 "Content-Encoding", 

295 doc="""The Content-Encoding entity-header field is used as a 

296 modifier to the media-type. When present, its value indicates 

297 what additional content codings have been applied to the 

298 entity-body, and thus what decoding mechanisms must be applied 

299 in order to obtain the media-type referenced by the Content-Type 

300 header field. 

301 

302 .. versionadded:: 0.9""", 

303 read_only=True, 

304 ) 

305 

306 @property 

307 def content_md5(self) -> str | None: 

308 """The ``Content-MD5`` header, an MD5 digest of the request body. 

309 

310 .. deprecated:: 3.2 

311 The header has not been used for a long time. Will be removed 

312 in Werkzeug 3.3. 

313 

314 .. versionadded:: 0.9 

315 """ 

316 import warnings 

317 

318 warnings.warn( 

319 "The 'content_md5' attribute is deprecated and will be removed in" 

320 " Werkzeug 3.3. The header has not been used for a long time.", 

321 DeprecationWarning, 

322 stacklevel=2, 

323 ) 

324 return self.headers.get("Content-MD5") 

325 

326 referrer = header_property[str]( 

327 "Referer", 

328 doc="""The Referer[sic] request-header field allows the client 

329 to specify, for the server's benefit, the address (URI) of the 

330 resource from which the Request-URI was obtained (the 

331 "referrer", although the header field is misspelled).""", 

332 read_only=True, 

333 ) 

334 date = header_property( 

335 "Date", 

336 None, 

337 parse_date, 

338 doc="""The Date general-header field represents the date and 

339 time at which the message was originated, having the same 

340 semantics as orig-date in RFC 822. 

341 

342 .. versionchanged:: 2.0 

343 The datetime object is timezone-aware. 

344 """, 

345 read_only=True, 

346 ) 

347 max_forwards = header_property( 

348 "Max-Forwards", 

349 None, 

350 int, 

351 doc="""The Max-Forwards request-header field provides a 

352 mechanism with the TRACE and OPTIONS methods to limit the number 

353 of proxies or gateways that can forward the request to the next 

354 inbound server.""", 

355 read_only=True, 

356 ) 

357 

358 def _parse_content_type(self) -> None: 

359 if not hasattr(self, "_parsed_content_type"): 

360 self._parsed_content_type = parse_options_header( 

361 self.headers.get("Content-Type", "") 

362 ) 

363 

364 @property 

365 def mimetype(self) -> str: 

366 """Like :attr:`content_type`, but without parameters (eg, without 

367 charset, type etc.) and always lowercase. For example if the content 

368 type is ``text/HTML; charset=utf-8`` the mimetype would be 

369 ``'text/html'``. 

370 """ 

371 self._parse_content_type() 

372 return self._parsed_content_type[0].lower() 

373 

374 @property 

375 def mimetype_params(self) -> dict[str, str]: 

376 """The mimetype parameters as dict. For example if the content 

377 type is ``text/html; charset=utf-8`` the params would be 

378 ``{'charset': 'utf-8'}``. 

379 """ 

380 self._parse_content_type() 

381 return self._parsed_content_type[1] 

382 

383 @property 

384 def pragma(self) -> HeaderSet: 

385 """The ``Pragma`` header. 

386 

387 .. deprecated:: 3.2 

388 Use ``cache_control`` instead. Will be removed in Werkzeug 3.3. 

389 """ 

390 import warnings 

391 

392 warnings.warn( 

393 "The 'pragma' attribute is deprecated and will be removed in" 

394 " Werkzeug 3.3. Use 'cache_control' instead.", 

395 DeprecationWarning, 

396 stacklevel=2, 

397 ) 

398 return HeaderSet.from_header(self.headers.get("Pragma")) 

399 

400 # Accept 

401 

402 @cached_property 

403 def accept_mimetypes(self) -> MIMEAccept: 

404 """List of content types (MIME types) the client supports, from the 

405 ``Accept`` header. 

406 """ 

407 return MIMEAccept.from_header(self.headers.get("Accept")) 

408 

409 @cached_property 

410 def accept_charsets(self) -> Accept: 

411 """Text encodings (charsets) the client accepts, from the 

412 ``Accept-Charset`` header. 

413 

414 .. deprecated:: 3.2 

415 The header has not been used for a long time. Clients do not send 

416 it. Assume UTF-8. Will be removed in Werkzeug 3.3. 

417 """ 

418 import warnings 

419 

420 from ..datastructures.accept import _CharsetAccept 

421 

422 warnings.warn( 

423 "The 'accept_charsets' attribute is deprecated and will be removed" 

424 " in Werkzeug 3.3. The header is not sent by browsers, and UTF-8 is" 

425 " assumed.", 

426 DeprecationWarning, 

427 stacklevel=2, 

428 ) 

429 return _CharsetAccept.from_header(self.headers.get("Accept-Charset")) 

430 

431 @cached_property 

432 def accept_encodings(self) -> Accept: 

433 """Content encodings (compression) the client accepts, from the 

434 ``Accept-Encoding`` header. 

435 """ 

436 return Accept.from_header(self.headers.get("Accept-Encoding")) 

437 

438 @cached_property 

439 def accept_languages(self) -> LanguageAccept: 

440 """Languages the client accepts, from the ``Accept-Language`` header. 

441 

442 .. versionchanged 0.5 

443 Returns ``LanguageAccept`` instead of ``Accept``. 

444 """ 

445 return LanguageAccept.from_header(self.headers.get("Accept-Language")) 

446 

447 # ETag 

448 

449 @cached_property 

450 def cache_control(self) -> RequestCacheControl: 

451 """A :class:`~werkzeug.datastructures.RequestCacheControl` object 

452 for the incoming cache control headers. 

453 """ 

454 return RequestCacheControl.from_header(self.headers.get("Cache-Control")) 

455 

456 @cached_property 

457 def if_match(self) -> ETags: 

458 """ETags parsed from the ``If-Match`` header.""" 

459 return ETags.from_header(self.headers.get("If-Match")) 

460 

461 @cached_property 

462 def if_none_match(self) -> ETags: 

463 """ETags parsed from the ``If-None-Match`` header.""" 

464 return ETags.from_header(self.headers.get("If-None-Match")) 

465 

466 @cached_property 

467 def if_modified_since(self) -> datetime | None: 

468 """The parsed `If-Modified-Since` header as a datetime object. 

469 

470 .. versionchanged:: 2.0 

471 The datetime object is timezone-aware. 

472 """ 

473 return parse_date(self.headers.get("If-Modified-Since")) 

474 

475 @cached_property 

476 def if_unmodified_since(self) -> datetime | None: 

477 """The parsed `If-Unmodified-Since` header as a datetime object. 

478 

479 .. versionchanged:: 2.0 

480 The datetime object is timezone-aware. 

481 """ 

482 return parse_date(self.headers.get("If-Unmodified-Since")) 

483 

484 @cached_property 

485 def if_range(self) -> IfRange: 

486 """The parsed ``If-Range`` header. 

487 

488 .. versionchanged:: 3.2 

489 A weak ETag is discarded. 

490 

491 .. versionchanged:: 2.0 

492 ``IfRange.date`` is timezone-aware. 

493 

494 .. versionadded:: 0.7 

495 """ 

496 return IfRange.from_header(self.headers.get("If-Range")) 

497 

498 @cached_property 

499 def range(self) -> Range | None: 

500 """The parsed `Range` header. 

501 

502 .. versionadded:: 0.7 

503 """ 

504 return Range.from_header(self.headers.get("Range")) 

505 

506 # User Agent 

507 

508 @cached_property 

509 def user_agent(self) -> UserAgent: 

510 """The user agent. Use ``user_agent.string`` to get the header 

511 value. Set :attr:`user_agent_class` to a subclass of 

512 :class:`~werkzeug.user_agent.UserAgent` to provide parsing for 

513 the other properties or other extended data. 

514 

515 .. versionchanged:: 2.1 

516 The built-in parser was removed. Set ``user_agent_class`` to a ``UserAgent`` 

517 subclass to parse data from the string. 

518 """ 

519 return self.user_agent_class(self.headers.get("User-Agent", "")) 

520 

521 # Authorization 

522 

523 @cached_property 

524 def authorization(self) -> Authorization | None: 

525 """The ``Authorization`` header parsed into an :class:`.Authorization` object. 

526 ``None`` if the header is not present. 

527 

528 .. versionchanged:: 2.3 

529 :class:`Authorization` is no longer a ``dict``. The ``token`` attribute 

530 was added for auth schemes that use a token instead of parameters. 

531 """ 

532 return Authorization.from_header(self.headers.get("Authorization")) 

533 

534 # CORS 

535 

536 origin = header_property[str]( 

537 "Origin", 

538 doc=( 

539 "The host that the request originated from. Set" 

540 " :attr:`~CORSResponseMixin.access_control_allow_origin` on" 

541 " the response to indicate which origins are allowed." 

542 ), 

543 read_only=True, 

544 ) 

545 

546 access_control_request_headers = header_property[HeaderSet]( 

547 "Access-Control-Request-Headers", 

548 load_func=HeaderSet.from_header, 

549 doc=( 

550 "Sent with a preflight request to indicate which headers" 

551 " will be sent with the cross origin request. Set" 

552 " :attr:`~CORSResponseMixin.access_control_allow_headers`" 

553 " on the response to indicate which headers are allowed." 

554 ), 

555 read_only=True, 

556 ) 

557 

558 access_control_request_method = header_property[str]( 

559 "Access-Control-Request-Method", 

560 doc=( 

561 "Sent with a preflight request to indicate which method" 

562 " will be used for the cross origin request. Set" 

563 " :attr:`~CORSResponseMixin.access_control_allow_methods`" 

564 " on the response to indicate which methods are allowed." 

565 ), 

566 read_only=True, 

567 ) 

568 

569 sec_fetch_site = header_property[SecFetchSite]( 

570 "Sec-Fetch-Site", 

571 load_func=SecFetchSite, 

572 read_only=True, 

573 doc="""Indicates the relationship between a request initiator's origin 

574 and the origin of the requested resource. 

575 

576 Values are members of the :class:`.SecFetchSite` enum. 

577 

578 .. versionadded:: 3.2 

579 """, 

580 ) 

581 

582 sec_fetch_mode = header_property[SecFetchMode]( 

583 "Sec-Fetch-Mode", 

584 load_func=SecFetchMode, 

585 read_only=True, 

586 doc="""Distinguishes between requests originating from a user navigating 

587 between HTML pages, and requests to load images and other resources. 

588 

589 Values are members of the :class:`.SecFetchMode` enum. 

590 

591 .. versionadded:: 3.2 

592 """, 

593 ) 

594 

595 sec_fetch_user = header_property[bool]( 

596 "Sec-Fetch-User", 

597 load_func=lambda value: value == "?1", 

598 read_only=True, 

599 doc="""Indicates whether a navigation request was originated by the user. 

600 

601 .. versionadded:: 3.2 

602 """, 

603 ) 

604 

605 sec_fetch_dest = header_property[SecFetchDest]( 

606 "Sec-Fetch-Dest", 

607 load_func=SecFetchDest, 

608 read_only=True, 

609 doc="""Indicates how the response to the request is expected to be used. 

610 

611 Values are members of the :class:`.SecFetchDest` enum. 

612 

613 .. versionadded:: 3.2 

614 """, 

615 ) 

616 

617 @property 

618 def is_json(self) -> bool: 

619 """Check if the mimetype indicates JSON data, either 

620 :mimetype:`application/json` or :mimetype:`application/*+json`. 

621 """ 

622 mt = self.mimetype 

623 return ( 

624 mt == "application/json" 

625 or mt.startswith("application/") 

626 and mt.endswith("+json") 

627 )