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

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

172 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 .._header_property import header_property 

9from .._internal import _plain_int 

10from ..datastructures.accept import Accept 

11from ..datastructures.accept import LanguageAccept 

12from ..datastructures.accept import MIMEAccept 

13from ..datastructures.auth import Authorization 

14from ..datastructures.cache_control import RequestCacheControl 

15from ..datastructures.etag import ETagSet 

16from ..datastructures.headers import Headers 

17from ..datastructures.range import IfRange 

18from ..datastructures.range import Range 

19from ..datastructures.set import HeaderSet 

20from ..datastructures.structures import ImmutableMultiDict 

21from ..http import parse_date 

22from ..http import parse_list_header 

23from ..http import parse_options_header 

24from ..http import SecFetchDest 

25from ..http import SecFetchMode 

26from ..http import SecFetchSite 

27from ..user_agent import _UserAgent 

28from ..utils import cached_property 

29from .http import parse_cookie 

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 4.0. 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 4.0. 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 4.0. It will always be ``Sequence``. 

86 #: 

87 #: .. versionadded:: 0.6 

88 list_storage_class: None = None 

89 

90 user_agent_class: None = None 

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 .. deprecated 3.2 

98 Will be removed in Werkzeug 4.0. ``user_agent`` is a string and can be 

99 parsed directly if needed. 

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) -> ImmutableMultiDict[str, str]: 

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

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

161 

162 .. versionchanged:: 2.3 

163 Invalid bytes remain percent encoded. 

164 """ 

165 items = parse_qsl( 

166 self.query_string.decode(), 

167 keep_blank_values=True, 

168 errors="werkzeug.url_quote", 

169 ) 

170 

171 if self.parameter_storage_class is not None: 

172 import warnings 

173 

174 warnings.warn( 

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

176 " removed in Werkzeug 4.0. It will always be 'ImmutableMultiDict'.", 

177 DeprecationWarning, 

178 stacklevel=2, 

179 ) 

180 return self.parameter_storage_class(items) 

181 

182 return ImmutableMultiDict(items) 

183 

184 @cached_property 

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

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

187 

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

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

190 front of the application. 

191 

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

193 """ 

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

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

196 elif self.remote_addr is not None: 

197 items = (self.remote_addr,) 

198 else: 

199 items = () 

200 

201 if self.list_storage_class is not None: 

202 import warnings 

203 

204 warnings.warn( 

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

206 " removed in Werkzeug 4.0. It will always be 'Sequence'.", 

207 DeprecationWarning, 

208 stacklevel=2, 

209 ) 

210 return self.list_storage_class(items) 

211 

212 return items 

213 

214 @cached_property 

215 def full_path(self) -> str: 

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

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

218 

219 @cached_property 

220 def is_secure(self) -> bool: 

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

222 (HTTPS or WSS). 

223 """ 

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

225 

226 @cached_property 

227 def url(self) -> str: 

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

229 and query string.""" 

230 return get_current_url( 

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

232 ) 

233 

234 @cached_property 

235 def base_url(self) -> str: 

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

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

238 

239 @cached_property 

240 def root_url(self) -> str: 

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

242 that the application is accessed from. 

243 """ 

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

245 

246 @cached_property 

247 def host_url(self) -> str: 

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

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

250 

251 @cached_property 

252 def host(self) -> str: 

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

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

255 

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

257 """ 

258 return get_host( 

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

260 ) 

261 

262 @cached_property 

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

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

265 the request.""" 

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

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

268 

269 if self.dict_storage_class is not None: 

270 import warnings 

271 

272 warnings.warn( 

273 "Setting 'Request.dict_storage_class' is deprecated and will be" 

274 " removed in Werkzeug 4.0. It will always be 'ImmutableMultiDict'.", 

275 DeprecationWarning, 

276 stacklevel=2, 

277 ) 

278 kwargs["cls"] = self.dict_storage_class 

279 

280 return parse_cookie(wsgi_combined_cookie, **kwargs) 

281 

282 # Common Descriptors 

283 

284 content_type = header_property[str | None]( 

285 "Content-Type", 

286 read_only=True, 

287 doc="""The ``Content-Type`` header. The type of data in the body, with 

288 optional parameters for additional detail. 

289 

290 A ``str``, or ``None`` if not set. 

291 

292 :attr:`mimetype` and :attr:`mimetype_params` allow working with the two 

293 parts of the value separately. 

294 """, 

295 ) 

296 

297 @cached_property 

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

299 """The ``Content-Length`` header. The size of the body in bytes. 

300 

301 An ``int``, or ``None`` if not set. 

302 """ 

303 if not ( 

304 value := self.headers.get("Content-Length") 

305 ) or "chunked" in HeaderSet.from_header(self.headers.get("Transfer-Encoding")): 

306 return None 

307 

308 try: 

309 return max(0, _plain_int(value)) 

310 except ValueError: 

311 return 0 

312 

313 content_encoding = header_property[str | None]( 

314 "Content-Encoding", 

315 read_only=True, 

316 doc="""The ``Content-Encoding`` header. An additional encoding applied 

317 to the body beyond the ``Content-Type``. 

318 

319 A ``str``, or ``None`` if not set. 

320 

321 .. versionadded:: 0.9 

322 """, 

323 ) 

324 

325 @cached_property 

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

327 """The ``Content-MD5`` header. An MD5 digest of the body. 

328 

329 A ``str``, or ``None`` if not set. 

330 

331 .. deprecated:: 3.2 

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

333 in Werkzeug 4.0. 

334 

335 .. versionadded:: 0.9 

336 """ 

337 import warnings 

338 

339 warnings.warn( 

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

341 " Werkzeug 4.0. The header has not been used for a long time.", 

342 DeprecationWarning, 

343 stacklevel=2, 

344 ) 

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

346 

347 referrer = header_property[str | None]( 

348 "Referer", 

349 read_only=True, 

350 doc="""The ``Referer`` [sic] header. The URL the client made the request 

351 from. 

352 

353 A ``str``, or ``None`` if not set. 

354 """, 

355 ) 

356 

357 date = header_property[datetime | None]( 

358 "Date", 

359 load_func=parse_date, 

360 read_only=True, 

361 doc="""The ``Date`` header. When the client generated the request. 

362 

363 A :class:`~datetime.datetime`, or ``None`` if not set. 

364 

365 .. versionchanged:: 2.0 

366 The datetime object is timezone-aware. 

367 """, 

368 ) 

369 

370 max_forwards = header_property[int | None]( 

371 "Max-Forwards", 

372 load_func=int, 

373 read_only=True, 

374 doc="""The ``Max-Forwards`` header. How many times to forward the 

375 request for ``TRACE`` or ``OPTIONS`` methods. 

376 

377 An ``int``, or ``None`` if not set. 

378 """, 

379 ) 

380 

381 _parsed_content_type: tuple[str, dict[str, str]] | None = None 

382 

383 def _parse_content_type(self) -> None: 

384 if self._parsed_content_type is None: 

385 self._parsed_content_type = parse_options_header(self.content_type) 

386 

387 @cached_property 

388 def mimetype(self) -> str: 

389 """The value from :attr:`content_type`, lowercase. For example, 

390 ``text/HTML; charset=utf-8`` becomes ``text/html``. 

391 

392 Unlike :attr:`.Response.mimetype`, this will be ``""`` if not set, and 

393 will be lowercase rather than the exact value. 

394 """ 

395 self._parse_content_type() 

396 # Unlike content_type, this will be "" if the header isn't present, 

397 # instead of None. Checking ==/startswith/endswith is common for this, 

398 # so it's more convenient than None and essentially as accurate. 

399 return self._parsed_content_type[0].lower() # type: ignore[index] 

400 

401 @cached_property 

402 def mimetype_params(self) -> cabc.Mapping[str, str]: 

403 """The parameters from :attr:`content_type``. For example, 

404 ``text/html; charset=utf-8`` becomes ``{"charset": "utf-8"}``. 

405 """ 

406 self._parse_content_type() 

407 return self._parsed_content_type[1] # type: ignore[index] 

408 

409 @cached_property 

410 def pragma(self) -> HeaderSet: 

411 """The ``Pragma`` header. 

412 

413 A :class:`.HeaderSet`, empty if not set. 

414 

415 .. deprecated:: 3.2 

416 Use ``cache_control`` instead. Will be removed in Werkzeug 4.0. 

417 """ 

418 import warnings 

419 

420 warnings.warn( 

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

422 " Werkzeug 4.0. Use 'cache_control' instead.", 

423 DeprecationWarning, 

424 stacklevel=2, 

425 ) 

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

427 

428 # Accept 

429 

430 accept_mimetypes = header_property[MIMEAccept]( 

431 "Accept", 

432 load_func=MIMEAccept.from_header, 

433 read_only=True, 

434 doc="""The ``Accept`` header. The client's preferences for the content 

435 type of the response body. 

436 

437 A :class:`.MIMEAccept`, empty if not set. 

438 """, 

439 ) 

440 

441 @cached_property 

442 def accept_charsets(self) -> Accept: 

443 """The ``Accept-Charset`` header. The client's preferences for the text 

444 encoding of the response body. 

445 

446 An :class:`.Accept`, empty if not set. 

447 

448 .. deprecated:: 3.2 

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

450 it. Assume UTF-8. Will be removed in Werkzeug 4.0. 

451 """ 

452 import warnings 

453 

454 from ..datastructures.accept import _CharsetAccept 

455 

456 warnings.warn( 

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

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

459 " assumed.", 

460 DeprecationWarning, 

461 stacklevel=2, 

462 ) 

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

464 

465 accept_encodings = header_property[Accept]( 

466 "Accept-Encoding", 

467 load_func=Accept.from_header, 

468 read_only=True, 

469 doc="""The ``Accept-Encoding`` header. The client's preferences for a 

470 further encoding applied to the response body beyond its content type. 

471 

472 An :class:`.Accept`, empty if not set. 

473 """, 

474 ) 

475 

476 accept_languages = header_property[LanguageAccept]( 

477 "Accept-Language", 

478 load_func=LanguageAccept.from_header, 

479 read_only=True, 

480 doc="""The ``Accept-Language`` header. The client's preferences for the 

481 natural language of the response body. 

482 

483 A :class:`.LanguageAccept`, empty if not set. 

484 

485 .. versionchanged 0.5 

486 Returns ``LanguageAccept`` instead of ``Accept``. 

487 """, 

488 ) 

489 

490 # ETag 

491 

492 cache_control = header_property[RequestCacheControl]( 

493 "Cache-Control", 

494 load_func=RequestCacheControl.from_header, 

495 read_only=True, 

496 doc="""The ``Cache-Control`` header. Controls how the application should 

497 cache the request. 

498 

499 A :class:`.RequestCacheControl`, empty if not set. 

500 """, 

501 ) 

502 

503 if_match = header_property[ETagSet]( 

504 "If-Match", 

505 load_func=ETagSet.from_header, 

506 read_only=True, 

507 doc="""The ``If-Match`` header. If the response's ETag is present in 

508 this set, it returns ``412`` instead. 

509 

510 An :class:`.ETags`, empty if not set. 

511 """, 

512 ) 

513 

514 if_none_match = header_property[ETagSet]( 

515 "If-None-Match", 

516 load_func=ETagSet.from_header, 

517 read_only=True, 

518 doc="""The ``If-None-Match`` header. If the response's ETag is present 

519 in this set, it returns ``304`` for ``GET`` requests, or ``412`` for 

520 other requests. 

521 

522 An :class:`.ETags`, empty if not set. 

523 """, 

524 ) 

525 

526 if_modified_since = header_property[datetime | None]( 

527 "If-Modified-Since", 

528 load_func=parse_date, 

529 read_only=True, 

530 doc="""The ``If-Modified-Since`` header. If the response's modification 

531 time is not more recent than this, it returns ``304`` instead. 

532 

533 A :class:`~datetime.datetime`, or ``None`` if not set. 

534 

535 .. versionchanged:: 2.0 

536 The datetime object is timezone-aware. 

537 """, 

538 ) 

539 

540 if_unmodified_since = header_property[datetime | None]( 

541 "If-Unmodified-Since", 

542 load_func=parse_date, 

543 read_only=True, 

544 doc="""The ``If-Unmodified-Since`` header. If the response's 

545 modification time is more recent than this, it returns ``412`` instead. 

546 

547 A :class:`~datetime.datetime`, or ``None`` if not set. 

548 

549 .. versionchanged:: 2.0 

550 The datetime object is timezone-aware. 

551 """, 

552 ) 

553 

554 if_range = header_property[IfRange]( 

555 "If-Range", 

556 load_func=IfRange.from_header, 

557 read_only=True, 

558 doc="""The ``If-Range`` header. If the response does not satisfy the 

559 condition, it ignores the ``Range`` header. 

560 

561 An :class:`.IfRange`, empty if not set. 

562 

563 .. versionchanged:: 3.2 

564 A weak ETag is discarded. 

565 

566 .. versionchanged:: 2.0 

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

568 

569 .. versionadded:: 0.7 

570 """, 

571 ) 

572 

573 range = header_property[Range | None]( 

574 "Range", 

575 load_func=Range.from_header, 

576 read_only=True, 

577 doc="""The ``Range`` header. Partial ranges to return instead of the 

578 full representation. 

579 

580 A :class:`.Range`, or ``None`` if not set. 

581 

582 .. versionadded:: 0.7 

583 """, 

584 ) 

585 

586 # User Agent 

587 

588 @cached_property 

589 def user_agent(self) -> str | None: 

590 """The ``User-Agent`` header. Identifies the client application to some 

591 degree. There are libraries that can parse this value, but it is 

592 generally a bad idea to change the response based on it. 

593 

594 A ``str``, or ``None`` if not set. 

595 

596 .. versionchanged:: 3.2 

597 This is a string. ``UserAgent`` and ``user_agent_class`` are 

598 deprecated. Parse this directly if needed. 

599 

600 .. versionchanged:: 2.1 

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

602 subclass to parse data from the string. 

603 """ 

604 value = self.headers.get("User-Agent", "") 

605 

606 if self.user_agent_class is not None: 

607 import warnings 

608 

609 warnings.warn( 

610 "Setting 'Request.user_agent_class' is deprecated and will be" 

611 " removed in Werkzeug 4.0. 'user_agent' is a string and can be" 

612 " parsed directly if needed.", 

613 DeprecationWarning, 

614 stacklevel=2, 

615 ) 

616 return self.user_agent_class(value) 

617 

618 return _UserAgent(value) 

619 

620 # Authorization 

621 

622 authorization = header_property[Authorization | None]( 

623 "Authorization", 

624 load_func=Authorization.from_header, 

625 read_only=True, 

626 doc="""The ``Authorization`` header. Credentials used when accessing a 

627 protected part of the application. 

628 

629 An :class:`.Authorization`, or ``None`` if not set. 

630 

631 .. versionchanged:: 2.3 

632 The ``Authorization`` class is no longer a ``dict``. The ``token`` 

633 attribute was added for auth schemes that use a token instead of 

634 parameters. 

635 """, 

636 ) 

637 

638 # CORS 

639 

640 origin = header_property[str | None]( 

641 "Origin", 

642 read_only=True, 

643 doc="""The ``Origin`` header. The scheme, hostname, and port of the 

644 location the client made the request from. 

645 

646 A ``str``, or ``None`` if not set. 

647 

648 Set :attr:`.Response.access_control_allow_origin` to indicate that this 

649 origin is allowed. 

650 """, 

651 ) 

652 

653 access_control_request_headers = header_property[HeaderSet]( 

654 "Access-Control-Request-Headers", 

655 load_func=HeaderSet.from_header, 

656 read_only=True, 

657 doc="""The ``Access-Control-Request-Headers`` header. Sent in a 

658 preflight request to indicate which headers will be sent in the 

659 cross-origin request. 

660 

661 A :class:`.HeaderSet`, empty if not set. 

662 

663 Set :attr:`.Response.access_control_allow_headers` to indicate which 

664 headers are allowed. 

665 """, 

666 ) 

667 

668 access_control_request_method = header_property[str | None]( 

669 "Access-Control-Request-Method", 

670 read_only=True, 

671 doc="""The ``Access-Control-Request-Method`` header. Sent in a 

672 preflight request to indicate which method will be used for the 

673 cross-origin request. 

674 

675 A ``str``, or ``None`` if not set. 

676 

677 Set :attr:`.Response.access_control_allow_methods` to indicate which 

678 methods are allowed. 

679 """, 

680 ) 

681 

682 sec_fetch_site = header_property[SecFetchSite | None]( 

683 "Sec-Fetch-Site", 

684 load_func=SecFetchSite, 

685 read_only=True, 

686 doc="""The ``Sec-Fetch-Site`` header. The relationship between the 

687 client's current URL and the origin of the requested resource. 

688 

689 A member of :class:`.SecFetchSite`, or ``None`` if not set. 

690 

691 .. versionadded:: 3.2 

692 """, 

693 ) 

694 

695 sec_fetch_mode = header_property[SecFetchMode | None]( 

696 "Sec-Fetch-Mode", 

697 load_func=SecFetchMode, 

698 read_only=True, 

699 doc="""The ``Sec-Fetch-Mode`` header. Distinguishes between requests 

700 originating from a user navigating between HTML pages, and requests to 

701 load images and other resources. 

702 

703 A member of :class:`.SecFetchMode`, or ``None`` if not set. 

704 

705 .. versionadded:: 3.2 

706 """, 

707 ) 

708 

709 sec_fetch_user = header_property[bool]( 

710 "Sec-Fetch-User", 

711 default=False, 

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

713 read_only=True, 

714 doc="""The ``Sec-Fetch-User`` header. Whether a navigation request was 

715 originated by the user. 

716 

717 A ``bool``, ``False`` if not set. 

718 

719 .. versionadded:: 3.2 

720 """, 

721 ) 

722 

723 sec_fetch_dest = header_property[SecFetchDest | None]( 

724 "Sec-Fetch-Dest", 

725 load_func=SecFetchDest, 

726 read_only=True, 

727 doc="""The ``Sec-Fetch-Dest`` header. How the response to the request 

728 is expected to be used. 

729 

730 A member of :class:`.SecFetchDest`, or ``None`` if not set. 

731 

732 .. versionadded:: 3.2 

733 """, 

734 ) 

735 

736 @property 

737 def is_json(self) -> bool: 

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

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

740 """ 

741 mt = self.mimetype 

742 return ( 

743 mt == "application/json" 

744 or mt.startswith("application/") 

745 and mt.endswith("+json") 

746 )