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

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

1592 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 asyncio 

60import base64 

61import binascii 

62import datetime 

63import email.utils 

64import functools 

65import gzip 

66import hashlib 

67import hmac 

68import http.cookies 

69import mimetypes 

70import numbers 

71import os.path 

72import re 

73import socket 

74import stat 

75import string 

76import sys 

77import threading 

78import time 

79import traceback 

80import types 

81import urllib.parse 

82import warnings 

83from inspect import isclass 

84from io import BytesIO 

85from urllib.parse import urlencode 

86 

87import tornado 

88from tornado import escape, gen, httputil, iostream, locale, template 

89from tornado.concurrent import Future, future_set_result_unless_cancelled 

90from tornado.escape import _unicode, utf8 

91from tornado.httpserver import HTTPServer 

92from tornado.log import access_log, app_log, gen_log 

93from tornado.routing import ( 

94 AnyMatches, 

95 DefaultHostMatches, 

96 HostMatches, 

97 ReversibleRouter, 

98 ReversibleRuleRouter, 

99 Rule, 

100 URLSpec, 

101 _RuleList, 

102) 

103from tornado.util import ObjectDict, _websocket_mask, unicode_type 

104 

105url = URLSpec 

106 

107from collections.abc import Awaitable, Callable, Generator, Iterable 

108from types import TracebackType 

109from typing import ( 

110 Any, 

111 Optional, 

112 Type, 

113 TypeVar, 

114 Union, 

115 cast, 

116 overload, 

117) 

118 

119# The following types are accepted by RequestHandler.set_header 

120# and related methods. 

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

122 

123_CookieSecretTypes = Union[str, bytes, dict[int, str], dict[int, bytes]] 

124 

125 

126MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1 

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

128 

129Signed values older than this version cannot be decoded. 

130 

131.. versionadded:: 3.2.1 

132""" 

133 

134MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2 

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

136 

137Signed values newer than this version cannot be decoded. 

138 

139.. versionadded:: 3.2.1 

140""" 

141 

142DEFAULT_SIGNED_VALUE_VERSION = 2 

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

144 

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

146 

147.. versionadded:: 3.2.1 

148""" 

149 

150DEFAULT_SIGNED_VALUE_MIN_VERSION = 1 

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

152 

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

154 

155.. versionadded:: 3.2.1 

156""" 

157 

158 

159class _ArgDefaultMarker: 

160 pass 

161 

162 

163_ARG_DEFAULT = _ArgDefaultMarker() 

164 

165 

166class RequestHandler: 

167 """Base class for HTTP request handlers. 

168 

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

170 "Entry points" section below. 

171 

172 Applications should not construct `RequestHandler` objects 

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

174 `~RequestHandler.initialize` instead). 

175 

176 """ 

177 

178 SUPPORTED_METHODS: tuple[str, ...] = ( 

179 "GET", 

180 "HEAD", 

181 "POST", 

182 "DELETE", 

183 "PATCH", 

184 "PUT", 

185 "OPTIONS", 

186 ) 

187 

188 _template_loaders: dict[str, template.BaseLoader] = {} 

189 _template_loader_lock = threading.Lock() 

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

191 

192 _stream_request_body = False 

193 

194 # Will be set in _execute. 

195 _transforms: list["OutputTransform"] 

196 path_args: list[str] 

197 path_kwargs: dict[str, str] 

198 

199 def __init__( 

200 self, 

201 application: "Application", 

202 request: httputil.HTTPServerRequest, 

203 **kwargs: Any, 

204 ) -> None: 

205 super().__init__() 

206 

207 self.application = application 

208 self.request = request 

209 self._headers_written = False 

210 self._finished = False 

211 self._auto_finish = True 

212 self._prepared_future = None 

213 self.ui = ObjectDict( 

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

215 ) 

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

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

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

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

220 # possible conflicts. 

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

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

223 self.clear() 

224 assert self.request.connection is not None 

225 # TODO: need to add set_close_callback to HTTPConnection interface 

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

227 self.on_connection_close 

228 ) 

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

230 

231 def _initialize(self) -> None: 

232 pass 

233 

234 initialize: Callable[..., None] = _initialize 

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

236 

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

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

239 

240 Example:: 

241 

242 class ProfileHandler(RequestHandler): 

243 def initialize(self, database): 

244 self.database = database 

245 

246 def get(self, username): 

247 ... 

248 

249 app = Application([ 

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

251 ]) 

252 """ 

253 

254 @property 

255 def settings(self) -> dict[str, Any]: 

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

257 return self.application.settings 

258 

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

260 raise HTTPError(405) 

261 

262 head: Callable[..., Awaitable[None] | None] = _unimplemented_method 

263 get: Callable[..., Awaitable[None] | None] = _unimplemented_method 

264 post: Callable[..., Awaitable[None] | None] = _unimplemented_method 

265 delete: Callable[..., Awaitable[None] | None] = _unimplemented_method 

266 patch: Callable[..., Awaitable[None] | None] = _unimplemented_method 

267 put: Callable[..., Awaitable[None] | None] = _unimplemented_method 

268 options: Callable[..., Awaitable[None] | None] = _unimplemented_method 

269 

270 def prepare(self) -> Awaitable[None] | None: 

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

272 

273 Override this method to perform common initialization regardless 

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

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

276 

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

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

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

280 until the ``Awaitable`` is done. 

281 

282 .. versionadded:: 3.1 

283 Asynchronous support. 

284 """ 

285 pass 

286 

287 def on_finish(self) -> None: 

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

289 

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

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

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

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

294 occurred) 

295 

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

297 to the client. 

298 """ 

299 pass 

300 

301 def on_connection_close(self) -> None: 

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

303 

304 Override this to clean up resources associated with 

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

306 the connection was closed during asynchronous processing; if you 

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

308 instead. 

309 

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

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

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

313 connection. 

314 """ 

315 if _has_stream_request_body(self.__class__): 

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

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

318 self.request._body_future.exception() 

319 

320 def clear(self) -> None: 

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

322 self._headers = httputil.HTTPHeaders( 

323 { 

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

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

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

327 } 

328 ) 

329 self.set_default_headers() 

330 self._write_buffer: list[bytes] = [] 

331 self._status_code = 200 

332 self._reason = httputil.responses[200] 

333 

334 def set_default_headers(self) -> None: 

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

336 

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

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

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

340 during error handling. 

341 """ 

342 pass 

343 

344 def set_status(self, status_code: int, reason: str | None = None) -> None: 

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

346 

347 :arg int status_code: Response status code. 

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

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

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

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

352 status code. 

353 

354 .. versionchanged:: 5.0 

355 

356 No longer validates that the response code is in 

357 `http.client.responses`. 

358 """ 

359 self._status_code = status_code 

360 if reason is not None: 

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

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

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

364 # to tolerate internal errors cleanly. 

365 # 

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

367 # escape the reason when rendering error pages). 

368 reason = "Unknown" 

369 self._reason = escape.native_str(reason) 

370 else: 

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

372 

373 def get_status(self) -> int: 

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

375 return self._status_code 

376 

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

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

379 

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

381 are formatted according to the HTTP specification for the 

382 ``Date`` header). 

383 

384 """ 

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

386 

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

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

389 

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

391 to return multiple values for the same header. 

392 """ 

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

394 

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

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

397 

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

399 set by `add_header`. 

400 """ 

401 if name in self._headers: 

402 del self._headers[name] 

403 

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

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

406 

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

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

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

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

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

412 if isinstance(value, str): 

413 retval = value 

414 elif isinstance(value, bytes): 

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

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

417 retval = value.decode("latin1") 

418 elif isinstance(value, numbers.Integral): 

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

420 return str(value) 

421 elif isinstance(value, datetime.datetime): 

422 return httputil.format_timestamp(value) 

423 else: 

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

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

426 # additional headers or split the request. 

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

428 raise ValueError("Unsafe header value %r" % retval) 

429 return retval 

430 

431 @overload 

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

433 pass 

434 

435 @overload 

436 def get_argument( 

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

438 ) -> str: 

439 pass 

440 

441 @overload 

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

443 pass 

444 

445 def get_argument( 

446 self, 

447 name: str, 

448 default: None | str | _ArgDefaultMarker = _ARG_DEFAULT, 

449 strip: bool = True, 

450 ) -> str | None: 

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

452 

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

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

455 

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

457 last value. 

458 

459 This method searches both the query and body arguments. 

460 """ 

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

462 

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

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

465 

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

467 

468 This method searches both the query and body arguments. 

469 """ 

470 

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

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

473 # `get_argument`.) 

474 assert isinstance(strip, bool) 

475 

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

477 

478 @overload 

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

480 pass 

481 

482 @overload 

483 def get_body_argument( 

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

485 ) -> str: 

486 pass 

487 

488 @overload 

489 def get_body_argument( 

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

491 ) -> str | None: 

492 pass 

493 

494 def get_body_argument( 

495 self, 

496 name: str, 

497 default: None | str | _ArgDefaultMarker = _ARG_DEFAULT, 

498 strip: bool = True, 

499 ) -> str | None: 

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

501 from the request body. 

502 

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

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

505 

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

507 last value. 

508 

509 .. versionadded:: 3.2 

510 """ 

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

512 

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

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

515 

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

517 

518 .. versionadded:: 3.2 

519 """ 

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

521 

522 @overload 

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

524 pass 

525 

526 @overload 

527 def get_query_argument( 

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

529 ) -> str: 

530 pass 

531 

532 @overload 

533 def get_query_argument( 

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

535 ) -> str | None: 

536 pass 

537 

538 def get_query_argument( 

539 self, 

540 name: str, 

541 default: None | str | _ArgDefaultMarker = _ARG_DEFAULT, 

542 strip: bool = True, 

543 ) -> str | None: 

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

545 from the request query string. 

546 

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

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

549 

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

551 last value. 

552 

553 .. versionadded:: 3.2 

554 """ 

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

556 

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

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

559 

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

561 

562 .. versionadded:: 3.2 

563 """ 

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

565 

566 def _get_argument( 

567 self, 

568 name: str, 

569 default: None | str | _ArgDefaultMarker, 

570 source: dict[str, list[bytes]], 

571 strip: bool = True, 

572 ) -> str | None: 

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

574 if not args: 

575 if isinstance(default, _ArgDefaultMarker): 

576 raise MissingArgumentError(name) 

577 return default 

578 return args[-1] 

579 

580 def _get_arguments( 

581 self, name: str, source: dict[str, list[bytes]], strip: bool = True 

582 ) -> list[str]: 

583 values = [] 

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

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

586 if isinstance(s, unicode_type): 

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

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

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

590 if strip: 

591 s = s.strip() 

592 values.append(s) 

593 return values 

594 

595 def decode_argument(self, value: bytes, name: str | None = None) -> str: 

596 """Decodes an argument from the request. 

597 

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

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

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

601 

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

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

604 

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

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

607 """ 

608 try: 

609 return _unicode(value) 

610 except UnicodeDecodeError: 

611 raise HTTPError( 

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

613 ) 

614 

615 @property 

616 def cookies(self) -> dict[str, http.cookies.Morsel]: 

617 """An alias for 

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

619 return self.request.cookies 

620 

621 @overload 

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

623 pass 

624 

625 @overload 

626 def get_cookie(self, name: str, default: None = None) -> str | None: 

627 pass 

628 

629 def get_cookie(self, name: str, default: str | None = None) -> str | None: 

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

631 

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

633 

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

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

636 handler. 

637 """ 

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

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

640 return default 

641 

642 def set_cookie( 

643 self, 

644 name: str, 

645 value: str | bytes, 

646 domain: str | None = None, 

647 expires: float | tuple | datetime.datetime | None = None, 

648 path: str = "/", 

649 expires_days: float | None = None, 

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

651 *, 

652 max_age: int | None = None, 

653 httponly: bool = False, 

654 secure: bool = False, 

655 samesite: str | None = None, 

656 **kwargs: Any, 

657 ) -> None: 

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

659 

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

661 they are not present until the next request. 

662 

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

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

665 for more information. 

666 

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

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

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

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

671 is used). 

672 

673 .. deprecated:: 6.3 

674 Keyword arguments are currently accepted case-insensitively. 

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

676 arguments. 

677 """ 

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

679 name = escape.native_str(name) 

680 value = escape.native_str(value) 

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

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

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

684 # in the next feature release. 

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

686 for attr_name, attr_value in [ 

687 ("name", name), 

688 ("domain", domain), 

689 ("path", path), 

690 ("samesite", samesite), 

691 ]: 

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

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

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

695 # semicolons. 

696 # 

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

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

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

700 # flush()). We may want to add a call to self._new_cookie.output() at the end of this 

701 # method to ensure that exceptions are raised when they will be most useful. 

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

703 raise http.cookies.CookieError( 

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

705 ) 

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

707 # Also check for disallowed characters in deprecated kwargs. 

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

709 raise http.cookies.CookieError( 

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

711 ) 

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

713 self._new_cookie: http.cookies.SimpleCookie = http.cookies.SimpleCookie() 

714 if name in self._new_cookie: 

715 del self._new_cookie[name] 

716 self._new_cookie[name] = value 

717 morsel = self._new_cookie[name] 

718 if domain: 

719 morsel["domain"] = domain 

720 if expires_days is not None and not expires: 

721 expires = datetime.datetime.now(datetime.UTC) + datetime.timedelta( 

722 days=expires_days 

723 ) 

724 if expires: 

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

726 if path: 

727 morsel["path"] = path 

728 if max_age: 

729 # Note change from _ to -. 

730 # Normalize through int() so that a non-int max_age (which could 

731 # smuggle extra cookie attributes past the attribute allowlist) 

732 # is rejected instead of being written to the header verbatim. 

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

734 if httponly: 

735 # Note that SimpleCookie ignores the value here. The presence of an 

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

737 morsel["httponly"] = True 

738 if secure: 

739 morsel["secure"] = True 

740 if samesite: 

741 morsel["samesite"] = samesite 

742 if kwargs: 

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

744 # kwargs for backwards compatibility until we can remove deprecated 

745 # features. 

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

747 morsel[k] = v 

748 warnings.warn( 

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

750 "(should be lowercase)", 

751 DeprecationWarning, 

752 ) 

753 

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

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

756 

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

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

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

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

761 arguments are ignored. 

762 

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

764 seen until the following request. 

765 

766 .. versionchanged:: 6.3 

767 

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

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

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

771 """ 

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

773 if excluded_arg in kwargs: 

774 raise TypeError( 

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

776 ) 

777 expires = datetime.datetime.now(datetime.UTC) - datetime.timedelta(days=365) 

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

779 

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

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

782 

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

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

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

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

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

788 when setting cookies. 

789 

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

791 until the following request. 

792 

793 .. versionchanged:: 3.2 

794 

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

796 

797 .. versionchanged:: 6.3 

798 

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

800 

801 .. deprecated:: 6.3 

802 

803 The increasingly complex rules governing cookies have made it 

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

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

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

807 """ 

808 for name in self.request.cookies: 

809 self.clear_cookie(name, **kwargs) 

810 

811 def set_signed_cookie( 

812 self, 

813 name: str, 

814 value: str | bytes, 

815 expires_days: float | None = 30, 

816 version: int | None = None, 

817 **kwargs: Any, 

818 ) -> None: 

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

820 

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

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

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

824 

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

826 

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

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

829 parameter to `get_signed_cookie`. 

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

831 

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

833 strings (unlike regular cookies) 

834 

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

836 seen until the following request. 

837 

838 .. versionchanged:: 3.2.1 

839 

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

841 and made it the default. 

842 

843 .. versionchanged:: 6.3 

844 

845 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to 

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

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

848 """ 

849 self.set_cookie( 

850 name, 

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

852 expires_days=expires_days, 

853 **kwargs, 

854 ) 

855 

856 set_secure_cookie = set_signed_cookie 

857 

858 def create_signed_value( 

859 self, name: str, value: str | bytes, version: int | None = None 

860 ) -> bytes: 

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

862 

863 Normally used via set_signed_cookie, but provided as a separate 

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

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

866 

867 .. versionchanged:: 3.2.1 

868 

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

870 and made it the default. 

871 """ 

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

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

874 key_version = None 

875 if isinstance(secret, dict): 

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

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

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

879 

880 return create_signed_value( 

881 secret, name, value, version=version, key_version=key_version 

882 ) 

883 

884 def get_signed_cookie( 

885 self, 

886 name: str, 

887 value: str | None = None, 

888 max_age_days: float = 31, 

889 min_version: int | None = None, 

890 ) -> bytes | None: 

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

892 

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

894 `get_cookie`). 

895 

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

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

898 `set_signed_cookie` in this handler. 

899 

900 .. versionchanged:: 3.2.1 

901 

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

903 both versions 1 and 2 are accepted by default. 

904 

905 .. versionchanged:: 6.3 

906 

907 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to 

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

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

910 

911 """ 

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

913 if value is None: 

914 value = self.get_cookie(name) 

915 return decode_signed_value( 

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

917 name, 

918 value, 

919 max_age_days=max_age_days, 

920 min_version=min_version, 

921 ) 

922 

923 get_secure_cookie = get_signed_cookie 

924 

925 def get_signed_cookie_key_version( 

926 self, name: str, value: str | None = None 

927 ) -> int | None: 

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

929 

930 The version is returned as int. 

931 

932 .. versionchanged:: 6.3 

933 

934 Renamed from ``get_secure_cookie_key_version`` to 

935 ``set_signed_cookie_key_version`` to avoid confusion with other 

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

937 remains as an alias. 

938 

939 """ 

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

941 if value is None: 

942 value = self.get_cookie(name) 

943 if value is None: 

944 return None 

945 return get_signature_key_version(value) 

946 

947 get_secure_cookie_key_version = get_signed_cookie_key_version 

948 

949 def redirect( 

950 self, url: str, permanent: bool = False, status: int | None = None 

951 ) -> None: 

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

953 

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

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

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

957 The default is 302 (temporary). 

958 """ 

959 if self._headers_written: 

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

961 if status is None: 

962 status = 301 if permanent else 302 

963 else: 

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

965 self.set_status(status) 

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

967 self.finish() 

968 

969 def write(self, chunk: str | bytes | dict) -> None: 

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

971 

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

973 

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

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

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

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

978 

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

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

981 wrapped in a dictionary. More details at 

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

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

984 """ 

985 if self._finished: 

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

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

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

989 if isinstance(chunk, list): 

990 message += ( 

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

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

993 ) 

994 raise TypeError(message) 

995 if isinstance(chunk, dict): 

996 chunk = escape.json_encode(chunk) 

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

998 chunk = utf8(chunk) 

999 self._write_buffer.append(chunk) 

1000 

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

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

1003 

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

1005 after it. 

1006 

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

1008 Awaiting this `.Future` is optional. 

1009 

1010 .. versionchanged:: 5.1 

1011 

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

1013 """ 

1014 if self._finished: 

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

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

1017 

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

1019 js_embed = [] 

1020 js_files = [] 

1021 css_embed = [] 

1022 css_files = [] 

1023 html_heads = [] 

1024 html_bodies = [] 

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

1026 embed_part = module.embedded_javascript() 

1027 if embed_part: 

1028 js_embed.append(utf8(embed_part)) 

1029 file_part = module.javascript_files() 

1030 if file_part: 

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

1032 js_files.append(_unicode(file_part)) 

1033 else: 

1034 js_files.extend(file_part) 

1035 embed_part = module.embedded_css() 

1036 if embed_part: 

1037 css_embed.append(utf8(embed_part)) 

1038 file_part = module.css_files() 

1039 if file_part: 

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

1041 css_files.append(_unicode(file_part)) 

1042 else: 

1043 css_files.extend(file_part) 

1044 head_part = module.html_head() 

1045 if head_part: 

1046 html_heads.append(utf8(head_part)) 

1047 body_part = module.html_body() 

1048 if body_part: 

1049 html_bodies.append(utf8(body_part)) 

1050 

1051 if js_files: 

1052 # Maintain order of JavaScript files given by modules 

1053 js = self.render_linked_js(js_files) 

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

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

1056 if js_embed: 

1057 js_bytes = self.render_embed_js(js_embed) 

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

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

1060 if css_files: 

1061 css = self.render_linked_css(css_files) 

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

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

1064 if css_embed: 

1065 css_bytes = self.render_embed_css(css_embed) 

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

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

1068 if html_heads: 

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

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

1071 if html_bodies: 

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

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

1074 return self.finish(html) 

1075 

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

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

1078 rendered webpage. 

1079 

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

1081 """ 

1082 paths = [] 

1083 unique_paths: set[str] = set() 

1084 

1085 for path in js_files: 

1086 if not is_absolute(path): 

1087 path = self.static_url(path) 

1088 if path not in unique_paths: 

1089 paths.append(path) 

1090 unique_paths.add(path) 

1091 

1092 return "".join( 

1093 '<script src="' 

1094 + escape.xhtml_escape(p) 

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

1096 for p in paths 

1097 ) 

1098 

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

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

1101 rendered webpage. 

1102 

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

1104 """ 

1105 return ( 

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

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

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

1109 ) 

1110 

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

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

1113 rendered webpage. 

1114 

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

1116 """ 

1117 paths = [] 

1118 unique_paths: set[str] = set() 

1119 

1120 for path in css_files: 

1121 if not is_absolute(path): 

1122 path = self.static_url(path) 

1123 if path not in unique_paths: 

1124 paths.append(path) 

1125 unique_paths.add(path) 

1126 

1127 return "".join( 

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

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

1130 for p in paths 

1131 ) 

1132 

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

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

1135 rendered webpage. 

1136 

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

1138 """ 

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

1140 

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

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

1143 

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

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

1146 """ 

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

1148 template_path = self.get_template_path() 

1149 if not template_path: 

1150 frame = sys._getframe(0) 

1151 web_file = frame.f_code.co_filename 

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

1153 frame = frame.f_back 

1154 assert frame.f_code.co_filename is not None 

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

1156 with RequestHandler._template_loader_lock: 

1157 if template_path not in RequestHandler._template_loaders: 

1158 loader = self.create_template_loader(template_path) 

1159 RequestHandler._template_loaders[template_path] = loader 

1160 else: 

1161 loader = RequestHandler._template_loaders[template_path] 

1162 t = loader.load(template_name) 

1163 namespace = self.get_template_namespace() 

1164 namespace.update(kwargs) 

1165 return t.generate(**namespace) 

1166 

1167 def get_template_namespace(self) -> dict[str, Any]: 

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

1169 

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

1171 

1172 The results of this method will be combined with additional 

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

1174 to `render` or `render_string`. 

1175 """ 

1176 namespace = dict( 

1177 handler=self, 

1178 request=self.request, 

1179 current_user=self.current_user, 

1180 locale=self.locale, 

1181 _=self.locale.translate, 

1182 pgettext=self.locale.pgettext, 

1183 static_url=self.static_url, 

1184 xsrf_form_html=self.xsrf_form_html, 

1185 reverse_url=self.reverse_url, 

1186 ) 

1187 namespace.update(self.ui) 

1188 return namespace 

1189 

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

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

1192 

1193 May be overridden by subclasses. By default returns a 

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

1195 ``autoescape`` and ``template_whitespace`` application 

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

1197 supplied, uses that instead. 

1198 """ 

1199 settings = self.application.settings 

1200 if "template_loader" in settings: 

1201 return settings["template_loader"] 

1202 kwargs = {} 

1203 if "autoescape" in settings: 

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

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

1206 kwargs["autoescape"] = settings["autoescape"] 

1207 if "template_whitespace" in settings: 

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

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

1210 

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

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

1213 

1214 .. versionchanged:: 4.0 

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

1216 

1217 .. versionchanged:: 6.0 

1218 

1219 The ``callback`` argument was removed. 

1220 """ 

1221 assert self.request.connection is not None 

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

1223 self._write_buffer = [] 

1224 if not self._headers_written: 

1225 self._headers_written = True 

1226 for transform in self._transforms: 

1227 assert chunk is not None 

1228 ( 

1229 self._status_code, 

1230 self._headers, 

1231 chunk, 

1232 ) = transform.transform_first_chunk( 

1233 self._status_code, self._headers, chunk, include_footers 

1234 ) 

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

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

1237 chunk = b"" 

1238 

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

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

1241 # is sent). 

1242 if hasattr(self, "_new_cookie"): 

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

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

1245 

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

1247 return self.request.connection.write_headers( 

1248 start_line, self._headers, chunk 

1249 ) 

1250 else: 

1251 for transform in self._transforms: 

1252 chunk = transform.transform_chunk(chunk, include_footers) 

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

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

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

1256 else: 

1257 future: Future[None] = Future() 

1258 future.set_result(None) 

1259 return future 

1260 

1261 def _should_not_send_content(self, status_code: int) -> bool: 

1262 """Check if we should not send body content for given `status_code`""" 

1263 return status_code in (204, 304) or (100 <= status_code < 200) 

1264 

1265 def finish(self, chunk: str | bytes | dict | None = None) -> "Future[None]": 

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

1267 

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

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

1270 

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

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

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

1274 data can be sent. 

1275 

1276 .. versionchanged:: 5.1 

1277 

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

1279 """ 

1280 if self._finished: 

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

1282 

1283 if chunk is not None: 

1284 self.write(chunk) 

1285 

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

1287 # we have not flushed any content yet. 

1288 if not self._headers_written: 

1289 if ( 

1290 self._status_code == 200 

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

1292 and "Etag" not in self._headers 

1293 ): 

1294 self.set_etag_header() 

1295 if self.check_etag_header(): 

1296 self._write_buffer = [] 

1297 self.set_status(304) 

1298 if self._should_not_send_content(self._status_code): 

1299 if self._write_buffer: 

1300 raise RuntimeError( 

1301 f"Cannot send body with status code HTTP{self._status_code}" 

1302 ) 

1303 self._clear_representation_headers() 

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

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

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

1307 

1308 assert self.request.connection is not None 

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

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

1311 # garbage collection of the RequestHandler when there 

1312 # are keepalive connections) 

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

1314 

1315 future = self.flush(include_footers=True) 

1316 self.request.connection.finish() 

1317 self._log() 

1318 self._finished = True 

1319 self.on_finish() 

1320 self._break_cycles() 

1321 return future 

1322 

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

1324 """Take control of the underlying stream. 

1325 

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

1327 further HTTP processing. Intended for implementing protocols 

1328 like websockets that tunnel over an HTTP handshake. 

1329 

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

1331 

1332 .. versionadded:: 5.1 

1333 """ 

1334 self._finished = True 

1335 # TODO: add detach to HTTPConnection? 

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

1337 

1338 def _break_cycles(self) -> None: 

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

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

1341 self.ui = None # type: ignore 

1342 

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

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

1345 

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

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

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

1349 and replaced with the error page. 

1350 

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

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

1353 """ 

1354 if self._headers_written: 

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

1356 if not self._finished: 

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

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

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

1360 # socket. 

1361 try: 

1362 self.finish() 

1363 except Exception: 

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

1365 return 

1366 self.clear() 

1367 

1368 reason = kwargs.get("reason") 

1369 if "exc_info" in kwargs: 

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

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

1372 reason = exception.reason 

1373 self.set_status(status_code, reason=reason) 

1374 try: 

1375 if status_code != 304: 

1376 self.write_error(status_code, **kwargs) 

1377 except Exception: 

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

1379 if not self._finished: 

1380 self.finish() 

1381 

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

1383 """Override to implement custom error pages. 

1384 

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

1386 to produce output as usual. 

1387 

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

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

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

1391 the "current" exception for purposes of methods like 

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

1393 """ 

1394 if self._should_not_send_content(status_code): 

1395 self.finish() 

1396 elif self.settings.get("serve_traceback") and "exc_info" in kwargs: 

1397 # in debug mode, try to send a traceback 

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

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

1400 self.write(line) 

1401 self.finish() 

1402 else: 

1403 self.finish( 

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

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

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

1407 ) 

1408 

1409 @property 

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

1411 """The locale for the current session. 

1412 

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

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

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

1416 header. 

1417 

1418 .. versionchanged: 4.1 

1419 Added a property setter. 

1420 """ 

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

1422 loc = self.get_user_locale() 

1423 if loc is not None: 

1424 self._locale = loc 

1425 else: 

1426 self._locale = self.get_browser_locale() 

1427 assert self._locale 

1428 return self._locale 

1429 

1430 @locale.setter 

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

1432 self._locale = value 

1433 

1434 def get_user_locale(self) -> tornado.locale.Locale | None: 

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

1436 

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

1438 

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

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

1441 """ 

1442 return None 

1443 

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

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

1446 

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

1448 """ 

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

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

1451 locales = [] 

1452 for language in languages: 

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

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

1455 try: 

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

1457 if score < 0: 

1458 raise ValueError() 

1459 except (ValueError, TypeError): 

1460 score = 0.0 

1461 else: 

1462 score = 1.0 

1463 if score > 0: 

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

1465 if locales: 

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

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

1468 return locale.get(*codes) 

1469 return locale.get(default) 

1470 

1471 @property 

1472 def current_user(self) -> Any: 

1473 """The authenticated user for this request. 

1474 

1475 This is set in one of two ways: 

1476 

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

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

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

1480 and is cached for future access:: 

1481 

1482 def get_current_user(self): 

1483 user_cookie = self.get_signed_cookie("user") 

1484 if user_cookie: 

1485 return json.loads(user_cookie) 

1486 return None 

1487 

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

1489 `prepare()`:: 

1490 

1491 @gen.coroutine 

1492 def prepare(self): 

1493 user_id_cookie = self.get_signed_cookie("user_id") 

1494 if user_id_cookie: 

1495 self.current_user = yield load_user(user_id_cookie) 

1496 

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

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

1499 asynchronous operations. 

1500 

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

1502 """ 

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

1504 self._current_user = self.get_current_user() 

1505 return self._current_user 

1506 

1507 @current_user.setter 

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

1509 self._current_user = value 

1510 

1511 def get_current_user(self) -> Any: 

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

1513 

1514 This method may not be a coroutine. 

1515 """ 

1516 return None 

1517 

1518 def get_login_url(self) -> str: 

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

1520 

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

1522 """ 

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

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

1525 

1526 def get_template_path(self) -> str | None: 

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

1528 

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

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

1531 """ 

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

1533 

1534 @property 

1535 def xsrf_token(self) -> bytes: 

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

1537 

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

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

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

1541 as a potential forgery. 

1542 

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

1544 

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

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

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

1548 UTF-8. 

1549 

1550 .. versionchanged:: 3.2.2 

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

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

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

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

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

1556 unless the ``xsrf_cookie_version`` `Application` setting is 

1557 set to 1. 

1558 

1559 .. versionchanged:: 4.3 

1560 The ``xsrf_cookie_kwargs`` `Application` setting may be 

1561 used to supply additional cookie options (which will be 

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

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

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

1565 ``_xsrf`` cookie. 

1566 """ 

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

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

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

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

1571 if output_version == 1: 

1572 self._xsrf_token = binascii.b2a_hex(token) 

1573 elif output_version == 2: 

1574 mask = os.urandom(4) 

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

1576 [ 

1577 b"2", 

1578 binascii.b2a_hex(mask), 

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

1580 utf8(str(int(timestamp))), 

1581 ] 

1582 ) 

1583 else: 

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

1585 if version is None: 

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

1587 cookie_kwargs["expires_days"] = 30 

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

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

1590 return self._xsrf_token 

1591 

1592 def _get_raw_xsrf_token(self) -> tuple[int | None, bytes, float]: 

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

1594 

1595 The raw_xsrf_token is a tuple containing: 

1596 

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

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

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

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

1601 for version 1 cookies) 

1602 """ 

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

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

1605 cookie = self.get_cookie(cookie_name) 

1606 if cookie: 

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

1608 else: 

1609 version, token, timestamp = None, None, None 

1610 if token is None: 

1611 version = None 

1612 token = os.urandom(16) 

1613 timestamp = time.time() 

1614 assert token is not None 

1615 assert timestamp is not None 

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

1617 return self._raw_xsrf_token 

1618 

1619 def _decode_xsrf_token( 

1620 self, cookie: str 

1621 ) -> tuple[int | None, bytes | None, float | None]: 

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

1623 _get_raw_xsrf_token. 

1624 """ 

1625 

1626 try: 

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

1628 

1629 if m: 

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

1631 if version == 2: 

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

1633 

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

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

1636 timestamp = int(timestamp_str) 

1637 return version, token, timestamp 

1638 else: 

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

1640 raise Exception("Unknown xsrf cookie version") 

1641 else: 

1642 version = 1 

1643 try: 

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

1645 except (binascii.Error, TypeError): 

1646 token = utf8(cookie) 

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

1648 timestamp = int(time.time()) 

1649 return (version, token, timestamp) 

1650 except Exception: 

1651 # Catch exceptions and return nothing instead of failing. 

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

1653 return None, None, None 

1654 

1655 def check_xsrf_cookie(self) -> None: 

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

1657 

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

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

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

1661 reject the form submission as a potential forgery. 

1662 

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

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

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

1666 

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

1668 

1669 .. versionchanged:: 3.2.2 

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

1671 supported. 

1672 """ 

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

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

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

1676 # information please see 

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

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

1679 input_token = ( 

1680 self.get_argument("_xsrf", None) 

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

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

1683 ) 

1684 if not input_token: 

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

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

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

1688 if not token: 

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

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

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

1692 

1693 def xsrf_form_html(self) -> str: 

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

1695 

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

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

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

1699 HTML within all of your HTML forms. 

1700 

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

1702 xsrf_form_html() %}`` 

1703 

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

1705 """ 

1706 return ( 

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

1708 + escape.xhtml_escape(self.xsrf_token) 

1709 + '"/>' 

1710 ) 

1711 

1712 def _cross_origin_protection(self) -> None: 

1713 # Safe methods are not checked. WebSocket connections (which begin 

1714 # with a GET) are checked separately in WebSocketHandler.get, after 

1715 # prepare(). 

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

1717 return 

1718 

1719 if self.application.settings.get("xsrf_cookies"): 

1720 self.check_xsrf_cookie() 

1721 if self.application.settings.get("cross_origin_protection"): 

1722 error = self._check_cross_origin(self.check_trusted_origin) 

1723 if error is not None: 

1724 raise HTTPError(403, error) 

1725 

1726 def _check_cross_origin(self, check: Callable[[str], bool]) -> str | None: 

1727 """Implements the cross-origin protection algorithm. 

1728 

1729 ``check`` is called with the ``Origin`` header for requests that 

1730 appear to be cross-origin. Returns None if the request is allowed, 

1731 or a message describing why it is not. 

1732 """ 

1733 match sfs := self.request.headers.get("sec-fetch-site"): 

1734 case "same-origin" | "none": 

1735 # Same origin or direct navigation 

1736 return None 

1737 case None: 

1738 # No sec-fetch-site: Old browser, non-HTTPS, or non-browser client. 

1739 # Browsers should send Origin. 

1740 origin = self.request.headers.get("origin") 

1741 if origin is None: 

1742 return None # probably non-browser client 

1743 if self._origin_matches_host(origin) or check(origin): 

1744 return None 

1745 return "Origin %r not allowed" % origin 

1746 case _: # cross-site, same-site 

1747 origin = self.request.headers.get("origin") 

1748 if origin and check(origin): 

1749 return None 

1750 return "Sec-Fetch-Site %s & origin %r not allowed" % (sfs, origin) 

1751 

1752 def _origin_matches_host(self, origin: str) -> bool: 

1753 origin = urllib.parse.urlsplit(origin).netloc.lower() 

1754 host = self.request.headers.get("Host", "").lower() 

1755 

1756 # Check to see that origin matches host directly, including ports. 

1757 # Note that ideally we'd also match the scheme, but that would make it a 

1758 # requirement to configure x-forwarded-scheme headers correctly and 

1759 # introduce additional configuration risk. Since this method comes into 

1760 # play on a fallback path (mainly non-HTTPS), this is acceptable. 

1761 return origin == host 

1762 

1763 def check_trusted_origin(self, origin: str) -> bool: 

1764 """Check if a cross-origin request should be allowed based on its origin. 

1765 

1766 This method is part of :ref:`cross_origin_protection`. It is called 

1767 only when the ``cross_origin_protection`` application setting is 

1768 enabled, for state-modifying requests (POST, PUT, DELETE, etc.) that 

1769 appear to be cross-origin, with the value of the ``Origin`` HTTP 

1770 header (e.g. ``https://example.com``). It is not called for requests 

1771 that are known to be same-origin, or for requests without an 

1772 ``Origin`` header (which do not come from a browser). 

1773 

1774 This method is called before `prepare`, so it must not rely on 

1775 anything that `prepare` does (such as authentication). It should 

1776 decide based only on the origin (and other attributes of the request 

1777 itself). 

1778 

1779 Return True to allow the request, or False to reject it with a 403 

1780 error. By default, it checks if the origin is found in the 

1781 application setting ``trusted_origins``. 

1782 

1783 Overriding this method to always return True excludes this handler from 

1784 cross-origin protection, and may be appropriate for endpoints designed 

1785 to be invoked from any origin:: 

1786 

1787 def check_trusted_origin(self, origin): 

1788 return True 

1789 

1790 .. warning:: 

1791 

1792 This is an important security measure; don't disable it 

1793 without understanding the security implications. Endpoints that 

1794 accept requests from any origin must not rely on cookies for 

1795 authentication (or must use some other form of XSRF protection, 

1796 such as ``xsrf_cookies``). 

1797 

1798 Applications that support both cookies and non-cookie credentials 

1799 (such as a token in the URL or an ``Authorization`` header) may 

1800 override this method to allow cross-origin requests that carry 

1801 such a credential. In this case, the override must verify the 

1802 credential, not merely check that one is present, unless the 

1803 application's authentication rejects any request with an invalid 

1804 credential. Otherwise an attacker could add an invalid token to a 

1805 cross-site request, which would pass this check and then be 

1806 authenticated by the victim's cookies. Applications whose 

1807 credentials cannot be verified here (for example, because 

1808 verification is asynchronous) should disable 

1809 ``cross_origin_protection`` and perform their own checks after 

1810 authentication. 

1811 

1812 Applications that wish to allow cross-origin but same-site requests may 

1813 do so by overriding this method to parse the origin and see if it is a 

1814 subdomain of a trusted domain:: 

1815 

1816 def check_trusted_origin(self, origin): 

1817 parsed_origin = urllib.parse.urlsplit(origin) 

1818 return parsed_origin.netloc.endswith(".mydomain.com") 

1819 

1820 This method is also the default implementation of 

1821 `.WebSocketHandler.check_origin`, so overriding it affects websocket 

1822 connections as well (unless ``check_origin`` is also overridden). 

1823 

1824 .. versionadded:: 6.6 

1825 """ 

1826 return origin in self.application.settings.get("trusted_origins", []) 

1827 

1828 def static_url( 

1829 self, path: str, include_host: bool | None = None, **kwargs: Any 

1830 ) -> str: 

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

1832 

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

1834 application (which specifies the root directory of your static 

1835 files). 

1836 

1837 This method returns a versioned url (by default appending 

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

1839 cached indefinitely. This can be disabled by passing 

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

1841 other static file implementations are not required to support 

1842 this, but they may support other options). 

1843 

1844 By default this method returns URLs relative to the current 

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

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

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

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

1849 

1850 ``path`` should name one of the application's known static files, 

1851 typically as a literal in a template. It should not be derived from 

1852 untrusted input such as request arguments: computing the version 

1853 reads the file, and each distinct path occupies an entry in a cache 

1854 of versions. Map untrusted input to a known set of files before 

1855 calling this method. 

1856 

1857 In the default implementation, a ``path`` that leads outside of 

1858 ``static_path`` (e.g. with ``../``) raises `HTTPError` (403). 

1859 Symlinks inside ``static_path`` are followed when computing the 

1860 version, even if they point outside of it; see `StaticFileHandler`. 

1861 

1862 .. versionchanged:: 6.6 

1863 Paths outside of ``static_path`` now raise `HTTPError`, and 

1864 files that are not regular files (or are larger than 

1865 `StaticFileHandler.MAX_VERSION_CONTENT_SIZE`) get no version. 

1866 """ 

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

1868 get_url = self.settings.get( 

1869 "static_handler_class", StaticFileHandler 

1870 ).make_static_url 

1871 

1872 if include_host is None: 

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

1874 

1875 if include_host: 

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

1877 else: 

1878 base = "" 

1879 

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

1881 

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

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

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

1885 raise Exception( 

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

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

1888 ) 

1889 

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

1891 """Alias for `Application.reverse_url`.""" 

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

1893 

1894 def compute_etag(self) -> str | None: 

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

1896 

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

1898 

1899 May be overridden to provide custom etag implementations, 

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

1901 """ 

1902 hasher = hashlib.sha1() 

1903 for part in self._write_buffer: 

1904 hasher.update(part) 

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

1906 

1907 def set_etag_header(self) -> None: 

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

1909 

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

1911 

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

1913 """ 

1914 etag = self.compute_etag() 

1915 if etag is not None: 

1916 self.set_header("Etag", etag) 

1917 

1918 def check_etag_header(self) -> bool: 

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

1920 

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

1922 returned. For example:: 

1923 

1924 self.set_etag_header() 

1925 if self.check_etag_header(): 

1926 self.set_status(304) 

1927 return 

1928 

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

1930 but may be called earlier for applications that override 

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

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

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

1934 """ 

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

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

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

1938 etags = re.findall( 

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

1940 ) 

1941 if not computed_etag or not etags: 

1942 return False 

1943 

1944 match = False 

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

1946 match = True 

1947 else: 

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

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

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

1951 

1952 for etag in etags: 

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

1954 match = True 

1955 break 

1956 return match 

1957 

1958 async def _execute( 

1959 self, transforms: list["OutputTransform"], *args: bytes, **kwargs: bytes 

1960 ) -> None: 

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

1962 self._transforms = transforms 

1963 try: 

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

1965 raise HTTPError(405) 

1966 

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

1968 if not _has_stream_request_body(self.__class__): 

1969 try: 

1970 self.request._parse_body() 

1971 except httputil.HTTPInputError as e: 

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

1973 

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

1975 self.path_kwargs = { 

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

1977 } 

1978 # XSRF checks (if enabled): cookie-based and/or cross-origin protection. 

1979 self._cross_origin_protection() 

1980 

1981 result = self.prepare() 

1982 if result is not None: 

1983 result = await result # type: ignore 

1984 if self._prepared_future is not None: 

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

1986 # and are ready for the body to arrive. 

1987 future_set_result_unless_cancelled(self._prepared_future, None) 

1988 if self._finished: 

1989 return 

1990 

1991 if _has_stream_request_body(self.__class__): 

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

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

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

1995 # instead. 

1996 try: 

1997 await self.request._body_future 

1998 except iostream.StreamClosedError: 

1999 return 

2000 

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

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

2003 if result is not None: 

2004 result = await result 

2005 if self._auto_finish and not self._finished: 

2006 self.finish() 

2007 except (Exception, asyncio.CancelledError) as e: 

2008 task = asyncio.current_task() 

2009 if ( 

2010 isinstance(e, asyncio.CancelledError) 

2011 and task is not None 

2012 and task.cancelling() 

2013 ): 

2014 # This handler's own task was cancelled (as opposed to the 

2015 # handler awaiting something else that was cancelled). This is 

2016 # not an error to be logged, but send a response so the 

2017 # connection is not left hanging, then let the cancellation 

2018 # propagate. 

2019 result = None 

2020 if not self._finished: 

2021 try: 

2022 self.send_error(500) 

2023 except Exception: 

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

2025 raise 

2026 try: 

2027 self._handle_request_exception(e) 

2028 except Exception: 

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

2030 finally: 

2031 # Unset result to avoid circular references 

2032 result = None 

2033 finally: 

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

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

2036 # now (to unblock the HTTP server). 

2037 self._prepared_future.set_result(None) 

2038 

2039 def data_received(self, chunk: bytes) -> Awaitable[None] | None: 

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

2041 

2042 Requires the `.stream_request_body` decorator. 

2043 

2044 May be a coroutine for flow control. 

2045 """ 

2046 raise NotImplementedError() 

2047 

2048 def _log(self) -> None: 

2049 """Logs the current request. 

2050 

2051 Sort of deprecated since this functionality was moved to the 

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

2053 that have overridden this method. 

2054 """ 

2055 self.application.log_request(self) 

2056 

2057 def _request_summary(self) -> str: 

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

2059 self.request.method, 

2060 self.request.uri, 

2061 self.request.remote_ip, 

2062 ) 

2063 

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

2065 if isinstance(e, Finish): 

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

2067 if not self._finished: 

2068 self.finish(*e.args) 

2069 return 

2070 try: 

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

2072 except Exception: 

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

2074 # to avoid leaking the connection. 

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

2076 if self._finished: 

2077 # Extra errors after the request has been finished should 

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

2079 # send a response. 

2080 return 

2081 if isinstance(e, HTTPError): 

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

2083 else: 

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

2085 

2086 def log_exception( 

2087 self, 

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

2089 value: BaseException | None, 

2090 tb: TracebackType | None, 

2091 ) -> None: 

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

2093 

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

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

2096 other exceptions as errors with stack traces (on the 

2097 ``tornado.application`` logger). 

2098 

2099 .. versionadded:: 3.1 

2100 """ 

2101 if isinstance(value, HTTPError): 

2102 log_message = value.get_message() 

2103 if log_message: 

2104 format = "%d %s: %s" 

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

2106 gen_log.warning(format, *args) 

2107 else: 

2108 app_log.error( 

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

2110 self._request_summary(), 

2111 self.request, 

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

2113 ) 

2114 

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

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

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

2118 self._active_modules: dict[str, UIModule] = {} 

2119 if name not in self._active_modules: 

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

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

2122 return _unicode(rendered) 

2123 

2124 return render 

2125 

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

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

2128 

2129 def _clear_representation_headers(self) -> None: 

2130 # 304 responses should not contain representation metadata 

2131 # headers (defined in 

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

2133 # not explicitly allowed by 

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

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

2136 for h in headers: 

2137 self.clear_header(h) 

2138 

2139 

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

2141 

2142 

2143def stream_request_body(cls: type[_RequestHandlerType]) -> type[_RequestHandlerType]: 

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

2145 

2146 This decorator implies the following changes: 

2147 

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

2149 be included in `RequestHandler.get_argument`. 

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

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

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

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

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

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

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

2157 until those futures have completed. 

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

2159 the entire body has been read. 

2160 

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

2162 for example usage. 

2163 """ # noqa: E501 

2164 if not issubclass(cls, RequestHandler): 

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

2166 cls._stream_request_body = True 

2167 return cls 

2168 

2169 

2170def _has_stream_request_body(cls: type[RequestHandler]) -> bool: 

2171 if not issubclass(cls, RequestHandler): 

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

2173 return cls._stream_request_body 

2174 

2175 

2176def removeslash( 

2177 method: Callable[..., Awaitable[None] | None], 

2178) -> Callable[..., Awaitable[None] | None]: 

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

2180 

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

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

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

2184 """ 

2185 

2186 @functools.wraps(method) 

2187 def wrapper( # type: ignore 

2188 self: RequestHandler, *args, **kwargs 

2189 ) -> Awaitable[None] | None: 

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

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

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

2193 if _is_protocol_relative(uri): 

2194 # A path like //example.com// would otherwise redirect 

2195 # to another host. 

2196 raise HTTPError( 

2197 403, "cannot redirect path with two initial slashes" 

2198 ) 

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

2200 if self.request.query: 

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

2202 self.redirect(uri, permanent=True) 

2203 return None 

2204 else: 

2205 raise HTTPError(404) 

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

2207 

2208 return wrapper 

2209 

2210 

2211def addslash( 

2212 method: Callable[..., Awaitable[None] | None], 

2213) -> Callable[..., Awaitable[None] | None]: 

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

2215 

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

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

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

2219 """ 

2220 

2221 @functools.wraps(method) 

2222 def wrapper( # type: ignore 

2223 self: RequestHandler, *args, **kwargs 

2224 ) -> Awaitable[None] | None: 

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

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

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

2228 if _is_protocol_relative(uri): 

2229 # A path like //example.com would otherwise redirect to 

2230 # another host. 

2231 raise HTTPError( 

2232 403, "cannot redirect path with two initial slashes" 

2233 ) 

2234 if self.request.query: 

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

2236 self.redirect(uri, permanent=True) 

2237 return None 

2238 raise HTTPError(404) 

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

2240 

2241 return wrapper 

2242 

2243 

2244class _ApplicationRouter(ReversibleRuleRouter): 

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

2246 

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

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

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

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

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

2252 `_ApplicationRouter` instance. 

2253 """ 

2254 

2255 def __init__( 

2256 self, application: "Application", rules: _RuleList | None = None 

2257 ) -> None: 

2258 assert isinstance(application, Application) 

2259 self.application = application 

2260 super().__init__(rules) 

2261 

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

2263 rule = super().process_rule(rule) 

2264 

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

2266 rule.target = _ApplicationRouter( 

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

2268 ) 

2269 

2270 return rule 

2271 

2272 def get_target_delegate( 

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

2274 ) -> httputil.HTTPMessageDelegate | None: 

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

2276 return self.application.get_handler_delegate( 

2277 request, target, **target_params 

2278 ) 

2279 

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

2281 

2282 

2283class Application(ReversibleRouter): 

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

2285 

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

2287 HTTPServer to serve the application:: 

2288 

2289 application = web.Application([ 

2290 (r"/", MainPageHandler), 

2291 ]) 

2292 http_server = httpserver.HTTPServer(application) 

2293 http_server.listen(8080) 

2294 

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

2296 objects or tuples of values corresponding to the arguments of 

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

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

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

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

2301 

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

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

2304 

2305 application = web.Application([ 

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

2307 (r"/", MainPageHandler), 

2308 (r"/feed", FeedHandler), 

2309 ]), 

2310 ]) 

2311 

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

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

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

2315 

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

2317 instantiate an instance of the first request class whose regexp 

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

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

2320 

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

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

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

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

2325 `StaticFileHandler` can be installed automatically with the 

2326 static_path setting described below):: 

2327 

2328 application = web.Application([ 

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

2330 ]) 

2331 

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

2333 a host regular expression as the first argument:: 

2334 

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

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

2337 ]) 

2338 

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

2340 parameter value is matched against host regular expressions. 

2341 

2342 

2343 .. warning:: 

2344 

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

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

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

2348 other private networks. Appropriate host patterns must be used 

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

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

2351 may be vulnerable to DNS rebinding. 

2352 

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

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

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

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

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

2358 `StaticFileHandler` can be specified with the 

2359 ``static_handler_class`` setting. 

2360 

2361 .. versionchanged:: 4.5 

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

2363 

2364 """ 

2365 

2366 def __init__( 

2367 self, 

2368 handlers: _RuleList | None = None, 

2369 default_host: str | None = None, 

2370 transforms: list[type["OutputTransform"]] | None = None, 

2371 **settings: Any, 

2372 ) -> None: 

2373 if transforms is None: 

2374 self.transforms: list[type[OutputTransform]] = [] 

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

2376 self.transforms.append(GZipContentEncoding) 

2377 else: 

2378 self.transforms = transforms 

2379 self.default_host = default_host 

2380 self.settings = settings 

2381 self.ui_modules = { 

2382 "linkify": _linkify, 

2383 "xsrf_form_html": _xsrf_form_html, 

2384 "Template": TemplateModule, 

2385 } 

2386 self.ui_methods: dict[str, Callable[..., str]] = {} 

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

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

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

2390 path = self.settings["static_path"] 

2391 handlers = list(handlers or []) 

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

2393 static_handler_class = settings.get( 

2394 "static_handler_class", StaticFileHandler 

2395 ) 

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

2397 static_handler_args["path"] = path 

2398 for pattern in [ 

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

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

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

2402 ]: 

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

2404 

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

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

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

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

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

2410 

2411 self.wildcard_router = _ApplicationRouter(self, handlers) 

2412 self.default_router = _ApplicationRouter( 

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

2414 ) 

2415 

2416 # Automatically reload modified modules 

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

2418 from tornado import autoreload 

2419 

2420 autoreload.start() 

2421 

2422 def listen( 

2423 self, 

2424 port: int, 

2425 address: str | None = None, 

2426 *, 

2427 family: socket.AddressFamily = socket.AF_UNSPEC, 

2428 backlog: int = tornado.netutil._DEFAULT_BACKLOG, 

2429 flags: int | None = None, 

2430 reuse_port: bool = False, 

2431 **kwargs: Any, 

2432 ) -> HTTPServer: 

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

2434 

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

2436 calling its listen method. Keyword arguments not supported by 

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

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

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

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

2441 

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

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

2444 the server. 

2445 

2446 Returns the `.HTTPServer` object. 

2447 

2448 .. versionchanged:: 4.3 

2449 Now returns the `.HTTPServer` object. 

2450 

2451 .. versionchanged:: 6.2 

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

2453 including ``reuse_port``. 

2454 """ 

2455 server = HTTPServer(self, **kwargs) 

2456 server.listen( 

2457 port, 

2458 address=address, 

2459 family=family, 

2460 backlog=backlog, 

2461 flags=flags, 

2462 reuse_port=reuse_port, 

2463 ) 

2464 return server 

2465 

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

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

2468 

2469 Host patterns are processed sequentially in the order they were 

2470 added. All matching patterns will be considered. 

2471 """ 

2472 host_matcher = HostMatches(host_pattern) 

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

2474 

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

2476 

2477 if self.default_host is not None: 

2478 self.wildcard_router.add_rules( 

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

2480 ) 

2481 

2482 def add_transform(self, transform_class: type["OutputTransform"]) -> None: 

2483 self.transforms.append(transform_class) 

2484 

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

2486 if isinstance(methods, types.ModuleType): 

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

2488 elif isinstance(methods, list): 

2489 for m in methods: 

2490 self._load_ui_methods(m) 

2491 else: 

2492 for name, fn in methods.items(): 

2493 if ( 

2494 not name.startswith("_") 

2495 and hasattr(fn, "__call__") 

2496 and name[0].lower() == name[0] 

2497 ): 

2498 self.ui_methods[name] = fn 

2499 

2500 def _load_ui_modules(self, modules: Any) -> None: 

2501 if isinstance(modules, types.ModuleType): 

2502 self._load_ui_modules({n: getattr(modules, n) for n in dir(modules)}) 

2503 elif isinstance(modules, list): 

2504 for m in modules: 

2505 self._load_ui_modules(m) 

2506 else: 

2507 assert isinstance(modules, dict) 

2508 for name, cls in modules.items(): 

2509 try: 

2510 if issubclass(cls, UIModule): 

2511 self.ui_modules[name] = cls 

2512 except TypeError: 

2513 pass 

2514 

2515 def __call__(self, request: httputil.HTTPServerRequest) -> Awaitable[None] | None: 

2516 # Legacy HTTPServer interface 

2517 dispatcher = self.find_handler(request) 

2518 return dispatcher.execute() 

2519 

2520 def find_handler( 

2521 self, request: httputil.HTTPServerRequest, **kwargs: Any 

2522 ) -> "_HandlerDelegate": 

2523 route = self.default_router.find_handler(request) 

2524 if route is not None: 

2525 return cast("_HandlerDelegate", route) 

2526 

2527 if self.settings.get("default_handler_class"): 

2528 return self.get_handler_delegate( 

2529 request, 

2530 self.settings["default_handler_class"], 

2531 self.settings.get("default_handler_args", {}), 

2532 ) 

2533 

2534 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404}) 

2535 

2536 def get_handler_delegate( 

2537 self, 

2538 request: httputil.HTTPServerRequest, 

2539 target_class: type[RequestHandler], 

2540 target_kwargs: dict[str, Any] | None = None, 

2541 path_args: list[bytes] | None = None, 

2542 path_kwargs: dict[str, bytes] | None = None, 

2543 ) -> "_HandlerDelegate": 

2544 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request 

2545 for application and `RequestHandler` subclass. 

2546 

2547 :arg httputil.HTTPServerRequest request: current HTTP request. 

2548 :arg RequestHandler target_class: a `RequestHandler` class. 

2549 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor. 

2550 :arg list path_args: positional arguments for ``target_class`` HTTP method that 

2551 will be executed while handling a request (``get``, ``post`` or any other). 

2552 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method. 

2553 """ 

2554 return _HandlerDelegate( 

2555 self, request, target_class, target_kwargs, path_args, path_kwargs 

2556 ) 

2557 

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

2559 """Returns a URL path for handler named ``name`` 

2560 

2561 The handler must be added to the application as a named `URLSpec`. 

2562 

2563 Args will be substituted for capturing groups in the `URLSpec` regex. 

2564 They will be converted to strings if necessary, encoded as utf8, 

2565 and url-escaped. 

2566 """ 

2567 reversed_url = self.default_router.reverse_url(name, *args) 

2568 if reversed_url is not None: 

2569 return reversed_url 

2570 

2571 raise KeyError("%s not found in named urls" % name) 

2572 

2573 def log_request(self, handler: RequestHandler) -> None: 

2574 """Writes a completed HTTP request to the logs. 

2575 

2576 By default writes to the python root logger. To change 

2577 this behavior either subclass Application and override this method, 

2578 or pass a function in the application settings dictionary as 

2579 ``log_function``. 

2580 """ 

2581 if "log_function" in self.settings: 

2582 self.settings["log_function"](handler) 

2583 return 

2584 if handler.get_status() < 400: 

2585 log_method = access_log.info 

2586 elif handler.get_status() < 500: 

2587 log_method = access_log.warning 

2588 else: 

2589 log_method = access_log.error 

2590 request_time = 1000.0 * handler.request.request_time() 

2591 log_method( 

2592 "%d %s %.2fms", 

2593 handler.get_status(), 

2594 handler._request_summary(), 

2595 request_time, 

2596 ) 

2597 

2598 

2599class _HandlerDelegate(httputil.HTTPMessageDelegate): 

2600 def __init__( 

2601 self, 

2602 application: Application, 

2603 request: httputil.HTTPServerRequest, 

2604 handler_class: type[RequestHandler], 

2605 handler_kwargs: dict[str, Any] | None, 

2606 path_args: list[bytes] | None, 

2607 path_kwargs: dict[str, bytes] | None, 

2608 ) -> None: 

2609 self.application = application 

2610 self.connection = request.connection 

2611 self.request = request 

2612 self.handler_class = handler_class 

2613 self.handler_kwargs = handler_kwargs or {} 

2614 self.path_args = path_args or [] 

2615 self.path_kwargs = path_kwargs or {} 

2616 self.chunks = bytearray() 

2617 self.stream_request_body = _has_stream_request_body(self.handler_class) 

2618 

2619 def headers_received( 

2620 self, 

2621 start_line: httputil.RequestStartLine | httputil.ResponseStartLine, 

2622 headers: httputil.HTTPHeaders, 

2623 ) -> Awaitable[None] | None: 

2624 if self.stream_request_body: 

2625 self.request._body_future = Future() 

2626 return self.execute() 

2627 return None 

2628 

2629 def data_received(self, data: bytes) -> Awaitable[None] | None: 

2630 if self.stream_request_body: 

2631 return self.handler.data_received(data) 

2632 else: 

2633 self.chunks += data 

2634 return None 

2635 

2636 def finish(self) -> None: 

2637 if self.stream_request_body: 

2638 future_set_result_unless_cancelled(self.request._body_future, None) 

2639 else: 

2640 # Note that the body gets parsed in RequestHandler._execute so it can be in 

2641 # the right exception handler scope. 

2642 self.request.body = bytes(self.chunks) 

2643 self.execute() 

2644 

2645 def on_connection_close(self) -> None: 

2646 if self.stream_request_body: 

2647 self.handler.on_connection_close() 

2648 else: 

2649 self.chunks = None # type: ignore 

2650 

2651 def execute(self) -> Awaitable[None] | None: 

2652 # If template cache is disabled (usually in the debug mode), 

2653 # re-compile templates and reload static files on every 

2654 # request so you don't need to restart to see changes 

2655 if not self.application.settings.get("compiled_template_cache", True): 

2656 with RequestHandler._template_loader_lock: 

2657 for loader in RequestHandler._template_loaders.values(): 

2658 loader.reset() 

2659 if not self.application.settings.get("static_hash_cache", True): 

2660 static_handler_class = self.application.settings.get( 

2661 "static_handler_class", StaticFileHandler 

2662 ) 

2663 static_handler_class.reset() 

2664 

2665 self.handler = self.handler_class( 

2666 self.application, self.request, **self.handler_kwargs 

2667 ) 

2668 transforms = [t(self.request) for t in self.application.transforms] 

2669 

2670 if self.stream_request_body: 

2671 self.handler._prepared_future = Future() 

2672 # Note that if an exception escapes handler._execute it will be 

2673 # trapped in the Future it returns (which we are ignoring here, 

2674 # leaving it to be logged when the Future is GC'd). 

2675 # However, that shouldn't happen because _execute has a blanket 

2676 # except handler, and we cannot easily access the IOLoop here to 

2677 # call add_future (because of the requirement to remain compatible 

2678 # with WSGI) 

2679 fut = gen.convert_yielded( 

2680 self.handler._execute(transforms, *self.path_args, **self.path_kwargs) 

2681 ) 

2682 fut.add_done_callback(lambda f: f.cancelled() or f.result()) 

2683 # If we are streaming the request body, then execute() is finished 

2684 # when the handler has prepared to receive the body. If not, 

2685 # it doesn't matter when execute() finishes (so we return None) 

2686 return self.handler._prepared_future 

2687 

2688 

2689class HTTPError(Exception): 

2690 """An exception that will turn into an HTTP error response. 

2691 

2692 Raising an `HTTPError` is a convenient alternative to calling 

2693 `RequestHandler.send_error` since it automatically ends the 

2694 current function. 

2695 

2696 To customize the response sent with an `HTTPError`, override 

2697 `RequestHandler.write_error`. 

2698 

2699 :arg int status_code: HTTP status code. Must be listed in 

2700 `httplib.responses <http.client.responses>` unless the ``reason`` 

2701 keyword argument is given. 

2702 :arg str log_message: Message to be written to the log for this error 

2703 (will not be shown to the user unless the `Application` is in debug 

2704 mode). May contain ``%s``-style placeholders, which will be filled 

2705 in with remaining positional parameters. 

2706 :arg str reason: Keyword-only argument. The HTTP "reason" phrase 

2707 to pass in the status line along with ``status_code`` (for example, 

2708 the "Not Found" in ``HTTP/1.1 404 Not Found``). Normally 

2709 determined automatically from ``status_code``, but can be used 

2710 to use a non-standard numeric code. This is not a general-purpose 

2711 error message. 

2712 """ 

2713 

2714 def __init__( 

2715 self, 

2716 status_code: int = 500, 

2717 log_message: str | None = None, 

2718 *args: Any, 

2719 **kwargs: Any, 

2720 ) -> None: 

2721 self.status_code = status_code 

2722 self._log_message = log_message 

2723 self.args = args 

2724 self.reason = kwargs.get("reason", None) 

2725 

2726 @property 

2727 def log_message(self) -> str | None: 

2728 """ 

2729 A backwards compatible way of accessing log_message. 

2730 """ 

2731 if self._log_message and not self.args: 

2732 return self._log_message.replace("%", "%%") 

2733 return self._log_message 

2734 

2735 def get_message(self) -> str | None: 

2736 if self._log_message and self.args: 

2737 return self._log_message % self.args 

2738 return self._log_message 

2739 

2740 def __str__(self) -> str: 

2741 message = "HTTP %d: %s" % ( 

2742 self.status_code, 

2743 self.reason or httputil.responses.get(self.status_code, "Unknown"), 

2744 ) 

2745 log_message = self.get_message() 

2746 if log_message: 

2747 return message + " (" + log_message + ")" 

2748 else: 

2749 return message 

2750 

2751 

2752class Finish(Exception): 

2753 """An exception that ends the request without producing an error response. 

2754 

2755 When `Finish` is raised in a `RequestHandler`, the request will 

2756 end (calling `RequestHandler.finish` if it hasn't already been 

2757 called), but the error-handling methods (including 

2758 `RequestHandler.write_error`) will not be called. 

2759 

2760 If `Finish()` was created with no arguments, the pending response 

2761 will be sent as-is. If `Finish()` was given an argument, that 

2762 argument will be passed to `RequestHandler.finish()`. 

2763 

2764 This can be a more convenient way to implement custom error pages 

2765 than overriding ``write_error`` (especially in library code):: 

2766 

2767 if self.current_user is None: 

2768 self.set_status(401) 

2769 self.set_header('WWW-Authenticate', 'Basic realm="something"') 

2770 raise Finish() 

2771 

2772 .. versionchanged:: 4.3 

2773 Arguments passed to ``Finish()`` will be passed on to 

2774 `RequestHandler.finish`. 

2775 """ 

2776 

2777 pass 

2778 

2779 

2780class MissingArgumentError(HTTPError): 

2781 """Exception raised by `RequestHandler.get_argument`. 

2782 

2783 This is a subclass of `HTTPError`, so if it is uncaught a 400 response 

2784 code will be used instead of 500 (and a stack trace will not be logged). 

2785 

2786 .. versionadded:: 3.1 

2787 """ 

2788 

2789 def __init__(self, arg_name: str) -> None: 

2790 super().__init__(400, "Missing argument %s" % arg_name) 

2791 self.arg_name = arg_name 

2792 

2793 

2794class ErrorHandler(RequestHandler): 

2795 """Generates an error response with ``status_code`` for all requests.""" 

2796 

2797 def initialize(self, status_code: int) -> None: 

2798 self.set_status(status_code) 

2799 

2800 def prepare(self) -> None: 

2801 raise HTTPError(self._status_code) 

2802 

2803 def check_xsrf_cookie(self) -> None: 

2804 # POSTs to an ErrorHandler don't actually have side effects, 

2805 # so we don't need to check the xsrf token. This allows POSTs 

2806 # to the wrong url to return a 404 instead of 403. 

2807 pass 

2808 

2809 def check_trusted_origin(self, origin: str) -> bool: 

2810 # POSTs to an ErrorHandler don't actually have side effects, 

2811 # so we can treat any origin as trusted. This allows POSTs 

2812 # to the wrong url to return a 404 instead of 403. 

2813 return True 

2814 

2815 

2816class RedirectHandler(RequestHandler): 

2817 """Redirects the client to the given URL for all GET requests. 

2818 

2819 You should provide the keyword argument ``url`` to the handler, e.g.:: 

2820 

2821 application = web.Application([ 

2822 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 

2823 ]) 

2824 

2825 `RedirectHandler` supports regular expression substitutions. E.g., to 

2826 swap the first and second parts of a path while preserving the remainder:: 

2827 

2828 application = web.Application([ 

2829 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 

2830 ]) 

2831 

2832 The final URL is formatted with `str.format` and the substrings that match 

2833 the capturing groups. In the above example, a request to "/a/b/c" would be 

2834 formatted like:: 

2835 

2836 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 

2837 

2838 Use Python's :ref:`format string syntax <formatstrings>` to customize how 

2839 values are substituted. 

2840 

2841 .. versionchanged:: 4.5 

2842 Added support for substitutions into the destination URL. 

2843 

2844 .. versionchanged:: 5.0 

2845 If any query arguments are present, they will be copied to the 

2846 destination URL. 

2847 

2848 .. versionchanged:: 6.6 

2849 Substituted values may no longer change the scheme or netloc (user 

2850 info, host, and port) of the destination URL. For example, with a 

2851 ``url`` of ``"/{0}/b"``, a request that would redirect to 

2852 ``//example.com/b`` (a protocol-relative URL pointing to another host) 

2853 now fails with a 400 error. As a consequence, substitutions cannot be 

2854 used in the scheme or netloc part of the ``url``. 

2855 """ 

2856 

2857 def initialize(self, url: str, permanent: bool = True) -> None: 

2858 self._url = url 

2859 self._permanent = permanent 

2860 

2861 def get(self, *args: Any, **kwargs: Any) -> None: 

2862 to_url = self._url.format(*args, **kwargs) 

2863 # The scheme and netloc of the redirect must come from the template, not 

2864 # from the request. Find out what they are from the part of the template 

2865 # before the first substitution. (Formatting the whole template with a 

2866 # placeholder doesn't work: whatever the placeholder is, it can turn the 

2867 # template into a URL with a different netloc, as "/" does for "/{0}/b".) 

2868 prefix = "" 

2869 has_substitution = False 

2870 for literal, field, _, _ in string.Formatter().parse(self._url): 

2871 prefix += literal 

2872 if field is not None: 

2873 has_substitution = True 

2874 break 

2875 try: 

2876 if self.request.query_arguments: 

2877 # Query arguments are added before the check because url_concat 

2878 # can change the rest of the URL. 

2879 # TODO: figure out typing for the next line. 

2880 to_url = httputil.url_concat( 

2881 to_url, 

2882 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 

2883 ) 

2884 scheme, netloc = _scheme_and_netloc(prefix) 

2885 unchanged = _scheme_and_netloc(to_url) == (scheme, netloc) 

2886 if has_substitution and scheme in ("http", "https") and not netloc: 

2887 # The host would come entirely from a substitution (or a 

2888 # browser would find one where urlsplit doesn't, as in 

2889 # "https:///host"). 

2890 unchanged = False 

2891 except ValueError: 

2892 unchanged = False 

2893 if not unchanged: 

2894 raise HTTPError(400, "Redirect URL cannot change the scheme or netloc") 

2895 if httputil._FORBIDDEN_HEADER_CHARS_RE.search(to_url): 

2896 # Let this be a 400 rather than a 500 from set_header. 

2897 raise HTTPError(400, "Redirect URL contains invalid characters") 

2898 self.redirect(to_url, permanent=self._permanent) 

2899 

2900 

2901class StaticFileHandler(RequestHandler): 

2902 """A simple handler that can serve static content from a directory. 

2903 

2904 A `StaticFileHandler` is configured automatically if you pass the 

2905 ``static_path`` keyword argument to `Application`. This handler 

2906 can be customized with the ``static_url_prefix``, ``static_handler_class``, 

2907 and ``static_handler_args`` settings. 

2908 

2909 To map an additional path to this handler for a static data directory 

2910 you would add a line to your application like:: 

2911 

2912 application = web.Application([ 

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

2914 ]) 

2915 

2916 The handler constructor requires a ``path`` argument, which specifies the 

2917 local root directory of the content to be served. 

2918 

2919 Note that a capture group in the regex is required to parse the value for 

2920 the ``path`` argument to the get() method (different than the constructor 

2921 argument above); see `URLSpec` for details. 

2922 

2923 To serve a file like ``index.html`` automatically when a directory is 

2924 requested, set ``static_handler_args=dict(default_filename="index.html")`` 

2925 in your application settings, or add ``default_filename`` as an initializer 

2926 argument for your ``StaticFileHandler``. 

2927 

2928 Symlinks are not followed out of the directory being served: if a path 

2929 resolves to a location outside it, the request is rejected with a 403 

2930 error. Deployments that deliberately serve content through symlinks 

2931 pointing elsewhere can set the ``allowed_symlink_directory`` initializer 

2932 argument to a directory containing all of the intended targets, or to a 

2933 list of such directories. It defaults to the ``path`` argument. 

2934 

2935 This symlink check applies only when serving files. `make_static_url` 

2936 (and therefore `RequestHandler.static_url`) runs without a handler 

2937 instance, so it cannot see ``allowed_symlink_directory``, and it will 

2938 compute a version hash for any regular file that a symlink inside the 

2939 static directory points to, wherever that file is. The file's contents 

2940 are never served this way, but its hash (and whether it exists) is 

2941 revealed in the URL. Symlinks in the static directory should therefore 

2942 only be created by people trusted with the files they point to. 

2943 

2944 To maximize the effectiveness of browser caching, this class supports 

2945 versioned urls (by default using the argument ``?v=``). If a version 

2946 is given, we instruct the browser to cache this file indefinitely. 

2947 `make_static_url` (also available as `RequestHandler.static_url`) can 

2948 be used to construct a versioned url. 

2949 

2950 This handler is intended primarily for use in development and light-duty 

2951 file serving; for heavy traffic it will be more efficient to use 

2952 a dedicated static file server (such as nginx or Apache). We support 

2953 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 

2954 some browsers require this functionality to be present to seek in 

2955 HTML5 audio or video). 

2956 

2957 **Subclassing notes** 

2958 

2959 This class is designed to be extensible by subclassing, but because 

2960 of the way static urls are generated with class methods rather than 

2961 instance methods, the inheritance patterns are somewhat unusual. 

2962 Be sure to use the ``@classmethod`` decorator when overriding a 

2963 class method. Instance methods may use the attributes ``self.path`` 

2964 ``self.absolute_path``, and ``self.modified``. 

2965 

2966 Subclasses should only override methods discussed in this section; 

2967 overriding other methods is error-prone. Overriding 

2968 ``StaticFileHandler.get`` is particularly problematic due to the 

2969 tight coupling with ``compute_etag`` and other methods. 

2970 

2971 To change the way static urls are generated (e.g. to match the behavior 

2972 of another server or CDN), override `make_static_url`, `parse_url_path`, 

2973 `get_cache_time`, and/or `get_version`. 

2974 

2975 To replace all interaction with the filesystem (e.g. to serve 

2976 static content from a database), override `get_content`, 

2977 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 

2978 `validate_absolute_path`. 

2979 

2980 .. versionchanged:: 3.1 

2981 Many of the methods for subclasses were added in Tornado 3.1. 

2982 

2983 .. versionchanged:: 6.5.9 

2984 Symlinks pointing outside the served directory are no longer 

2985 followed, and the ``allowed_symlink_directory`` argument was added 

2986 to configure this. 

2987 

2988 .. versionchanged:: 6.5.10 

2989 ``allowed_symlink_directory`` may now be a list of directories. 

2990 """ 

2991 

2992 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 

2993 

2994 #: The largest content (in bytes) that `get_content_version` will hash. 

2995 #: Hashing is synchronous, so this bounds how long 

2996 #: `~RequestHandler.static_url` can block the event loop. Larger files 

2997 #: get no version. Subclasses may change it. 

2998 #: 

2999 #: .. versionadded:: 6.6 

3000 MAX_VERSION_CONTENT_SIZE = 64 * 1024 * 1024 

3001 

3002 #: The maximum number of versions that `get_version` keeps cached. When 

3003 #: the cache is full, the least recently used entry is discarded (and its 

3004 #: file will be read again if it is needed). This is far more than most 

3005 #: applications have static files; it exists to bound memory usage if 

3006 #: `~RequestHandler.static_url` is called with many distinct paths. 

3007 #: Subclasses may change it. 

3008 #: 

3009 #: .. versionadded:: 6.6 

3010 MAX_CACHED_VERSIONS = 10000 

3011 

3012 # Ordered from least to most recently used. 

3013 _static_hashes: dict[str, str | None] = {} 

3014 _lock = threading.Lock() # protects _static_hashes 

3015 

3016 # A single directory or a list of them. None means "not set", in which 

3017 # case the symlink check falls back to the directory being served. The 

3018 # default lives on the class, not in initialize(), because this attribute 

3019 # was introduced in a security patch and some projects (notably Jupyter) 

3020 # subclass StaticFileHandler with their own initialize() that does not 

3021 # call ours. 

3022 allowed_symlink_directory: str | list[str] | None = None 

3023 

3024 def initialize( 

3025 self, 

3026 path: str, 

3027 default_filename: str | None = None, 

3028 allowed_symlink_directory: str | list[str] | None = None, 

3029 ) -> None: 

3030 self.root = path 

3031 self.default_filename = default_filename 

3032 self.allowed_symlink_directory = allowed_symlink_directory 

3033 

3034 @classmethod 

3035 def reset(cls) -> None: 

3036 with cls._lock: 

3037 cls._static_hashes = {} 

3038 

3039 def head(self, path: str) -> Awaitable[None]: 

3040 return self.get(path, include_body=False) 

3041 

3042 async def get(self, path: str, include_body: bool = True) -> None: 

3043 # Set up our path instance variables. 

3044 self.path = self.parse_url_path(path) 

3045 del path # make sure we don't refer to path instead of self.path again 

3046 absolute_path = self.get_absolute_path(self.root, self.path) 

3047 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 

3048 if self.absolute_path is None: 

3049 return 

3050 

3051 self.modified = self.get_modified_time() 

3052 self.set_headers() 

3053 

3054 if self.should_return_304(): 

3055 self.set_status(304) 

3056 return 

3057 

3058 request_range = None 

3059 range_header = self.request.headers.get("Range") 

3060 if range_header: 

3061 # As per RFC 2616 14.16, if an invalid Range header is specified, 

3062 # the request will be treated as if the header didn't exist. 

3063 request_range = httputil._parse_request_range(range_header) 

3064 

3065 size = self.get_content_size() 

3066 if request_range: 

3067 start, end = request_range 

3068 if start is not None and start < 0: 

3069 start += size 

3070 if start < 0: 

3071 start = 0 

3072 if ( 

3073 start is not None 

3074 and (start >= size or (end is not None and start >= end)) 

3075 ) or end == 0: 

3076 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 

3077 # the first requested byte is equal to or greater than the 

3078 # content, or when a suffix with length 0 is specified. 

3079 # https://tools.ietf.org/html/rfc7233#section-2.1 

3080 # A byte-range-spec is invalid if the last-byte-pos value is present 

3081 # and less than the first-byte-pos. 

3082 self.set_status(416) # Range Not Satisfiable 

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

3084 self.set_header("Content-Range", f"bytes */{size}") 

3085 return 

3086 if end is not None and end > size: 

3087 # Clients sometimes blindly use a large range to limit their 

3088 # download size; cap the endpoint at the actual file size. 

3089 end = size 

3090 # Note: only return HTTP 206 if less than the entire range has been 

3091 # requested. Not only is this semantically correct, but Chrome 

3092 # refuses to play audio if it gets an HTTP 206 in response to 

3093 # ``Range: bytes=0-``. 

3094 if size != (end or size) - (start or 0): 

3095 self.set_status(206) # Partial Content 

3096 self.set_header( 

3097 "Content-Range", httputil._get_content_range(start, end, size) 

3098 ) 

3099 else: 

3100 start = end = None 

3101 

3102 if start is not None and end is not None: 

3103 content_length = end - start 

3104 elif end is not None: 

3105 content_length = end 

3106 elif start is not None: 

3107 content_length = size - start 

3108 else: 

3109 content_length = size 

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

3111 

3112 if include_body: 

3113 content = self.get_content(self.absolute_path, start, end) 

3114 if isinstance(content, bytes): 

3115 content = [content] 

3116 for chunk in content: 

3117 try: 

3118 self.write(chunk) 

3119 await self.flush() 

3120 except iostream.StreamClosedError: 

3121 return 

3122 else: 

3123 assert self.request.method == "HEAD" 

3124 

3125 def compute_etag(self) -> str | None: 

3126 """Sets the ``Etag`` header based on static url version. 

3127 

3128 This allows efficient ``If-None-Match`` checks against cached 

3129 versions, and sends the correct ``Etag`` for a partial response 

3130 (i.e. the same ``Etag`` as the full file). 

3131 

3132 .. versionadded:: 3.1 

3133 """ 

3134 assert self.absolute_path is not None 

3135 version_hash = self._get_cached_version(self.absolute_path) 

3136 if not version_hash: 

3137 return None 

3138 return f'"{version_hash}"' 

3139 

3140 def set_headers(self) -> None: 

3141 """Sets the content and caching headers on the response. 

3142 

3143 .. versionadded:: 3.1 

3144 """ 

3145 self.set_header("Accept-Ranges", "bytes") 

3146 self.set_etag_header() 

3147 

3148 if self.modified is not None: 

3149 self.set_header("Last-Modified", self.modified) 

3150 

3151 content_type = self.get_content_type() 

3152 if content_type: 

3153 self.set_header("Content-Type", content_type) 

3154 

3155 cache_time = self.get_cache_time(self.path, self.modified, content_type) 

3156 if cache_time > 0: 

3157 self.set_header( 

3158 "Expires", 

3159 datetime.datetime.now(datetime.UTC) 

3160 + datetime.timedelta(seconds=cache_time), 

3161 ) 

3162 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 

3163 

3164 self.set_extra_headers(self.path) 

3165 

3166 def should_return_304(self) -> bool: 

3167 """Returns True if the headers indicate that we should return 304. 

3168 

3169 .. versionadded:: 3.1 

3170 """ 

3171 # If client sent If-None-Match, use it, ignore If-Modified-Since 

3172 if self.request.headers.get("If-None-Match"): 

3173 return self.check_etag_header() 

3174 

3175 # Check the If-Modified-Since, and don't send the result if the 

3176 # content has not been modified 

3177 ims_value = self.request.headers.get("If-Modified-Since") 

3178 if ims_value is not None: 

3179 try: 

3180 if_since = email.utils.parsedate_to_datetime(ims_value) 

3181 except Exception: 

3182 return False 

3183 if if_since.tzinfo is None: 

3184 if_since = if_since.replace(tzinfo=datetime.UTC) 

3185 assert self.modified is not None 

3186 if if_since >= self.modified: 

3187 return True 

3188 

3189 return False 

3190 

3191 @classmethod 

3192 def get_absolute_path(cls, root: str, path: str) -> str: 

3193 """Returns the absolute location of ``path`` relative to ``root``. 

3194 

3195 ``root`` is the path configured for this `StaticFileHandler` 

3196 (in most cases the ``static_path`` `Application` setting). 

3197 

3198 This class method may be overridden in subclasses. By default 

3199 it returns a filesystem path, but other strings may be used 

3200 as long as they are unique and understood by the subclass's 

3201 overridden `get_content`. 

3202 

3203 Raises `HTTPError` (403) if ``path`` leads outside of ``root`` (as 

3204 written, e.g. with ``../``; symlinks are checked later by 

3205 `validate_absolute_path`). This check is also what keeps 

3206 `get_version` (and therefore `~RequestHandler.static_url`) from 

3207 reading files outside of the static directory. 

3208 

3209 .. versionadded:: 3.1 

3210 

3211 .. versionchanged:: 6.6 

3212 Paths outside of ``root`` now raise `HTTPError`. 

3213 """ 

3214 abspath = os.path.abspath(os.path.join(root, path)) 

3215 cls._check_in_root(root, abspath, path) 

3216 return abspath 

3217 

3218 def validate_absolute_path(self, root: str, absolute_path: str) -> str | None: 

3219 """Validate and return the absolute path. 

3220 

3221 ``root`` is the configured path for the `StaticFileHandler`, 

3222 and ``path`` is the result of `get_absolute_path` 

3223 

3224 This is an instance method called during request processing, 

3225 so it may raise `HTTPError` or use methods like 

3226 `RequestHandler.redirect` (return None after redirecting to 

3227 halt further processing). This is where 404 errors for missing files 

3228 are generated. 

3229 

3230 This method may modify the path before returning it, but note that 

3231 any such modifications will not be understood by `make_static_url`. 

3232 

3233 In instance methods, this method's result is available as 

3234 ``self.absolute_path``. 

3235 

3236 .. versionadded:: 3.1 

3237 """ 

3238 root = self._check_in_root(root, absolute_path, self.path) 

3239 # Symlinks may point anywhere inside allowed_symlink_directory, which 

3240 # defaults to the directory being served. Resolve that default here, 

3241 # against the same root the checks above used, and normalize the one 

3242 # directory that is allowed by default (or configured as a plain 

3243 # string) to a list, so that _resolve_symlink_target has only one 

3244 # shape to deal with. 

3245 allowed_symlink_directories = self.allowed_symlink_directory or root 

3246 if isinstance(allowed_symlink_directories, str): 

3247 allowed_symlink_directories = [allowed_symlink_directories] 

3248 # The check above is on the path as written, which is cheap and 

3249 # rejects traversal without touching the filesystem. Resolve 

3250 # symlinks only once it has passed, so that the remaining checks - 

3251 # and the path this method returns - refer to the file that will 

3252 # actually be opened rather than to the link. The two checks 

3253 # cannot be merged: this one must stay on the unresolved path, 

3254 # because allowed_symlink_directory may be wider than the root and 

3255 # would then let a ../ traversal through. 

3256 absolute_path = self._resolve_symlink_target( 

3257 absolute_path, allowed_symlink_directories, self.path 

3258 ) 

3259 if os.path.isdir(absolute_path) and self.default_filename is not None: 

3260 # need to look at the request.path here for when path is empty 

3261 # but there is some prefix to the path that was already 

3262 # trimmed by the routing 

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

3264 if _is_protocol_relative(self.request.path): 

3265 # A redirect with two initial slashes is a "protocol-relative" URL. 

3266 # This means the next path segment is treated as a hostname instead 

3267 # of a part of the path, making this effectively an open redirect. 

3268 # Reject paths starting with two slashes to prevent this. 

3269 # This is only reachable under certain configurations. 

3270 raise HTTPError( 

3271 403, "cannot redirect path with two initial slashes" 

3272 ) 

3273 self.redirect(self.request.path + "/", permanent=True) 

3274 return None 

3275 absolute_path = os.path.join(absolute_path, self.default_filename) 

3276 # The default filename may be a symlink even when the directory 

3277 # containing it is not. Resolving here rather than deferring a 

3278 # single resolution until after this block is deliberate: the 

3279 # isdir() above, and the redirect it can trigger, must not run 

3280 # on a path that has already escaped, or the difference between 

3281 # a redirect and a 403 would reveal whether an out-of-bounds 

3282 # directory exists. 

3283 absolute_path = self._resolve_symlink_target( 

3284 absolute_path, allowed_symlink_directories, self.path 

3285 ) 

3286 # Stat the resolved path once and keep the result, instead of 

3287 # letting os.path.exists, os.path.isfile and the later header 

3288 # generation each resolve the path again. Every one of those is a 

3289 # separate trip through the filesystem that could observe a 

3290 # different file from the one validated here. 

3291 self._stat_result = self._stat_regular_file(absolute_path, self.path) 

3292 return absolute_path 

3293 

3294 @staticmethod 

3295 def _stat_regular_file(absolute_path: str, path_message: str) -> os.stat_result: 

3296 """Stats ``absolute_path`` and checks that it is a regular file. 

3297 

3298 Raises `HTTPError` (404) if the path cannot be stat'ed, or (403) if 

3299 it is anything other than a regular file. Directories, devices (such 

3300 as ``/dev/zero``, or ``CON`` and ``NUL``, which exist in every 

3301 directory on Windows), FIFOs and sockets must never be read: some of 

3302 them never end, and opening a FIFO blocks until there is a writer. 

3303 ``path_message`` is used only to build the error message. 

3304 """ 

3305 try: 

3306 stat_result = os.stat(absolute_path) 

3307 except OSError: 

3308 # Matches the previous os.path.exists() check, which is also 

3309 # false when the path cannot be stat'ed at all. 

3310 raise HTTPError(404) 

3311 if not stat.S_ISREG(stat_result.st_mode): 

3312 raise HTTPError(403, "%s is not a file", path_message) 

3313 return stat_result 

3314 

3315 @staticmethod 

3316 def _check_in_root(root: str, absolute_path: str, path_message: str) -> str: 

3317 """Checks that ``absolute_path`` (as written) is inside ``root``. 

3318 

3319 Returns the normalized root, with a trailing separator. Raises 

3320 `HTTPError` (403) if the path is outside of it. ``path_message`` 

3321 is used only to build the error message. 

3322 """ 

3323 # os.path.abspath strips a trailing /. 

3324 # We must add it back to `root` so that we only match files 

3325 # in a directory named `root` instead of files starting with 

3326 # that prefix. 

3327 root = os.path.abspath(root) 

3328 if not root.endswith(os.path.sep): 

3329 # abspath always removes a trailing slash, except when 

3330 # root is '/'. This is an unusual case, but several projects 

3331 # have independently discovered this technique to disable 

3332 # Tornado's path validation and (hopefully) do their own, 

3333 # so we need to support it. 

3334 root += os.path.sep 

3335 # The trailing slash also needs to be temporarily added back 

3336 # the requested path so a request to root/ will match. 

3337 if not (absolute_path + os.path.sep).startswith(root): 

3338 raise HTTPError(403, "%s is not in root static directory", path_message) 

3339 if os.name == "nt" and ":" in absolute_path[len(root) :]: 

3340 # On Windows, "file.txt:name" names the NTFS alternate data 

3341 # stream "name" of file.txt. abspath, stat and open all accept 

3342 # this syntax, so without this check the stream (which may 

3343 # hold data such as the Zone.Identifier recorded for downloaded 

3344 # files) would be served as if it were a file in the static 

3345 # directory. A colon can never appear in an ordinary Windows 

3346 # file name, so rejecting it anywhere below the root (the root 

3347 # itself contains the drive letter's colon) loses nothing. 

3348 raise HTTPError(403, "%s is not a file", path_message) 

3349 return root 

3350 

3351 @staticmethod 

3352 def _resolve_symlink_target( 

3353 absolute_path: str, 

3354 allowed_symlink_directories: list[str], 

3355 path_message: str, 

3356 ) -> str: 

3357 """Resolves symlinks in ``absolute_path`` and validates the result. 

3358 

3359 The path checks in `validate_absolute_path` operate on the path as 

3360 written, which says nothing about where a symlink points: a symlink 

3361 inside the static directory can name any file on the system. The 

3362 resolved path must therefore stay inside one of 

3363 ``allowed_symlink_directories``. 

3364 

3365 This is a static method so that it can only see the values its 

3366 caller checked against: ``allowed_symlink_directories`` are the 

3367 directories `validate_absolute_path` settled on, which are not 

3368 necessarily ``self.allowed_symlink_directory`` or ``self.root``. 

3369 It is always a list, so that the single-directory case does not 

3370 need a second code path here. ``path_message`` is used only to 

3371 build the error message. 

3372 

3373 Raises `HTTPError` (403) if the resolved path escapes all of those 

3374 directories. 

3375 

3376 Note that this is a check against the state of the filesystem at 

3377 one moment: a path that is replaced by a symlink after this returns 

3378 but before the file is opened would not be caught. Closing that 

3379 race entirely would require holding file descriptors across the 

3380 whole operation. This check is not a substitute for filesystem 

3381 permissions on a directory that untrusted users can write to. 

3382 """ 

3383 # strict=True is deliberately not used here: it is unavailable on 

3384 # Python 3.9, and a path that does not exist resolves to itself, 

3385 # which the caller then reports as a 404. 

3386 resolved_path = os.path.realpath(absolute_path) 

3387 for allowed_symlink_directory in allowed_symlink_directories: 

3388 # realpath() is applied to the allowed directory as well, because 

3389 # it may itself be reached through a symlink (a static directory 

3390 # of /var/www that links to /srv/www, say). Comparing a resolved 

3391 # path against an unresolved directory would reject every request. 

3392 allowed_directory = os.path.realpath(allowed_symlink_directory) 

3393 if not allowed_directory.endswith(os.path.sep): 

3394 # As in validate_absolute_path, the separator must not be 

3395 # doubled when the directory is the filesystem root. 

3396 allowed_directory += os.path.sep 

3397 # The trailing separator lets the allowed directory itself match, 

3398 # as well as anything beneath it. This is reachable because the 

3399 # path may still be a directory at this point (when 

3400 # default_filename is set). 

3401 if (resolved_path + os.path.sep).startswith(allowed_directory): 

3402 return resolved_path 

3403 # Deliberately the same error as the check on the path as written, 

3404 # so that a caller cannot tell which way a path left the served 

3405 # directory. 

3406 raise HTTPError(403, "%s is not in root static directory", path_message) 

3407 

3408 @classmethod 

3409 def get_content( 

3410 cls, abspath: str, start: int | None = None, end: int | None = None 

3411 ) -> Generator[bytes, None, None]: 

3412 """Retrieve the content of the requested resource which is located 

3413 at the given absolute path. 

3414 

3415 This class method may be overridden by subclasses. Note that its 

3416 signature is different from other overridable class methods 

3417 (no ``settings`` argument); this is deliberate to ensure that 

3418 ``abspath`` is able to stand on its own as a cache key. 

3419 

3420 This method should either return a byte string or an iterator 

3421 of byte strings. The latter is preferred for large files 

3422 as it helps reduce memory fragmentation. 

3423 

3424 .. versionadded:: 3.1 

3425 

3426 .. versionchanged:: 6.6 

3427 Raises `HTTPError` if ``abspath`` is not a regular file. 

3428 """ 

3429 # validate_absolute_path has already checked this when serving a 

3430 # request, but get_content_version (for static_url) reaches this 

3431 # method with nothing but get_absolute_path in between. 

3432 cls._stat_regular_file(abspath, abspath) 

3433 with open(abspath, "rb") as file: 

3434 if start is not None: 

3435 file.seek(start) 

3436 if end is not None: 

3437 remaining: int | None = end - (start or 0) 

3438 else: 

3439 remaining = None 

3440 while True: 

3441 chunk_size = 64 * 1024 

3442 if remaining is not None and remaining < chunk_size: 

3443 chunk_size = remaining 

3444 chunk = file.read(chunk_size) 

3445 if chunk: 

3446 if remaining is not None: 

3447 remaining -= len(chunk) 

3448 yield chunk 

3449 else: 

3450 if remaining is not None: 

3451 assert remaining == 0 

3452 return 

3453 

3454 @classmethod 

3455 def get_content_version(cls, abspath: str) -> str: 

3456 """Returns a version string for the resource at the given path. 

3457 

3458 This class method may be overridden by subclasses. The 

3459 default implementation is a SHA-512 hash of the file's contents. 

3460 

3461 The hash is computed synchronously, so content larger than 

3462 `MAX_VERSION_CONTENT_SIZE` is not hashed (and gets no version) 

3463 rather than blocking the event loop for a long time. 

3464 

3465 .. versionadded:: 3.1 

3466 

3467 .. versionchanged:: 6.6 

3468 Content larger than `MAX_VERSION_CONTENT_SIZE` raises `ValueError`. 

3469 """ 

3470 data = cls.get_content(abspath) 

3471 chunks = [data] if isinstance(data, bytes) else data 

3472 hasher = hashlib.sha512() 

3473 size = 0 

3474 try: 

3475 for chunk in chunks: 

3476 size += len(chunk) 

3477 if size > cls.MAX_VERSION_CONTENT_SIZE: 

3478 raise ValueError( 

3479 "content is larger than MAX_VERSION_CONTENT_SIZE (%d bytes)" 

3480 % cls.MAX_VERSION_CONTENT_SIZE 

3481 ) 

3482 hasher.update(chunk) 

3483 finally: 

3484 # Close the file now, rather than when the generator is collected, 

3485 # if we stopped early. 

3486 close = getattr(chunks, "close", None) 

3487 if close is not None: 

3488 close() 

3489 return hasher.hexdigest() 

3490 

3491 def _stat(self) -> os.stat_result: 

3492 assert self.absolute_path is not None 

3493 # validate_absolute_path normally populates this, so that the size 

3494 # and modification time reported here describe the same file that 

3495 # was validated. Subclasses that override validate_absolute_path 

3496 # without calling super() fall back to stat'ing here. 

3497 if not hasattr(self, "_stat_result"): 

3498 self._stat_result = os.stat(self.absolute_path) 

3499 return self._stat_result 

3500 

3501 def get_content_size(self) -> int: 

3502 """Retrieve the total size of the resource at the given path. 

3503 

3504 This method may be overridden by subclasses. 

3505 

3506 .. versionadded:: 3.1 

3507 

3508 .. versionchanged:: 4.0 

3509 This method is now always called, instead of only when 

3510 partial results are requested. 

3511 """ 

3512 stat_result = self._stat() 

3513 return stat_result.st_size 

3514 

3515 def get_modified_time(self) -> datetime.datetime | None: 

3516 """Returns the time that ``self.absolute_path`` was last modified. 

3517 

3518 May be overridden in subclasses. Should return a `~datetime.datetime` 

3519 object or None. 

3520 

3521 .. versionadded:: 3.1 

3522 

3523 .. versionchanged:: 6.4 

3524 Now returns an aware datetime object instead of a naive one. 

3525 Subclasses that override this method may return either kind. 

3526 """ 

3527 stat_result = self._stat() 

3528 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 

3529 # which truncates the fractional portion of the timestamp. It 

3530 # was changed from that form to stat_result.st_mtime to 

3531 # satisfy mypy (which disallows the bracket operator), but the 

3532 # latter form returns a float instead of an int. For 

3533 # consistency with the past (and because we have a unit test 

3534 # that relies on this), we truncate the float here, although 

3535 # I'm not sure that's the right thing to do. 

3536 modified = datetime.datetime.fromtimestamp( 

3537 int(stat_result.st_mtime), datetime.UTC 

3538 ) 

3539 return modified 

3540 

3541 def get_content_type(self) -> str: 

3542 """Returns the ``Content-Type`` header to be used for this request. 

3543 

3544 .. versionadded:: 3.1 

3545 """ 

3546 assert self.absolute_path is not None 

3547 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 

3548 # per RFC 6713, use the appropriate type for a gzip compressed file 

3549 if encoding == "gzip": 

3550 return "application/gzip" 

3551 # As of 2015-07-21 there is no bzip2 encoding defined at 

3552 # http://www.iana.org/assignments/media-types/media-types.xhtml 

3553 # So for that (and any other encoding), use octet-stream. 

3554 elif encoding is not None: 

3555 return "application/octet-stream" 

3556 elif mime_type is not None: 

3557 return mime_type 

3558 # if mime_type not detected, use application/octet-stream 

3559 else: 

3560 return "application/octet-stream" 

3561 

3562 def set_extra_headers(self, path: str) -> None: 

3563 """For subclass to add extra headers to the response""" 

3564 pass 

3565 

3566 def get_cache_time( 

3567 self, path: str, modified: datetime.datetime | None, mime_type: str 

3568 ) -> int: 

3569 """Override to customize cache control behavior. 

3570 

3571 Return a positive number of seconds to make the result 

3572 cacheable for that amount of time or 0 to mark resource as 

3573 cacheable for an unspecified amount of time (subject to 

3574 browser heuristics). 

3575 

3576 By default returns cache expiry of 10 years for resources requested 

3577 with ``v`` argument. 

3578 """ 

3579 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 

3580 

3581 @classmethod 

3582 def make_static_url( 

3583 cls, settings: dict[str, Any], path: str, include_version: bool = True 

3584 ) -> str: 

3585 """Constructs a versioned url for the given path. 

3586 

3587 This method may be overridden in subclasses (but note that it 

3588 is a class method rather than an instance method). Subclasses 

3589 are only required to implement the signature 

3590 ``make_static_url(cls, settings, path)``; other keyword 

3591 arguments may be passed through `~RequestHandler.static_url` 

3592 but are not standard. 

3593 

3594 ``settings`` is the `Application.settings` dictionary. ``path`` 

3595 is the static path being requested. The url returned should be 

3596 relative to the current host. 

3597 

3598 ``include_version`` determines whether the generated URL should 

3599 include the query string containing the version hash of the 

3600 file corresponding to the given ``path``. 

3601 

3602 """ 

3603 url = settings.get("static_url_prefix", "/static/") + path 

3604 if not include_version: 

3605 return url 

3606 

3607 version_hash = cls.get_version(settings, path) 

3608 if not version_hash: 

3609 return url 

3610 

3611 return f"{url}?v={version_hash}" 

3612 

3613 def parse_url_path(self, url_path: str) -> str: 

3614 """Converts a static URL path into a filesystem path. 

3615 

3616 ``url_path`` is the path component of the URL with 

3617 ``static_url_prefix`` removed. The return value should be 

3618 filesystem path relative to ``static_path``. 

3619 

3620 This is the inverse of `make_static_url`. 

3621 """ 

3622 if os.path.sep != "/": 

3623 url_path = url_path.replace("/", os.path.sep) 

3624 return url_path 

3625 

3626 @classmethod 

3627 def get_version(cls, settings: dict[str, Any], path: str) -> str | None: 

3628 """Generate the version string to be used in static URLs. 

3629 

3630 ``settings`` is the `Application.settings` dictionary and ``path`` 

3631 is the relative location of the requested asset on the filesystem. 

3632 The returned value should be a string, or ``None`` if no version 

3633 could be determined. 

3634 

3635 As with `~RequestHandler.static_url`, ``path`` should name a known 

3636 static file and should not be derived from untrusted input. Versions 

3637 are cached (up to `MAX_CACHED_VERSIONS` entries) until ``reset`` is 

3638 called. 

3639 

3640 .. versionchanged:: 3.1 

3641 This method was previously recommended for subclasses to override; 

3642 `get_content_version` is now preferred as it allows the base 

3643 class to handle caching of the result. 

3644 """ 

3645 abs_path = cls.get_absolute_path(settings["static_path"], path) 

3646 return cls._get_cached_version(abs_path) 

3647 

3648 @classmethod 

3649 def _get_cached_version(cls, abs_path: str) -> str | None: 

3650 with cls._lock: 

3651 hashes = cls._static_hashes 

3652 if abs_path in hashes: 

3653 # Move the entry to the end to mark it as most recently used. 

3654 hsh = hashes[abs_path] = hashes.pop(abs_path) 

3655 else: 

3656 try: 

3657 hsh = cls.get_content_version(abs_path) 

3658 except Exception as e: 

3659 gen_log.error("Could not open static file %r: %s", abs_path, e) 

3660 hsh = None 

3661 while hashes and len(hashes) >= cls.MAX_CACHED_VERSIONS: 

3662 del hashes[next(iter(hashes))] 

3663 hashes[abs_path] = hsh 

3664 return hsh or None 

3665 

3666 

3667class FallbackHandler(RequestHandler): 

3668 """A `RequestHandler` that wraps another HTTP server callback. 

3669 

3670 The fallback is a callable object that accepts an 

3671 `~.httputil.HTTPServerRequest`, such as an `Application` or 

3672 `tornado.wsgi.WSGIContainer`. This is most useful to use both 

3673 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 

3674 usage:: 

3675 

3676 wsgi_app = tornado.wsgi.WSGIContainer( 

3677 django.core.handlers.wsgi.WSGIHandler()) 

3678 application = tornado.web.Application([ 

3679 (r"/foo", FooHandler), 

3680 (r".*", FallbackHandler, dict(fallback=wsgi_app)), 

3681 ]) 

3682 """ 

3683 

3684 def initialize( 

3685 self, fallback: Callable[[httputil.HTTPServerRequest], None] 

3686 ) -> None: 

3687 self.fallback = fallback 

3688 

3689 def prepare(self) -> None: 

3690 self.fallback(self.request) 

3691 self._finished = True 

3692 self.on_finish() 

3693 

3694 

3695class OutputTransform: 

3696 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 

3697 

3698 Applications are not expected to create their own OutputTransforms 

3699 or interact with them directly; the framework chooses which transforms 

3700 (if any) to apply. 

3701 """ 

3702 

3703 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3704 pass 

3705 

3706 def transform_first_chunk( 

3707 self, 

3708 status_code: int, 

3709 headers: httputil.HTTPHeaders, 

3710 chunk: bytes, 

3711 finishing: bool, 

3712 ) -> tuple[int, httputil.HTTPHeaders, bytes]: 

3713 return status_code, headers, chunk 

3714 

3715 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3716 return chunk 

3717 

3718 

3719class GZipContentEncoding(OutputTransform): 

3720 """Applies the gzip content encoding to the response. 

3721 

3722 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 

3723 

3724 .. versionchanged:: 4.0 

3725 Now compresses all mime types beginning with ``text/``, instead 

3726 of just a whitelist. (the whitelist is still used for certain 

3727 non-text mime types). 

3728 """ 

3729 

3730 # Whitelist of compressible mime types (in addition to any types 

3731 # beginning with "text/"). 

3732 CONTENT_TYPES = { 

3733 "application/javascript", 

3734 "application/x-javascript", 

3735 "application/xml", 

3736 "application/atom+xml", 

3737 "application/json", 

3738 "application/xhtml+xml", 

3739 "image/svg+xml", 

3740 } 

3741 # Python's GzipFile defaults to level 9, while most other gzip 

3742 # tools (including gzip itself) default to 6, which is probably a 

3743 # better CPU/size tradeoff. 

3744 GZIP_LEVEL = 6 

3745 # Responses that are too short are unlikely to benefit from gzipping 

3746 # after considering the "Content-Encoding: gzip" header and the header 

3747 # inside the gzip encoding. 

3748 # Note that responses written in multiple chunks will be compressed 

3749 # regardless of size. 

3750 MIN_LENGTH = 1024 

3751 

3752 def __init__(self, request: httputil.HTTPServerRequest) -> None: 

3753 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 

3754 

3755 def _compressible_type(self, ctype: str) -> bool: 

3756 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 

3757 

3758 def transform_first_chunk( 

3759 self, 

3760 status_code: int, 

3761 headers: httputil.HTTPHeaders, 

3762 chunk: bytes, 

3763 finishing: bool, 

3764 ) -> tuple[int, httputil.HTTPHeaders, bytes]: 

3765 # TODO: can/should this type be inherited from the superclass? 

3766 if "Vary" in headers: 

3767 headers["Vary"] += ", Accept-Encoding" 

3768 else: 

3769 headers["Vary"] = "Accept-Encoding" 

3770 if self._gzipping: 

3771 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 

3772 self._gzipping = ( 

3773 self._compressible_type(ctype) 

3774 and (not finishing or len(chunk) >= self.MIN_LENGTH) 

3775 and ("Content-Encoding" not in headers) 

3776 ) 

3777 if self._gzipping: 

3778 headers["Content-Encoding"] = "gzip" 

3779 self._gzip_value = BytesIO() 

3780 self._gzip_file = gzip.GzipFile( 

3781 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 

3782 ) 

3783 chunk = self.transform_chunk(chunk, finishing) 

3784 if "Content-Length" in headers: 

3785 # The original content length is no longer correct. 

3786 # If this is the last (and only) chunk, we can set the new 

3787 # content-length; otherwise we remove it and fall back to 

3788 # chunked encoding. 

3789 if finishing: 

3790 headers["Content-Length"] = str(len(chunk)) 

3791 else: 

3792 del headers["Content-Length"] 

3793 return status_code, headers, chunk 

3794 

3795 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 

3796 if self._gzipping: 

3797 self._gzip_file.write(chunk) 

3798 if finishing: 

3799 self._gzip_file.close() 

3800 else: 

3801 self._gzip_file.flush() 

3802 chunk = self._gzip_value.getvalue() 

3803 self._gzip_value.truncate(0) 

3804 self._gzip_value.seek(0) 

3805 return chunk 

3806 

3807 

3808def authenticated( 

3809 method: Callable[..., Awaitable[None] | None], 

3810) -> Callable[..., Awaitable[None] | None]: 

3811 """Decorate methods with this to require that the user be logged in. 

3812 

3813 If the user is not logged in, they will be redirected to the configured 

3814 `login url <RequestHandler.get_login_url>`. 

3815 

3816 If you configure a login url with a query parameter, Tornado will 

3817 assume you know what you're doing and use it as-is. If not, it 

3818 will add a `next` parameter so the login page knows where to send 

3819 you once you're logged in. 

3820 """ 

3821 

3822 @functools.wraps(method) 

3823 def wrapper( # type: ignore 

3824 self: RequestHandler, *args, **kwargs 

3825 ) -> Awaitable[None] | None: 

3826 if not self.current_user: 

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

3828 url = self.get_login_url() 

3829 if "?" not in url: 

3830 if urllib.parse.urlsplit(url).scheme: 

3831 # if login url is absolute, make next absolute too 

3832 next_url = self.request.full_url() 

3833 else: 

3834 assert self.request.uri is not None 

3835 next_url = self.request.uri 

3836 url += "?" + urlencode(dict(next=next_url)) 

3837 self.redirect(url) 

3838 return None 

3839 raise HTTPError(403) 

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

3841 

3842 return wrapper 

3843 

3844 

3845class UIModule: 

3846 """A reusable, modular UI unit on a page. 

3847 

3848 UI modules often execute additional queries, and they can include 

3849 additional CSS and JavaScript that will be included in the output 

3850 page, which is automatically inserted on page render. 

3851 

3852 Subclasses of UIModule must override the `render` method. 

3853 """ 

3854 

3855 def __init__(self, handler: RequestHandler) -> None: 

3856 self.handler = handler 

3857 self.request = handler.request 

3858 self.ui = handler.ui 

3859 self.locale = handler.locale 

3860 

3861 @property 

3862 def current_user(self) -> Any: 

3863 return self.handler.current_user 

3864 

3865 def render(self, *args: Any, **kwargs: Any) -> str | bytes: 

3866 """Override in subclasses to return this module's output.""" 

3867 raise NotImplementedError() 

3868 

3869 def embedded_javascript(self) -> str | None: 

3870 """Override to return a JavaScript string 

3871 to be embedded in the page.""" 

3872 return None 

3873 

3874 def javascript_files(self) -> Iterable[str] | None: 

3875 """Override to return a list of JavaScript files needed by this module. 

3876 

3877 If the return values are relative paths, they will be passed to 

3878 `RequestHandler.static_url`; otherwise they will be used as-is. 

3879 """ 

3880 return None 

3881 

3882 def embedded_css(self) -> str | None: 

3883 """Override to return a CSS string 

3884 that will be embedded in the page.""" 

3885 return None 

3886 

3887 def css_files(self) -> Iterable[str] | None: 

3888 """Override to returns a list of CSS files required by this module. 

3889 

3890 If the return values are relative paths, they will be passed to 

3891 `RequestHandler.static_url`; otherwise they will be used as-is. 

3892 """ 

3893 return None 

3894 

3895 def html_head(self) -> str | None: 

3896 """Override to return an HTML string that will be put in the <head/> 

3897 element. 

3898 """ 

3899 return None 

3900 

3901 def html_body(self) -> str | None: 

3902 """Override to return an HTML string that will be put at the end of 

3903 the <body/> element. 

3904 """ 

3905 return None 

3906 

3907 def render_string(self, path: str, **kwargs: Any) -> bytes: 

3908 """Renders a template and returns it as a string.""" 

3909 return self.handler.render_string(path, **kwargs) 

3910 

3911 

3912class _linkify(UIModule): 

3913 def render(self, text: str, **kwargs: Any) -> str: 

3914 return escape.linkify(text, **kwargs) 

3915 

3916 

3917class _xsrf_form_html(UIModule): 

3918 def render(self) -> str: 

3919 return self.handler.xsrf_form_html() 

3920 

3921 

3922class TemplateModule(UIModule): 

3923 """UIModule that simply renders the given template. 

3924 

3925 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 

3926 but the module version gets its own namespace (with kwargs passed to 

3927 Template()) instead of inheriting the outer template's namespace. 

3928 

3929 Templates rendered through this module also get access to UIModule's 

3930 automatic JavaScript/CSS features. Simply call set_resources 

3931 inside the template and give it keyword arguments corresponding to 

3932 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 

3933 Note that these resources are output once per template file, not once 

3934 per instantiation of the template, so they must not depend on 

3935 any arguments to the template. 

3936 """ 

3937 

3938 def __init__(self, handler: RequestHandler) -> None: 

3939 super().__init__(handler) 

3940 # keep resources in both a list and a dict to preserve order 

3941 self._resource_list: list[dict[str, Any]] = [] 

3942 self._resource_dict: dict[str, dict[str, Any]] = {} 

3943 

3944 def render(self, path: str, **kwargs: Any) -> bytes: 

3945 def set_resources(**kwargs) -> str: # type: ignore 

3946 if path not in self._resource_dict: 

3947 self._resource_list.append(kwargs) 

3948 self._resource_dict[path] = kwargs 

3949 else: 

3950 if self._resource_dict[path] != kwargs: 

3951 raise ValueError( 

3952 "set_resources called with different " 

3953 "resources for the same template" 

3954 ) 

3955 return "" 

3956 

3957 return self.render_string(path, set_resources=set_resources, **kwargs) 

3958 

3959 def _get_resources(self, key: str) -> Iterable[str]: 

3960 return (r[key] for r in self._resource_list if key in r) 

3961 

3962 def embedded_javascript(self) -> str: 

3963 return "\n".join(self._get_resources("embedded_javascript")) 

3964 

3965 def javascript_files(self) -> Iterable[str]: 

3966 result = [] 

3967 for f in self._get_resources("javascript_files"): 

3968 if isinstance(f, (unicode_type, bytes)): 

3969 result.append(f) 

3970 else: 

3971 result.extend(f) 

3972 return result 

3973 

3974 def embedded_css(self) -> str: 

3975 return "\n".join(self._get_resources("embedded_css")) 

3976 

3977 def css_files(self) -> Iterable[str]: 

3978 result = [] 

3979 for f in self._get_resources("css_files"): 

3980 if isinstance(f, (unicode_type, bytes)): 

3981 result.append(f) 

3982 else: 

3983 result.extend(f) 

3984 return result 

3985 

3986 def html_head(self) -> str: 

3987 return "".join(self._get_resources("html_head")) 

3988 

3989 def html_body(self) -> str: 

3990 return "".join(self._get_resources("html_body")) 

3991 

3992 

3993class _UIModuleNamespace: 

3994 """Lazy namespace which creates UIModule proxies bound to a handler.""" 

3995 

3996 def __init__( 

3997 self, handler: RequestHandler, ui_modules: dict[str, type[UIModule]] 

3998 ) -> None: 

3999 self.handler = handler 

4000 self.ui_modules = ui_modules 

4001 

4002 def __getitem__(self, key: str) -> Callable[..., str]: 

4003 return self.handler._ui_module(key, self.ui_modules[key]) 

4004 

4005 def __getattr__(self, key: str) -> Callable[..., str]: 

4006 try: 

4007 return self[key] 

4008 except KeyError as e: 

4009 raise AttributeError(str(e)) 

4010 

4011 

4012def create_signed_value( 

4013 secret: _CookieSecretTypes, 

4014 name: str, 

4015 value: str | bytes, 

4016 version: int | None = None, 

4017 clock: Callable[[], float] | None = None, 

4018 key_version: int | None = None, 

4019) -> bytes: 

4020 if version is None: 

4021 version = DEFAULT_SIGNED_VALUE_VERSION 

4022 if clock is None: 

4023 clock = time.time 

4024 

4025 timestamp = utf8(str(int(clock()))) 

4026 value = base64.b64encode(utf8(value)) 

4027 if version == 1: 

4028 assert not isinstance(secret, dict) 

4029 signature = _create_signature_v1(secret, name, value, timestamp) 

4030 value = b"|".join([value, timestamp, signature]) 

4031 return value 

4032 elif version == 2: 

4033 # The v2 format consists of a version number and a series of 

4034 # length-prefixed fields "%d:%s", the last of which is a 

4035 # signature, all separated by pipes. All numbers are in 

4036 # decimal format with no leading zeros. The signature is an 

4037 # HMAC-SHA256 of the whole string up to that point, including 

4038 # the final pipe. 

4039 # 

4040 # The fields are: 

4041 # - format version (i.e. 2; no length prefix) 

4042 # - key version (integer, default is 0) 

4043 # - timestamp (integer seconds since epoch) 

4044 # - name (not encoded; assumed to be ~alphanumeric) 

4045 # - value (base64-encoded) 

4046 # - signature (hex-encoded; no length prefix) 

4047 def format_field(s: str | bytes) -> bytes: 

4048 return utf8("%d:" % len(s)) + utf8(s) 

4049 

4050 to_sign = b"|".join( 

4051 [ 

4052 b"2", 

4053 format_field(str(key_version or 0)), 

4054 format_field(timestamp), 

4055 format_field(name), 

4056 format_field(value), 

4057 b"", 

4058 ] 

4059 ) 

4060 

4061 if isinstance(secret, dict): 

4062 assert ( 

4063 key_version is not None 

4064 ), "Key version must be set when sign key dict is used" 

4065 assert version >= 2, "Version must be at least 2 for key version support" 

4066 secret = secret[key_version] 

4067 

4068 signature = _create_signature_v2(secret, to_sign) 

4069 return to_sign + signature 

4070 else: 

4071 raise ValueError("Unsupported version %d" % version) 

4072 

4073 

4074# A leading version number in decimal 

4075# with no leading zeros, followed by a pipe. 

4076_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$") 

4077 

4078 

4079def _get_version(value: bytes) -> int: 

4080 # Figures out what version value is. Version 1 did not include an 

4081 # explicit version field and started with arbitrary base64 data, 

4082 # which makes this tricky. 

4083 m = _signed_value_version_re.match(value) 

4084 if m is None: 

4085 version = 1 

4086 else: 

4087 try: 

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

4089 if version > 999: 

4090 # Certain payloads from the version-less v1 format may 

4091 # be parsed as valid integers. Due to base64 padding 

4092 # restrictions, this can only happen for numbers whose 

4093 # length is a multiple of 4, so we can treat all 

4094 # numbers up to 999 as versions, and for the rest we 

4095 # fall back to v1 format. 

4096 version = 1 

4097 except ValueError: 

4098 version = 1 

4099 return version 

4100 

4101 

4102def decode_signed_value( 

4103 secret: _CookieSecretTypes, 

4104 name: str, 

4105 value: None | str | bytes, 

4106 max_age_days: float = 31, 

4107 clock: Callable[[], float] | None = None, 

4108 min_version: int | None = None, 

4109) -> bytes | None: 

4110 if clock is None: 

4111 clock = time.time 

4112 if min_version is None: 

4113 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 

4114 if min_version > 2: 

4115 raise ValueError("Unsupported min_version %d" % min_version) 

4116 if not value: 

4117 return None 

4118 

4119 value = utf8(value) 

4120 version = _get_version(value) 

4121 

4122 if version < min_version: 

4123 return None 

4124 if version == 1: 

4125 assert not isinstance(secret, dict) 

4126 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 

4127 elif version == 2: 

4128 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 

4129 else: 

4130 return None 

4131 

4132 

4133def _decode_signed_value_v1( 

4134 secret: str | bytes, 

4135 name: str, 

4136 value: bytes, 

4137 max_age_days: float, 

4138 clock: Callable[[], float], 

4139) -> bytes | None: 

4140 parts = utf8(value).split(b"|") 

4141 if len(parts) != 3: 

4142 return None 

4143 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 

4144 if not hmac.compare_digest(parts[2], signature): 

4145 gen_log.warning("Invalid cookie signature %r", value) 

4146 return None 

4147 try: 

4148 timestamp = int(parts[1]) 

4149 except ValueError: 

4150 return None 

4151 if timestamp < clock() - max_age_days * 86400: 

4152 gen_log.warning("Expired cookie %r", value) 

4153 return None 

4154 if timestamp > clock() + 31 * 86400: 

4155 # _cookie_signature does not hash a delimiter between the 

4156 # parts of the cookie, so an attacker could transfer trailing 

4157 # digits from the payload to the timestamp without altering the 

4158 # signature. For backwards compatibility, sanity-check timestamp 

4159 # here instead of modifying _cookie_signature. 

4160 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 

4161 return None 

4162 if parts[1].startswith(b"0"): 

4163 gen_log.warning("Tampered cookie %r", value) 

4164 return None 

4165 try: 

4166 return base64.b64decode(parts[0]) 

4167 except Exception: 

4168 return None 

4169 

4170 

4171def _decode_fields_v2(value: bytes) -> tuple[int, bytes, bytes, bytes, bytes]: 

4172 def _consume_field(s: bytes) -> tuple[bytes, bytes]: 

4173 length, _, rest = s.partition(b":") 

4174 n = int(length) 

4175 field_value = rest[:n] 

4176 # In python 3, indexing bytes returns small integers; we must 

4177 # use a slice to get a byte string as in python 2. 

4178 if rest[n : n + 1] != b"|": 

4179 raise ValueError("malformed v2 signed value field") 

4180 rest = rest[n + 1 :] 

4181 return field_value, rest 

4182 

4183 rest = value[2:] # remove version number 

4184 key_version, rest = _consume_field(rest) 

4185 timestamp, rest = _consume_field(rest) 

4186 name_field, rest = _consume_field(rest) 

4187 value_field, passed_sig = _consume_field(rest) 

4188 return int(key_version), timestamp, name_field, value_field, passed_sig 

4189 

4190 

4191def _decode_signed_value_v2( 

4192 secret: _CookieSecretTypes, 

4193 name: str, 

4194 value: bytes, 

4195 max_age_days: float, 

4196 clock: Callable[[], float], 

4197) -> bytes | None: 

4198 try: 

4199 ( 

4200 key_version, 

4201 timestamp_bytes, 

4202 name_field, 

4203 value_field, 

4204 passed_sig, 

4205 ) = _decode_fields_v2(value) 

4206 except ValueError: 

4207 return None 

4208 signed_string = value[: -len(passed_sig)] 

4209 

4210 if isinstance(secret, dict): 

4211 try: 

4212 secret = secret[key_version] 

4213 except KeyError: 

4214 return None 

4215 

4216 expected_sig = _create_signature_v2(secret, signed_string) 

4217 if not hmac.compare_digest(passed_sig, expected_sig): 

4218 return None 

4219 if name_field != utf8(name): 

4220 return None 

4221 try: 

4222 timestamp = int(timestamp_bytes) 

4223 except ValueError: 

4224 return None 

4225 if timestamp < clock() - max_age_days * 86400: 

4226 # The signature has expired. 

4227 return None 

4228 try: 

4229 return base64.b64decode(value_field) 

4230 except Exception: 

4231 return None 

4232 

4233 

4234def get_signature_key_version(value: str | bytes) -> int | None: 

4235 value = utf8(value) 

4236 version = _get_version(value) 

4237 if version < 2: 

4238 return None 

4239 try: 

4240 key_version, _, _, _, _ = _decode_fields_v2(value) 

4241 except ValueError: 

4242 return None 

4243 

4244 return key_version 

4245 

4246 

4247def _create_signature_v1(secret: str | bytes, *parts: str | bytes) -> bytes: 

4248 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 

4249 for part in parts: 

4250 hash.update(utf8(part)) 

4251 return utf8(hash.hexdigest()) 

4252 

4253 

4254def _create_signature_v2(secret: str | bytes, s: bytes) -> bytes: 

4255 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 

4256 hash.update(utf8(s)) 

4257 return utf8(hash.hexdigest()) 

4258 

4259 

4260def is_absolute(path: str) -> bool: 

4261 return any(path.startswith(x) for x in ["/", "http:", "https:"]) 

4262 

4263 

4264def _normalize_url_like_browser(url: str) -> str: 

4265 """Approximates how a browser cleans up a URL before parsing it. 

4266 

4267 Browsers follow the WHATWG URL spec, which strips leading and trailing 

4268 control characters and spaces, removes tabs and newlines anywhere in the 

4269 URL, and (for http and https) treats backslashes like forward slashes. Checks 

4270 on redirect URLs built from untrusted input must account for this. 

4271 """ 

4272 url = url.strip("".join(chr(i) for i in range(0x21))) 

4273 url = url.replace("\t", "").replace("\n", "").replace("\r", "") 

4274 return url.replace("\\", "/") 

4275 

4276 

4277def _is_protocol_relative(url: str) -> bool: 

4278 """Returns True if a browser would treat ``url`` as a protocol-relative URL. 

4279 

4280 A protocol-relative URL (``//example.com/path``) names a host, so redirecting 

4281 to one that was built from untrusted input is an open redirect. 

4282 """ 

4283 return _normalize_url_like_browser(url).startswith("//") 

4284 

4285 

4286def _scheme_and_netloc(url: str) -> tuple[str, str]: 

4287 """Returns the scheme and netloc a browser would see in ``url``. 

4288 

4289 The netloc includes the user info and port as well as the host. 

4290 

4291 Both are empty for a relative URL. Raises `ValueError` for some malformed 

4292 URLs. 

4293 """ 

4294 url = _normalize_url_like_browser(url) 

4295 # urlsplit follows RFC 3986, in which "///example.com" has an empty netloc, 

4296 # but browsers resolving it against an http or https URL skip any number of 

4297 # slashes before the host. Collapse them so urlsplit sees the same host. 

4298 url = re.sub(r"^//+", "//", url) 

4299 parts = urllib.parse.urlsplit(url) 

4300 return parts.scheme.lower(), parts.netloc.lower()