Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/tornado/web.py: 22%

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

1491 statements  

1# 

2# Copyright 2009 Facebook 

3# 

4# Licensed under the Apache License, Version 2.0 (the "License"); you may 

5# not use this file except in compliance with the License. You may obtain 

6# a copy of the License at 

7# 

8# http://www.apache.org/licenses/LICENSE-2.0 

9# 

10# Unless required by applicable law or agreed to in writing, software 

11# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 

12# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 

13# License for the specific language governing permissions and limitations 

14# under the License. 

15 

16"""``tornado.web`` provides a simple web framework with asynchronous 

17features that allow it to scale to large numbers of open connections, 

18making it ideal for `long polling 

19<http://en.wikipedia.org/wiki/Push_technology#Long_polling>`_. 

20 

21Here is a simple "Hello, world" example app: 

22 

23.. testcode:: 

24 

25 import asyncio 

26 import tornado 

27 

28 class MainHandler(tornado.web.RequestHandler): 

29 def get(self): 

30 self.write("Hello, world") 

31 

32 async def main(): 

33 application = tornado.web.Application([ 

34 (r"/", MainHandler), 

35 ]) 

36 application.listen(8888) 

37 await asyncio.Event().wait() 

38 

39 if __name__ == "__main__": 

40 asyncio.run(main()) 

41 

42See the :doc:`guide` for additional information. 

43 

44Thread-safety notes 

45------------------- 

46 

47In general, methods on `RequestHandler` and elsewhere in Tornado are 

48not thread-safe. In particular, methods such as 

49`~RequestHandler.write()`, `~RequestHandler.finish()`, and 

50`~RequestHandler.flush()` must only be called from the main thread. If 

51you use multiple threads it is important to use `.IOLoop.add_callback` 

52to transfer control back to the main thread before finishing the 

53request, or to limit your use of other threads to 

54`.IOLoop.run_in_executor` and ensure that your callbacks running in 

55the executor do not refer to Tornado objects. 

56 

57""" 

58 

59import base64 

60import binascii 

61import datetime 

62import email.utils 

63import functools 

64import gzip 

65import hashlib 

66import hmac 

67import http.cookies 

68from inspect import isclass 

69from io import BytesIO 

70import mimetypes 

71import numbers 

72import os.path 

73import re 

74import socket 

75import stat 

76import sys 

77import threading 

78import time 

79import warnings 

80import tornado 

81import traceback 

82import types 

83import urllib.parse 

84from urllib.parse import urlencode 

85 

86from tornado.concurrent import Future, future_set_result_unless_cancelled 

87from tornado import escape 

88from tornado import gen 

89from tornado.httpserver import HTTPServer 

90from tornado import httputil 

91from tornado import iostream 

92from tornado import locale 

93from tornado.log import access_log, app_log, gen_log 

94from tornado import template 

95from tornado.escape import utf8, _unicode 

96from tornado.routing import ( 

97 AnyMatches, 

98 DefaultHostMatches, 

99 HostMatches, 

100 ReversibleRouter, 

101 Rule, 

102 ReversibleRuleRouter, 

103 URLSpec, 

104 _RuleList, 

105) 

106from tornado.util import ObjectDict, unicode_type, _websocket_mask 

107 

108url = URLSpec 

109 

110from typing import ( 

111 Dict, 

112 Any, 

113 Union, 

114 Optional, 

115 Awaitable, 

116 Tuple, 

117 List, 

118 Callable, 

119 Iterable, 

120 Generator, 

121 Type, 

122 TypeVar, 

123 cast, 

124 overload, 

125) 

126from types import TracebackType 

127import typing 

128 

129if typing.TYPE_CHECKING: 

130 from typing import Set # noqa: F401 

131 

132 

133# The following types are accepted by RequestHandler.set_header 

134# and related methods. 

135_HeaderTypes = Union[bytes, unicode_type, int, numbers.Integral, datetime.datetime] 

136 

137_CookieSecretTypes = Union[str, bytes, Dict[int, str], Dict[int, bytes]] 

138 

139 

140MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1 

141"""The oldest signed value version supported by this version of Tornado. 

142 

143Signed values older than this version cannot be decoded. 

144 

145.. versionadded:: 3.2.1 

146""" 

147 

148MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2 

149"""The newest signed value version supported by this version of Tornado. 

150 

151Signed values newer than this version cannot be decoded. 

152 

153.. versionadded:: 3.2.1 

154""" 

155 

156DEFAULT_SIGNED_VALUE_VERSION = 2 

157"""The signed value version produced by `.RequestHandler.create_signed_value`. 

158 

159May be overridden by passing a ``version`` keyword argument. 

160 

161.. versionadded:: 3.2.1 

162""" 

163 

164DEFAULT_SIGNED_VALUE_MIN_VERSION = 1 

165"""The oldest signed value accepted by `.RequestHandler.get_signed_cookie`. 

166 

167May be overridden by passing a ``min_version`` keyword argument. 

168 

169.. versionadded:: 3.2.1 

170""" 

171 

172 

173class _ArgDefaultMarker: 

174 pass 

175 

176 

177_ARG_DEFAULT = _ArgDefaultMarker() 

178 

179 

180class RequestHandler: 

181 """Base class for HTTP request handlers. 

182 

183 Subclasses must define at least one of the methods defined in the 

184 "Entry points" section below. 

185 

186 Applications should not construct `RequestHandler` objects 

187 directly and subclasses should not override ``__init__`` (override 

188 `~RequestHandler.initialize` instead). 

189 

190 """ 

191 

192 SUPPORTED_METHODS: Tuple[str, ...] = ( 

193 "GET", 

194 "HEAD", 

195 "POST", 

196 "DELETE", 

197 "PATCH", 

198 "PUT", 

199 "OPTIONS", 

200 ) 

201 

202 _template_loaders = {} # type: Dict[str, template.BaseLoader] 

203 _template_loader_lock = threading.Lock() 

204 _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]") 

205 

206 _stream_request_body = False 

207 

208 # Will be set in _execute. 

209 _transforms = None # type: List[OutputTransform] 

210 path_args = None # type: List[str] 

211 path_kwargs = None # type: Dict[str, str] 

212 

213 def __init__( 

214 self, 

215 application: "Application", 

216 request: httputil.HTTPServerRequest, 

217 **kwargs: Any, 

218 ) -> None: 

219 super().__init__() 

220 

221 self.application = application 

222 self.request = request 

223 self._headers_written = False 

224 self._finished = False 

225 self._auto_finish = True 

226 self._prepared_future = None 

227 self.ui = ObjectDict( 

228 (n, self._ui_method(m)) for n, m in application.ui_methods.items() 

229 ) 

230 # UIModules are available as both `modules` and `_tt_modules` in the 

231 # template namespace. Historically only `modules` was available 

232 # but could be clobbered by user additions to the namespace. 

233 # The template {% module %} directive looks in `_tt_modules` to avoid 

234 # possible conflicts. 

235 self.ui["_tt_modules"] = _UIModuleNamespace(self, application.ui_modules) 

236 self.ui["modules"] = self.ui["_tt_modules"] 

237 self.clear() 

238 assert self.request.connection is not None 

239 # TODO: need to add set_close_callback to HTTPConnection interface 

240 self.request.connection.set_close_callback( # type: ignore 

241 self.on_connection_close 

242 ) 

243 self.initialize(**kwargs) # type: ignore 

244 

245 def _initialize(self) -> None: 

246 pass 

247 

248 initialize = _initialize # type: Callable[..., None] 

249 """Hook for subclass initialization. Called for each request. 

250 

251 A dictionary passed as the third argument of a ``URLSpec`` will be 

252 supplied as keyword arguments to ``initialize()``. 

253 

254 Example:: 

255 

256 class ProfileHandler(RequestHandler): 

257 def initialize(self, database): 

258 self.database = database 

259 

260 def get(self, username): 

261 ... 

262 

263 app = Application([ 

264 (r'/user/(.*)', ProfileHandler, dict(database=database)), 

265 ]) 

266 """ 

267 

268 @property 

269 def settings(self) -> Dict[str, Any]: 

270 """An alias for `self.application.settings <Application.settings>`.""" 

271 return self.application.settings 

272 

273 def _unimplemented_method(self, *args: str, **kwargs: str) -> None: 

274 raise HTTPError(405) 

275 

276 head = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

277 get = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

278 post = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

279 delete = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

280 patch = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

281 put = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

282 options = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 

283 

284 def prepare(self) -> Optional[Awaitable[None]]: 

285 """Called at the beginning of a request before `get`/`post`/etc. 

286 

287 Override this method to perform common initialization regardless 

288 of the request method. There is no guarantee that ``prepare`` will 

289 be called if an error occurs that is handled by the framework. 

290 

291 Asynchronous support: Use ``async def`` or decorate this method with 

292 `.gen.coroutine` to make it asynchronous. 

293 If this method returns an ``Awaitable`` execution will not proceed 

294 until the ``Awaitable`` is done. 

295 

296 .. versionadded:: 3.1 

297 Asynchronous support. 

298 """ 

299 pass 

300 

301 def on_finish(self) -> None: 

302 """Called after the end of a request. 

303 

304 Override this method to perform cleanup, logging, etc. This method is primarily intended as 

305 a counterpart to `prepare`. However, there are a few error cases where ``on_finish`` may be 

306 called when ``prepare`` has not. (These are considered bugs and may be fixed in the future, 

307 but for now you may need to check to see if the initialization work done in ``prepare`` has 

308 occurred) 

309 

310 ``on_finish`` may not produce any output, as it is called after the response has been sent 

311 to the client. 

312 """ 

313 pass 

314 

315 def on_connection_close(self) -> None: 

316 """Called in async handlers if the client closed the connection. 

317 

318 Override this to clean up resources associated with 

319 long-lived connections. Note that this method is called only if 

320 the connection was closed during asynchronous processing; if you 

321 need to do cleanup after every request override `on_finish` 

322 instead. 

323 

324 Proxies may keep a connection open for a time (perhaps 

325 indefinitely) after the client has gone away, so this method 

326 may not be called promptly after the end user closes their 

327 connection. 

328 """ 

329 if _has_stream_request_body(self.__class__): 

330 if not self.request._body_future.done(): 

331 self.request._body_future.set_exception(iostream.StreamClosedError()) 

332 self.request._body_future.exception() 

333 

334 def clear(self) -> None: 

335 """Resets all headers and content for this response.""" 

336 self._headers = httputil.HTTPHeaders( 

337 { 

338 "Server": "TornadoServer/%s" % tornado.version, 

339 "Content-Type": "text/html; charset=UTF-8", 

340 "Date": httputil.format_timestamp(time.time()), 

341 } 

342 ) 

343 self.set_default_headers() 

344 self._write_buffer = [] # type: List[bytes] 

345 self._status_code = 200 

346 self._reason = httputil.responses[200] 

347 

348 def set_default_headers(self) -> None: 

349 """Override this to set HTTP headers at the beginning of the request. 

350 

351 For example, this is the place to set a custom ``Server`` header. 

352 Note that setting such headers in the normal flow of request 

353 processing may not do what you want, since headers may be reset 

354 during error handling. 

355 """ 

356 pass 

357 

358 def set_status(self, status_code: int, reason: Optional[str] = None) -> None: 

359 """Sets the status code for our response. 

360 

361 :arg int status_code: Response status code. 

362 :arg str reason: Human-readable reason phrase describing the status 

363 code (for example, the "Not Found" in ``HTTP/1.1 404 Not Found``). 

364 Normally determined automatically from `http.client.responses`; this 

365 argument should only be used if you need to use a non-standard 

366 status code. 

367 

368 .. versionchanged:: 5.0 

369 

370 No longer validates that the response code is in 

371 `http.client.responses`. 

372 """ 

373 self._status_code = status_code 

374 if reason is not None: 

375 if "<" in reason or not httputil._ABNF.reason_phrase.fullmatch(reason): 

376 # Logically this would be better as an exception, but this method 

377 # is called on error-handling paths that would need some refactoring 

378 # to tolerate internal errors cleanly. 

379 # 

380 # The check for "<" is a defense-in-depth against XSS attacks (we also 

381 # escape the reason when rendering error pages). 

382 reason = "Unknown" 

383 self._reason = escape.native_str(reason) 

384 else: 

385 self._reason = httputil.responses.get(status_code, "Unknown") 

386 

387 def get_status(self) -> int: 

388 """Returns the status code for our response.""" 

389 return self._status_code 

390 

391 def set_header(self, name: str, value: _HeaderTypes) -> None: 

392 """Sets the given response header name and value. 

393 

394 All header values are converted to strings (`datetime` objects 

395 are formatted according to the HTTP specification for the 

396 ``Date`` header). 

397 

398 """ 

399 self._headers[name] = self._convert_header_value(value) 

400 

401 def add_header(self, name: str, value: _HeaderTypes) -> None: 

402 """Adds the given response header and value. 

403 

404 Unlike `set_header`, `add_header` may be called multiple times 

405 to return multiple values for the same header. 

406 """ 

407 self._headers.add(name, self._convert_header_value(value)) 

408 

409 def clear_header(self, name: str) -> None: 

410 """Clears an outgoing header, undoing a previous `set_header` call. 

411 

412 Note that this method does not apply to multi-valued headers 

413 set by `add_header`. 

414 """ 

415 if name in self._headers: 

416 del self._headers[name] 

417 

418 # https://www.rfc-editor.org/rfc/rfc9110#name-field-values 

419 _VALID_HEADER_CHARS = re.compile(r"[\x09\x20-\x7e\x80-\xff]*") 

420 

421 def _convert_header_value(self, value: _HeaderTypes) -> str: 

422 # Convert the input value to a str. This type check is a bit 

423 # subtle: The bytes case only executes on python 3, and the 

424 # unicode case only executes on python 2, because the other 

425 # cases are covered by the first match for str. 

426 if isinstance(value, str): 

427 retval = value 

428 elif isinstance(value, bytes): 

429 # Non-ascii characters in headers are not well supported, 

430 # but if you pass bytes, use latin1 so they pass through as-is. 

431 retval = value.decode("latin1") 

432 elif isinstance(value, numbers.Integral): 

433 # return immediately since we know the converted value will be safe 

434 return str(value) 

435 elif isinstance(value, datetime.datetime): 

436 return httputil.format_timestamp(value) 

437 else: 

438 raise TypeError("Unsupported header value %r" % value) 

439 # If \n is allowed into the header, it is possible to inject 

440 # additional headers or split the request. 

441 if RequestHandler._VALID_HEADER_CHARS.fullmatch(retval) is None: 

442 raise ValueError("Unsafe header value %r", retval) 

443 return retval 

444 

445 @overload 

446 def get_argument(self, name: str, default: str, strip: bool = True) -> str: 

447 pass 

448 

449 @overload 

450 def get_argument( # noqa: F811 

451 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True 

452 ) -> str: 

453 pass 

454 

455 @overload 

456 def get_argument( # noqa: F811 

457 self, name: str, default: None, strip: bool = True 

458 ) -> Optional[str]: 

459 pass 

460 

461 def get_argument( # noqa: F811 

462 self, 

463 name: str, 

464 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 

465 strip: bool = True, 

466 ) -> Optional[str]: 

467 """Returns the value of the argument with the given name. 

468 

469 If default is not provided, the argument is considered to be 

470 required, and we raise a `MissingArgumentError` if it is missing. 

471 

472 If the argument appears in the request more than once, we return the 

473 last value. 

474 

475 This method searches both the query and body arguments. 

476 """ 

477 return self._get_argument(name, default, self.request.arguments, strip) 

478 

479 def get_arguments(self, name: str, strip: bool = True) -> List[str]: 

480 """Returns a list of the arguments with the given name. 

481 

482 If the argument is not present, returns an empty list. 

483 

484 This method searches both the query and body arguments. 

485 """ 

486 

487 # Make sure `get_arguments` isn't accidentally being called with a 

488 # positional argument that's assumed to be a default (like in 

489 # `get_argument`.) 

490 assert isinstance(strip, bool) 

491 

492 return self._get_arguments(name, self.request.arguments, strip) 

493 

494 @overload 

495 def get_body_argument(self, name: str, default: str, strip: bool = True) -> str: 

496 pass 

497 

498 @overload 

499 def get_body_argument( # noqa: F811 

500 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True 

501 ) -> str: 

502 pass 

503 

504 @overload 

505 def get_body_argument( # noqa: F811 

506 self, name: str, default: None, strip: bool = True 

507 ) -> Optional[str]: 

508 pass 

509 

510 def get_body_argument( # noqa: F811 

511 self, 

512 name: str, 

513 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 

514 strip: bool = True, 

515 ) -> Optional[str]: 

516 """Returns the value of the argument with the given name 

517 from the request body. 

518 

519 If default is not provided, the argument is considered to be 

520 required, and we raise a `MissingArgumentError` if it is missing. 

521 

522 If the argument appears in the url more than once, we return the 

523 last value. 

524 

525 .. versionadded:: 3.2 

526 """ 

527 return self._get_argument(name, default, self.request.body_arguments, strip) 

528 

529 def get_body_arguments(self, name: str, strip: bool = True) -> List[str]: 

530 """Returns a list of the body arguments with the given name. 

531 

532 If the argument is not present, returns an empty list. 

533 

534 .. versionadded:: 3.2 

535 """ 

536 return self._get_arguments(name, self.request.body_arguments, strip) 

537 

538 @overload 

539 def get_query_argument(self, name: str, default: str, strip: bool = True) -> str: 

540 pass 

541 

542 @overload 

543 def get_query_argument( # noqa: F811 

544 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True 

545 ) -> str: 

546 pass 

547 

548 @overload 

549 def get_query_argument( # noqa: F811 

550 self, name: str, default: None, strip: bool = True 

551 ) -> Optional[str]: 

552 pass 

553 

554 def get_query_argument( # noqa: F811 

555 self, 

556 name: str, 

557 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 

558 strip: bool = True, 

559 ) -> Optional[str]: 

560 """Returns the value of the argument with the given name 

561 from the request query string. 

562 

563 If default is not provided, the argument is considered to be 

564 required, and we raise a `MissingArgumentError` if it is missing. 

565 

566 If the argument appears in the url more than once, we return the 

567 last value. 

568 

569 .. versionadded:: 3.2 

570 """ 

571 return self._get_argument(name, default, self.request.query_arguments, strip) 

572 

573 def get_query_arguments(self, name: str, strip: bool = True) -> List[str]: 

574 """Returns a list of the query arguments with the given name. 

575 

576 If the argument is not present, returns an empty list. 

577 

578 .. versionadded:: 3.2 

579 """ 

580 return self._get_arguments(name, self.request.query_arguments, strip) 

581 

582 def _get_argument( 

583 self, 

584 name: str, 

585 default: Union[None, str, _ArgDefaultMarker], 

586 source: Dict[str, List[bytes]], 

587 strip: bool = True, 

588 ) -> Optional[str]: 

589 args = self._get_arguments(name, source, strip=strip) 

590 if not args: 

591 if isinstance(default, _ArgDefaultMarker): 

592 raise MissingArgumentError(name) 

593 return default 

594 return args[-1] 

595 

596 def _get_arguments( 

597 self, name: str, source: Dict[str, List[bytes]], strip: bool = True 

598 ) -> List[str]: 

599 values = [] 

600 for v in source.get(name, []): 

601 s = self.decode_argument(v, name=name) 

602 if isinstance(s, unicode_type): 

603 # Get rid of any weird control chars (unless decoding gave 

604 # us bytes, in which case leave it alone) 

605 s = RequestHandler._remove_control_chars_regex.sub(" ", s) 

606 if strip: 

607 s = s.strip() 

608 values.append(s) 

609 return values 

610 

611 def decode_argument(self, value: bytes, name: Optional[str] = None) -> str: 

612 """Decodes an argument from the request. 

613 

614 The argument has been percent-decoded and is now a byte string. 

615 By default, this method decodes the argument as utf-8 and returns 

616 a unicode string, but this may be overridden in subclasses. 

617 

618 This method is used as a filter for both `get_argument()` and for 

619 values extracted from the url and passed to `get()`/`post()`/etc. 

620 

621 The name of the argument is provided if known, but may be None 

622 (e.g. for unnamed groups in the url regex). 

623 """ 

624 try: 

625 return _unicode(value) 

626 except UnicodeDecodeError: 

627 raise HTTPError( 

628 400, "Invalid unicode in {}: {!r}".format(name or "url", value[:40]) 

629 ) 

630 

631 @property 

632 def cookies(self) -> Dict[str, http.cookies.Morsel]: 

633 """An alias for 

634 `self.request.cookies <.httputil.HTTPServerRequest.cookies>`.""" 

635 return self.request.cookies 

636 

637 @overload 

638 def get_cookie(self, name: str, default: str) -> str: 

639 pass 

640 

641 @overload 

642 def get_cookie(self, name: str, default: None = None) -> Optional[str]: 

643 pass 

644 

645 def get_cookie(self, name: str, default: Optional[str] = None) -> Optional[str]: 

646 """Returns the value of the request cookie with the given name. 

647 

648 If the named cookie is not present, returns ``default``. 

649 

650 This method only returns cookies that were present in the request. 

651 It does not see the outgoing cookies set by `set_cookie` in this 

652 handler. 

653 """ 

654 if self.request.cookies is not None and name in self.request.cookies: 

655 return self.request.cookies[name].value 

656 return default 

657 

658 def set_cookie( 

659 self, 

660 name: str, 

661 value: Union[str, bytes], 

662 domain: Optional[str] = None, 

663 expires: Optional[Union[float, Tuple, datetime.datetime]] = None, 

664 path: str = "/", 

665 expires_days: Optional[float] = None, 

666 # Keyword-only args start here for historical reasons. 

667 *, 

668 max_age: Optional[int] = None, 

669 httponly: bool = False, 

670 secure: bool = False, 

671 samesite: Optional[str] = None, 

672 **kwargs: Any, 

673 ) -> None: 

674 """Sets an outgoing cookie name/value with the given options. 

675 

676 Newly-set cookies are not immediately visible via `get_cookie`; 

677 they are not present until the next request. 

678 

679 Most arguments are passed directly to `http.cookies.Morsel` directly. 

680 See https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie 

681 for more information. 

682 

683 ``expires`` may be a numeric timestamp as returned by `time.time`, 

684 a time tuple as returned by `time.gmtime`, or a 

685 `datetime.datetime` object. ``expires_days`` is provided as a convenience 

686 to set an expiration time in days from today (if both are set, ``expires`` 

687 is used). 

688 

689 .. deprecated:: 6.3 

690 Keyword arguments are currently accepted case-insensitively. 

691 In Tornado 7.0 this will be changed to only accept lowercase 

692 arguments. 

693 """ 

694 # The cookie library only accepts type str, in both python 2 and 3 

695 name = escape.native_str(name) 

696 value = escape.native_str(value) 

697 if re.search(r"[\x00-\x20]", value): 

698 # Legacy check for control characters in cookie values. This check is no longer needed 

699 # since the cookie library escapes these characters correctly now. It will be removed 

700 # in the next feature release. 

701 raise ValueError(f"Invalid cookie {name!r}: {value!r}") 

702 for attr_name, attr_value in [ 

703 ("name", name), 

704 ("domain", domain), 

705 ("path", path), 

706 ("samesite", samesite), 

707 ]: 

708 # Cookie attributes may not contain control characters or semicolons (except when 

709 # escaped in the value). A check for control characters was added to the http.cookies 

710 # library in a Feb 2026 security release; as of March it still does not check for 

711 # semicolons. 

712 # 

713 # When a semicolon check is added to the standard library (and the release has had time 

714 # for adoption), this check may be removed, but be mindful of the fact that this may 

715 # change the timing of the exception (to the generation of the Set-Cookie header in 

716 # flush()). We m 

717 if attr_value is not None and re.search(r"[\x00-\x20\x3b\x7f]", attr_value): 

718 raise http.cookies.CookieError( 

719 f"Invalid cookie attribute {attr_name}={attr_value!r} for cookie {name!r}" 

720 ) 

721 for k, v in kwargs.items(): 

722 # Also check for disallowed characters in deprecated kwargs. 

723 if re.search(r"[\x00-\x20\x3b\x7f]", str(v)): 

724 raise http.cookies.CookieError( 

725 f"Invalid cookie attribute {k}={v!r} for cookie {name!r}" 

726 ) 

727 if not hasattr(self, "_new_cookie"): 

728 self._new_cookie = ( 

729 http.cookies.SimpleCookie() 

730 ) # type: http.cookies.SimpleCookie 

731 if name in self._new_cookie: 

732 del self._new_cookie[name] 

733 self._new_cookie[name] = value 

734 morsel = self._new_cookie[name] 

735 if domain: 

736 morsel["domain"] = domain 

737 if expires_days is not None and not expires: 

738 expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta( 

739 days=expires_days 

740 ) 

741 if expires: 

742 morsel["expires"] = httputil.format_timestamp(expires) 

743 if path: 

744 morsel["path"] = path 

745 if max_age: 

746 # Note change from _ to -. 

747 morsel["max-age"] = str(max_age) 

748 if httponly: 

749 # Note that SimpleCookie ignores the value here. The presense of an 

750 # httponly (or secure) key is treated as true. 

751 morsel["httponly"] = True 

752 if secure: 

753 morsel["secure"] = True 

754 if samesite: 

755 morsel["samesite"] = samesite 

756 if kwargs: 

757 # The setitem interface is case-insensitive, so continue to support 

758 # kwargs for backwards compatibility until we can remove deprecated 

759 # features. 

760 for k, v in kwargs.items(): 

761 morsel[k] = v 

762 warnings.warn( 

763 f"Deprecated arguments to set_cookie: {set(kwargs.keys())} " 

764 "(should be lowercase)", 

765 DeprecationWarning, 

766 ) 

767 

768 def clear_cookie(self, name: str, **kwargs: Any) -> None: 

769 """Deletes the cookie with the given name. 

770 

771 This method accepts the same arguments as `set_cookie`, except for 

772 ``expires`` and ``max_age``. Clearing a cookie requires the same 

773 ``domain`` and ``path`` arguments as when it was set. In some cases the 

774 ``samesite`` and ``secure`` arguments are also required to match. Other 

775 arguments are ignored. 

776 

777 Similar to `set_cookie`, the effect of this method will not be 

778 seen until the following request. 

779 

780 .. versionchanged:: 6.3 

781 

782 Now accepts all keyword arguments that ``set_cookie`` does. 

783 The ``samesite`` and ``secure`` flags have recently become 

784 required for clearing ``samesite="none"`` cookies. 

785 """ 

786 for excluded_arg in ["expires", "max_age"]: 

787 if excluded_arg in kwargs: 

788 raise TypeError( 

789 f"clear_cookie() got an unexpected keyword argument '{excluded_arg}'" 

790 ) 

791 expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta( 

792 days=365 

793 ) 

794 self.set_cookie(name, value="", expires=expires, **kwargs) 

795 

796 def clear_all_cookies(self, **kwargs: Any) -> None: 

797 """Attempt to delete all the cookies the user sent with this request. 

798 

799 See `clear_cookie` for more information on keyword arguments. Due to 

800 limitations of the cookie protocol, it is impossible to determine on the 

801 server side which values are necessary for the ``domain``, ``path``, 

802 ``samesite``, or ``secure`` arguments, this method can only be 

803 successful if you consistently use the same values for these arguments 

804 when setting cookies. 

805 

806 Similar to `set_cookie`, the effect of this method will not be seen 

807 until the following request. 

808 

809 .. versionchanged:: 3.2 

810 

811 Added the ``path`` and ``domain`` parameters. 

812 

813 .. versionchanged:: 6.3 

814 

815 Now accepts all keyword arguments that ``set_cookie`` does. 

816 

817 .. deprecated:: 6.3 

818 

819 The increasingly complex rules governing cookies have made it 

820 impossible for a ``clear_all_cookies`` method to work reliably 

821 since all we know about cookies are their names. Applications 

822 should generally use ``clear_cookie`` one at a time instead. 

823 """ 

824 for name in self.request.cookies: 

825 self.clear_cookie(name, **kwargs) 

826 

827 def set_signed_cookie( 

828 self, 

829 name: str, 

830 value: Union[str, bytes], 

831 expires_days: Optional[float] = 30, 

832 version: Optional[int] = None, 

833 **kwargs: Any, 

834 ) -> None: 

835 """Signs and timestamps a cookie so it cannot be forged. 

836 

837 You must specify the ``cookie_secret`` setting in your Application 

838 to use this method. It should be a long, random sequence of bytes 

839 to be used as the HMAC secret for the signature. 

840 

841 To read a cookie set with this method, use `get_signed_cookie()`. 

842 

843 Note that the ``expires_days`` parameter sets the lifetime of the 

844 cookie in the browser, but is independent of the ``max_age_days`` 

845 parameter to `get_signed_cookie`. 

846 A value of None limits the lifetime to the current browser session. 

847 

848 Secure cookies may contain arbitrary byte values, not just unicode 

849 strings (unlike regular cookies) 

850 

851 Similar to `set_cookie`, the effect of this method will not be 

852 seen until the following request. 

853 

854 .. versionchanged:: 3.2.1 

855 

856 Added the ``version`` argument. Introduced cookie version 2 

857 and made it the default. 

858 

859 .. versionchanged:: 6.3 

860 

861 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

862 avoid confusion with other uses of "secure" in cookie attributes 

863 and prefixes. The old name remains as an alias. 

864 """ 

865 self.set_cookie( 

866 name, 

867 self.create_signed_value(name, value, version=version), 

868 expires_days=expires_days, 

869 **kwargs, 

870 ) 

871 

872 set_secure_cookie = set_signed_cookie 

873 

874 def create_signed_value( 

875 self, name: str, value: Union[str, bytes], version: Optional[int] = None 

876 ) -> bytes: 

877 """Signs and timestamps a string so it cannot be forged. 

878 

879 Normally used via set_signed_cookie, but provided as a separate 

880 method for non-cookie uses. To decode a value not stored 

881 as a cookie use the optional value argument to get_signed_cookie. 

882 

883 .. versionchanged:: 3.2.1 

884 

885 Added the ``version`` argument. Introduced cookie version 2 

886 and made it the default. 

887 """ 

888 self.require_setting("cookie_secret", "secure cookies") 

889 secret = self.application.settings["cookie_secret"] 

890 key_version = None 

891 if isinstance(secret, dict): 

892 if self.application.settings.get("key_version") is None: 

893 raise Exception("key_version setting must be used for secret_key dicts") 

894 key_version = self.application.settings["key_version"] 

895 

896 return create_signed_value( 

897 secret, name, value, version=version, key_version=key_version 

898 ) 

899 

900 def get_signed_cookie( 

901 self, 

902 name: str, 

903 value: Optional[str] = None, 

904 max_age_days: float = 31, 

905 min_version: Optional[int] = None, 

906 ) -> Optional[bytes]: 

907 """Returns the given signed cookie if it validates, or None. 

908 

909 The decoded cookie value is returned as a byte string (unlike 

910 `get_cookie`). 

911 

912 Similar to `get_cookie`, this method only returns cookies that 

913 were present in the request. It does not see outgoing cookies set by 

914 `set_signed_cookie` in this handler. 

915 

916 .. versionchanged:: 3.2.1 

917 

918 Added the ``min_version`` argument. Introduced cookie version 2; 

919 both versions 1 and 2 are accepted by default. 

920 

921 .. versionchanged:: 6.3 

922 

923 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

924 avoid confusion with other uses of "secure" in cookie attributes 

925 and prefixes. The old name remains as an alias. 

926 

927 """ 

928 self.require_setting("cookie_secret", "secure cookies") 

929 if value is None: 

930 value = self.get_cookie(name) 

931 return decode_signed_value( 

932 self.application.settings["cookie_secret"], 

933 name, 

934 value, 

935 max_age_days=max_age_days, 

936 min_version=min_version, 

937 ) 

938 

939 get_secure_cookie = get_signed_cookie 

940 

941 def get_signed_cookie_key_version( 

942 self, name: str, value: Optional[str] = None 

943 ) -> Optional[int]: 

944 """Returns the signing key version of the secure cookie. 

945 

946 The version is returned as int. 

947 

948 .. versionchanged:: 6.3 

949 

950 Renamed from ``get_secure_cookie_key_version`` to 

951 ``set_signed_cookie_key_version`` to avoid confusion with other 

952 uses of "secure" in cookie attributes and prefixes. The old name 

953 remains as an alias. 

954 

955 """ 

956 self.require_setting("cookie_secret", "secure cookies") 

957 if value is None: 

958 value = self.get_cookie(name) 

959 if value is None: 

960 return None 

961 return get_signature_key_version(value) 

962 

963 get_secure_cookie_key_version = get_signed_cookie_key_version 

964 

965 def redirect( 

966 self, url: str, permanent: bool = False, status: Optional[int] = None 

967 ) -> None: 

968 """Sends a redirect to the given (optionally relative) URL. 

969 

970 If the ``status`` argument is specified, that value is used as the 

971 HTTP status code; otherwise either 301 (permanent) or 302 

972 (temporary) is chosen based on the ``permanent`` argument. 

973 The default is 302 (temporary). 

974 """ 

975 if self._headers_written: 

976 raise Exception("Cannot redirect after headers have been written") 

977 if status is None: 

978 status = 301 if permanent else 302 

979 else: 

980 assert isinstance(status, int) and 300 <= status <= 399 

981 self.set_status(status) 

982 self.set_header("Location", utf8(url)) 

983 self.finish() 

984 

985 def write(self, chunk: Union[str, bytes, dict]) -> None: 

986 """Writes the given chunk to the output buffer. 

987 

988 To write the output to the network, use the `flush()` method below. 

989 

990 If the given chunk is a dictionary, we write it as JSON and set 

991 the Content-Type of the response to be ``application/json``. 

992 (if you want to send JSON as a different ``Content-Type``, call 

993 ``set_header`` *after* calling ``write()``). 

994 

995 Note that lists are not converted to JSON because of a potential 

996 cross-site security vulnerability. All JSON output should be 

997 wrapped in a dictionary. More details at 

998 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and 

999 https://github.com/facebook/tornado/issues/1009 

1000 """ 

1001 if self._finished: 

1002 raise RuntimeError("Cannot write() after finish()") 

1003 if not isinstance(chunk, (bytes, unicode_type, dict)): 

1004 message = "write() only accepts bytes, unicode, and dict objects" 

1005 if isinstance(chunk, list): 

1006 message += ( 

1007 ". Lists not accepted for security reasons; see " 

1008 + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write" # noqa: E501 

1009 ) 

1010 raise TypeError(message) 

1011 if isinstance(chunk, dict): 

1012 chunk = escape.json_encode(chunk) 

1013 self.set_header("Content-Type", "application/json; charset=UTF-8") 

1014 chunk = utf8(chunk) 

1015 self._write_buffer.append(chunk) 

1016 

1017 def render(self, template_name: str, **kwargs: Any) -> "Future[None]": 

1018 """Renders the template with the given arguments as the response. 

1019 

1020 ``render()`` calls ``finish()``, so no other output methods can be called 

1021 after it. 

1022 

1023 Returns a `.Future` with the same semantics as the one returned by `finish`. 

1024 Awaiting this `.Future` is optional. 

1025 

1026 .. versionchanged:: 5.1 

1027 

1028 Now returns a `.Future` instead of ``None``. 

1029 """ 

1030 if self._finished: 

1031 raise RuntimeError("Cannot render() after finish()") 

1032 html = self.render_string(template_name, **kwargs) 

1033 

1034 # Insert the additional JS and CSS added by the modules on the page 

1035 js_embed = [] 

1036 js_files = [] 

1037 css_embed = [] 

1038 css_files = [] 

1039 html_heads = [] 

1040 html_bodies = [] 

1041 for module in getattr(self, "_active_modules", {}).values(): 

1042 embed_part = module.embedded_javascript() 

1043 if embed_part: 

1044 js_embed.append(utf8(embed_part)) 

1045 file_part = module.javascript_files() 

1046 if file_part: 

1047 if isinstance(file_part, (unicode_type, bytes)): 

1048 js_files.append(_unicode(file_part)) 

1049 else: 

1050 js_files.extend(file_part) 

1051 embed_part = module.embedded_css() 

1052 if embed_part: 

1053 css_embed.append(utf8(embed_part)) 

1054 file_part = module.css_files() 

1055 if file_part: 

1056 if isinstance(file_part, (unicode_type, bytes)): 

1057 css_files.append(_unicode(file_part)) 

1058 else: 

1059 css_files.extend(file_part) 

1060 head_part = module.html_head() 

1061 if head_part: 

1062 html_heads.append(utf8(head_part)) 

1063 body_part = module.html_body() 

1064 if body_part: 

1065 html_bodies.append(utf8(body_part)) 

1066 

1067 if js_files: 

1068 # Maintain order of JavaScript files given by modules 

1069 js = self.render_linked_js(js_files) 

1070 sloc = html.rindex(b"</body>") 

1071 html = html[:sloc] + utf8(js) + b"\n" + html[sloc:] 

1072 if js_embed: 

1073 js_bytes = self.render_embed_js(js_embed) 

1074 sloc = html.rindex(b"</body>") 

1075 html = html[:sloc] + js_bytes + b"\n" + html[sloc:] 

1076 if css_files: 

1077 css = self.render_linked_css(css_files) 

1078 hloc = html.index(b"</head>") 

1079 html = html[:hloc] + utf8(css) + b"\n" + html[hloc:] 

1080 if css_embed: 

1081 css_bytes = self.render_embed_css(css_embed) 

1082 hloc = html.index(b"</head>") 

1083 html = html[:hloc] + css_bytes + b"\n" + html[hloc:] 

1084 if html_heads: 

1085 hloc = html.index(b"</head>") 

1086 html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:] 

1087 if html_bodies: 

1088 hloc = html.index(b"</body>") 

1089 html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:] 

1090 return self.finish(html) 

1091 

1092 def render_linked_js(self, js_files: Iterable[str]) -> str: 

1093 """Default method used to render the final js links for the 

1094 rendered webpage. 

1095 

1096 Override this method in a sub-classed controller to change the output. 

1097 """ 

1098 paths = [] 

1099 unique_paths = set() # type: Set[str] 

1100 

1101 for path in js_files: 

1102 if not is_absolute(path): 

1103 path = self.static_url(path) 

1104 if path not in unique_paths: 

1105 paths.append(path) 

1106 unique_paths.add(path) 

1107 

1108 return "".join( 

1109 '<script src="' 

1110 + escape.xhtml_escape(p) 

1111 + '" type="text/javascript"></script>' 

1112 for p in paths 

1113 ) 

1114 

1115 def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes: 

1116 """Default method used to render the final embedded js for the 

1117 rendered webpage. 

1118 

1119 Override this method in a sub-classed controller to change the output. 

1120 """ 

1121 return ( 

1122 b'<script type="text/javascript">\n//<![CDATA[\n' 

1123 + b"\n".join(js_embed) 

1124 + b"\n//]]>\n</script>" 

1125 ) 

1126 

1127 def render_linked_css(self, css_files: Iterable[str]) -> str: 

1128 """Default method used to render the final css links for the 

1129 rendered webpage. 

1130 

1131 Override this method in a sub-classed controller to change the output. 

1132 """ 

1133 paths = [] 

1134 unique_paths = set() # type: Set[str] 

1135 

1136 for path in css_files: 

1137 if not is_absolute(path): 

1138 path = self.static_url(path) 

1139 if path not in unique_paths: 

1140 paths.append(path) 

1141 unique_paths.add(path) 

1142 

1143 return "".join( 

1144 '<link href="' + escape.xhtml_escape(p) + '" ' 

1145 'type="text/css" rel="stylesheet"/>' 

1146 for p in paths 

1147 ) 

1148 

1149 def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes: 

1150 """Default method used to render the final embedded css for the 

1151 rendered webpage. 

1152 

1153 Override this method in a sub-classed controller to change the output. 

1154 """ 

1155 return b'<style type="text/css">\n' + b"\n".join(css_embed) + b"\n</style>" 

1156 

1157 def render_string(self, template_name: str, **kwargs: Any) -> bytes: 

1158 """Generate the given template with the given arguments. 

1159 

1160 We return the generated byte string (in utf8). To generate and 

1161 write a template as a response, use render() above. 

1162 """ 

1163 # If no template_path is specified, use the path of the calling file 

1164 template_path = self.get_template_path() 

1165 if not template_path: 

1166 frame = sys._getframe(0) 

1167 web_file = frame.f_code.co_filename 

1168 while frame.f_code.co_filename == web_file and frame.f_back is not None: 

1169 frame = frame.f_back 

1170 assert frame.f_code.co_filename is not None 

1171 template_path = os.path.dirname(frame.f_code.co_filename) 

1172 with RequestHandler._template_loader_lock: 

1173 if template_path not in RequestHandler._template_loaders: 

1174 loader = self.create_template_loader(template_path) 

1175 RequestHandler._template_loaders[template_path] = loader 

1176 else: 

1177 loader = RequestHandler._template_loaders[template_path] 

1178 t = loader.load(template_name) 

1179 namespace = self.get_template_namespace() 

1180 namespace.update(kwargs) 

1181 return t.generate(**namespace) 

1182 

1183 def get_template_namespace(self) -> Dict[str, Any]: 

1184 """Returns a dictionary to be used as the default template namespace. 

1185 

1186 May be overridden by subclasses to add or modify values. 

1187 

1188 The results of this method will be combined with additional 

1189 defaults in the `tornado.template` module and keyword arguments 

1190 to `render` or `render_string`. 

1191 """ 

1192 namespace = dict( 

1193 handler=self, 

1194 request=self.request, 

1195 current_user=self.current_user, 

1196 locale=self.locale, 

1197 _=self.locale.translate, 

1198 pgettext=self.locale.pgettext, 

1199 static_url=self.static_url, 

1200 xsrf_form_html=self.xsrf_form_html, 

1201 reverse_url=self.reverse_url, 

1202 ) 

1203 namespace.update(self.ui) 

1204 return namespace 

1205 

1206 def create_template_loader(self, template_path: str) -> template.BaseLoader: 

1207 """Returns a new template loader for the given path. 

1208 

1209 May be overridden by subclasses. By default returns a 

1210 directory-based loader on the given path, using the 

1211 ``autoescape`` and ``template_whitespace`` application 

1212 settings. If a ``template_loader`` application setting is 

1213 supplied, uses that instead. 

1214 """ 

1215 settings = self.application.settings 

1216 if "template_loader" in settings: 

1217 return settings["template_loader"] 

1218 kwargs = {} 

1219 if "autoescape" in settings: 

1220 # autoescape=None means "no escaping", so we have to be sure 

1221 # to only pass this kwarg if the user asked for it. 

1222 kwargs["autoescape"] = settings["autoescape"] 

1223 if "template_whitespace" in settings: 

1224 kwargs["whitespace"] = settings["template_whitespace"] 

1225 return template.Loader(template_path, **kwargs) 

1226 

1227 def flush(self, include_footers: bool = False) -> "Future[None]": 

1228 """Flushes the current output buffer to the network. 

1229 

1230 .. versionchanged:: 4.0 

1231 Now returns a `.Future` if no callback is given. 

1232 

1233 .. versionchanged:: 6.0 

1234 

1235 The ``callback`` argument was removed. 

1236 """ 

1237 assert self.request.connection is not None 

1238 chunk = b"".join(self._write_buffer) 

1239 self._write_buffer = [] 

1240 if not self._headers_written: 

1241 self._headers_written = True 

1242 for transform in self._transforms: 

1243 assert chunk is not None 

1244 ( 

1245 self._status_code, 

1246 self._headers, 

1247 chunk, 

1248 ) = transform.transform_first_chunk( 

1249 self._status_code, self._headers, chunk, include_footers 

1250 ) 

1251 # Ignore the chunk and only write the headers for HEAD requests 

1252 if self.request.method == "HEAD": 

1253 chunk = b"" 

1254 

1255 # Finalize the cookie headers (which have been stored in a side 

1256 # object so an outgoing cookie could be overwritten before it 

1257 # is sent). 

1258 if hasattr(self, "_new_cookie"): 

1259 for cookie in self._new_cookie.values(): 

1260 self.add_header("Set-Cookie", cookie.OutputString(None)) 

1261 

1262 start_line = httputil.ResponseStartLine("", self._status_code, self._reason) 

1263 return self.request.connection.write_headers( 

1264 start_line, self._headers, chunk 

1265 ) 

1266 else: 

1267 for transform in self._transforms: 

1268 chunk = transform.transform_chunk(chunk, include_footers) 

1269 # Ignore the chunk and only write the headers for HEAD requests 

1270 if self.request.method != "HEAD": 

1271 return self.request.connection.write(chunk) 

1272 else: 

1273 future = Future() # type: Future[None] 

1274 future.set_result(None) 

1275 return future 

1276 

1277 def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]": 

1278 """Finishes this response, ending the HTTP request. 

1279 

1280 Passing a ``chunk`` to ``finish()`` is equivalent to passing that 

1281 chunk to ``write()`` and then calling ``finish()`` with no arguments. 

1282 

1283 Returns a `.Future` which may optionally be awaited to track the sending 

1284 of the response to the client. This `.Future` resolves when all the response 

1285 data has been sent, and raises an error if the connection is closed before all 

1286 data can be sent. 

1287 

1288 .. versionchanged:: 5.1 

1289 

1290 Now returns a `.Future` instead of ``None``. 

1291 """ 

1292 if self._finished: 

1293 raise RuntimeError("finish() called twice") 

1294 

1295 if chunk is not None: 

1296 self.write(chunk) 

1297 

1298 # Automatically support ETags and add the Content-Length header if 

1299 # we have not flushed any content yet. 

1300 if not self._headers_written: 

1301 if ( 

1302 self._status_code == 200 

1303 and self.request.method in ("GET", "HEAD") 

1304 and "Etag" not in self._headers 

1305 ): 

1306 self.set_etag_header() 

1307 if self.check_etag_header(): 

1308 self._write_buffer = [] 

1309 self.set_status(304) 

1310 if self._status_code in (204, 304) or (100 <= self._status_code < 200): 

1311 assert not self._write_buffer, ( 

1312 "Cannot send body with %s" % self._status_code 

1313 ) 

1314 self._clear_representation_headers() 

1315 elif "Content-Length" not in self._headers: 

1316 content_length = sum(len(part) for part in self._write_buffer) 

1317 self.set_header("Content-Length", content_length) 

1318 

1319 assert self.request.connection is not None 

1320 # Now that the request is finished, clear the callback we 

1321 # set on the HTTPConnection (which would otherwise prevent the 

1322 # garbage collection of the RequestHandler when there 

1323 # are keepalive connections) 

1324 self.request.connection.set_close_callback(None) # type: ignore 

1325 

1326 future = self.flush(include_footers=True) 

1327 self.request.connection.finish() 

1328 self._log() 

1329 self._finished = True 

1330 self.on_finish() 

1331 self._break_cycles() 

1332 return future 

1333 

1334 def detach(self) -> iostream.IOStream: 

1335 """Take control of the underlying stream. 

1336 

1337 Returns the underlying `.IOStream` object and stops all 

1338 further HTTP processing. Intended for implementing protocols 

1339 like websockets that tunnel over an HTTP handshake. 

1340 

1341 This method is only supported when HTTP/1.1 is used. 

1342 

1343 .. versionadded:: 5.1 

1344 """ 

1345 self._finished = True 

1346 # TODO: add detach to HTTPConnection? 

1347 return self.request.connection.detach() # type: ignore 

1348 

1349 def _break_cycles(self) -> None: 

1350 # Break up a reference cycle between this handler and the 

1351 # _ui_module closures to allow for faster GC on CPython. 

1352 self.ui = None # type: ignore 

1353 

1354 def send_error(self, status_code: int = 500, **kwargs: Any) -> None: 

1355 """Sends the given HTTP error code to the browser. 

1356 

1357 If `flush()` has already been called, it is not possible to send 

1358 an error, so this method will simply terminate the response. 

1359 If output has been written but not yet flushed, it will be discarded 

1360 and replaced with the error page. 

1361 

1362 Override `write_error()` to customize the error page that is returned. 

1363 Additional keyword arguments are passed through to `write_error`. 

1364 """ 

1365 if self._headers_written: 

1366 gen_log.error("Cannot send error response after headers written") 

1367 if not self._finished: 

1368 # If we get an error between writing headers and finishing, 

1369 # we are unlikely to be able to finish due to a 

1370 # Content-Length mismatch. Try anyway to release the 

1371 # socket. 

1372 try: 

1373 self.finish() 

1374 except Exception: 

1375 gen_log.error("Failed to flush partial response", exc_info=True) 

1376 return 

1377 self.clear() 

1378 

1379 reason = kwargs.get("reason") 

1380 if "exc_info" in kwargs: 

1381 exception = kwargs["exc_info"][1] 

1382 if isinstance(exception, HTTPError) and exception.reason: 

1383 reason = exception.reason 

1384 self.set_status(status_code, reason=reason) 

1385 try: 

1386 if status_code != 304: 

1387 self.write_error(status_code, **kwargs) 

1388 except Exception: 

1389 app_log.error("Uncaught exception in write_error", exc_info=True) 

1390 if not self._finished: 

1391 self.finish() 

1392 

1393 def write_error(self, status_code: int, **kwargs: Any) -> None: 

1394 """Override to implement custom error pages. 

1395 

1396 ``write_error`` may call `write`, `render`, `set_header`, etc 

1397 to produce output as usual. 

1398 

1399 If this error was caused by an uncaught exception (including 

1400 HTTPError), an ``exc_info`` triple will be available as 

1401 ``kwargs["exc_info"]``. Note that this exception may not be 

1402 the "current" exception for purposes of methods like 

1403 ``sys.exc_info()`` or ``traceback.format_exc``. 

1404 """ 

1405 if self.settings.get("serve_traceback") and "exc_info" in kwargs: 

1406 # in debug mode, try to send a traceback 

1407 self.set_header("Content-Type", "text/plain") 

1408 for line in traceback.format_exception(*kwargs["exc_info"]): 

1409 self.write(line) 

1410 self.finish() 

1411 else: 

1412 self.finish( 

1413 "<html><title>%(code)d: %(message)s</title>" 

1414 "<body>%(code)d: %(message)s</body></html>" 

1415 % {"code": status_code, "message": escape.xhtml_escape(self._reason)} 

1416 ) 

1417 

1418 @property 

1419 def locale(self) -> tornado.locale.Locale: 

1420 """The locale for the current session. 

1421 

1422 Determined by either `get_user_locale`, which you can override to 

1423 set the locale based on, e.g., a user preference stored in a 

1424 database, or `get_browser_locale`, which uses the ``Accept-Language`` 

1425 header. 

1426 

1427 .. versionchanged: 4.1 

1428 Added a property setter. 

1429 """ 

1430 if not hasattr(self, "_locale"): 

1431 loc = self.get_user_locale() 

1432 if loc is not None: 

1433 self._locale = loc 

1434 else: 

1435 self._locale = self.get_browser_locale() 

1436 assert self._locale 

1437 return self._locale 

1438 

1439 @locale.setter 

1440 def locale(self, value: tornado.locale.Locale) -> None: 

1441 self._locale = value 

1442 

1443 def get_user_locale(self) -> Optional[tornado.locale.Locale]: 

1444 """Override to determine the locale from the authenticated user. 

1445 

1446 If None is returned, we fall back to `get_browser_locale()`. 

1447 

1448 This method should return a `tornado.locale.Locale` object, 

1449 most likely obtained via a call like ``tornado.locale.get("en")`` 

1450 """ 

1451 return None 

1452 

1453 def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale: 

1454 """Determines the user's locale from ``Accept-Language`` header. 

1455 

1456 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 

1457 """ 

1458 if "Accept-Language" in self.request.headers: 

1459 languages = self.request.headers["Accept-Language"].split(",") 

1460 locales = [] 

1461 for language in languages: 

1462 parts = language.strip().split(";") 

1463 if len(parts) > 1 and parts[1].strip().startswith("q="): 

1464 try: 

1465 score = float(parts[1].strip()[2:]) 

1466 if score < 0: 

1467 raise ValueError() 

1468 except (ValueError, TypeError): 

1469 score = 0.0 

1470 else: 

1471 score = 1.0 

1472 if score > 0: 

1473 locales.append((parts[0], score)) 

1474 if locales: 

1475 locales.sort(key=lambda pair: pair[1], reverse=True) 

1476 codes = [loc[0] for loc in locales] 

1477 return locale.get(*codes) 

1478 return locale.get(default) 

1479 

1480 @property 

1481 def current_user(self) -> Any: 

1482 """The authenticated user for this request. 

1483 

1484 This is set in one of two ways: 

1485 

1486 * A subclass may override `get_current_user()`, which will be called 

1487 automatically the first time ``self.current_user`` is accessed. 

1488 `get_current_user()` will only be called once per request, 

1489 and is cached for future access:: 

1490 

1491 def get_current_user(self): 

1492 user_cookie = self.get_signed_cookie("user") 

1493 if user_cookie: 

1494 return json.loads(user_cookie) 

1495 return None 

1496 

1497 * It may be set as a normal variable, typically from an overridden 

1498 `prepare()`:: 

1499 

1500 @gen.coroutine 

1501 def prepare(self): 

1502 user_id_cookie = self.get_signed_cookie("user_id") 

1503 if user_id_cookie: 

1504 self.current_user = yield load_user(user_id_cookie) 

1505 

1506 Note that `prepare()` may be a coroutine while `get_current_user()` 

1507 may not, so the latter form is necessary if loading the user requires 

1508 asynchronous operations. 

1509 

1510 The user object may be any type of the application's choosing. 

1511 """ 

1512 if not hasattr(self, "_current_user"): 

1513 self._current_user = self.get_current_user() 

1514 return self._current_user 

1515 

1516 @current_user.setter 

1517 def current_user(self, value: Any) -> None: 

1518 self._current_user = value 

1519 

1520 def get_current_user(self) -> Any: 

1521 """Override to determine the current user from, e.g., a cookie. 

1522 

1523 This method may not be a coroutine. 

1524 """ 

1525 return None 

1526 

1527 def get_login_url(self) -> str: 

1528 """Override to customize the login URL based on the request. 

1529 

1530 By default, we use the ``login_url`` application setting. 

1531 """ 

1532 self.require_setting("login_url", "@tornado.web.authenticated") 

1533 return self.application.settings["login_url"] 

1534 

1535 def get_template_path(self) -> Optional[str]: 

1536 """Override to customize template path for each handler. 

1537 

1538 By default, we use the ``template_path`` application setting. 

1539 Return None to load templates relative to the calling file. 

1540 """ 

1541 return self.application.settings.get("template_path") 

1542 

1543 @property 

1544 def xsrf_token(self) -> bytes: 

1545 """The XSRF-prevention token for the current user/session. 

1546 

1547 To prevent cross-site request forgery, we set an '_xsrf' cookie 

1548 and include the same '_xsrf' value as an argument with all POST 

1549 requests. If the two do not match, we reject the form submission 

1550 as a potential forgery. 

1551 

1552 See http://en.wikipedia.org/wiki/Cross-site_request_forgery 

1553 

1554 This property is of type `bytes`, but it contains only ASCII 

1555 characters. If a character string is required, there is no 

1556 need to base64-encode it; just decode the byte string as 

1557 UTF-8. 

1558 

1559 .. versionchanged:: 3.2.2 

1560 The xsrf token will now be have a random mask applied in every 

1561 request, which makes it safe to include the token in pages 

1562 that are compressed. See http://breachattack.com for more 

1563 information on the issue fixed by this change. Old (version 1) 

1564 cookies will be converted to version 2 when this method is called 

1565 unless the ``xsrf_cookie_version`` `Application` setting is 

1566 set to 1. 

1567 

1568 .. versionchanged:: 4.3 

1569 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1570 used to supply additional cookie options (which will be 

1571 passed directly to `set_cookie`). For example, 

1572 ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)`` 

1573 will set the ``secure`` and ``httponly`` flags on the 

1574 ``_xsrf`` cookie. 

1575 """ 

1576 if not hasattr(self, "_xsrf_token"): 

1577 version, token, timestamp = self._get_raw_xsrf_token() 

1578 output_version = self.settings.get("xsrf_cookie_version", 2) 

1579 cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {}) 

1580 if output_version == 1: 

1581 self._xsrf_token = binascii.b2a_hex(token) 

1582 elif output_version == 2: 

1583 mask = os.urandom(4) 

1584 self._xsrf_token = b"|".join( 

1585 [ 

1586 b"2", 

1587 binascii.b2a_hex(mask), 

1588 binascii.b2a_hex(_websocket_mask(mask, token)), 

1589 utf8(str(int(timestamp))), 

1590 ] 

1591 ) 

1592 else: 

1593 raise ValueError("unknown xsrf cookie version %d", output_version) 

1594 if version is None: 

1595 if self.current_user and "expires_days" not in cookie_kwargs: 

1596 cookie_kwargs["expires_days"] = 30 

1597 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf") 

1598 self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs) 

1599 return self._xsrf_token 

1600 

1601 def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]: 

1602 """Read or generate the xsrf token in its raw form. 

1603 

1604 The raw_xsrf_token is a tuple containing: 

1605 

1606 * version: the version of the cookie from which this token was read, 

1607 or None if we generated a new token in this request. 

1608 * token: the raw token data; random (non-ascii) bytes. 

1609 * timestamp: the time this token was generated (will not be accurate 

1610 for version 1 cookies) 

1611 """ 

1612 if not hasattr(self, "_raw_xsrf_token"): 

1613 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf") 

1614 cookie = self.get_cookie(cookie_name) 

1615 if cookie: 

1616 version, token, timestamp = self._decode_xsrf_token(cookie) 

1617 else: 

1618 version, token, timestamp = None, None, None 

1619 if token is None: 

1620 version = None 

1621 token = os.urandom(16) 

1622 timestamp = time.time() 

1623 assert token is not None 

1624 assert timestamp is not None 

1625 self._raw_xsrf_token = (version, token, timestamp) 

1626 return self._raw_xsrf_token 

1627 

1628 def _decode_xsrf_token( 

1629 self, cookie: str 

1630 ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]: 

1631 """Convert a cookie string into a the tuple form returned by 

1632 _get_raw_xsrf_token. 

1633 """ 

1634 

1635 try: 

1636 m = _signed_value_version_re.match(utf8(cookie)) 

1637 

1638 if m: 

1639 version = int(m.group(1)) 

1640 if version == 2: 

1641 _, mask_str, masked_token, timestamp_str = cookie.split("|") 

1642 

1643 mask = binascii.a2b_hex(utf8(mask_str)) 

1644 token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token))) 

1645 timestamp = int(timestamp_str) 

1646 return version, token, timestamp 

1647 else: 

1648 # Treat unknown versions as not present instead of failing. 

1649 raise Exception("Unknown xsrf cookie version") 

1650 else: 

1651 version = 1 

1652 try: 

1653 token = binascii.a2b_hex(utf8(cookie)) 

1654 except (binascii.Error, TypeError): 

1655 token = utf8(cookie) 

1656 # We don't have a usable timestamp in older versions. 

1657 timestamp = int(time.time()) 

1658 return (version, token, timestamp) 

1659 except Exception: 

1660 # Catch exceptions and return nothing instead of failing. 

1661 gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True) 

1662 return None, None, None 

1663 

1664 def check_xsrf_cookie(self) -> None: 

1665 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument. 

1666 

1667 To prevent cross-site request forgery, we set an ``_xsrf`` 

1668 cookie and include the same value as a non-cookie 

1669 field with all ``POST`` requests. If the two do not match, we 

1670 reject the form submission as a potential forgery. 

1671 

1672 The ``_xsrf`` value may be set as either a form field named ``_xsrf`` 

1673 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken`` 

1674 (the latter is accepted for compatibility with Django). 

1675 

1676 See http://en.wikipedia.org/wiki/Cross-site_request_forgery 

1677 

1678 .. versionchanged:: 3.2.2 

1679 Added support for cookie version 2. Both versions 1 and 2 are 

1680 supported. 

1681 """ 

1682 # Prior to release 1.1.1, this check was ignored if the HTTP header 

1683 # ``X-Requested-With: XMLHTTPRequest`` was present. This exception 

1684 # has been shown to be insecure and has been removed. For more 

1685 # information please see 

1686 # http://www.djangoproject.com/weblog/2011/feb/08/security/ 

1687 # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails 

1688 input_token = ( 

1689 self.get_argument("_xsrf", None) 

1690 or self.request.headers.get("X-Xsrftoken") 

1691 or self.request.headers.get("X-Csrftoken") 

1692 ) 

1693 if not input_token: 

1694 raise HTTPError(403, "'_xsrf' argument missing from POST") 

1695 _, token, _ = self._decode_xsrf_token(input_token) 

1696 _, expected_token, _ = self._get_raw_xsrf_token() 

1697 if not token: 

1698 raise HTTPError(403, "'_xsrf' argument has invalid format") 

1699 if not hmac.compare_digest(utf8(token), utf8(expected_token)): 

1700 raise HTTPError(403, "XSRF cookie does not match POST argument") 

1701 

1702 def xsrf_form_html(self) -> str: 

1703 """An HTML ``<input/>`` element to be included with all POST forms. 

1704 

1705 It defines the ``_xsrf`` input value, which we check on all POST 

1706 requests to prevent cross-site request forgery. If you have set 

1707 the ``xsrf_cookies`` application setting, you must include this 

1708 HTML within all of your HTML forms. 

1709 

1710 In a template, this method should be called with ``{% module 

1711 xsrf_form_html() %}`` 

1712 

1713 See `check_xsrf_cookie()` above for more information. 

1714 """ 

1715 return ( 

1716 '<input type="hidden" name="_xsrf" value="' 

1717 + escape.xhtml_escape(self.xsrf_token) 

1718 + '"/>' 

1719 ) 

1720 

1721 def static_url( 

1722 self, path: str, include_host: Optional[bool] = None, **kwargs: Any 

1723 ) -> str: 

1724 """Returns a static URL for the given relative static file path. 

1725 

1726 This method requires you set the ``static_path`` setting in your 

1727 application (which specifies the root directory of your static 

1728 files). 

1729 

1730 This method returns a versioned url (by default appending 

1731 ``?v=<signature>``), which allows the static files to be 

1732 cached indefinitely. This can be disabled by passing 

1733 ``include_version=False`` (in the default implementation; 

1734 other static file implementations are not required to support 

1735 this, but they may support other options). 

1736 

1737 By default this method returns URLs relative to the current 

1738 host, but if ``include_host`` is true the URL returned will be 

1739 absolute. If this handler has an ``include_host`` attribute, 

1740 that value will be used as the default for all `static_url` 

1741 calls that do not pass ``include_host`` as a keyword argument. 

1742 

1743 """ 

1744 self.require_setting("static_path", "static_url") 

1745 get_url = self.settings.get( 

1746 "static_handler_class", StaticFileHandler 

1747 ).make_static_url 

1748 

1749 if include_host is None: 

1750 include_host = getattr(self, "include_host", False) 

1751 

1752 if include_host: 

1753 base = self.request.protocol + "://" + self.request.host 

1754 else: 

1755 base = "" 

1756 

1757 return base + get_url(self.settings, path, **kwargs) 

1758 

1759 def require_setting(self, name: str, feature: str = "this feature") -> None: 

1760 """Raises an exception if the given app setting is not defined.""" 

1761 if not self.application.settings.get(name): 

1762 raise Exception( 

1763 "You must define the '%s' setting in your " 

1764 "application to use %s" % (name, feature) 

1765 ) 

1766 

1767 def reverse_url(self, name: str, *args: Any) -> str: 

1768 """Alias for `Application.reverse_url`.""" 

1769 return self.application.reverse_url(name, *args) 

1770 

1771 def compute_etag(self) -> Optional[str]: 

1772 """Computes the etag header to be used for this request. 

1773 

1774 By default uses a hash of the content written so far. 

1775 

1776 May be overridden to provide custom etag implementations, 

1777 or may return None to disable tornado's default etag support. 

1778 """ 

1779 hasher = hashlib.sha1() 

1780 for part in self._write_buffer: 

1781 hasher.update(part) 

1782 return '"%s"' % hasher.hexdigest() 

1783 

1784 def set_etag_header(self) -> None: 

1785 """Sets the response's Etag header using ``self.compute_etag()``. 

1786 

1787 Note: no header will be set if ``compute_etag()`` returns ``None``. 

1788 

1789 This method is called automatically when the request is finished. 

1790 """ 

1791 etag = self.compute_etag() 

1792 if etag is not None: 

1793 self.set_header("Etag", etag) 

1794 

1795 def check_etag_header(self) -> bool: 

1796 """Checks the ``Etag`` header against requests's ``If-None-Match``. 

1797 

1798 Returns ``True`` if the request's Etag matches and a 304 should be 

1799 returned. For example:: 

1800 

1801 self.set_etag_header() 

1802 if self.check_etag_header(): 

1803 self.set_status(304) 

1804 return 

1805 

1806 This method is called automatically when the request is finished, 

1807 but may be called earlier for applications that override 

1808 `compute_etag` and want to do an early check for ``If-None-Match`` 

1809 before completing the request. The ``Etag`` header should be set 

1810 (perhaps with `set_etag_header`) before calling this method. 

1811 """ 

1812 computed_etag = utf8(self._headers.get("Etag", "")) 

1813 # Find all weak and strong etag values from If-None-Match header 

1814 # because RFC 7232 allows multiple etag values in a single header. 

1815 etags = re.findall( 

1816 rb'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", "")) 

1817 ) 

1818 if not computed_etag or not etags: 

1819 return False 

1820 

1821 match = False 

1822 if etags[0] == b"*": 

1823 match = True 

1824 else: 

1825 # Use a weak comparison when comparing entity-tags. 

1826 def val(x: bytes) -> bytes: 

1827 return x[2:] if x.startswith(b"W/") else x 

1828 

1829 for etag in etags: 

1830 if val(etag) == val(computed_etag): 

1831 match = True 

1832 break 

1833 return match 

1834 

1835 async def _execute( 

1836 self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes 

1837 ) -> None: 

1838 """Executes this request with the given output transforms.""" 

1839 self._transforms = transforms 

1840 try: 

1841 if self.request.method not in self.SUPPORTED_METHODS: 

1842 raise HTTPError(405) 

1843 

1844 # If we're not in stream_request_body mode, this is the place where we parse the body. 

1845 if not _has_stream_request_body(self.__class__): 

1846 try: 

1847 self.request._parse_body() 

1848 except httputil.HTTPInputError as e: 

1849 raise HTTPError(400, "Invalid body: %s" % e) from e 

1850 

1851 self.path_args = [self.decode_argument(arg) for arg in args] 

1852 self.path_kwargs = { 

1853 k: self.decode_argument(v, name=k) for (k, v) in kwargs.items() 

1854 } 

1855 # If XSRF cookies are turned on, reject form submissions without 

1856 # the proper cookie 

1857 if self.request.method not in ( 

1858 "GET", 

1859 "HEAD", 

1860 "OPTIONS", 

1861 ) and self.application.settings.get("xsrf_cookies"): 

1862 self.check_xsrf_cookie() 

1863 

1864 result = self.prepare() 

1865 if result is not None: 

1866 result = await result # type: ignore 

1867 if self._prepared_future is not None: 

1868 # Tell the Application we've finished with prepare() 

1869 # and are ready for the body to arrive. 

1870 future_set_result_unless_cancelled(self._prepared_future, None) 

1871 if self._finished: 

1872 return 

1873 

1874 if _has_stream_request_body(self.__class__): 

1875 # In streaming mode request.body is a Future that signals 

1876 # the body has been completely received. The Future has no 

1877 # result; the data has been passed to self.data_received 

1878 # instead. 

1879 try: 

1880 await self.request._body_future 

1881 except iostream.StreamClosedError: 

1882 return 

1883 

1884 method = getattr(self, self.request.method.lower()) 

1885 result = method(*self.path_args, **self.path_kwargs) 

1886 if result is not None: 

1887 result = await result 

1888 if self._auto_finish and not self._finished: 

1889 self.finish() 

1890 except Exception as e: 

1891 try: 

1892 self._handle_request_exception(e) 

1893 except Exception: 

1894 app_log.error("Exception in exception handler", exc_info=True) 

1895 finally: 

1896 # Unset result to avoid circular references 

1897 result = None 

1898 if self._prepared_future is not None and not self._prepared_future.done(): 

1899 # In case we failed before setting _prepared_future, do it 

1900 # now (to unblock the HTTP server). Note that this is not 

1901 # in a finally block to avoid GC issues prior to Python 3.4. 

1902 self._prepared_future.set_result(None) 

1903 

1904 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: 

1905 """Implement this method to handle streamed request data. 

1906 

1907 Requires the `.stream_request_body` decorator. 

1908 

1909 May be a coroutine for flow control. 

1910 """ 

1911 raise NotImplementedError() 

1912 

1913 def _log(self) -> None: 

1914 """Logs the current request. 

1915 

1916 Sort of deprecated since this functionality was moved to the 

1917 Application, but left in place for the benefit of existing apps 

1918 that have overridden this method. 

1919 """ 

1920 self.application.log_request(self) 

1921 

1922 def _request_summary(self) -> str: 

1923 return "{} {} ({})".format( 

1924 self.request.method, 

1925 self.request.uri, 

1926 self.request.remote_ip, 

1927 ) 

1928 

1929 def _handle_request_exception(self, e: BaseException) -> None: 

1930 if isinstance(e, Finish): 

1931 # Not an error; just finish the request without logging. 

1932 if not self._finished: 

1933 self.finish(*e.args) 

1934 return 

1935 try: 

1936 self.log_exception(*sys.exc_info()) 

1937 except Exception: 

1938 # An error here should still get a best-effort send_error() 

1939 # to avoid leaking the connection. 

1940 app_log.error("Error in exception logger", exc_info=True) 

1941 if self._finished: 

1942 # Extra errors after the request has been finished should 

1943 # be logged, but there is no reason to continue to try and 

1944 # send a response. 

1945 return 

1946 if isinstance(e, HTTPError): 

1947 self.send_error(e.status_code, exc_info=sys.exc_info()) 

1948 else: 

1949 self.send_error(500, exc_info=sys.exc_info()) 

1950 

1951 def log_exception( 

1952 self, 

1953 typ: "Optional[Type[BaseException]]", 

1954 value: Optional[BaseException], 

1955 tb: Optional[TracebackType], 

1956 ) -> None: 

1957 """Override to customize logging of uncaught exceptions. 

1958 

1959 By default logs instances of `HTTPError` as warnings without 

1960 stack traces (on the ``tornado.general`` logger), and all 

1961 other exceptions as errors with stack traces (on the 

1962 ``tornado.application`` logger). 

1963 

1964 .. versionadded:: 3.1 

1965 """ 

1966 if isinstance(value, HTTPError): 

1967 log_message = value.get_message() 

1968 if log_message: 

1969 format = "%d %s: %s" 

1970 args = [value.status_code, self._request_summary(), log_message] 

1971 gen_log.warning(format, *args) 

1972 else: 

1973 app_log.error( 

1974 "Uncaught exception %s\n%r", 

1975 self._request_summary(), 

1976 self.request, 

1977 exc_info=(typ, value, tb), # type: ignore 

1978 ) 

1979 

1980 def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]: 

1981 def render(*args, **kwargs) -> str: # type: ignore 

1982 if not hasattr(self, "_active_modules"): 

1983 self._active_modules = {} # type: Dict[str, UIModule] 

1984 if name not in self._active_modules: 

1985 self._active_modules[name] = module(self) 

1986 rendered = self._active_modules[name].render(*args, **kwargs) 

1987 return _unicode(rendered) 

1988 

1989 return render 

1990 

1991 def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]: 

1992 return lambda *args, **kwargs: method(self, *args, **kwargs) 

1993 

1994 def _clear_representation_headers(self) -> None: 

1995 # 304 responses should not contain representation metadata 

1996 # headers (defined in 

1997 # https://tools.ietf.org/html/rfc7231#section-3.1) 

1998 # not explicitly allowed by 

1999 # https://tools.ietf.org/html/rfc7232#section-4.1 

2000 headers = ["Content-Encoding", "Content-Language", "Content-Type"] 

2001 for h in headers: 

2002 self.clear_header(h) 

2003 

2004 

2005_RequestHandlerType = TypeVar("_RequestHandlerType", bound=RequestHandler) 

2006 

2007 

2008def stream_request_body(cls: Type[_RequestHandlerType]) -> Type[_RequestHandlerType]: 

2009 """Apply to `RequestHandler` subclasses to enable streaming body support. 

2010 

2011 This decorator implies the following changes: 

2012 

2013 * `.HTTPServerRequest.body` is undefined, and body arguments will not 

2014 be included in `RequestHandler.get_argument`. 

2015 * `RequestHandler.prepare` is called when the request headers have been 

2016 read instead of after the entire body has been read. 

2017 * The subclass must define a method ``data_received(self, data):``, which 

2018 will be called zero or more times as data is available. Note that 

2019 if the request has an empty body, ``data_received`` may not be called. 

2020 * ``prepare`` and ``data_received`` may return Futures (such as via 

2021 ``@gen.coroutine``, in which case the next method will not be called 

2022 until those futures have completed. 

2023 * The regular HTTP method (``post``, ``put``, etc) will be called after 

2024 the entire body has been read. 

2025 

2026 See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_ 

2027 for example usage. 

2028 """ # noqa: E501 

2029 if not issubclass(cls, RequestHandler): 

2030 raise TypeError("expected subclass of RequestHandler, got %r", cls) 

2031 cls._stream_request_body = True 

2032 return cls 

2033 

2034 

2035def _has_stream_request_body(cls: Type[RequestHandler]) -> bool: 

2036 if not issubclass(cls, RequestHandler): 

2037 raise TypeError("expected subclass of RequestHandler, got %r", cls) 

2038 return cls._stream_request_body 

2039 

2040 

2041def removeslash( 

2042 method: Callable[..., Optional[Awaitable[None]]], 

2043) -> Callable[..., Optional[Awaitable[None]]]: 

2044 """Use this decorator to remove trailing slashes from the request path. 

2045 

2046 For example, a request to ``/foo/`` would redirect to ``/foo`` with this 

2047 decorator. Your request handler mapping should use a regular expression 

2048 like ``r'/foo/*'`` in conjunction with using the decorator. 

2049 """ 

2050 

2051 @functools.wraps(method) 

2052 def wrapper( # type: ignore 

2053 self: RequestHandler, *args, **kwargs 

2054 ) -> Optional[Awaitable[None]]: 

2055 if self.request.path.endswith("/"): 

2056 if self.request.method in ("GET", "HEAD"): 

2057 uri = self.request.path.rstrip("/") 

2058 if uri: # don't try to redirect '/' to '' 

2059 if self.request.query: 

2060 uri += "?" + self.request.query 

2061 self.redirect(uri, permanent=True) 

2062 return None 

2063 else: 

2064 raise HTTPError(404) 

2065 return method(self, *args, **kwargs) 

2066 

2067 return wrapper 

2068 

2069 

2070def addslash( 

2071 method: Callable[..., Optional[Awaitable[None]]], 

2072) -> Callable[..., Optional[Awaitable[None]]]: 

2073 """Use this decorator to add a missing trailing slash to the request path. 

2074 

2075 For example, a request to ``/foo`` would redirect to ``/foo/`` with this 

2076 decorator. Your request handler mapping should use a regular expression 

2077 like ``r'/foo/?'`` in conjunction with using the decorator. 

2078 """ 

2079 

2080 @functools.wraps(method) 

2081 def wrapper( # type: ignore 

2082 self: RequestHandler, *args, **kwargs 

2083 ) -> Optional[Awaitable[None]]: 

2084 if not self.request.path.endswith("/"): 

2085 if self.request.method in ("GET", "HEAD"): 

2086 uri = self.request.path + "/" 

2087 if self.request.query: 

2088 uri += "?" + self.request.query 

2089 self.redirect(uri, permanent=True) 

2090 return None 

2091 raise HTTPError(404) 

2092 return method(self, *args, **kwargs) 

2093 

2094 return wrapper 

2095 

2096 

2097class _ApplicationRouter(ReversibleRuleRouter): 

2098 """Routing implementation used internally by `Application`. 

2099 

2100 Provides a binding between `Application` and `RequestHandler`. 

2101 This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways: 

2102 * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and 

2103 * it allows to use a list/tuple of rules as `~.routing.Rule` target. 

2104 ``process_rule`` implementation will substitute this list with an appropriate 

2105 `_ApplicationRouter` instance. 

2106 """ 

2107 

2108 def __init__( 

2109 self, application: "Application", rules: Optional[_RuleList] = None 

2110 ) -> None: 

2111 assert isinstance(application, Application) 

2112 self.application = application 

2113 super().__init__(rules) 

2114 

2115 def process_rule(self, rule: Rule) -> Rule: 

2116 rule = super().process_rule(rule) 

2117 

2118 if isinstance(rule.target, (list, tuple)): 

2119 rule.target = _ApplicationRouter( 

2120 self.application, rule.target # type: ignore 

2121 ) 

2122 

2123 return rule 

2124 

2125 def get_target_delegate( 

2126 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any 

2127 ) -> Optional[httputil.HTTPMessageDelegate]: 

2128 if isclass(target) and issubclass(target, RequestHandler): 

2129 return self.application.get_handler_delegate( 

2130 request, target, **target_params 

2131 ) 

2132 

2133 return super().get_target_delegate(target, request, **target_params) 

2134 

2135 

2136class Application(ReversibleRouter): 

2137 r"""A collection of request handlers that make up a web application. 

2138 

2139 Instances of this class are callable and can be passed directly to 

2140 HTTPServer to serve the application:: 

2141 

2142 application = web.Application([ 

2143 (r"/", MainPageHandler), 

2144 ]) 

2145 http_server = httpserver.HTTPServer(application) 

2146 http_server.listen(8080) 

2147 

2148 The constructor for this class takes in a list of `~.routing.Rule` 

2149 objects or tuples of values corresponding to the arguments of 

2150 `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``, 

2151 the values in square brackets being optional. The default matcher is 

2152 `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used 

2153 instead of ``(PathMatches(regexp), target)``. 

2154 

2155 A common routing target is a `RequestHandler` subclass, but you can also 

2156 use lists of rules as a target, which create a nested routing configuration:: 

2157 

2158 application = web.Application([ 

2159 (HostMatches("example.com"), [ 

2160 (r"/", MainPageHandler), 

2161 (r"/feed", FeedHandler), 

2162 ]), 

2163 ]) 

2164 

2165 In addition to this you can use nested `~.routing.Router` instances, 

2166 `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets 

2167 (see `~.routing` module docs for more information). 

2168 

2169 When we receive requests, we iterate over the list in order and 

2170 instantiate an instance of the first request class whose regexp 

2171 matches the request path. The request class can be specified as 

2172 either a class object or a (fully-qualified) name. 

2173 

2174 A dictionary may be passed as the third element (``target_kwargs``) 

2175 of the tuple, which will be used as keyword arguments to the handler's 

2176 constructor and `~RequestHandler.initialize` method. This pattern 

2177 is used for the `StaticFileHandler` in this example (note that a 

2178 `StaticFileHandler` can be installed automatically with the 

2179 static_path setting described below):: 

2180 

2181 application = web.Application([ 

2182 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 

2183 ]) 

2184 

2185 We support virtual hosts with the `add_handlers` method, which takes in 

2186 a host regular expression as the first argument:: 

2187 

2188 application.add_handlers(r"www\.myhost\.com", [ 

2189 (r"/article/([0-9]+)", ArticleHandler), 

2190 ]) 

2191 

2192 If there's no match for the current request's host, then ``default_host`` 

2193 parameter value is matched against host regular expressions. 

2194 

2195 

2196 .. warning:: 

2197 

2198 Applications that do not use TLS may be vulnerable to :ref:`DNS 

2199 rebinding <dnsrebinding>` attacks. This attack is especially 

2200 relevant to applications that only listen on ``127.0.0.1`` or 

2201 other private networks. Appropriate host patterns must be used 

2202 (instead of the default of ``r'.*'``) to prevent this risk. The 

2203 ``default_host`` argument must not be used in applications that 

2204 may be vulnerable to DNS rebinding. 

2205 

2206 You can serve static files by sending the ``static_path`` setting 

2207 as a keyword argument. We will serve those files from the 

2208 ``/static/`` URI (this is configurable with the 

2209 ``static_url_prefix`` setting), and we will serve ``/favicon.ico`` 

2210 and ``/robots.txt`` from the same directory. A custom subclass of 

2211 `StaticFileHandler` can be specified with the 

2212 ``static_handler_class`` setting. 

2213 

2214 .. versionchanged:: 4.5 

2215 Integration with the new `tornado.routing` module. 

2216 

2217 """ 

2218 

2219 def __init__( 

2220 self, 

2221 handlers: Optional[_RuleList] = None, 

2222 default_host: Optional[str] = None, 

2223 transforms: Optional[List[Type["OutputTransform"]]] = None, 

2224 **settings: Any, 

2225 ) -> None: 

2226 if transforms is None: 

2227 self.transforms = [] # type: List[Type[OutputTransform]] 

2228 if settings.get("compress_response") or settings.get("gzip"): 

2229 self.transforms.append(GZipContentEncoding) 

2230 else: 

2231 self.transforms = transforms 

2232 self.default_host = default_host 

2233 self.settings = settings 

2234 self.ui_modules = { 

2235 "linkify": _linkify, 

2236 "xsrf_form_html": _xsrf_form_html, 

2237 "Template": TemplateModule, 

2238 } 

2239 self.ui_methods = {} # type: Dict[str, Callable[..., str]] 

2240 self._load_ui_modules(settings.get("ui_modules", {})) 

2241 self._load_ui_methods(settings.get("ui_methods", {})) 

2242 if self.settings.get("static_path"): 

2243 path = self.settings["static_path"] 

2244 handlers = list(handlers or []) 

2245 static_url_prefix = settings.get("static_url_prefix", "/static/") 

2246 static_handler_class = settings.get( 

2247 "static_handler_class", StaticFileHandler 

2248 ) 

2249 static_handler_args = settings.get("static_handler_args", {}) 

2250 static_handler_args["path"] = path 

2251 for pattern in [ 

2252 re.escape(static_url_prefix) + r"(.*)", 

2253 r"/(favicon\.ico)", 

2254 r"/(robots\.txt)", 

2255 ]: 

2256 handlers.insert(0, (pattern, static_handler_class, static_handler_args)) 

2257 

2258 if self.settings.get("debug"): 

2259 self.settings.setdefault("autoreload", True) 

2260 self.settings.setdefault("compiled_template_cache", False) 

2261 self.settings.setdefault("static_hash_cache", False) 

2262 self.settings.setdefault("serve_traceback", True) 

2263 

2264 self.wildcard_router = _ApplicationRouter(self, handlers) 

2265 self.default_router = _ApplicationRouter( 

2266 self, [Rule(AnyMatches(), self.wildcard_router)] 

2267 ) 

2268 

2269 # Automatically reload modified modules 

2270 if self.settings.get("autoreload"): 

2271 from tornado import autoreload 

2272 

2273 autoreload.start() 

2274 

2275 def listen( 

2276 self, 

2277 port: int, 

2278 address: Optional[str] = None, 

2279 *, 

2280 family: socket.AddressFamily = socket.AF_UNSPEC, 

2281 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2282 flags: Optional[int] = None, 

2283 reuse_port: bool = False, 

2284 **kwargs: Any, 

2285 ) -> HTTPServer: 

2286 """Starts an HTTP server for this application on the given port. 

2287 

2288 This is a convenience alias for creating an `.HTTPServer` object and 

2289 calling its listen method. Keyword arguments not supported by 

2290 `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer` 

2291 constructor. For advanced uses (e.g. multi-process mode), do not use 

2292 this method; create an `.HTTPServer` and call its 

2293 `.TCPServer.bind`/`.TCPServer.start` methods directly. 

2294 

2295 Note that after calling this method you still need to call 

2296 ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start 

2297 the server. 

2298 

2299 Returns the `.HTTPServer` object. 

2300 

2301 .. versionchanged:: 4.3 

2302 Now returns the `.HTTPServer` object. 

2303 

2304 .. versionchanged:: 6.2 

2305 Added support for new keyword arguments in `.TCPServer.listen`, 

2306 including ``reuse_port``. 

2307 """ 

2308 server = HTTPServer(self, **kwargs) 

2309 server.listen( 

2310 port, 

2311 address=address, 

2312 family=family, 

2313 backlog=backlog, 

2314 flags=flags, 

2315 reuse_port=reuse_port, 

2316 ) 

2317 return server 

2318 

2319 def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None: 

2320 """Appends the given handlers to our handler list. 

2321 

2322 Host patterns are processed sequentially in the order they were 

2323 added. All matching patterns will be considered. 

2324 """ 

2325 host_matcher = HostMatches(host_pattern) 

2326 rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers)) 

2327 

2328 self.default_router.rules.insert(-1, rule) 

2329 

2330 if self.default_host is not None: 

2331 self.wildcard_router.add_rules( 

2332 [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)] 

2333 ) 

2334 

2335 def add_transform(self, transform_class: Type["OutputTransform"]) -> None: 

2336 self.transforms.append(transform_class) 

2337 

2338 def _load_ui_methods(self, methods: Any) -> None: 

2339 if isinstance(methods, types.ModuleType): 

2340 self._load_ui_methods({n: getattr(methods, n) for n in dir(methods)}) 

2341 elif isinstance(methods, list): 

2342 for m in methods: 

2343 self._load_ui_methods(m) 

2344 else: 

2345 for name, fn in methods.items(): 

2346 if ( 

2347 not name.startswith("_") 

2348 and hasattr(fn, "__call__") 

2349 and name[0].lower() == name[0] 

2350 ): 

2351 self.ui_methods[name] = fn 

2352 

2353 def _load_ui_modules(self, modules: Any) -> None: 

2354 if isinstance(modules, types.ModuleType): 

2355 self._load_ui_modules({n: getattr(modules, n) for n in dir(modules)}) 

2356 elif isinstance(modules, list): 

2357 for m in modules: 

2358 self._load_ui_modules(m) 

2359 else: 

2360 assert isinstance(modules, dict) 

2361 for name, cls in modules.items(): 

2362 try: 

2363 if issubclass(cls, UIModule): 

2364 self.ui_modules[name] = cls 

2365 except TypeError: 

2366 pass 

2367 

2368 def __call__( 

2369 self, request: httputil.HTTPServerRequest 

2370 ) -> Optional[Awaitable[None]]: 

2371 # Legacy HTTPServer interface 

2372 dispatcher = self.find_handler(request) 

2373 return dispatcher.execute() 

2374 

2375 def find_handler( 

2376 self, request: httputil.HTTPServerRequest, **kwargs: Any 

2377 ) -> "_HandlerDelegate": 

2378 route = self.default_router.find_handler(request) 

2379 if route is not None: 

2380 return cast("_HandlerDelegate", route) 

2381 

2382 if self.settings.get("default_handler_class"): 

2383 return self.get_handler_delegate( 

2384 request, 

2385 self.settings["default_handler_class"], 

2386 self.settings.get("default_handler_args", {}), 

2387 ) 

2388 

2389 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404}) 

2390 

2391 def get_handler_delegate( 

2392 self, 

2393 request: httputil.HTTPServerRequest, 

2394 target_class: Type[RequestHandler], 

2395 target_kwargs: Optional[Dict[str, Any]] = None, 

2396 path_args: Optional[List[bytes]] = None, 

2397 path_kwargs: Optional[Dict[str, bytes]] = None, 

2398 ) -> "_HandlerDelegate": 

2399 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request 

2400 for application and `RequestHandler` subclass. 

2401 

2402 :arg httputil.HTTPServerRequest request: current HTTP request. 

2403 :arg RequestHandler target_class: a `RequestHandler` class. 

2404 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor. 

2405 :arg list path_args: positional arguments for ``target_class`` HTTP method that 

2406 will be executed while handling a request (``get``, ``post`` or any other). 

2407 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method. 

2408 """ 

2409 return _HandlerDelegate( 

2410 self, request, target_class, target_kwargs, path_args, path_kwargs 

2411 ) 

2412 

2413 def reverse_url(self, name: str, *args: Any) -> str: 

2414 """Returns a URL path for handler named ``name`` 

2415 

2416 The handler must be added to the application as a named `URLSpec`. 

2417 

2418 Args will be substituted for capturing groups in the `URLSpec` regex. 

2419 They will be converted to strings if necessary, encoded as utf8, 

2420 and url-escaped. 

2421 """ 

2422 reversed_url = self.default_router.reverse_url(name, *args) 

2423 if reversed_url is not None: 

2424 return reversed_url 

2425 

2426 raise KeyError("%s not found in named urls" % name) 

2427 

2428 def log_request(self, handler: RequestHandler) -> None: 

2429 """Writes a completed HTTP request to the logs. 

2430 

2431 By default writes to the python root logger. To change 

2432 this behavior either subclass Application and override this method, 

2433 or pass a function in the application settings dictionary as 

2434 ``log_function``. 

2435 """ 

2436 if "log_function" in self.settings: 

2437 self.settings["log_function"](handler) 

2438 return 

2439 if handler.get_status() < 400: 

2440 log_method = access_log.info 

2441 elif handler.get_status() < 500: 

2442 log_method = access_log.warning 

2443 else: 

2444 log_method = access_log.error 

2445 request_time = 1000.0 * handler.request.request_time() 

2446 log_method( 

2447 "%d %s %.2fms", 

2448 handler.get_status(), 

2449 handler._request_summary(), 

2450 request_time, 

2451 ) 

2452 

2453 

2454class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2455 def __init__( 

2456 self, 

2457 application: Application, 

2458 request: httputil.HTTPServerRequest, 

2459 handler_class: Type[RequestHandler], 

2460 handler_kwargs: Optional[Dict[str, Any]], 

2461 path_args: Optional[List[bytes]], 

2462 path_kwargs: Optional[Dict[str, bytes]], 

2463 ) -> None: 

2464 self.application = application 

2465 self.connection = request.connection 

2466 self.request = request 

2467 self.handler_class = handler_class 

2468 self.handler_kwargs = handler_kwargs or {} 

2469 self.path_args = path_args or [] 

2470 self.path_kwargs = path_kwargs or {} 

2471 self.chunks = [] # type: List[bytes] 

2472 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2473 

2474 def headers_received( 

2475 self, 

2476 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], 

2477 headers: httputil.HTTPHeaders, 

2478 ) -> Optional[Awaitable[None]]: 

2479 if self.stream_request_body: 

2480 self.request._body_future = Future() 

2481 return self.execute() 

2482 return None 

2483 

2484 def data_received(self, data: bytes) -> Optional[Awaitable[None]]: 

2485 if self.stream_request_body: 

2486 return self.handler.data_received(data) 

2487 else: 

2488 self.chunks.append(data) 

2489 return None 

2490 

2491 def finish(self) -> None: 

2492 if self.stream_request_body: 

2493 future_set_result_unless_cancelled(self.request._body_future, None) 

2494 else: 

2495 # Note that the body gets parsed in RequestHandler._execute so it can be in 

2496 # the right exception handler scope. 

2497 self.request.body = b"".join(self.chunks) 

2498 self.execute() 

2499 

2500 def on_connection_close(self) -> None: 

2501 if self.stream_request_body: 

2502 self.handler.on_connection_close() 

2503 else: 

2504 self.chunks = None # type: ignore 

2505 

2506 def execute(self) -> Optional[Awaitable[None]]: 

2507 # If template cache is disabled (usually in the debug mode), 

2508 # re-compile templates and reload static files on every 

2509 # request so you don't need to restart to see changes 

2510 if not self.application.settings.get("compiled_template_cache", True): 

2511 with RequestHandler._template_loader_lock: 

2512 for loader in RequestHandler._template_loaders.values(): 

2513 loader.reset() 

2514 if not self.application.settings.get("static_hash_cache", True): 

2515 static_handler_class = self.application.settings.get( 

2516 "static_handler_class", StaticFileHandler 

2517 ) 

2518 static_handler_class.reset() 

2519 

2520 self.handler = self.handler_class( 

2521 self.application, self.request, **self.handler_kwargs 

2522 ) 

2523 transforms = [t(self.request) for t in self.application.transforms] 

2524 

2525 if self.stream_request_body: 

2526 self.handler._prepared_future = Future() 

2527 # Note that if an exception escapes handler._execute it will be 

2528 # trapped in the Future it returns (which we are ignoring here, 

2529 # leaving it to be logged when the Future is GC'd). 

2530 # However, that shouldn't happen because _execute has a blanket 

2531 # except handler, and we cannot easily access the IOLoop here to 

2532 # call add_future (because of the requirement to remain compatible 

2533 # with WSGI) 

2534 fut = gen.convert_yielded( 

2535 self.handler._execute(transforms, *self.path_args, **self.path_kwargs) 

2536 ) 

2537 fut.add_done_callback(lambda f: f.result()) 

2538 # If we are streaming the request body, then execute() is finished 

2539 # when the handler has prepared to receive the body. If not, 

2540 # it doesn't matter when execute() finishes (so we return None) 

2541 return self.handler._prepared_future 

2542 

2543 

2544class HTTPError(Exception): 

2545 """An exception that will turn into an HTTP error response. 

2546 

2547 Raising an `HTTPError` is a convenient alternative to calling 

2548 `RequestHandler.send_error` since it automatically ends the 

2549 current function. 

2550 

2551 To customize the response sent with an `HTTPError`, override 

2552 `RequestHandler.write_error`. 

2553 

2554 :arg int status_code: HTTP status code. Must be listed in 

2555 `httplib.responses <http.client.responses>` unless the ``reason`` 

2556 keyword argument is given. 

2557 :arg str log_message: Message to be written to the log for this error 

2558 (will not be shown to the user unless the `Application` is in debug 

2559 mode). May contain ``%s``-style placeholders, which will be filled 

2560 in with remaining positional parameters. 

2561 :arg str reason: Keyword-only argument. The HTTP "reason" phrase 

2562 to pass in the status line along with ``status_code`` (for example, 

2563 the "Not Found" in ``HTTP/1.1 404 Not Found``). Normally 

2564 determined automatically from ``status_code``, but can be used 

2565 to use a non-standard numeric code. This is not a general-purpose 

2566 error message. 

2567 """ 

2568 

2569 def __init__( 

2570 self, 

2571 status_code: int = 500, 

2572 log_message: Optional[str] = None, 

2573 *args: Any, 

2574 **kwargs: Any, 

2575 ) -> None: 

2576 self.status_code = status_code 

2577 self._log_message = log_message 

2578 self.args = args 

2579 self.reason = kwargs.get("reason", None) 

2580 

2581 @property 

2582 def log_message(self) -> Optional[str]: 

2583 """ 

2584 A backwards compatible way of accessing log_message. 

2585 """ 

2586 if self._log_message and not self.args: 

2587 return self._log_message.replace("%", "%%") 

2588 return self._log_message 

2589 

2590 def get_message(self) -> Optional[str]: 

2591 if self._log_message and self.args: 

2592 return self._log_message % self.args 

2593 return self._log_message 

2594 

2595 def __str__(self) -> str: 

2596 message = "HTTP %d: %s" % ( 

2597 self.status_code, 

2598 self.reason or httputil.responses.get(self.status_code, "Unknown"), 

2599 ) 

2600 log_message = self.get_message() 

2601 if log_message: 

2602 return message + " (" + log_message + ")" 

2603 else: 

2604 return message 

2605 

2606 

2607class Finish(Exception): 

2608 """An exception that ends the request without producing an error response. 

2609 

2610 When `Finish` is raised in a `RequestHandler`, the request will 

2611 end (calling `RequestHandler.finish` if it hasn't already been 

2612 called), but the error-handling methods (including 

2613 `RequestHandler.write_error`) will not be called. 

2614 

2615 If `Finish()` was created with no arguments, the pending response 

2616 will be sent as-is. If `Finish()` was given an argument, that 

2617 argument will be passed to `RequestHandler.finish()`. 

2618 

2619 This can be a more convenient way to implement custom error pages 

2620 than overriding ``write_error`` (especially in library code):: 

2621 

2622 if self.current_user is None: 

2623 self.set_status(401) 

2624 self.set_header('WWW-Authenticate', 'Basic realm="something"') 

2625 raise Finish() 

2626 

2627 .. versionchanged:: 4.3 

2628 Arguments passed to ``Finish()`` will be passed on to 

2629 `RequestHandler.finish`. 

2630 """ 

2631 

2632 pass 

2633 

2634 

2635class MissingArgumentError(HTTPError): 

2636 """Exception raised by `RequestHandler.get_argument`. 

2637 

2638 This is a subclass of `HTTPError`, so if it is uncaught a 400 response 

2639 code will be used instead of 500 (and a stack trace will not be logged). 

2640 

2641 .. versionadded:: 3.1 

2642 """ 

2643 

2644 def __init__(self, arg_name: str) -> None: 

2645 super().__init__(400, "Missing argument %s" % arg_name) 

2646 self.arg_name = arg_name 

2647 

2648 

2649class ErrorHandler(RequestHandler): 

2650 """Generates an error response with ``status_code`` for all requests.""" 

2651 

2652 def initialize(self, status_code: int) -> None: 

2653 self.set_status(status_code) 

2654 

2655 def prepare(self) -> None: 

2656 raise HTTPError(self._status_code) 

2657 

2658 def check_xsrf_cookie(self) -> None: 

2659 # POSTs to an ErrorHandler don't actually have side effects, 

2660 # so we don't need to check the xsrf token. This allows POSTs 

2661 # to the wrong url to return a 404 instead of 403. 

2662 pass 

2663 

2664 

2665class RedirectHandler(RequestHandler): 

2666 """Redirects the client to the given URL for all GET requests. 

2667 

2668 You should provide the keyword argument ``url`` to the handler, e.g.:: 

2669 

2670 application = web.Application([ 

2671 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 

2672 ]) 

2673 

2674 `RedirectHandler` supports regular expression substitutions. E.g., to 

2675 swap the first and second parts of a path while preserving the remainder:: 

2676 

2677 application = web.Application([ 

2678 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 

2679 ]) 

2680 

2681 The final URL is formatted with `str.format` and the substrings that match 

2682 the capturing groups. In the above example, a request to "/a/b/c" would be 

2683 formatted like:: 

2684 

2685 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 

2686 

2687 Use Python's :ref:`format string syntax <formatstrings>` to customize how 

2688 values are substituted. 

2689 

2690 .. versionchanged:: 4.5 

2691 Added support for substitutions into the destination URL. 

2692 

2693 .. versionchanged:: 5.0 

2694 If any query arguments are present, they will be copied to the 

2695 destination URL. 

2696 """ 

2697 

2698 def initialize(self, url: str, permanent: bool = True) -> None: 

2699 self._url = url 

2700 self._permanent = permanent 

2701 

2702 def get(self, *args: Any, **kwargs: Any) -> None: 

2703 to_url = self._url.format(*args, **kwargs) 

2704 if self.request.query_arguments: 

2705 # TODO: figure out typing for the next line. 

2706 to_url = httputil.url_concat( 

2707 to_url, 

2708 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 

2709 ) 

2710 self.redirect(to_url, permanent=self._permanent) 

2711 

2712 

2713class StaticFileHandler(RequestHandler): 

2714 """A simple handler that can serve static content from a directory. 

2715 

2716 A `StaticFileHandler` is configured automatically if you pass the 

2717 ``static_path`` keyword argument to `Application`. This handler 

2718 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2719 and ``static_handler_args`` settings. 

2720 

2721 To map an additional path to this handler for a static data directory 

2722 you would add a line to your application like:: 

2723 

2724 application = web.Application([ 

2725 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 

2726 ]) 

2727 

2728 The handler constructor requires a ``path`` argument, which specifies the 

2729 local root directory of the content to be served. 

2730 

2731 Note that a capture group in the regex is required to parse the value for 

2732 the ``path`` argument to the get() method (different than the constructor 

2733 argument above); see `URLSpec` for details. 

2734 

2735 To serve a file like ``index.html`` automatically when a directory is 

2736 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2737 in your application settings, or add ``default_filename`` as an initializer 

2738 argument for your ``StaticFileHandler``. 

2739 

2740 Symlinks are not followed out of the directory being served: if a path 

2741 resolves to a location outside it, the request is rejected with a 403 

2742 error. Deployments that deliberately serve content through symlinks 

2743 pointing elsewhere can set the ``allowed_symlink_directory`` initializer 

2744 argument to a directory containing all of the intended targets, or to a 

2745 list of such directories. It defaults to the ``path`` argument. 

2746 

2747 To maximize the effectiveness of browser caching, this class supports 

2748 versioned urls (by default using the argument ``?v=``). If a version 

2749 is given, we instruct the browser to cache this file indefinitely. 

2750 `make_static_url` (also available as `RequestHandler.static_url`) can 

2751 be used to construct a versioned url. 

2752 

2753 This handler is intended primarily for use in development and light-duty 

2754 file serving; for heavy traffic it will be more efficient to use 

2755 a dedicated static file server (such as nginx or Apache). We support 

2756 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2757 some browsers require this functionality to be present to seek in 

2758 HTML5 audio or video). 

2759 

2760 **Subclassing notes** 

2761 

2762 This class is designed to be extensible by subclassing, but because 

2763 of the way static urls are generated with class methods rather than 

2764 instance methods, the inheritance patterns are somewhat unusual. 

2765 Be sure to use the ``@classmethod`` decorator when overriding a 

2766 class method. Instance methods may use the attributes ``self.path`` 

2767 ``self.absolute_path``, and ``self.modified``. 

2768 

2769 Subclasses should only override methods discussed in this section; 

2770 overriding other methods is error-prone. Overriding 

2771 ``StaticFileHandler.get`` is particularly problematic due to the 

2772 tight coupling with ``compute_etag`` and other methods. 

2773 

2774 To change the way static urls are generated (e.g. to match the behavior 

2775 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2776 `get_cache_time`, and/or `get_version`. 

2777 

2778 To replace all interaction with the filesystem (e.g. to serve 

2779 static content from a database), override `get_content`, 

2780 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2781 `validate_absolute_path`. 

2782 

2783 .. versionchanged:: 3.1 

2784 Many of the methods for subclasses were added in Tornado 3.1. 

2785 

2786 .. versionchanged:: 6.5.9 

2787 Symlinks pointing outside the served directory are no longer 

2788 followed, and the ``allowed_symlink_directory`` argument was added 

2789 to configure this. 

2790 

2791 .. versionchanged:: 6.5.10 

2792 ``allowed_symlink_directory`` may now be a list of directories. 

2793 """ 

2794 

2795 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2796 

2797 _static_hashes = {} # type: Dict[str, Optional[str]] 

2798 _lock = threading.Lock() # protects _static_hashes 

2799 

2800 # A single directory or a list of them. None means "not set", in which 

2801 # case the symlink check falls back to the directory being served. The 

2802 # default lives on the class, not in initialize(), because this attribute 

2803 # was introduced in a security patch and some projects (notably Jupyter) 

2804 # subclass StaticFileHandler with their own initialize() that does not 

2805 # call ours. 

2806 allowed_symlink_directory = None # type: Optional[Union[str, List[str]]] 

2807 

2808 def initialize( 

2809 self, 

2810 path: str, 

2811 default_filename: Optional[str] = None, 

2812 allowed_symlink_directory: Optional[Union[str, List[str]]] = None, 

2813 ) -> None: 

2814 self.root = path 

2815 self.default_filename = default_filename 

2816 self.allowed_symlink_directory = allowed_symlink_directory 

2817 

2818 @classmethod 

2819 def reset(cls) -> None: 

2820 with cls._lock: 

2821 cls._static_hashes = {} 

2822 

2823 def head(self, path: str) -> Awaitable[None]: 

2824 return self.get(path, include_body=False) 

2825 

2826 async def get(self, path: str, include_body: bool = True) -> None: 

2827 # Set up our path instance variables. 

2828 self.path = self.parse_url_path(path) 

2829 del path # make sure we don't refer to path instead of self.path again 

2830 absolute_path = self.get_absolute_path(self.root, self.path) 

2831 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

2832 if self.absolute_path is None: 

2833 return 

2834 

2835 self.modified = self.get_modified_time() 

2836 self.set_headers() 

2837 

2838 if self.should_return_304(): 

2839 self.set_status(304) 

2840 return 

2841 

2842 request_range = None 

2843 range_header = self.request.headers.get("Range") 

2844 if range_header: 

2845 # As per RFC 2616 14.16, if an invalid Range header is specified, 

2846 # the request will be treated as if the header didn't exist. 

2847 request_range = httputil._parse_request_range(range_header) 

2848 

2849 size = self.get_content_size() 

2850 if request_range: 

2851 start, end = request_range 

2852 if start is not None and start < 0: 

2853 start += size 

2854 if start < 0: 

2855 start = 0 

2856 if ( 

2857 start is not None 

2858 and (start >= size or (end is not None and start >= end)) 

2859 ) or end == 0: 

2860 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

2861 # the first requested byte is equal to or greater than the 

2862 # content, or when a suffix with length 0 is specified. 

2863 # https://tools.ietf.org/html/rfc7233#section-2.1 

2864 # A byte-range-spec is invalid if the last-byte-pos value is present 

2865 # and less than the first-byte-pos. 

2866 self.set_status(416) # Range Not Satisfiable 

2867 self.set_header("Content-Type", "text/plain") 

2868 self.set_header("Content-Range", f"bytes */{size}") 

2869 return 

2870 if end is not None and end > size: 

2871 # Clients sometimes blindly use a large range to limit their 

2872 # download size; cap the endpoint at the actual file size. 

2873 end = size 

2874 # Note: only return HTTP 206 if less than the entire range has been 

2875 # requested. Not only is this semantically correct, but Chrome 

2876 # refuses to play audio if it gets an HTTP 206 in response to 

2877 # ``Range: bytes=0-``. 

2878 if size != (end or size) - (start or 0): 

2879 self.set_status(206) # Partial Content 

2880 self.set_header( 

2881 "Content-Range", httputil._get_content_range(start, end, size) 

2882 ) 

2883 else: 

2884 start = end = None 

2885 

2886 if start is not None and end is not None: 

2887 content_length = end - start 

2888 elif end is not None: 

2889 content_length = end 

2890 elif start is not None: 

2891 content_length = size - start 

2892 else: 

2893 content_length = size 

2894 self.set_header("Content-Length", content_length) 

2895 

2896 if include_body: 

2897 content = self.get_content(self.absolute_path, start, end) 

2898 if isinstance(content, bytes): 

2899 content = [content] 

2900 for chunk in content: 

2901 try: 

2902 self.write(chunk) 

2903 await self.flush() 

2904 except iostream.StreamClosedError: 

2905 return 

2906 else: 

2907 assert self.request.method == "HEAD" 

2908 

2909 def compute_etag(self) -> Optional[str]: 

2910 """Sets the ``Etag`` header based on static url version. 

2911 

2912 This allows efficient ``If-None-Match`` checks against cached 

2913 versions, and sends the correct ``Etag`` for a partial response 

2914 (i.e. the same ``Etag`` as the full file). 

2915 

2916 .. versionadded:: 3.1 

2917 """ 

2918 assert self.absolute_path is not None 

2919 version_hash = self._get_cached_version(self.absolute_path) 

2920 if not version_hash: 

2921 return None 

2922 return f'"{version_hash}"' 

2923 

2924 def set_headers(self) -> None: 

2925 """Sets the content and caching headers on the response. 

2926 

2927 .. versionadded:: 3.1 

2928 """ 

2929 self.set_header("Accept-Ranges", "bytes") 

2930 self.set_etag_header() 

2931 

2932 if self.modified is not None: 

2933 self.set_header("Last-Modified", self.modified) 

2934 

2935 content_type = self.get_content_type() 

2936 if content_type: 

2937 self.set_header("Content-Type", content_type) 

2938 

2939 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

2940 if cache_time > 0: 

2941 self.set_header( 

2942 "Expires", 

2943 datetime.datetime.now(datetime.timezone.utc) 

2944 + datetime.timedelta(seconds=cache_time), 

2945 ) 

2946 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

2947 

2948 self.set_extra_headers(self.path) 

2949 

2950 def should_return_304(self) -> bool: 

2951 """Returns True if the headers indicate that we should return 304. 

2952 

2953 .. versionadded:: 3.1 

2954 """ 

2955 # If client sent If-None-Match, use it, ignore If-Modified-Since 

2956 if self.request.headers.get("If-None-Match"): 

2957 return self.check_etag_header() 

2958 

2959 # Check the If-Modified-Since, and don't send the result if the 

2960 # content has not been modified 

2961 ims_value = self.request.headers.get("If-Modified-Since") 

2962 if ims_value is not None: 

2963 try: 

2964 if_since = email.utils.parsedate_to_datetime(ims_value) 

2965 except Exception: 

2966 return False 

2967 if if_since.tzinfo is None: 

2968 if_since = if_since.replace(tzinfo=datetime.timezone.utc) 

2969 assert self.modified is not None 

2970 if if_since >= self.modified: 

2971 return True 

2972 

2973 return False 

2974 

2975 @classmethod 

2976 def get_absolute_path(cls, root: str, path: str) -> str: 

2977 """Returns the absolute location of ``path`` relative to ``root``. 

2978 

2979 ``root`` is the path configured for this `StaticFileHandler` 

2980 (in most cases the ``static_path`` `Application` setting). 

2981 

2982 This class method may be overridden in subclasses. By default 

2983 it returns a filesystem path, but other strings may be used 

2984 as long as they are unique and understood by the subclass's 

2985 overridden `get_content`. 

2986 

2987 .. versionadded:: 3.1 

2988 """ 

2989 abspath = os.path.abspath(os.path.join(root, path)) 

2990 return abspath 

2991 

2992 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 

2993 """Validate and return the absolute path. 

2994 

2995 ``root`` is the configured path for the `StaticFileHandler`, 

2996 and ``path`` is the result of `get_absolute_path` 

2997 

2998 This is an instance method called during request processing, 

2999 so it may raise `HTTPError` or use methods like 

3000 `RequestHandler.redirect` (return None after redirecting to 

3001 halt further processing). This is where 404 errors for missing files 

3002 are generated. 

3003 

3004 This method may modify the path before returning it, but note that 

3005 any such modifications will not be understood by `make_static_url`. 

3006 

3007 In instance methods, this method's result is available as 

3008 ``self.absolute_path``. 

3009 

3010 .. versionadded:: 3.1 

3011 """ 

3012 # os.path.abspath strips a trailing /. 

3013 # We must add it back to `root` so that we only match files 

3014 # in a directory named `root` instead of files starting with 

3015 # that prefix. 

3016 root = os.path.abspath(root) 

3017 if not root.endswith(os.path.sep): 

3018 # abspath always removes a trailing slash, except when 

3019 # root is '/'. This is an unusual case, but several projects 

3020 # have independently discovered this technique to disable 

3021 # Tornado's path validation and (hopefully) do their own, 

3022 # so we need to support it. 

3023 root += os.path.sep 

3024 # The trailing slash also needs to be temporarily added back 

3025 # the requested path so a request to root/ will match. 

3026 if not (absolute_path + os.path.sep).startswith(root): 

3027 raise HTTPError(403, "%s is not in root static directory", self.path) 

3028 # Symlinks may point anywhere inside allowed_symlink_directory, which 

3029 # defaults to the directory being served. Resolve that default here, 

3030 # against the same root the checks above used, and normalize the one 

3031 # directory that is allowed by default (or configured as a plain 

3032 # string) to a list, so that _resolve_symlink_target has only one 

3033 # shape to deal with. 

3034 allowed_symlink_directories = self.allowed_symlink_directory or root 

3035 if isinstance(allowed_symlink_directories, str): 

3036 allowed_symlink_directories = [allowed_symlink_directories] 

3037 # The check above is on the path as written, which is cheap and 

3038 # rejects traversal without touching the filesystem. Resolve 

3039 # symlinks only once it has passed, so that the remaining checks - 

3040 # and the path this method returns - refer to the file that will 

3041 # actually be opened rather than to the link. The two checks 

3042 # cannot be merged: this one must stay on the unresolved path, 

3043 # because allowed_symlink_directory may be wider than the root and 

3044 # would then let a ../ traversal through. 

3045 absolute_path = self._resolve_symlink_target( 

3046 absolute_path, allowed_symlink_directories, self.path 

3047 ) 

3048 if os.path.isdir(absolute_path) and self.default_filename is not None: 

3049 # need to look at the request.path here for when path is empty 

3050 # but there is some prefix to the path that was already 

3051 # trimmed by the routing 

3052 if not self.request.path.endswith("/"): 

3053 if self.request.path.startswith("//"): 

3054 # A redirect with two initial slashes is a "protocol-relative" URL. 

3055 # This means the next path segment is treated as a hostname instead 

3056 # of a part of the path, making this effectively an open redirect. 

3057 # Reject paths starting with two slashes to prevent this. 

3058 # This is only reachable under certain configurations. 

3059 raise HTTPError( 

3060 403, "cannot redirect path with two initial slashes" 

3061 ) 

3062 self.redirect(self.request.path + "/", permanent=True) 

3063 return None 

3064 absolute_path = os.path.join(absolute_path, self.default_filename) 

3065 # The default filename may be a symlink even when the directory 

3066 # containing it is not. Resolving here rather than deferring a 

3067 # single resolution until after this block is deliberate: the 

3068 # isdir() above, and the redirect it can trigger, must not run 

3069 # on a path that has already escaped, or the difference between 

3070 # a redirect and a 403 would reveal whether an out-of-bounds 

3071 # directory exists. 

3072 absolute_path = self._resolve_symlink_target( 

3073 absolute_path, allowed_symlink_directories, self.path 

3074 ) 

3075 # Stat the resolved path once and keep the result, instead of 

3076 # letting os.path.exists, os.path.isfile and the later header 

3077 # generation each resolve the path again. Every one of those is a 

3078 # separate trip through the filesystem that could observe a 

3079 # different file from the one validated here. 

3080 try: 

3081 stat_result = os.stat(absolute_path) 

3082 except OSError: 

3083 # Matches the previous os.path.exists() check, which is also 

3084 # false when the path cannot be stat'ed at all. 

3085 raise HTTPError(404) 

3086 if not stat.S_ISREG(stat_result.st_mode): 

3087 raise HTTPError(403, "%s is not a file", self.path) 

3088 self._stat_result = stat_result 

3089 return absolute_path 

3090 

3091 @staticmethod 

3092 def _resolve_symlink_target( 

3093 absolute_path: str, 

3094 allowed_symlink_directories: List[str], 

3095 path_message: str, 

3096 ) -> str: 

3097 """Resolves symlinks in ``absolute_path`` and validates the result. 

3098 

3099 The path checks in `validate_absolute_path` operate on the path as 

3100 written, which says nothing about where a symlink points: a symlink 

3101 inside the static directory can name any file on the system. The 

3102 resolved path must therefore stay inside one of 

3103 ``allowed_symlink_directories``. 

3104 

3105 This is a static method so that it can only see the values its 

3106 caller checked against: ``allowed_symlink_directories`` are the 

3107 directories `validate_absolute_path` settled on, which are not 

3108 necessarily ``self.allowed_symlink_directory`` or ``self.root``. 

3109 It is always a list, so that the single-directory case does not 

3110 need a second code path here. ``path_message`` is used only to 

3111 build the error message. 

3112 

3113 Raises `HTTPError` (403) if the resolved path escapes all of those 

3114 directories. 

3115 

3116 Note that this is a check against the state of the filesystem at 

3117 one moment: a path that is replaced by a symlink after this returns 

3118 but before the file is opened would not be caught. Closing that 

3119 race entirely would require holding file descriptors across the 

3120 whole operation. This check is not a substitute for filesystem 

3121 permissions on a directory that untrusted users can write to. 

3122 """ 

3123 # strict=True is deliberately not used here: it is unavailable on 

3124 # Python 3.9, and a path that does not exist resolves to itself, 

3125 # which the caller then reports as a 404. 

3126 resolved_path = os.path.realpath(absolute_path) 

3127 for allowed_symlink_directory in allowed_symlink_directories: 

3128 # realpath() is applied to the allowed directory as well, because 

3129 # it may itself be reached through a symlink (a static directory 

3130 # of /var/www that links to /srv/www, say). Comparing a resolved 

3131 # path against an unresolved directory would reject every request. 

3132 allowed_directory = os.path.realpath(allowed_symlink_directory) 

3133 if not allowed_directory.endswith(os.path.sep): 

3134 # As in validate_absolute_path, the separator must not be 

3135 # doubled when the directory is the filesystem root. 

3136 allowed_directory += os.path.sep 

3137 # The trailing separator lets the allowed directory itself match, 

3138 # as well as anything beneath it. This is reachable because the 

3139 # path may still be a directory at this point (when 

3140 # default_filename is set). 

3141 if (resolved_path + os.path.sep).startswith(allowed_directory): 

3142 return resolved_path 

3143 # Deliberately the same error as the check on the path as written, 

3144 # so that a caller cannot tell which way a path left the served 

3145 # directory. 

3146 raise HTTPError(403, "%s is not in root static directory", path_message) 

3147 

3148 @classmethod 

3149 def get_content( 

3150 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 

3151 ) -> Generator[bytes, None, None]: 

3152 """Retrieve the content of the requested resource which is located 

3153 at the given absolute path. 

3154 

3155 This class method may be overridden by subclasses. Note that its 

3156 signature is different from other overridable class methods 

3157 (no ``settings`` argument); this is deliberate to ensure that 

3158 ``abspath`` is able to stand on its own as a cache key. 

3159 

3160 This method should either return a byte string or an iterator 

3161 of byte strings. The latter is preferred for large files 

3162 as it helps reduce memory fragmentation. 

3163 

3164 .. versionadded:: 3.1 

3165 """ 

3166 with open(abspath, "rb") as file: 

3167 if start is not None: 

3168 file.seek(start) 

3169 if end is not None: 

3170 remaining = end - (start or 0) # type: Optional[int] 

3171 else: 

3172 remaining = None 

3173 while True: 

3174 chunk_size = 64 * 1024 

3175 if remaining is not None and remaining < chunk_size: 

3176 chunk_size = remaining 

3177 chunk = file.read(chunk_size) 

3178 if chunk: 

3179 if remaining is not None: 

3180 remaining -= len(chunk) 

3181 yield chunk 

3182 else: 

3183 if remaining is not None: 

3184 assert remaining == 0 

3185 return 

3186 

3187 @classmethod 

3188 def get_content_version(cls, abspath: str) -> str: 

3189 """Returns a version string for the resource at the given path. 

3190 

3191 This class method may be overridden by subclasses. The 

3192 default implementation is a SHA-512 hash of the file's contents. 

3193 

3194 .. versionadded:: 3.1 

3195 """ 

3196 data = cls.get_content(abspath) 

3197 hasher = hashlib.sha512() 

3198 if isinstance(data, bytes): 

3199 hasher.update(data) 

3200 else: 

3201 for chunk in data: 

3202 hasher.update(chunk) 

3203 return hasher.hexdigest() 

3204 

3205 def _stat(self) -> os.stat_result: 

3206 assert self.absolute_path is not None 

3207 # validate_absolute_path normally populates this, so that the size 

3208 # and modification time reported here describe the same file that 

3209 # was validated. Subclasses that override validate_absolute_path 

3210 # without calling super() fall back to stat'ing here. 

3211 if not hasattr(self, "_stat_result"): 

3212 self._stat_result = os.stat(self.absolute_path) 

3213 return self._stat_result 

3214 

3215 def get_content_size(self) -> int: 

3216 """Retrieve the total size of the resource at the given path. 

3217 

3218 This method may be overridden by subclasses. 

3219 

3220 .. versionadded:: 3.1 

3221 

3222 .. versionchanged:: 4.0 

3223 This method is now always called, instead of only when 

3224 partial results are requested. 

3225 """ 

3226 stat_result = self._stat() 

3227 return stat_result.st_size 

3228 

3229 def get_modified_time(self) -> Optional[datetime.datetime]: 

3230 """Returns the time that ``self.absolute_path`` was last modified. 

3231 

3232 May be overridden in subclasses. Should return a `~datetime.datetime` 

3233 object or None. 

3234 

3235 .. versionadded:: 3.1 

3236 

3237 .. versionchanged:: 6.4 

3238 Now returns an aware datetime object instead of a naive one. 

3239 Subclasses that override this method may return either kind. 

3240 """ 

3241 stat_result = self._stat() 

3242 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

3243 # which truncates the fractional portion of the timestamp. It 

3244 # was changed from that form to stat_result.st_mtime to 

3245 # satisfy mypy (which disallows the bracket operator), but the 

3246 # latter form returns a float instead of an int. For 

3247 # consistency with the past (and because we have a unit test 

3248 # that relies on this), we truncate the float here, although 

3249 # I'm not sure that's the right thing to do. 

3250 modified = datetime.datetime.fromtimestamp( 

3251 int(stat_result.st_mtime), datetime.timezone.utc 

3252 ) 

3253 return modified 

3254 

3255 def get_content_type(self) -> str: 

3256 """Returns the ``Content-Type`` header to be used for this request. 

3257 

3258 .. versionadded:: 3.1 

3259 """ 

3260 assert self.absolute_path is not None 

3261 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3262 # per RFC 6713, use the appropriate type for a gzip compressed file 

3263 if encoding == "gzip": 

3264 return "application/gzip" 

3265 # As of 2015-07-21 there is no bzip2 encoding defined at 

3266 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3267 # So for that (and any other encoding), use octet-stream. 

3268 elif encoding is not None: 

3269 return "application/octet-stream" 

3270 elif mime_type is not None: 

3271 return mime_type 

3272 # if mime_type not detected, use application/octet-stream 

3273 else: 

3274 return "application/octet-stream" 

3275 

3276 def set_extra_headers(self, path: str) -> None: 

3277 """For subclass to add extra headers to the response""" 

3278 pass 

3279 

3280 def get_cache_time( 

3281 self, path: str, modified: Optional[datetime.datetime], mime_type: str 

3282 ) -> int: 

3283 """Override to customize cache control behavior. 

3284 

3285 Return a positive number of seconds to make the result 

3286 cacheable for that amount of time or 0 to mark resource as 

3287 cacheable for an unspecified amount of time (subject to 

3288 browser heuristics). 

3289 

3290 By default returns cache expiry of 10 years for resources requested 

3291 with ``v`` argument. 

3292 """ 

3293 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3294 

3295 @classmethod 

3296 def make_static_url( 

3297 cls, settings: Dict[str, Any], path: str, include_version: bool = True 

3298 ) -> str: 

3299 """Constructs a versioned url for the given path. 

3300 

3301 This method may be overridden in subclasses (but note that it 

3302 is a class method rather than an instance method). Subclasses 

3303 are only required to implement the signature 

3304 ``make_static_url(cls, settings, path)``; other keyword 

3305 arguments may be passed through `~RequestHandler.static_url` 

3306 but are not standard. 

3307 

3308 ``settings`` is the `Application.settings` dictionary. ``path`` 

3309 is the static path being requested. The url returned should be 

3310 relative to the current host. 

3311 

3312 ``include_version`` determines whether the generated URL should 

3313 include the query string containing the version hash of the 

3314 file corresponding to the given ``path``. 

3315 

3316 """ 

3317 url = settings.get("static_url_prefix", "/static/") + path 

3318 if not include_version: 

3319 return url 

3320 

3321 version_hash = cls.get_version(settings, path) 

3322 if not version_hash: 

3323 return url 

3324 

3325 return f"{url}?v={version_hash}" 

3326 

3327 def parse_url_path(self, url_path: str) -> str: 

3328 """Converts a static URL path into a filesystem path. 

3329 

3330 ``url_path`` is the path component of the URL with 

3331 ``static_url_prefix`` removed. The return value should be 

3332 filesystem path relative to ``static_path``. 

3333 

3334 This is the inverse of `make_static_url`. 

3335 """ 

3336 if os.path.sep != "/": 

3337 url_path = url_path.replace("/", os.path.sep) 

3338 return url_path 

3339 

3340 @classmethod 

3341 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 

3342 """Generate the version string to be used in static URLs. 

3343 

3344 ``settings`` is the `Application.settings` dictionary and ``path`` 

3345 is the relative location of the requested asset on the filesystem. 

3346 The returned value should be a string, or ``None`` if no version 

3347 could be determined. 

3348 

3349 .. versionchanged:: 3.1 

3350 This method was previously recommended for subclasses to override; 

3351 `get_content_version` is now preferred as it allows the base 

3352 class to handle caching of the result. 

3353 """ 

3354 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3355 return cls._get_cached_version(abs_path) 

3356 

3357 @classmethod 

3358 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 

3359 with cls._lock: 

3360 hashes = cls._static_hashes 

3361 if abs_path not in hashes: 

3362 try: 

3363 hashes[abs_path] = cls.get_content_version(abs_path) 

3364 except Exception: 

3365 gen_log.error("Could not open static file %r", abs_path) 

3366 hashes[abs_path] = None 

3367 hsh = hashes.get(abs_path) 

3368 if hsh: 

3369 return hsh 

3370 return None 

3371 

3372 

3373class FallbackHandler(RequestHandler): 

3374 """A `RequestHandler` that wraps another HTTP server callback. 

3375 

3376 The fallback is a callable object that accepts an 

3377 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3378 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3379 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3380 usage:: 

3381 

3382 wsgi_app = tornado.wsgi.WSGIContainer( 

3383 django.core.handlers.wsgi.WSGIHandler()) 

3384 application = tornado.web.Application([ 

3385 (r"/foo", FooHandler), 

3386 (r".*", FallbackHandler, dict(fallback=wsgi_app)), 

3387 ]) 

3388 """ 

3389 

3390 def initialize( 

3391 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3392 ) -> None: 

3393 self.fallback = fallback 

3394 

3395 def prepare(self) -> None: 

3396 self.fallback(self.request) 

3397 self._finished = True 

3398 self.on_finish() 

3399 

3400 

3401class OutputTransform: 

3402 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3403 

3404 Applications are not expected to create their own OutputTransforms 

3405 or interact with them directly; the framework chooses which transforms 

3406 (if any) to apply. 

3407 """ 

3408 

3409 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3410 pass 

3411 

3412 def transform_first_chunk( 

3413 self, 

3414 status_code: int, 

3415 headers: httputil.HTTPHeaders, 

3416 chunk: bytes, 

3417 finishing: bool, 

3418 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3419 return status_code, headers, chunk 

3420 

3421 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3422 return chunk 

3423 

3424 

3425class GZipContentEncoding(OutputTransform): 

3426 """Applies the gzip content encoding to the response. 

3427 

3428 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3429 

3430 .. versionchanged:: 4.0 

3431 Now compresses all mime types beginning with ``text/``, instead 

3432 of just a whitelist. (the whitelist is still used for certain 

3433 non-text mime types). 

3434 """ 

3435 

3436 # Whitelist of compressible mime types (in addition to any types 

3437 # beginning with "text/"). 

3438 CONTENT_TYPES = { 

3439 "application/javascript", 

3440 "application/x-javascript", 

3441 "application/xml", 

3442 "application/atom+xml", 

3443 "application/json", 

3444 "application/xhtml+xml", 

3445 "image/svg+xml", 

3446 } 

3447 # Python's GzipFile defaults to level 9, while most other gzip 

3448 # tools (including gzip itself) default to 6, which is probably a 

3449 # better CPU/size tradeoff. 

3450 GZIP_LEVEL = 6 

3451 # Responses that are too short are unlikely to benefit from gzipping 

3452 # after considering the "Content-Encoding: gzip" header and the header 

3453 # inside the gzip encoding. 

3454 # Note that responses written in multiple chunks will be compressed 

3455 # regardless of size. 

3456 MIN_LENGTH = 1024 

3457 

3458 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3459 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3460 

3461 def _compressible_type(self, ctype: str) -> bool: 

3462 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3463 

3464 def transform_first_chunk( 

3465 self, 

3466 status_code: int, 

3467 headers: httputil.HTTPHeaders, 

3468 chunk: bytes, 

3469 finishing: bool, 

3470 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 

3471 # TODO: can/should this type be inherited from the superclass? 

3472 if "Vary" in headers: 

3473 headers["Vary"] += ", Accept-Encoding" 

3474 else: 

3475 headers["Vary"] = "Accept-Encoding" 

3476 if self._gzipping: 

3477 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3478 self._gzipping = ( 

3479 self._compressible_type(ctype) 

3480 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3481 and ("Content-Encoding" not in headers) 

3482 ) 

3483 if self._gzipping: 

3484 headers["Content-Encoding"] = "gzip" 

3485 self._gzip_value = BytesIO() 

3486 self._gzip_file = gzip.GzipFile( 

3487 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3488 ) 

3489 chunk = self.transform_chunk(chunk, finishing) 

3490 if "Content-Length" in headers: 

3491 # The original content length is no longer correct. 

3492 # If this is the last (and only) chunk, we can set the new 

3493 # content-length; otherwise we remove it and fall back to 

3494 # chunked encoding. 

3495 if finishing: 

3496 headers["Content-Length"] = str(len(chunk)) 

3497 else: 

3498 del headers["Content-Length"] 

3499 return status_code, headers, chunk 

3500 

3501 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3502 if self._gzipping: 

3503 self._gzip_file.write(chunk) 

3504 if finishing: 

3505 self._gzip_file.close() 

3506 else: 

3507 self._gzip_file.flush() 

3508 chunk = self._gzip_value.getvalue() 

3509 self._gzip_value.truncate(0) 

3510 self._gzip_value.seek(0) 

3511 return chunk 

3512 

3513 

3514def authenticated( 

3515 method: Callable[..., Optional[Awaitable[None]]], 

3516) -> Callable[..., Optional[Awaitable[None]]]: 

3517 """Decorate methods with this to require that the user be logged in. 

3518 

3519 If the user is not logged in, they will be redirected to the configured 

3520 `login url <RequestHandler.get_login_url>`. 

3521 

3522 If you configure a login url with a query parameter, Tornado will 

3523 assume you know what you're doing and use it as-is. If not, it 

3524 will add a `next` parameter so the login page knows where to send 

3525 you once you're logged in. 

3526 """ 

3527 

3528 @functools.wraps(method) 

3529 def wrapper( # type: ignore 

3530 self: RequestHandler, *args, **kwargs 

3531 ) -> Optional[Awaitable[None]]: 

3532 if not self.current_user: 

3533 if self.request.method in ("GET", "HEAD"): 

3534 url = self.get_login_url() 

3535 if "?" not in url: 

3536 if urllib.parse.urlsplit(url).scheme: 

3537 # if login url is absolute, make next absolute too 

3538 next_url = self.request.full_url() 

3539 else: 

3540 assert self.request.uri is not None 

3541 next_url = self.request.uri 

3542 url += "?" + urlencode(dict(next=next_url)) 

3543 self.redirect(url) 

3544 return None 

3545 raise HTTPError(403) 

3546 return method(self, *args, **kwargs) 

3547 

3548 return wrapper 

3549 

3550 

3551class UIModule: 

3552 """A re-usable, modular UI unit on a page. 

3553 

3554 UI modules often execute additional queries, and they can include 

3555 additional CSS and JavaScript that will be included in the output 

3556 page, which is automatically inserted on page render. 

3557 

3558 Subclasses of UIModule must override the `render` method. 

3559 """ 

3560 

3561 def __init__(self, handler: RequestHandler) -> None: 

3562 self.handler = handler 

3563 self.request = handler.request 

3564 self.ui = handler.ui 

3565 self.locale = handler.locale 

3566 

3567 @property 

3568 def current_user(self) -> Any: 

3569 return self.handler.current_user 

3570 

3571 def render(self, *args: Any, **kwargs: Any) -> Union[str, bytes]: 

3572 """Override in subclasses to return this module's output.""" 

3573 raise NotImplementedError() 

3574 

3575 def embedded_javascript(self) -> Optional[str]: 

3576 """Override to return a JavaScript string 

3577 to be embedded in the page.""" 

3578 return None 

3579 

3580 def javascript_files(self) -> Optional[Iterable[str]]: 

3581 """Override to return a list of JavaScript files needed by this module. 

3582 

3583 If the return values are relative paths, they will be passed to 

3584 `RequestHandler.static_url`; otherwise they will be used as-is. 

3585 """ 

3586 return None 

3587 

3588 def embedded_css(self) -> Optional[str]: 

3589 """Override to return a CSS string 

3590 that will be embedded in the page.""" 

3591 return None 

3592 

3593 def css_files(self) -> Optional[Iterable[str]]: 

3594 """Override to returns a list of CSS files required by this module. 

3595 

3596 If the return values are relative paths, they will be passed to 

3597 `RequestHandler.static_url`; otherwise they will be used as-is. 

3598 """ 

3599 return None 

3600 

3601 def html_head(self) -> Optional[str]: 

3602 """Override to return an HTML string that will be put in the <head/> 

3603 element. 

3604 """ 

3605 return None 

3606 

3607 def html_body(self) -> Optional[str]: 

3608 """Override to return an HTML string that will be put at the end of 

3609 the <body/> element. 

3610 """ 

3611 return None 

3612 

3613 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3614 """Renders a template and returns it as a string.""" 

3615 return self.handler.render_string(path, **kwargs) 

3616 

3617 

3618class _linkify(UIModule): 

3619 def render(self, text: str, **kwargs: Any) -> str: 

3620 return escape.linkify(text, **kwargs) 

3621 

3622 

3623class _xsrf_form_html(UIModule): 

3624 def render(self) -> str: 

3625 return self.handler.xsrf_form_html() 

3626 

3627 

3628class TemplateModule(UIModule): 

3629 """UIModule that simply renders the given template. 

3630 

3631 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3632 but the module version gets its own namespace (with kwargs passed to 

3633 Template()) instead of inheriting the outer template's namespace. 

3634 

3635 Templates rendered through this module also get access to UIModule's 

3636 automatic JavaScript/CSS features. Simply call set_resources 

3637 inside the template and give it keyword arguments corresponding to 

3638 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3639 Note that these resources are output once per template file, not once 

3640 per instantiation of the template, so they must not depend on 

3641 any arguments to the template. 

3642 """ 

3643 

3644 def __init__(self, handler: RequestHandler) -> None: 

3645 super().__init__(handler) 

3646 # keep resources in both a list and a dict to preserve order 

3647 self._resource_list = [] # type: List[Dict[str, Any]] 

3648 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 

3649 

3650 def render(self, path: str, **kwargs: Any) -> bytes: 

3651 def set_resources(**kwargs) -> str: # type: ignore 

3652 if path not in self._resource_dict: 

3653 self._resource_list.append(kwargs) 

3654 self._resource_dict[path] = kwargs 

3655 else: 

3656 if self._resource_dict[path] != kwargs: 

3657 raise ValueError( 

3658 "set_resources called with different " 

3659 "resources for the same template" 

3660 ) 

3661 return "" 

3662 

3663 return self.render_string(path, set_resources=set_resources, **kwargs) 

3664 

3665 def _get_resources(self, key: str) -> Iterable[str]: 

3666 return (r[key] for r in self._resource_list if key in r) 

3667 

3668 def embedded_javascript(self) -> str: 

3669 return "\n".join(self._get_resources("embedded_javascript")) 

3670 

3671 def javascript_files(self) -> Iterable[str]: 

3672 result = [] 

3673 for f in self._get_resources("javascript_files"): 

3674 if isinstance(f, (unicode_type, bytes)): 

3675 result.append(f) 

3676 else: 

3677 result.extend(f) 

3678 return result 

3679 

3680 def embedded_css(self) -> str: 

3681 return "\n".join(self._get_resources("embedded_css")) 

3682 

3683 def css_files(self) -> Iterable[str]: 

3684 result = [] 

3685 for f in self._get_resources("css_files"): 

3686 if isinstance(f, (unicode_type, bytes)): 

3687 result.append(f) 

3688 else: 

3689 result.extend(f) 

3690 return result 

3691 

3692 def html_head(self) -> str: 

3693 return "".join(self._get_resources("html_head")) 

3694 

3695 def html_body(self) -> str: 

3696 return "".join(self._get_resources("html_body")) 

3697 

3698 

3699class _UIModuleNamespace: 

3700 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3701 

3702 def __init__( 

3703 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 

3704 ) -> None: 

3705 self.handler = handler 

3706 self.ui_modules = ui_modules 

3707 

3708 def __getitem__(self, key: str) -> Callable[..., str]: 

3709 return self.handler._ui_module(key, self.ui_modules[key]) 

3710 

3711 def __getattr__(self, key: str) -> Callable[..., str]: 

3712 try: 

3713 return self[key] 

3714 except KeyError as e: 

3715 raise AttributeError(str(e)) 

3716 

3717 

3718def create_signed_value( 

3719 secret: _CookieSecretTypes, 

3720 name: str, 

3721 value: Union[str, bytes], 

3722 version: Optional[int] = None, 

3723 clock: Optional[Callable[[], float]] = None, 

3724 key_version: Optional[int] = None, 

3725) -> bytes: 

3726 if version is None: 

3727 version = DEFAULT_SIGNED_VALUE_VERSION 

3728 if clock is None: 

3729 clock = time.time 

3730 

3731 timestamp = utf8(str(int(clock()))) 

3732 value = base64.b64encode(utf8(value)) 

3733 if version == 1: 

3734 assert not isinstance(secret, dict) 

3735 signature = _create_signature_v1(secret, name, value, timestamp) 

3736 value = b"|".join([value, timestamp, signature]) 

3737 return value 

3738 elif version == 2: 

3739 # The v2 format consists of a version number and a series of 

3740 # length-prefixed fields "%d:%s", the last of which is a 

3741 # signature, all separated by pipes. All numbers are in 

3742 # decimal format with no leading zeros. The signature is an 

3743 # HMAC-SHA256 of the whole string up to that point, including 

3744 # the final pipe. 

3745 # 

3746 # The fields are: 

3747 # - format version (i.e. 2; no length prefix) 

3748 # - key version (integer, default is 0) 

3749 # - timestamp (integer seconds since epoch) 

3750 # - name (not encoded; assumed to be ~alphanumeric) 

3751 # - value (base64-encoded) 

3752 # - signature (hex-encoded; no length prefix) 

3753 def format_field(s: Union[str, bytes]) -> bytes: 

3754 return utf8("%d:" % len(s)) + utf8(s) 

3755 

3756 to_sign = b"|".join( 

3757 [ 

3758 b"2", 

3759 format_field(str(key_version or 0)), 

3760 format_field(timestamp), 

3761 format_field(name), 

3762 format_field(value), 

3763 b"", 

3764 ] 

3765 ) 

3766 

3767 if isinstance(secret, dict): 

3768 assert ( 

3769 key_version is not None 

3770 ), "Key version must be set when sign key dict is used" 

3771 assert version >= 2, "Version must be at least 2 for key version support" 

3772 secret = secret[key_version] 

3773 

3774 signature = _create_signature_v2(secret, to_sign) 

3775 return to_sign + signature 

3776 else: 

3777 raise ValueError("Unsupported version %d" % version) 

3778 

3779 

3780# A leading version number in decimal 

3781# with no leading zeros, followed by a pipe. 

3782_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

3783 

3784 

3785def _get_version(value: bytes) -> int: 

3786 # Figures out what version value is. Version 1 did not include an 

3787 # explicit version field and started with arbitrary base64 data, 

3788 # which makes this tricky. 

3789 m = _signed_value_version_re.match(value) 

3790 if m is None: 

3791 version = 1 

3792 else: 

3793 try: 

3794 version = int(m.group(1)) 

3795 if version > 999: 

3796 # Certain payloads from the version-less v1 format may 

3797 # be parsed as valid integers. Due to base64 padding 

3798 # restrictions, this can only happen for numbers whose 

3799 # length is a multiple of 4, so we can treat all 

3800 # numbers up to 999 as versions, and for the rest we 

3801 # fall back to v1 format. 

3802 version = 1 

3803 except ValueError: 

3804 version = 1 

3805 return version 

3806 

3807 

3808def decode_signed_value( 

3809 secret: _CookieSecretTypes, 

3810 name: str, 

3811 value: Union[None, str, bytes], 

3812 max_age_days: float = 31, 

3813 clock: Optional[Callable[[], float]] = None, 

3814 min_version: Optional[int] = None, 

3815) -> Optional[bytes]: 

3816 if clock is None: 

3817 clock = time.time 

3818 if min_version is None: 

3819 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

3820 if min_version > 2: 

3821 raise ValueError("Unsupported min_version %d" % min_version) 

3822 if not value: 

3823 return None 

3824 

3825 value = utf8(value) 

3826 version = _get_version(value) 

3827 

3828 if version < min_version: 

3829 return None 

3830 if version == 1: 

3831 assert not isinstance(secret, dict) 

3832 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

3833 elif version == 2: 

3834 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

3835 else: 

3836 return None 

3837 

3838 

3839def _decode_signed_value_v1( 

3840 secret: Union[str, bytes], 

3841 name: str, 

3842 value: bytes, 

3843 max_age_days: float, 

3844 clock: Callable[[], float], 

3845) -> Optional[bytes]: 

3846 parts = utf8(value).split(b"|") 

3847 if len(parts) != 3: 

3848 return None 

3849 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

3850 if not hmac.compare_digest(parts[2], signature): 

3851 gen_log.warning("Invalid cookie signature %r", value) 

3852 return None 

3853 timestamp = int(parts[1]) 

3854 if timestamp < clock() - max_age_days * 86400: 

3855 gen_log.warning("Expired cookie %r", value) 

3856 return None 

3857 if timestamp > clock() + 31 * 86400: 

3858 # _cookie_signature does not hash a delimiter between the 

3859 # parts of the cookie, so an attacker could transfer trailing 

3860 # digits from the payload to the timestamp without altering the 

3861 # signature. For backwards compatibility, sanity-check timestamp 

3862 # here instead of modifying _cookie_signature. 

3863 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

3864 return None 

3865 if parts[1].startswith(b"0"): 

3866 gen_log.warning("Tampered cookie %r", value) 

3867 return None 

3868 try: 

3869 return base64.b64decode(parts[0]) 

3870 except Exception: 

3871 return None 

3872 

3873 

3874def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 

3875 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 

3876 length, _, rest = s.partition(b":") 

3877 n = int(length) 

3878 field_value = rest[:n] 

3879 # In python 3, indexing bytes returns small integers; we must 

3880 # use a slice to get a byte string as in python 2. 

3881 if rest[n : n + 1] != b"|": 

3882 raise ValueError("malformed v2 signed value field") 

3883 rest = rest[n + 1 :] 

3884 return field_value, rest 

3885 

3886 rest = value[2:] # remove version number 

3887 key_version, rest = _consume_field(rest) 

3888 timestamp, rest = _consume_field(rest) 

3889 name_field, rest = _consume_field(rest) 

3890 value_field, passed_sig = _consume_field(rest) 

3891 return int(key_version), timestamp, name_field, value_field, passed_sig 

3892 

3893 

3894def _decode_signed_value_v2( 

3895 secret: _CookieSecretTypes, 

3896 name: str, 

3897 value: bytes, 

3898 max_age_days: float, 

3899 clock: Callable[[], float], 

3900) -> Optional[bytes]: 

3901 try: 

3902 ( 

3903 key_version, 

3904 timestamp_bytes, 

3905 name_field, 

3906 value_field, 

3907 passed_sig, 

3908 ) = _decode_fields_v2(value) 

3909 except ValueError: 

3910 return None 

3911 signed_string = value[: -len(passed_sig)] 

3912 

3913 if isinstance(secret, dict): 

3914 try: 

3915 secret = secret[key_version] 

3916 except KeyError: 

3917 return None 

3918 

3919 expected_sig = _create_signature_v2(secret, signed_string) 

3920 if not hmac.compare_digest(passed_sig, expected_sig): 

3921 return None 

3922 if name_field != utf8(name): 

3923 return None 

3924 timestamp = int(timestamp_bytes) 

3925 if timestamp < clock() - max_age_days * 86400: 

3926 # The signature has expired. 

3927 return None 

3928 try: 

3929 return base64.b64decode(value_field) 

3930 except Exception: 

3931 return None 

3932 

3933 

3934def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 

3935 value = utf8(value) 

3936 version = _get_version(value) 

3937 if version < 2: 

3938 return None 

3939 try: 

3940 key_version, _, _, _, _ = _decode_fields_v2(value) 

3941 except ValueError: 

3942 return None 

3943 

3944 return key_version 

3945 

3946 

3947def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 

3948 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

3949 for part in parts: 

3950 hash.update(utf8(part)) 

3951 return utf8(hash.hexdigest()) 

3952 

3953 

3954def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 

3955 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

3956 hash.update(utf8(s)) 

3957 return utf8(hash.hexdigest()) 

3958 

3959 

3960def is_absolute(path: str) -> bool: 

3961 return any(path.startswith(x) for x in ["/", "http:", "https:"])