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

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

160 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. alternatively it makes sense to use an 

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

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

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

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

75 #: 

76 #: .. versionadded:: 0.6 

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

78 

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

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

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

82 #: 

83 #: .. versionchanged:: 1.0.0 

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

85 #: 

86 #: .. versionadded:: 0.6 

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

88 

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

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

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

92 #: 

93 #: .. versionadded:: 0.6 

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

95 

96 user_agent_class: type[UserAgent] = UserAgent 

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

98 parse the header. Defaults to 

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

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

101 data. 

102 

103 .. versionadded:: 2.0 

104 """ 

105 

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

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

108 #: will be accepted. 

109 #: 

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

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

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

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

114 #: 

115 #: .. versionadded:: 0.9 

116 trusted_hosts: list[str] | None = None 

117 

118 def __init__( 

119 self, 

120 method: str, 

121 scheme: str, 

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

123 root_path: str, 

124 path: str, 

125 query_string: bytes, 

126 headers: Headers, 

127 remote_addr: str | None, 

128 ) -> None: 

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

130 self.method = method.upper() 

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

132 #: ``https`` or ``wss``. 

133 self.scheme = scheme 

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

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

136 self.server = server 

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

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

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

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

141 #: path used for routing within the application. 

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

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

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

145 self.query_string = query_string 

146 #: The headers received with the request. 

147 self.headers = headers 

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

149 self.remote_addr = remote_addr 

150 

151 def __repr__(self) -> str: 

152 try: 

153 url = self.url 

154 except Exception as e: 

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

156 

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

158 

159 @cached_property 

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

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

162 mark). 

163 

164 By default an 

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

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

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

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

169 

170 .. versionchanged:: 2.3 

171 Invalid bytes remain percent encoded. 

172 """ 

173 return self.parameter_storage_class( 

174 parse_qsl( 

175 self.query_string.decode(), 

176 keep_blank_values=True, 

177 errors="werkzeug.url_quote", 

178 ) 

179 ) 

180 

181 @cached_property 

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

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

184 from the client ip to the last proxy server. 

185 """ 

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

187 return self.list_storage_class( 

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

189 ) 

190 elif self.remote_addr is not None: 

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

192 return self.list_storage_class() 

193 

194 @cached_property 

195 def full_path(self) -> str: 

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

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

198 

199 @property 

200 def is_secure(self) -> bool: 

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

202 (HTTPS or WSS). 

203 """ 

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

205 

206 @cached_property 

207 def url(self) -> str: 

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

209 and query string.""" 

210 return get_current_url( 

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

212 ) 

213 

214 @cached_property 

215 def base_url(self) -> str: 

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

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

218 

219 @cached_property 

220 def root_url(self) -> str: 

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

222 that the application is accessed from. 

223 """ 

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

225 

226 @cached_property 

227 def host_url(self) -> str: 

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

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

230 

231 @cached_property 

232 def host(self) -> str: 

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

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

235 """ 

236 return get_host( 

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

238 ) 

239 

240 @cached_property 

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

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

243 the request.""" 

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

245 return parse_cookie( # type: ignore 

246 wsgi_combined_cookie, cls=self.dict_storage_class 

247 ) 

248 

249 # Common Descriptors 

250 

251 content_type = header_property[str]( 

252 "Content-Type", 

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

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

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

256 the request been a GET.""", 

257 read_only=True, 

258 ) 

259 

260 @cached_property 

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

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

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

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

265 GET. 

266 """ 

267 return get_content_length( 

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

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

270 ) 

271 

272 content_encoding = header_property[str]( 

273 "Content-Encoding", 

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

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

276 what additional content codings have been applied to the 

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

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

279 header field. 

280 

281 .. versionadded:: 0.9""", 

282 read_only=True, 

283 ) 

284 content_md5 = header_property[str]( 

285 "Content-MD5", 

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

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

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

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

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

291 against malicious attacks.) 

292 

293 .. versionadded:: 0.9""", 

294 read_only=True, 

295 ) 

296 referrer = header_property[str]( 

297 "Referer", 

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

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

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

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

302 read_only=True, 

303 ) 

304 date = header_property( 

305 "Date", 

306 None, 

307 parse_date, 

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

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

310 semantics as orig-date in RFC 822. 

311 

312 .. versionchanged:: 2.0 

313 The datetime object is timezone-aware. 

314 """, 

315 read_only=True, 

316 ) 

317 max_forwards = header_property( 

318 "Max-Forwards", 

319 None, 

320 int, 

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

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

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

324 inbound server.""", 

325 read_only=True, 

326 ) 

327 

328 def _parse_content_type(self) -> None: 

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

330 self._parsed_content_type = parse_options_header( 

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

332 ) 

333 

334 @property 

335 def mimetype(self) -> str: 

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

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

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

339 ``'text/html'``. 

340 """ 

341 self._parse_content_type() 

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

343 

344 @property 

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

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

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

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

349 """ 

350 self._parse_content_type() 

351 return self._parsed_content_type[1] 

352 

353 @cached_property 

354 def pragma(self) -> HeaderSet: 

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

356 implementation-specific directives that might apply to any recipient 

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

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

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

360 """ 

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

362 

363 # Accept 

364 

365 @cached_property 

366 def accept_mimetypes(self) -> MIMEAccept: 

367 """List of mimetypes this client supports as 

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

369 """ 

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

371 

372 @cached_property 

373 def accept_charsets(self) -> CharsetAccept: 

374 """List of charsets this client supports as 

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

376 """ 

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

378 

379 @cached_property 

380 def accept_encodings(self) -> Accept: 

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

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

383 :attr:`accept_charset`. 

384 """ 

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

386 

387 @cached_property 

388 def accept_languages(self) -> LanguageAccept: 

389 """List of languages this client accepts as 

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

391 

392 .. versionchanged 0.5 

393 In previous versions this was a regular 

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

395 """ 

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

397 

398 # ETag 

399 

400 @cached_property 

401 def cache_control(self) -> RequestCacheControl: 

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

403 for the incoming cache control headers. 

404 """ 

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

406 return parse_cache_control_header(cache_control, None, RequestCacheControl) 

407 

408 @cached_property 

409 def if_match(self) -> ETags: 

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

411 

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

413 """ 

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

415 

416 @cached_property 

417 def if_none_match(self) -> ETags: 

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

419 

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

421 """ 

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

423 

424 @cached_property 

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

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

427 

428 .. versionchanged:: 2.0 

429 The datetime object is timezone-aware. 

430 """ 

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

432 

433 @cached_property 

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

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

436 

437 .. versionchanged:: 2.0 

438 The datetime object is timezone-aware. 

439 """ 

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

441 

442 @cached_property 

443 def if_range(self) -> IfRange: 

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

445 

446 .. versionchanged:: 2.0 

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

448 

449 .. versionadded:: 0.7 

450 """ 

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

452 

453 @cached_property 

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

455 """The parsed `Range` header. 

456 

457 .. versionadded:: 0.7 

458 

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

460 """ 

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

462 

463 # User Agent 

464 

465 @cached_property 

466 def user_agent(self) -> UserAgent: 

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

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

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

470 the other properties or other extended data. 

471 

472 .. versionchanged:: 2.1 

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

474 subclass to parse data from the string. 

475 """ 

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

477 

478 # Authorization 

479 

480 @cached_property 

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

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

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

484 

485 .. versionchanged:: 2.3 

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

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

488 """ 

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

490 

491 # CORS 

492 

493 origin = header_property[str]( 

494 "Origin", 

495 doc=( 

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

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

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

499 ), 

500 read_only=True, 

501 ) 

502 

503 access_control_request_headers = header_property( 

504 "Access-Control-Request-Headers", 

505 load_func=parse_set_header, 

506 doc=( 

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

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

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

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

511 ), 

512 read_only=True, 

513 ) 

514 

515 access_control_request_method = header_property[str]( 

516 "Access-Control-Request-Method", 

517 doc=( 

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

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

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

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

522 ), 

523 read_only=True, 

524 ) 

525 

526 @property 

527 def is_json(self) -> bool: 

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

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

530 """ 

531 mt = self.mimetype 

532 return ( 

533 mt == "application/json" 

534 or mt.startswith("application/") 

535 and mt.endswith("+json") 

536 )