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

173 statements  

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

1import typing as t 

2from datetime import datetime 

3 

4from .._internal import _to_str 

5from ..datastructures import Accept 

6from ..datastructures import Authorization 

7from ..datastructures import CharsetAccept 

8from ..datastructures import ETags 

9from ..datastructures import Headers 

10from ..datastructures import HeaderSet 

11from ..datastructures import IfRange 

12from ..datastructures import ImmutableList 

13from ..datastructures import ImmutableMultiDict 

14from ..datastructures import LanguageAccept 

15from ..datastructures import MIMEAccept 

16from ..datastructures import MultiDict 

17from ..datastructures import Range 

18from ..datastructures import RequestCacheControl 

19from ..http import parse_accept_header 

20from ..http import parse_authorization_header 

21from ..http import parse_cache_control_header 

22from ..http import parse_date 

23from ..http import parse_etags 

24from ..http import parse_if_range_header 

25from ..http import parse_list_header 

26from ..http import parse_options_header 

27from ..http import parse_range_header 

28from ..http import parse_set_header 

29from ..urls import url_decode 

30from ..user_agent import UserAgent 

31from ..utils import cached_property 

32from ..utils import header_property 

33from .http import parse_cookie 

34from .utils import get_current_url 

35from .utils import get_host 

36 

37 

38class Request: 

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

40 method, URL info, and headers. 

41 

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

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

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

45 

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

47 ``GET``. 

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

49 as ``https`` or ``wss``. 

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

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

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

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

54 matching. 

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

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

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

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

59 

60 .. versionadded:: 2.0 

61 """ 

62 

63 #: The charset used to decode most data in the request. 

64 charset = "utf-8" 

65 

66 #: the error handling procedure for errors, defaults to 'replace' 

67 encoding_errors = "replace" 

68 

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

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

71 #: multiple values per key. alternatively it makes sense to use an 

72 #: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which 

73 #: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict` 

74 #: which is the fastest but only remembers the last key. It is also 

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

76 #: 

77 #: .. versionadded:: 0.6 

78 parameter_storage_class: t.Type[MultiDict] = ImmutableMultiDict 

79 

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

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

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

83 #: 

84 #: .. versionchanged:: 1.0.0 

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

86 #: 

87 #: .. versionadded:: 0.6 

88 dict_storage_class: t.Type[MultiDict] = ImmutableMultiDict 

89 

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

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

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

93 #: 

94 #: .. versionadded:: 0.6 

95 list_storage_class: t.Type[t.List] = ImmutableList 

96 

97 user_agent_class: t.Type[UserAgent] = UserAgent 

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

99 parse the header. Defaults to 

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

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

102 data. 

103 

104 .. versionadded:: 2.0 

105 """ 

106 

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

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

109 #: will be accepted. 

110 #: 

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

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

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

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

115 #: 

116 #: .. versionadded:: 0.9 

117 trusted_hosts: t.Optional[t.List[str]] = None 

118 

119 def __init__( 

120 self, 

121 method: str, 

122 scheme: str, 

123 server: t.Optional[t.Tuple[str, t.Optional[int]]], 

124 root_path: str, 

125 path: str, 

126 query_string: bytes, 

127 headers: Headers, 

128 remote_addr: t.Optional[str], 

129 ) -> None: 

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

131 self.method = method.upper() 

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

133 #: ``https`` or ``wss``. 

134 self.scheme = scheme 

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

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

137 self.server = server 

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

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

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

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

142 #: path used for routing within the application. 

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

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

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

146 self.query_string = query_string 

147 #: The headers received with the request. 

148 self.headers = headers 

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

150 self.remote_addr = remote_addr 

151 

152 def __repr__(self) -> str: 

153 try: 

154 url = self.url 

155 except Exception as e: 

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

157 

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

159 

160 @property 

161 def url_charset(self) -> str: 

162 """The charset that is assumed for URLs. Defaults to the value 

163 of :attr:`charset`. 

164 

165 .. versionadded:: 0.6 

166 """ 

167 return self.charset 

168 

169 @cached_property 

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

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

172 mark). 

173 

174 By default an 

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

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

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

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

179 """ 

180 return url_decode( 

181 self.query_string, 

182 self.url_charset, 

183 errors=self.encoding_errors, 

184 cls=self.parameter_storage_class, 

185 ) 

186 

187 @cached_property 

188 def access_route(self) -> t.List[str]: 

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

190 from the client ip to the last proxy server. 

191 """ 

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

193 return self.list_storage_class( 

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

195 ) 

196 elif self.remote_addr is not None: 

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

198 return self.list_storage_class() 

199 

200 @cached_property 

201 def full_path(self) -> str: 

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

203 return f"{self.path}?{_to_str(self.query_string, self.url_charset)}" 

204 

205 @property 

206 def is_secure(self) -> bool: 

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

208 (HTTPS or WSS). 

209 """ 

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

211 

212 @cached_property 

213 def url(self) -> str: 

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

215 and query string.""" 

216 return get_current_url( 

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

218 ) 

219 

220 @cached_property 

221 def base_url(self) -> str: 

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

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

224 

225 @cached_property 

226 def root_url(self) -> str: 

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

228 that the application is accessed from. 

229 """ 

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

231 

232 @cached_property 

233 def host_url(self) -> str: 

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

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

236 

237 @cached_property 

238 def host(self) -> str: 

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

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

241 """ 

242 return get_host( 

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

244 ) 

245 

246 @cached_property 

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

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

249 the request.""" 

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

251 return parse_cookie( # type: ignore 

252 wsgi_combined_cookie, 

253 self.charset, 

254 self.encoding_errors, 

255 cls=self.dict_storage_class, 

256 ) 

257 

258 # Common Descriptors 

259 

260 content_type = header_property[str]( 

261 "Content-Type", 

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

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

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

265 the request been a GET.""", 

266 read_only=True, 

267 ) 

268 

269 @cached_property 

270 def content_length(self) -> t.Optional[int]: 

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

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

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

274 GET. 

275 """ 

276 if self.headers.get("Transfer-Encoding", "") == "chunked": 

277 return None 

278 

279 content_length = self.headers.get("Content-Length") 

280 if content_length is not None: 

281 try: 

282 return max(0, int(content_length)) 

283 except (ValueError, TypeError): 

284 pass 

285 

286 return None 

287 

288 content_encoding = header_property[str]( 

289 "Content-Encoding", 

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

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

292 what additional content codings have been applied to the 

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

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

295 header field. 

296 

297 .. versionadded:: 0.9""", 

298 read_only=True, 

299 ) 

300 content_md5 = header_property[str]( 

301 "Content-MD5", 

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

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

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

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

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

307 against malicious attacks.) 

308 

309 .. versionadded:: 0.9""", 

310 read_only=True, 

311 ) 

312 referrer = header_property[str]( 

313 "Referer", 

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

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

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

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

318 read_only=True, 

319 ) 

320 date = header_property( 

321 "Date", 

322 None, 

323 parse_date, 

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

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

326 semantics as orig-date in RFC 822. 

327 

328 .. versionchanged:: 2.0 

329 The datetime object is timezone-aware. 

330 """, 

331 read_only=True, 

332 ) 

333 max_forwards = header_property( 

334 "Max-Forwards", 

335 None, 

336 int, 

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

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

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

340 inbound server.""", 

341 read_only=True, 

342 ) 

343 

344 def _parse_content_type(self) -> None: 

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

346 self._parsed_content_type = parse_options_header( 

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

348 ) 

349 

350 @property 

351 def mimetype(self) -> str: 

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

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

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

355 ``'text/html'``. 

356 """ 

357 self._parse_content_type() 

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

359 

360 @property 

361 def mimetype_params(self) -> t.Dict[str, str]: 

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

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

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

365 """ 

366 self._parse_content_type() 

367 return self._parsed_content_type[1] 

368 

369 @cached_property 

370 def pragma(self) -> HeaderSet: 

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

372 implementation-specific directives that might apply to any recipient 

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

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

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

376 """ 

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

378 

379 # Accept 

380 

381 @cached_property 

382 def accept_mimetypes(self) -> MIMEAccept: 

383 """List of mimetypes this client supports as 

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

385 """ 

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

387 

388 @cached_property 

389 def accept_charsets(self) -> CharsetAccept: 

390 """List of charsets this client supports as 

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

392 """ 

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

394 

395 @cached_property 

396 def accept_encodings(self) -> Accept: 

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

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

399 :attr:`accept_charset`. 

400 """ 

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

402 

403 @cached_property 

404 def accept_languages(self) -> LanguageAccept: 

405 """List of languages this client accepts as 

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

407 

408 .. versionchanged 0.5 

409 In previous versions this was a regular 

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

411 """ 

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

413 

414 # ETag 

415 

416 @cached_property 

417 def cache_control(self) -> RequestCacheControl: 

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

419 for the incoming cache control headers. 

420 """ 

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

422 return parse_cache_control_header(cache_control, None, RequestCacheControl) 

423 

424 @cached_property 

425 def if_match(self) -> ETags: 

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

427 

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

429 """ 

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

431 

432 @cached_property 

433 def if_none_match(self) -> ETags: 

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

435 

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

437 """ 

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

439 

440 @cached_property 

441 def if_modified_since(self) -> t.Optional[datetime]: 

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

443 

444 .. versionchanged:: 2.0 

445 The datetime object is timezone-aware. 

446 """ 

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

448 

449 @cached_property 

450 def if_unmodified_since(self) -> t.Optional[datetime]: 

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

452 

453 .. versionchanged:: 2.0 

454 The datetime object is timezone-aware. 

455 """ 

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

457 

458 @cached_property 

459 def if_range(self) -> IfRange: 

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

461 

462 .. versionchanged:: 2.0 

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

464 

465 .. versionadded:: 0.7 

466 """ 

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

468 

469 @cached_property 

470 def range(self) -> t.Optional[Range]: 

471 """The parsed `Range` header. 

472 

473 .. versionadded:: 0.7 

474 

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

476 """ 

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

478 

479 # User Agent 

480 

481 @cached_property 

482 def user_agent(self) -> UserAgent: 

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

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

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

486 the other properties or other extended data. 

487 

488 .. versionchanged:: 2.0 

489 The built in parser is deprecated and will be removed in 

490 Werkzeug 2.1. A ``UserAgent`` subclass must be set to parse 

491 data from the string. 

492 """ 

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

494 

495 # Authorization 

496 

497 @cached_property 

498 def authorization(self) -> t.Optional[Authorization]: 

499 """The `Authorization` object in parsed form.""" 

500 return parse_authorization_header(self.headers.get("Authorization")) 

501 

502 # CORS 

503 

504 origin = header_property[str]( 

505 "Origin", 

506 doc=( 

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

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

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

510 ), 

511 read_only=True, 

512 ) 

513 

514 access_control_request_headers = header_property( 

515 "Access-Control-Request-Headers", 

516 load_func=parse_set_header, 

517 doc=( 

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

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

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

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

522 ), 

523 read_only=True, 

524 ) 

525 

526 access_control_request_method = header_property[str]( 

527 "Access-Control-Request-Method", 

528 doc=( 

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

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

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

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

533 ), 

534 read_only=True, 

535 ) 

536 

537 @property 

538 def is_json(self) -> bool: 

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

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

541 """ 

542 mt = self.mimetype 

543 return ( 

544 mt == "application/json" 

545 or mt.startswith("application/") 

546 and mt.endswith("+json") 

547 )