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

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

161 statements  

1from __future__ import annotations 

2 

3import typing as t 

4from datetime import datetime 

5from urllib.parse import parse_qsl 

6 

7from ..datastructures import Accept 

8from ..datastructures import Authorization 

9from ..datastructures import CharsetAccept 

10from ..datastructures import ETags 

11from ..datastructures import Headers 

12from ..datastructures import HeaderSet 

13from ..datastructures import IfRange 

14from ..datastructures import ImmutableList 

15from ..datastructures import ImmutableMultiDict 

16from ..datastructures import LanguageAccept 

17from ..datastructures import MIMEAccept 

18from ..datastructures import MultiDict 

19from ..datastructures import Range 

20from ..datastructures import RequestCacheControl 

21from ..http import parse_accept_header 

22from ..http import parse_cache_control_header 

23from ..http import parse_date 

24from ..http import parse_etags 

25from ..http import parse_if_range_header 

26from ..http import parse_list_header 

27from ..http import parse_options_header 

28from ..http import parse_range_header 

29from ..http import parse_set_header 

30from ..user_agent import UserAgent 

31from ..utils import cached_property 

32from ..utils import header_property 

33from .http import parse_cookie 

34from .utils import get_content_length 

35from .utils import get_current_url 

36from .utils import get_host 

37 

38 

39class Request: 

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

41 method, URL info, and headers. 

42 

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

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

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

46 

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

48 ``GET``. 

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

50 as ``https`` or ``wss``. 

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

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

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

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

55 matching. 

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

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

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

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

60 

61 .. versionchanged:: 3.0 

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

63 were removed. 

64 

65 .. versionadded:: 2.0 

66 """ 

67 

68 #: the class to use for `args` and `form`. The default is an 

69 #: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports 

70 #: multiple values per key. A :class:`~werkzeug.datastructures.ImmutableDict` 

71 #: is faster but only remembers the last key. It is also 

72 #: possible to use mutable structures, but this is not recommended. 

73 #: 

74 #: .. versionadded:: 0.6 

75 parameter_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict 

76 

77 #: The type to be used for dict values from the incoming WSGI 

78 #: environment. (For example for :attr:`cookies`.) By default an 

79 #: :class:`~werkzeug.datastructures.ImmutableMultiDict` is used. 

80 #: 

81 #: .. versionchanged:: 1.0.0 

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

83 #: 

84 #: .. versionadded:: 0.6 

85 dict_storage_class: type[MultiDict[str, t.Any]] = ImmutableMultiDict 

86 

87 #: the type to be used for list values from the incoming WSGI environment. 

88 #: By default an :class:`~werkzeug.datastructures.ImmutableList` is used 

89 #: (for example for :attr:`access_list`). 

90 #: 

91 #: .. versionadded:: 0.6 

92 list_storage_class: type[list[t.Any]] = ImmutableList 

93 

94 user_agent_class: type[UserAgent] = UserAgent 

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

96 parse the header. Defaults to 

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

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

99 data. 

100 

101 .. versionadded:: 2.0 

102 """ 

103 

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

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

106 #: will be accepted. 

107 #: 

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

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

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

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

112 #: 

113 #: .. versionadded:: 0.9 

114 trusted_hosts: list[str] | None = None 

115 

116 def __init__( 

117 self, 

118 method: str, 

119 scheme: str, 

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

121 root_path: str, 

122 path: str, 

123 query_string: bytes, 

124 headers: Headers, 

125 remote_addr: str | None, 

126 ) -> None: 

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

128 self.method = method.upper() 

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

130 #: ``https`` or ``wss``. 

131 self.scheme = scheme 

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

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

134 self.server = server 

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

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

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

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

139 #: path used for routing within the application. 

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

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

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

143 self.query_string = query_string 

144 #: The headers received with the request. 

145 self.headers = headers 

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

147 self.remote_addr = remote_addr 

148 

149 def __repr__(self) -> str: 

150 try: 

151 url = self.url 

152 except Exception as e: 

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

154 

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

156 

157 @cached_property 

158 def args(self) -> MultiDict[str, str]: 

159 """The parsed URL parameters (the part in the URL after the question 

160 mark). 

161 

162 By default an 

163 :class:`~werkzeug.datastructures.ImmutableMultiDict` 

164 is returned from this function. This can be changed by setting 

165 :attr:`parameter_storage_class` to a different type. This might 

166 be necessary if the order of the form data is important. 

167 

168 .. versionchanged:: 2.3 

169 Invalid bytes remain percent encoded. 

170 """ 

171 return self.parameter_storage_class( 

172 parse_qsl( 

173 self.query_string.decode(), 

174 keep_blank_values=True, 

175 errors="werkzeug.url_quote", 

176 ) 

177 ) 

178 

179 @cached_property 

180 def access_route(self) -> list[str]: 

181 """If a forwarded header exists this is a list of all ip addresses 

182 from the client ip to the last proxy server. 

183 """ 

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

185 return self.list_storage_class( 

186 parse_list_header(self.headers["X-Forwarded-For"]) 

187 ) 

188 elif self.remote_addr is not None: 

189 return self.list_storage_class([self.remote_addr]) 

190 return self.list_storage_class() 

191 

192 @cached_property 

193 def full_path(self) -> str: 

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

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

196 

197 @property 

198 def is_secure(self) -> bool: 

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

200 (HTTPS or WSS). 

201 """ 

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

203 

204 @cached_property 

205 def url(self) -> str: 

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

207 and query string.""" 

208 return get_current_url( 

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

210 ) 

211 

212 @cached_property 

213 def base_url(self) -> str: 

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

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

216 

217 @cached_property 

218 def root_url(self) -> str: 

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

220 that the application is accessed from. 

221 """ 

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

223 

224 @cached_property 

225 def host_url(self) -> str: 

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

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

228 

229 @cached_property 

230 def host(self) -> str: 

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

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

233 """ 

234 return get_host( 

235 self.scheme, self.headers.get("host"), self.server, self.trusted_hosts 

236 ) 

237 

238 @cached_property 

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

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

241 the request.""" 

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

243 return parse_cookie( # type: ignore 

244 wsgi_combined_cookie, cls=self.dict_storage_class 

245 ) 

246 

247 # Common Descriptors 

248 

249 content_type = header_property[str]( 

250 "Content-Type", 

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

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

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

254 the request been a GET.""", 

255 read_only=True, 

256 ) 

257 

258 @cached_property 

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

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

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

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

263 GET. 

264 """ 

265 return get_content_length( 

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

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

268 ) 

269 

270 content_encoding = header_property[str]( 

271 "Content-Encoding", 

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

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

274 what additional content codings have been applied to the 

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

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

277 header field. 

278 

279 .. versionadded:: 0.9""", 

280 read_only=True, 

281 ) 

282 content_md5 = header_property[str]( 

283 "Content-MD5", 

284 doc="""The Content-MD5 entity-header field, as defined in 

285 RFC 1864, is an MD5 digest of the entity-body for the purpose of 

286 providing an end-to-end message integrity check (MIC) of the 

287 entity-body. (Note: a MIC is good for detecting accidental 

288 modification of the entity-body in transit, but is not proof 

289 against malicious attacks.) 

290 

291 .. versionadded:: 0.9""", 

292 read_only=True, 

293 ) 

294 referrer = header_property[str]( 

295 "Referer", 

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

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

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

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

300 read_only=True, 

301 ) 

302 date = header_property( 

303 "Date", 

304 None, 

305 parse_date, 

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

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

308 semantics as orig-date in RFC 822. 

309 

310 .. versionchanged:: 2.0 

311 The datetime object is timezone-aware. 

312 """, 

313 read_only=True, 

314 ) 

315 max_forwards = header_property( 

316 "Max-Forwards", 

317 None, 

318 int, 

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

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

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

322 inbound server.""", 

323 read_only=True, 

324 ) 

325 

326 def _parse_content_type(self) -> None: 

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

328 self._parsed_content_type = parse_options_header( 

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

330 ) 

331 

332 @property 

333 def mimetype(self) -> str: 

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

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

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

337 ``'text/html'``. 

338 """ 

339 self._parse_content_type() 

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

341 

342 @property 

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

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

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

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

347 """ 

348 self._parse_content_type() 

349 return self._parsed_content_type[1] 

350 

351 @cached_property 

352 def pragma(self) -> HeaderSet: 

353 """The Pragma general-header field is used to include 

354 implementation-specific directives that might apply to any recipient 

355 along the request/response chain. All pragma directives specify 

356 optional behavior from the viewpoint of the protocol; however, some 

357 systems MAY require that behavior be consistent with the directives. 

358 """ 

359 return parse_set_header(self.headers.get("Pragma", "")) 

360 

361 # Accept 

362 

363 @cached_property 

364 def accept_mimetypes(self) -> MIMEAccept: 

365 """List of mimetypes this client supports as 

366 :class:`~werkzeug.datastructures.MIMEAccept` object. 

367 """ 

368 return parse_accept_header(self.headers.get("Accept"), MIMEAccept) 

369 

370 @cached_property 

371 def accept_charsets(self) -> CharsetAccept: 

372 """List of charsets this client supports as 

373 :class:`~werkzeug.datastructures.CharsetAccept` object. 

374 """ 

375 return parse_accept_header(self.headers.get("Accept-Charset"), CharsetAccept) 

376 

377 @cached_property 

378 def accept_encodings(self) -> Accept: 

379 """List of encodings this client accepts. Encodings in a HTTP term 

380 are compression encodings such as gzip. For charsets have a look at 

381 :attr:`accept_charset`. 

382 """ 

383 return parse_accept_header(self.headers.get("Accept-Encoding")) 

384 

385 @cached_property 

386 def accept_languages(self) -> LanguageAccept: 

387 """List of languages this client accepts as 

388 :class:`~werkzeug.datastructures.LanguageAccept` object. 

389 

390 .. versionchanged 0.5 

391 In previous versions this was a regular 

392 :class:`~werkzeug.datastructures.Accept` object. 

393 """ 

394 return parse_accept_header(self.headers.get("Accept-Language"), LanguageAccept) 

395 

396 # ETag 

397 

398 @cached_property 

399 def cache_control(self) -> RequestCacheControl: 

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

401 for the incoming cache control headers. 

402 """ 

403 cache_control = self.headers.get("Cache-Control") 

404 return parse_cache_control_header(cache_control, None, RequestCacheControl) 

405 

406 @cached_property 

407 def if_match(self) -> ETags: 

408 """An object containing all the etags in the `If-Match` header. 

409 

410 :rtype: :class:`~werkzeug.datastructures.ETags` 

411 """ 

412 return parse_etags(self.headers.get("If-Match")) 

413 

414 @cached_property 

415 def if_none_match(self) -> ETags: 

416 """An object containing all the etags in the `If-None-Match` header. 

417 

418 :rtype: :class:`~werkzeug.datastructures.ETags` 

419 """ 

420 return parse_etags(self.headers.get("If-None-Match")) 

421 

422 @cached_property 

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

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

425 

426 .. versionchanged:: 2.0 

427 The datetime object is timezone-aware. 

428 """ 

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

430 

431 @cached_property 

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

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

434 

435 .. versionchanged:: 2.0 

436 The datetime object is timezone-aware. 

437 """ 

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

439 

440 @cached_property 

441 def if_range(self) -> IfRange: 

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

443 

444 .. versionchanged:: 2.0 

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

446 

447 .. versionadded:: 0.7 

448 """ 

449 return parse_if_range_header(self.headers.get("If-Range")) 

450 

451 @cached_property 

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

453 """The parsed `Range` header. 

454 

455 .. versionadded:: 0.7 

456 

457 :rtype: :class:`~werkzeug.datastructures.Range` 

458 """ 

459 return parse_range_header(self.headers.get("Range")) 

460 

461 # User Agent 

462 

463 @cached_property 

464 def user_agent(self) -> UserAgent: 

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

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

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

468 the other properties or other extended data. 

469 

470 .. versionchanged:: 2.1 

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

472 subclass to parse data from the string. 

473 """ 

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

475 

476 # Authorization 

477 

478 @cached_property 

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

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

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

482 

483 .. versionchanged:: 2.3 

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

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

486 """ 

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

488 

489 # CORS 

490 

491 origin = header_property[str]( 

492 "Origin", 

493 doc=( 

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

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

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

497 ), 

498 read_only=True, 

499 ) 

500 

501 access_control_request_headers = header_property( 

502 "Access-Control-Request-Headers", 

503 load_func=parse_set_header, 

504 doc=( 

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

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

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

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

509 ), 

510 read_only=True, 

511 ) 

512 

513 access_control_request_method = header_property[str]( 

514 "Access-Control-Request-Method", 

515 doc=( 

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

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

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

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

520 ), 

521 read_only=True, 

522 ) 

523 

524 @property 

525 def is_json(self) -> bool: 

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

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

528 """ 

529 mt = self.mimetype 

530 return ( 

531 mt == "application/json" 

532 or mt.startswith("application/") 

533 and mt.endswith("+json") 

534 )