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 tornado.ioloop 26 import tornado.web 27 28 class MainHandler(tornado.web.RequestHandler): 29 def get(self): 30 self.write("Hello, world") 31 32 if __name__ == "__main__": 33 application = tornado.web.Application([ 34 (r"/", MainHandler), 35 ]) 36 application.listen(8888) 37 tornado.ioloop.IOLoop.current().start() 38 39.. testoutput:: 40 :hide: 41 42 43See the :doc:`guide` for additional information. 44 45Thread-safety notes 46------------------- 47 48In general, methods on `RequestHandler` and elsewhere in Tornado are 49not thread-safe. In particular, methods such as 50`~RequestHandler.write()`, `~RequestHandler.finish()`, and 51`~RequestHandler.flush()` must only be called from the main thread. If 52you use multiple threads it is important to use `.IOLoop.add_callback` 53to transfer control back to the main thread before finishing the 54request, or to limit your use of other threads to 55`.IOLoop.run_in_executor` and ensure that your callbacks running in 56the executor do not refer to Tornado objects. 57 58""" 59 60import base64 61import binascii 62import datetime 63import email.utils 64import functools 65import gzip 66import hashlib 67import hmac 68import http.cookies 69from inspect import isclass 70from io import BytesIO 71import mimetypes 72import numbers 73import os.path 74import re 75import sys 76import threading 77import time 78import tornado 79import traceback 80import types 81import urllib.parse 82from urllib.parse import urlencode 83 84from tornado.concurrent import Future, future_set_result_unless_cancelled 85from tornado import escape 86from tornado import gen 87from tornado.httpserver import HTTPServer 88from tornado import httputil 89from tornado import iostream 90import tornado.locale 91from tornado import locale 92from tornado.log import access_log, app_log, gen_log 93from tornado import template 94from tornado.escape import utf8, _unicode 95from tornado.routing import ( 96 AnyMatches, 97 DefaultHostMatches, 98 HostMatches, 99 ReversibleRouter, 100 Rule, 101 ReversibleRuleRouter, 102 URLSpec, 103 _RuleList, 104) 105from tornado.util import ObjectDict, unicode_type, _websocket_mask 106 107url = URLSpec 108 109from typing import ( 110 Dict, 111 Any, 112 Union, 113 Optional, 114 Awaitable, 115 Tuple, 116 List, 117 Callable, 118 Iterable, 119 Generator, 120 Type, 121 cast, 122 overload, 123) 124from types import TracebackType 125import typing 126 127if typing.TYPE_CHECKING: 128 from typing import Set # noqa: F401 129 130 131# The following types are accepted by RequestHandler.set_header 132# and related methods. 133_HeaderTypes = Union[bytes, unicode_type, int, numbers.Integral, datetime.datetime] 134 135_CookieSecretTypes = Union[str, bytes, Dict[int, str], Dict[int, bytes]] 136 137 138MIN_SUPPORTED_SIGNED_VALUE_VERSION = 1 139"""The oldest signed value version supported by this version of Tornado. 140 141Signed values older than this version cannot be decoded. 142 143.. versionadded:: 3.2.1 144""" 145 146MAX_SUPPORTED_SIGNED_VALUE_VERSION = 2 147"""The newest signed value version supported by this version of Tornado. 148 149Signed values newer than this version cannot be decoded. 150 151.. versionadded:: 3.2.1 152""" 153 154DEFAULT_SIGNED_VALUE_VERSION = 2 155"""The signed value version produced by `.RequestHandler.create_signed_value`. 156 157May be overridden by passing a ``version`` keyword argument. 158 159.. versionadded:: 3.2.1 160""" 161 162DEFAULT_SIGNED_VALUE_MIN_VERSION = 1 163"""The oldest signed value accepted by `.RequestHandler.get_secure_cookie`. 164 165May be overridden by passing a ``min_version`` keyword argument. 166 167.. versionadded:: 3.2.1 168""" 169 170 171class _ArgDefaultMarker: 172 pass 173 174 175_ARG_DEFAULT = _ArgDefaultMarker() 176 177 178class RequestHandler(object): 179 """Base class for HTTP request handlers. 180 181 Subclasses must define at least one of the methods defined in the 182 "Entry points" section below. 183 184 Applications should not construct `RequestHandler` objects 185 directly and subclasses should not override ``__init__`` (override 186 `~RequestHandler.initialize` instead). 187 188 """ 189 190 SUPPORTED_METHODS = ("GET", "HEAD", "POST", "DELETE", "PATCH", "PUT", "OPTIONS") 191 192 _template_loaders = {} # type: Dict[str, template.BaseLoader] 193 _template_loader_lock = threading.Lock() 194 _remove_control_chars_regex = re.compile(r"[\x00-\x08\x0e-\x1f]") 195 196 _stream_request_body = False 197 198 # Will be set in _execute. 199 _transforms = None # type: List[OutputTransform] 200 path_args = None # type: List[str] 201 path_kwargs = None # type: Dict[str, str] 202 203 def __init__( 204 self, 205 application: "Application", 206 request: httputil.HTTPServerRequest, 207 **kwargs: Any 208 ) -> None: 209 super().__init__() 210 211 self.application = application 212 self.request = request 213 self._headers_written = False 214 self._finished = False 215 self._auto_finish = True 216 self._prepared_future = None 217 self.ui = ObjectDict( 218 (n, self._ui_method(m)) for n, m in application.ui_methods.items() 219 ) 220 # UIModules are available as both `modules` and `_tt_modules` in the 221 # template namespace. Historically only `modules` was available 222 # but could be clobbered by user additions to the namespace. 223 # The template {% module %} directive looks in `_tt_modules` to avoid 224 # possible conflicts. 225 self.ui["_tt_modules"] = _UIModuleNamespace(self, application.ui_modules) 226 self.ui["modules"] = self.ui["_tt_modules"] 227 self.clear() 228 assert self.request.connection is not None 229 # TODO: need to add set_close_callback to HTTPConnection interface 230 self.request.connection.set_close_callback( # type: ignore 231 self.on_connection_close 232 ) 233 self.initialize(**kwargs) # type: ignore 234 235 def _initialize(self) -> None: 236 pass 237 238 initialize = _initialize # type: Callable[..., None] 239 """Hook for subclass initialization. Called for each request. 240 241 A dictionary passed as the third argument of a ``URLSpec`` will be 242 supplied as keyword arguments to ``initialize()``. 243 244 Example:: 245 246 class ProfileHandler(RequestHandler): 247 def initialize(self, database): 248 self.database = database 249 250 def get(self, username): 251 ... 252 253 app = Application([ 254 (r'/user/(.*)', ProfileHandler, dict(database=database)), 255 ]) 256 """ 257 258 @property 259 def settings(self) -> Dict[str, Any]: 260 """An alias for `self.application.settings <Application.settings>`.""" 261 return self.application.settings 262 263 def _unimplemented_method(self, *args: str, **kwargs: str) -> None: 264 raise HTTPError(405) 265 266 head = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 267 get = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 268 post = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 269 delete = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 270 patch = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 271 put = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 272 options = _unimplemented_method # type: Callable[..., Optional[Awaitable[None]]] 273 274 def prepare(self) -> Optional[Awaitable[None]]: 275 """Called at the beginning of a request before `get`/`post`/etc. 276 277 Override this method to perform common initialization regardless 278 of the request method. 279 280 Asynchronous support: Use ``async def`` or decorate this method with 281 `.gen.coroutine` to make it asynchronous. 282 If this method returns an ``Awaitable`` execution will not proceed 283 until the ``Awaitable`` is done. 284 285 .. versionadded:: 3.1 286 Asynchronous support. 287 """ 288 pass 289 290 def on_finish(self) -> None: 291 """Called after the end of a request. 292 293 Override this method to perform cleanup, logging, etc. 294 This method is a counterpart to `prepare`. ``on_finish`` may 295 not produce any output, as it is called after the response 296 has been sent to the client. 297 """ 298 pass 299 300 def on_connection_close(self) -> None: 301 """Called in async handlers if the client closed the connection. 302 303 Override this to clean up resources associated with 304 long-lived connections. Note that this method is called only if 305 the connection was closed during asynchronous processing; if you 306 need to do cleanup after every request override `on_finish` 307 instead. 308 309 Proxies may keep a connection open for a time (perhaps 310 indefinitely) after the client has gone away, so this method 311 may not be called promptly after the end user closes their 312 connection. 313 """ 314 if _has_stream_request_body(self.__class__): 315 if not self.request._body_future.done(): 316 self.request._body_future.set_exception(iostream.StreamClosedError()) 317 self.request._body_future.exception() 318 319 def clear(self) -> None: 320 """Resets all headers and content for this response.""" 321 self._headers = httputil.HTTPHeaders( 322 { 323 "Server": "TornadoServer/%s" % tornado.version, 324 "Content-Type": "text/html; charset=UTF-8", 325 "Date": httputil.format_timestamp(time.time()), 326 } 327 ) 328 self.set_default_headers() 329 self._write_buffer = [] # type: List[bytes] 330 self._status_code = 200 331 self._reason = httputil.responses[200] 332 333 def set_default_headers(self) -> None: 334 """Override this to set HTTP headers at the beginning of the request. 335 336 For example, this is the place to set a custom ``Server`` header. 337 Note that setting such headers in the normal flow of request 338 processing may not do what you want, since headers may be reset 339 during error handling. 340 """ 341 pass 342 343 def set_status(self, status_code: int, reason: Optional[str] = None) -> None: 344 """Sets the status code for our response. 345 346 :arg int status_code: Response status code. 347 :arg str reason: Human-readable reason phrase describing the status 348 code. If ``None``, it will be filled in from 349 `http.client.responses` or "Unknown". 350 351 .. versionchanged:: 5.0 352 353 No longer validates that the response code is in 354 `http.client.responses`. 355 """ 356 self._status_code = status_code 357 if reason is not None: 358 self._reason = escape.native_str(reason) 359 else: 360 self._reason = httputil.responses.get(status_code, "Unknown") 361 362 def get_status(self) -> int: 363 """Returns the status code for our response.""" 364 return self._status_code 365 366 def set_header(self, name: str, value: _HeaderTypes) -> None: 367 """Sets the given response header name and value. 368 369 All header values are converted to strings (`datetime` objects 370 are formatted according to the HTTP specification for the 371 ``Date`` header). 372 373 """ 374 self._headers[name] = self._convert_header_value(value) 375 376 def add_header(self, name: str, value: _HeaderTypes) -> None: 377 """Adds the given response header and value. 378 379 Unlike `set_header`, `add_header` may be called multiple times 380 to return multiple values for the same header. 381 """ 382 self._headers.add(name, self._convert_header_value(value)) 383 384 def clear_header(self, name: str) -> None: 385 """Clears an outgoing header, undoing a previous `set_header` call. 386 387 Note that this method does not apply to multi-valued headers 388 set by `add_header`. 389 """ 390 if name in self._headers: 391 del self._headers[name] 392 393 _INVALID_HEADER_CHAR_RE = re.compile(r"[\x00-\x1f]") 394 395 def _convert_header_value(self, value: _HeaderTypes) -> str: 396 # Convert the input value to a str. This type check is a bit 397 # subtle: The bytes case only executes on python 3, and the 398 # unicode case only executes on python 2, because the other 399 # cases are covered by the first match for str. 400 if isinstance(value, str): 401 retval = value 402 elif isinstance(value, bytes): # py3 403 # Non-ascii characters in headers are not well supported, 404 # but if you pass bytes, use latin1 so they pass through as-is. 405 retval = value.decode("latin1") 406 elif isinstance(value, unicode_type): # py2 407 # TODO: This is inconsistent with the use of latin1 above, 408 # but it's been that way for a long time. Should it change? 409 retval = escape.utf8(value) 410 elif isinstance(value, numbers.Integral): 411 # return immediately since we know the converted value will be safe 412 return str(value) 413 elif isinstance(value, datetime.datetime): 414 return httputil.format_timestamp(value) 415 else: 416 raise TypeError("Unsupported header value %r" % value) 417 # If \n is allowed into the header, it is possible to inject 418 # additional headers or split the request. 419 if RequestHandler._INVALID_HEADER_CHAR_RE.search(retval): 420 raise ValueError("Unsafe header value %r", retval) 421 return retval 422 423 @overload 424 def get_argument(self, name: str, default: str, strip: bool = True) -> str: 425 pass 426 427 @overload 428 def get_argument( # noqa: F811 429 self, name: str, default: _ArgDefaultMarker = _ARG_DEFAULT, strip: bool = True 430 ) -> str: 431 pass 432 433 @overload 434 def get_argument( # noqa: F811 435 self, name: str, default: None, strip: bool = True 436 ) -> Optional[str]: 437 pass 438 439 def get_argument( # noqa: F811 440 self, 441 name: str, 442 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 443 strip: bool = True, 444 ) -> Optional[str]: 445 """Returns the value of the argument with the given name. 446 447 If default is not provided, the argument is considered to be 448 required, and we raise a `MissingArgumentError` if it is missing. 449 450 If the argument appears in the request more than once, we return the 451 last value. 452 453 This method searches both the query and body arguments. 454 """ 455 return self._get_argument(name, default, self.request.arguments, strip) 456 457 def get_arguments(self, name: str, strip: bool = True) -> List[str]: 458 """Returns a list of the arguments with the given name. 459 460 If the argument is not present, returns an empty list. 461 462 This method searches both the query and body arguments. 463 """ 464 465 # Make sure `get_arguments` isn't accidentally being called with a 466 # positional argument that's assumed to be a default (like in 467 # `get_argument`.) 468 assert isinstance(strip, bool) 469 470 return self._get_arguments(name, self.request.arguments, strip) 471 472 def get_body_argument( 473 self, 474 name: str, 475 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 476 strip: bool = True, 477 ) -> Optional[str]: 478 """Returns the value of the argument with the given name 479 from the request body. 480 481 If default is not provided, the argument is considered to be 482 required, and we raise a `MissingArgumentError` if it is missing. 483 484 If the argument appears in the url more than once, we return the 485 last value. 486 487 .. versionadded:: 3.2 488 """ 489 return self._get_argument(name, default, self.request.body_arguments, strip) 490 491 def get_body_arguments(self, name: str, strip: bool = True) -> List[str]: 492 """Returns a list of the body arguments with the given name. 493 494 If the argument is not present, returns an empty list. 495 496 .. versionadded:: 3.2 497 """ 498 return self._get_arguments(name, self.request.body_arguments, strip) 499 500 def get_query_argument( 501 self, 502 name: str, 503 default: Union[None, str, _ArgDefaultMarker] = _ARG_DEFAULT, 504 strip: bool = True, 505 ) -> Optional[str]: 506 """Returns the value of the argument with the given name 507 from the request query string. 508 509 If default is not provided, the argument is considered to be 510 required, and we raise a `MissingArgumentError` if it is missing. 511 512 If the argument appears in the url more than once, we return the 513 last value. 514 515 .. versionadded:: 3.2 516 """ 517 return self._get_argument(name, default, self.request.query_arguments, strip) 518 519 def get_query_arguments(self, name: str, strip: bool = True) -> List[str]: 520 """Returns a list of the query arguments with the given name. 521 522 If the argument is not present, returns an empty list. 523 524 .. versionadded:: 3.2 525 """ 526 return self._get_arguments(name, self.request.query_arguments, strip) 527 528 def _get_argument( 529 self, 530 name: str, 531 default: Union[None, str, _ArgDefaultMarker], 532 source: Dict[str, List[bytes]], 533 strip: bool = True, 534 ) -> Optional[str]: 535 args = self._get_arguments(name, source, strip=strip) 536 if not args: 537 if isinstance(default, _ArgDefaultMarker): 538 raise MissingArgumentError(name) 539 return default 540 return args[-1] 541 542 def _get_arguments( 543 self, name: str, source: Dict[str, List[bytes]], strip: bool = True 544 ) -> List[str]: 545 values = [] 546 for v in source.get(name, []): 547 s = self.decode_argument(v, name=name) 548 if isinstance(s, unicode_type): 549 # Get rid of any weird control chars (unless decoding gave 550 # us bytes, in which case leave it alone) 551 s = RequestHandler._remove_control_chars_regex.sub(" ", s) 552 if strip: 553 s = s.strip() 554 values.append(s) 555 return values 556 557 def decode_argument(self, value: bytes, name: Optional[str] = None) -> str: 558 """Decodes an argument from the request. 559 560 The argument has been percent-decoded and is now a byte string. 561 By default, this method decodes the argument as utf-8 and returns 562 a unicode string, but this may be overridden in subclasses. 563 564 This method is used as a filter for both `get_argument()` and for 565 values extracted from the url and passed to `get()`/`post()`/etc. 566 567 The name of the argument is provided if known, but may be None 568 (e.g. for unnamed groups in the url regex). 569 """ 570 try: 571 return _unicode(value) 572 except UnicodeDecodeError: 573 raise HTTPError( 574 400, "Invalid unicode in %s: %r" % (name or "url", value[:40]) 575 ) 576 577 @property 578 def cookies(self) -> Dict[str, http.cookies.Morsel]: 579 """An alias for 580 `self.request.cookies <.httputil.HTTPServerRequest.cookies>`.""" 581 return self.request.cookies 582 583 def get_cookie(self, name: str, default: Optional[str] = None) -> Optional[str]: 584 """Returns the value of the request cookie with the given name. 585 586 If the named cookie is not present, returns ``default``. 587 588 This method only returns cookies that were present in the request. 589 It does not see the outgoing cookies set by `set_cookie` in this 590 handler. 591 """ 592 if self.request.cookies is not None and name in self.request.cookies: 593 return self.request.cookies[name].value 594 return default 595 596 def set_cookie( 597 self, 598 name: str, 599 value: Union[str, bytes], 600 domain: Optional[str] = None, 601 expires: Optional[Union[float, Tuple, datetime.datetime]] = None, 602 path: str = "/", 603 expires_days: Optional[float] = None, 604 **kwargs: Any 605 ) -> None: 606 """Sets an outgoing cookie name/value with the given options. 607 608 Newly-set cookies are not immediately visible via `get_cookie`; 609 they are not present until the next request. 610 611 expires may be a numeric timestamp as returned by `time.time`, 612 a time tuple as returned by `time.gmtime`, or a 613 `datetime.datetime` object. 614 615 Additional keyword arguments are set on the cookies.Morsel 616 directly. 617 See https://docs.python.org/3/library/http.cookies.html#http.cookies.Morsel 618 for available attributes. 619 """ 620 # The cookie library only accepts type str, in both python 2 and 3 621 name = escape.native_str(name) 622 value = escape.native_str(value) 623 if re.search(r"[\x00-\x20]", name + value): 624 # Don't let us accidentally inject bad stuff 625 raise ValueError("Invalid cookie %r: %r" % (name, value)) 626 if not hasattr(self, "_new_cookie"): 627 self._new_cookie = ( 628 http.cookies.SimpleCookie() 629 ) # type: http.cookies.SimpleCookie 630 if name in self._new_cookie: 631 del self._new_cookie[name] 632 self._new_cookie[name] = value 633 morsel = self._new_cookie[name] 634 if domain: 635 morsel["domain"] = domain 636 if expires_days is not None and not expires: 637 expires = datetime.datetime.utcnow() + datetime.timedelta(days=expires_days) 638 if expires: 639 morsel["expires"] = httputil.format_timestamp(expires) 640 if path: 641 morsel["path"] = path 642 for k, v in kwargs.items(): 643 if k == "max_age": 644 k = "max-age" 645 646 # skip falsy values for httponly and secure flags because 647 # SimpleCookie sets them regardless 648 if k in ["httponly", "secure"] and not v: 649 continue 650 651 morsel[k] = v 652 653 def clear_cookie( 654 self, name: str, path: str = "/", domain: Optional[str] = None 655 ) -> None: 656 """Deletes the cookie with the given name. 657 658 Due to limitations of the cookie protocol, you must pass the same 659 path and domain to clear a cookie as were used when that cookie 660 was set (but there is no way to find out on the server side 661 which values were used for a given cookie). 662 663 Similar to `set_cookie`, the effect of this method will not be 664 seen until the following request. 665 """ 666 expires = datetime.datetime.utcnow() - datetime.timedelta(days=365) 667 self.set_cookie(name, value="", path=path, expires=expires, domain=domain) 668 669 def clear_all_cookies(self, path: str = "/", domain: Optional[str] = None) -> None: 670 """Deletes all the cookies the user sent with this request. 671 672 See `clear_cookie` for more information on the path and domain 673 parameters. 674 675 Similar to `set_cookie`, the effect of this method will not be 676 seen until the following request. 677 678 .. versionchanged:: 3.2 679 680 Added the ``path`` and ``domain`` parameters. 681 """ 682 for name in self.request.cookies: 683 self.clear_cookie(name, path=path, domain=domain) 684 685 def set_secure_cookie( 686 self, 687 name: str, 688 value: Union[str, bytes], 689 expires_days: Optional[float] = 30, 690 version: Optional[int] = None, 691 **kwargs: Any 692 ) -> None: 693 """Signs and timestamps a cookie so it cannot be forged. 694 695 You must specify the ``cookie_secret`` setting in your Application 696 to use this method. It should be a long, random sequence of bytes 697 to be used as the HMAC secret for the signature. 698 699 To read a cookie set with this method, use `get_secure_cookie()`. 700 701 Note that the ``expires_days`` parameter sets the lifetime of the 702 cookie in the browser, but is independent of the ``max_age_days`` 703 parameter to `get_secure_cookie`. 704 A value of None limits the lifetime to the current browser session. 705 706 Secure cookies may contain arbitrary byte values, not just unicode 707 strings (unlike regular cookies) 708 709 Similar to `set_cookie`, the effect of this method will not be 710 seen until the following request. 711 712 .. versionchanged:: 3.2.1 713 714 Added the ``version`` argument. Introduced cookie version 2 715 and made it the default. 716 """ 717 self.set_cookie( 718 name, 719 self.create_signed_value(name, value, version=version), 720 expires_days=expires_days, 721 **kwargs 722 ) 723 724 def create_signed_value( 725 self, name: str, value: Union[str, bytes], version: Optional[int] = None 726 ) -> bytes: 727 """Signs and timestamps a string so it cannot be forged. 728 729 Normally used via set_secure_cookie, but provided as a separate 730 method for non-cookie uses. To decode a value not stored 731 as a cookie use the optional value argument to get_secure_cookie. 732 733 .. versionchanged:: 3.2.1 734 735 Added the ``version`` argument. Introduced cookie version 2 736 and made it the default. 737 """ 738 self.require_setting("cookie_secret", "secure cookies") 739 secret = self.application.settings["cookie_secret"] 740 key_version = None 741 if isinstance(secret, dict): 742 if self.application.settings.get("key_version") is None: 743 raise Exception("key_version setting must be used for secret_key dicts") 744 key_version = self.application.settings["key_version"] 745 746 return create_signed_value( 747 secret, name, value, version=version, key_version=key_version 748 ) 749 750 def get_secure_cookie( 751 self, 752 name: str, 753 value: Optional[str] = None, 754 max_age_days: float = 31, 755 min_version: Optional[int] = None, 756 ) -> Optional[bytes]: 757 """Returns the given signed cookie if it validates, or None. 758 759 The decoded cookie value is returned as a byte string (unlike 760 `get_cookie`). 761 762 Similar to `get_cookie`, this method only returns cookies that 763 were present in the request. It does not see outgoing cookies set by 764 `set_secure_cookie` in this handler. 765 766 .. versionchanged:: 3.2.1 767 768 Added the ``min_version`` argument. Introduced cookie version 2; 769 both versions 1 and 2 are accepted by default. 770 """ 771 self.require_setting("cookie_secret", "secure cookies") 772 if value is None: 773 value = self.get_cookie(name) 774 return decode_signed_value( 775 self.application.settings["cookie_secret"], 776 name, 777 value, 778 max_age_days=max_age_days, 779 min_version=min_version, 780 ) 781 782 def get_secure_cookie_key_version( 783 self, name: str, value: Optional[str] = None 784 ) -> Optional[int]: 785 """Returns the signing key version of the secure cookie. 786 787 The version is returned as int. 788 """ 789 self.require_setting("cookie_secret", "secure cookies") 790 if value is None: 791 value = self.get_cookie(name) 792 if value is None: 793 return None 794 return get_signature_key_version(value) 795 796 def redirect( 797 self, url: str, permanent: bool = False, status: Optional[int] = None 798 ) -> None: 799 """Sends a redirect to the given (optionally relative) URL. 800 801 If the ``status`` argument is specified, that value is used as the 802 HTTP status code; otherwise either 301 (permanent) or 302 803 (temporary) is chosen based on the ``permanent`` argument. 804 The default is 302 (temporary). 805 """ 806 if self._headers_written: 807 raise Exception("Cannot redirect after headers have been written") 808 if status is None: 809 status = 301 if permanent else 302 810 else: 811 assert isinstance(status, int) and 300 <= status <= 399 812 self.set_status(status) 813 self.set_header("Location", utf8(url)) 814 self.finish() 815 816 def write(self, chunk: Union[str, bytes, dict]) -> None: 817 """Writes the given chunk to the output buffer. 818 819 To write the output to the network, use the `flush()` method below. 820 821 If the given chunk is a dictionary, we write it as JSON and set 822 the Content-Type of the response to be ``application/json``. 823 (if you want to send JSON as a different ``Content-Type``, call 824 ``set_header`` *after* calling ``write()``). 825 826 Note that lists are not converted to JSON because of a potential 827 cross-site security vulnerability. All JSON output should be 828 wrapped in a dictionary. More details at 829 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and 830 https://github.com/facebook/tornado/issues/1009 831 """ 832 if self._finished: 833 raise RuntimeError("Cannot write() after finish()") 834 if not isinstance(chunk, (bytes, unicode_type, dict)): 835 message = "write() only accepts bytes, unicode, and dict objects" 836 if isinstance(chunk, list): 837 message += ( 838 ". Lists not accepted for security reasons; see " 839 + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write" # noqa: E501 840 ) 841 raise TypeError(message) 842 if isinstance(chunk, dict): 843 chunk = escape.json_encode(chunk) 844 self.set_header("Content-Type", "application/json; charset=UTF-8") 845 chunk = utf8(chunk) 846 self._write_buffer.append(chunk) 847 848 def render(self, template_name: str, **kwargs: Any) -> "Future[None]": 849 """Renders the template with the given arguments as the response. 850 851 ``render()`` calls ``finish()``, so no other output methods can be called 852 after it. 853 854 Returns a `.Future` with the same semantics as the one returned by `finish`. 855 Awaiting this `.Future` is optional. 856 857 .. versionchanged:: 5.1 858 859 Now returns a `.Future` instead of ``None``. 860 """ 861 if self._finished: 862 raise RuntimeError("Cannot render() after finish()") 863 html = self.render_string(template_name, **kwargs) 864 865 # Insert the additional JS and CSS added by the modules on the page 866 js_embed = [] 867 js_files = [] 868 css_embed = [] 869 css_files = [] 870 html_heads = [] 871 html_bodies = [] 872 for module in getattr(self, "_active_modules", {}).values(): 873 embed_part = module.embedded_javascript() 874 if embed_part: 875 js_embed.append(utf8(embed_part)) 876 file_part = module.javascript_files() 877 if file_part: 878 if isinstance(file_part, (unicode_type, bytes)): 879 js_files.append(_unicode(file_part)) 880 else: 881 js_files.extend(file_part) 882 embed_part = module.embedded_css() 883 if embed_part: 884 css_embed.append(utf8(embed_part)) 885 file_part = module.css_files() 886 if file_part: 887 if isinstance(file_part, (unicode_type, bytes)): 888 css_files.append(_unicode(file_part)) 889 else: 890 css_files.extend(file_part) 891 head_part = module.html_head() 892 if head_part: 893 html_heads.append(utf8(head_part)) 894 body_part = module.html_body() 895 if body_part: 896 html_bodies.append(utf8(body_part)) 897 898 if js_files: 899 # Maintain order of JavaScript files given by modules 900 js = self.render_linked_js(js_files) 901 sloc = html.rindex(b"</body>") 902 html = html[:sloc] + utf8(js) + b"\n" + html[sloc:] 903 if js_embed: 904 js_bytes = self.render_embed_js(js_embed) 905 sloc = html.rindex(b"</body>") 906 html = html[:sloc] + js_bytes + b"\n" + html[sloc:] 907 if css_files: 908 css = self.render_linked_css(css_files) 909 hloc = html.index(b"</head>") 910 html = html[:hloc] + utf8(css) + b"\n" + html[hloc:] 911 if css_embed: 912 css_bytes = self.render_embed_css(css_embed) 913 hloc = html.index(b"</head>") 914 html = html[:hloc] + css_bytes + b"\n" + html[hloc:] 915 if html_heads: 916 hloc = html.index(b"</head>") 917 html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:] 918 if html_bodies: 919 hloc = html.index(b"</body>") 920 html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:] 921 return self.finish(html) 922 923 def render_linked_js(self, js_files: Iterable[str]) -> str: 924 """Default method used to render the final js links for the 925 rendered webpage. 926 927 Override this method in a sub-classed controller to change the output. 928 """ 929 paths = [] 930 unique_paths = set() # type: Set[str] 931 932 for path in js_files: 933 if not is_absolute(path): 934 path = self.static_url(path) 935 if path not in unique_paths: 936 paths.append(path) 937 unique_paths.add(path) 938 939 return "".join( 940 '<script src="' 941 + escape.xhtml_escape(p) 942 + '" type="text/javascript"></script>' 943 for p in paths 944 ) 945 946 def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes: 947 """Default method used to render the final embedded js for the 948 rendered webpage. 949 950 Override this method in a sub-classed controller to change the output. 951 """ 952 return ( 953 b'<script type="text/javascript">\n//<![CDATA[\n' 954 + b"\n".join(js_embed) 955 + b"\n//]]>\n</script>" 956 ) 957 958 def render_linked_css(self, css_files: Iterable[str]) -> str: 959 """Default method used to render the final css links for the 960 rendered webpage. 961 962 Override this method in a sub-classed controller to change the output. 963 """ 964 paths = [] 965 unique_paths = set() # type: Set[str] 966 967 for path in css_files: 968 if not is_absolute(path): 969 path = self.static_url(path) 970 if path not in unique_paths: 971 paths.append(path) 972 unique_paths.add(path) 973 974 return "".join( 975 '<link href="' + escape.xhtml_escape(p) + '" ' 976 'type="text/css" rel="stylesheet"/>' 977 for p in paths 978 ) 979 980 def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes: 981 """Default method used to render the final embedded css for the 982 rendered webpage. 983 984 Override this method in a sub-classed controller to change the output. 985 """ 986 return b'<style type="text/css">\n' + b"\n".join(css_embed) + b"\n</style>" 987 988 def render_string(self, template_name: str, **kwargs: Any) -> bytes: 989 """Generate the given template with the given arguments. 990 991 We return the generated byte string (in utf8). To generate and 992 write a template as a response, use render() above. 993 """ 994 # If no template_path is specified, use the path of the calling file 995 template_path = self.get_template_path() 996 if not template_path: 997 frame = sys._getframe(0) 998 web_file = frame.f_code.co_filename 999 while frame.f_code.co_filename == web_file: 1000 frame = frame.f_back 1001 assert frame.f_code.co_filename is not None 1002 template_path = os.path.dirname(frame.f_code.co_filename) 1003 with RequestHandler._template_loader_lock: 1004 if template_path not in RequestHandler._template_loaders: 1005 loader = self.create_template_loader(template_path) 1006 RequestHandler._template_loaders[template_path] = loader 1007 else: 1008 loader = RequestHandler._template_loaders[template_path] 1009 t = loader.load(template_name) 1010 namespace = self.get_template_namespace() 1011 namespace.update(kwargs) 1012 return t.generate(**namespace) 1013 1014 def get_template_namespace(self) -> Dict[str, Any]: 1015 """Returns a dictionary to be used as the default template namespace. 1016 1017 May be overridden by subclasses to add or modify values. 1018 1019 The results of this method will be combined with additional 1020 defaults in the `tornado.template` module and keyword arguments 1021 to `render` or `render_string`. 1022 """ 1023 namespace = dict( 1024 handler=self, 1025 request=self.request, 1026 current_user=self.current_user, 1027 locale=self.locale, 1028 _=self.locale.translate, 1029 pgettext=self.locale.pgettext, 1030 static_url=self.static_url, 1031 xsrf_form_html=self.xsrf_form_html, 1032 reverse_url=self.reverse_url, 1033 ) 1034 namespace.update(self.ui) 1035 return namespace 1036 1037 def create_template_loader(self, template_path: str) -> template.BaseLoader: 1038 """Returns a new template loader for the given path. 1039 1040 May be overridden by subclasses. By default returns a 1041 directory-based loader on the given path, using the 1042 ``autoescape`` and ``template_whitespace`` application 1043 settings. If a ``template_loader`` application setting is 1044 supplied, uses that instead. 1045 """ 1046 settings = self.application.settings 1047 if "template_loader" in settings: 1048 return settings["template_loader"] 1049 kwargs = {} 1050 if "autoescape" in settings: 1051 # autoescape=None means "no escaping", so we have to be sure 1052 # to only pass this kwarg if the user asked for it. 1053 kwargs["autoescape"] = settings["autoescape"] 1054 if "template_whitespace" in settings: 1055 kwargs["whitespace"] = settings["template_whitespace"] 1056 return template.Loader(template_path, **kwargs) 1057 1058 def flush(self, include_footers: bool = False) -> "Future[None]": 1059 """Flushes the current output buffer to the network. 1060 1061 .. versionchanged:: 4.0 1062 Now returns a `.Future` if no callback is given. 1063 1064 .. versionchanged:: 6.0 1065 1066 The ``callback`` argument was removed. 1067 """ 1068 assert self.request.connection is not None 1069 chunk = b"".join(self._write_buffer) 1070 self._write_buffer = [] 1071 if not self._headers_written: 1072 self._headers_written = True 1073 for transform in self._transforms: 1074 assert chunk is not None 1075 ( 1076 self._status_code, 1077 self._headers, 1078 chunk, 1079 ) = transform.transform_first_chunk( 1080 self._status_code, self._headers, chunk, include_footers 1081 ) 1082 # Ignore the chunk and only write the headers for HEAD requests 1083 if self.request.method == "HEAD": 1084 chunk = b"" 1085 1086 # Finalize the cookie headers (which have been stored in a side 1087 # object so an outgoing cookie could be overwritten before it 1088 # is sent). 1089 if hasattr(self, "_new_cookie"): 1090 for cookie in self._new_cookie.values(): 1091 self.add_header("Set-Cookie", cookie.OutputString(None)) 1092 1093 start_line = httputil.ResponseStartLine("", self._status_code, self._reason) 1094 return self.request.connection.write_headers( 1095 start_line, self._headers, chunk 1096 ) 1097 else: 1098 for transform in self._transforms: 1099 chunk = transform.transform_chunk(chunk, include_footers) 1100 # Ignore the chunk and only write the headers for HEAD requests 1101 if self.request.method != "HEAD": 1102 return self.request.connection.write(chunk) 1103 else: 1104 future = Future() # type: Future[None] 1105 future.set_result(None) 1106 return future 1107 1108 def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]": 1109 """Finishes this response, ending the HTTP request. 1110 1111 Passing a ``chunk`` to ``finish()`` is equivalent to passing that 1112 chunk to ``write()`` and then calling ``finish()`` with no arguments. 1113 1114 Returns a `.Future` which may optionally be awaited to track the sending 1115 of the response to the client. This `.Future` resolves when all the response 1116 data has been sent, and raises an error if the connection is closed before all 1117 data can be sent. 1118 1119 .. versionchanged:: 5.1 1120 1121 Now returns a `.Future` instead of ``None``. 1122 """ 1123 if self._finished: 1124 raise RuntimeError("finish() called twice") 1125 1126 if chunk is not None: 1127 self.write(chunk) 1128 1129 # Automatically support ETags and add the Content-Length header if 1130 # we have not flushed any content yet. 1131 if not self._headers_written: 1132 if ( 1133 self._status_code == 200 1134 and self.request.method in ("GET", "HEAD") 1135 and "Etag" not in self._headers 1136 ): 1137 self.set_etag_header() 1138 if self.check_etag_header(): 1139 self._write_buffer = [] 1140 self.set_status(304) 1141 if self._status_code in (204, 304) or (100 <= self._status_code < 200): 1142 assert not self._write_buffer, ( 1143 "Cannot send body with %s" % self._status_code 1144 ) 1145 self._clear_representation_headers() 1146 elif "Content-Length" not in self._headers: 1147 content_length = sum(len(part) for part in self._write_buffer) 1148 self.set_header("Content-Length", content_length) 1149 1150 assert self.request.connection is not None 1151 # Now that the request is finished, clear the callback we 1152 # set on the HTTPConnection (which would otherwise prevent the 1153 # garbage collection of the RequestHandler when there 1154 # are keepalive connections) 1155 self.request.connection.set_close_callback(None) # type: ignore 1156 1157 future = self.flush(include_footers=True) 1158 self.request.connection.finish() 1159 self._log() 1160 self._finished = True 1161 self.on_finish() 1162 self._break_cycles() 1163 return future 1164 1165 def detach(self) -> iostream.IOStream: 1166 """Take control of the underlying stream. 1167 1168 Returns the underlying `.IOStream` object and stops all 1169 further HTTP processing. Intended for implementing protocols 1170 like websockets that tunnel over an HTTP handshake. 1171 1172 This method is only supported when HTTP/1.1 is used. 1173 1174 .. versionadded:: 5.1 1175 """ 1176 self._finished = True 1177 # TODO: add detach to HTTPConnection? 1178 return self.request.connection.detach() # type: ignore 1179 1180 def _break_cycles(self) -> None: 1181 # Break up a reference cycle between this handler and the 1182 # _ui_module closures to allow for faster GC on CPython. 1183 self.ui = None # type: ignore 1184 1185 def send_error(self, status_code: int = 500, **kwargs: Any) -> None: 1186 """Sends the given HTTP error code to the browser. 1187 1188 If `flush()` has already been called, it is not possible to send 1189 an error, so this method will simply terminate the response. 1190 If output has been written but not yet flushed, it will be discarded 1191 and replaced with the error page. 1192 1193 Override `write_error()` to customize the error page that is returned. 1194 Additional keyword arguments are passed through to `write_error`. 1195 """ 1196 if self._headers_written: 1197 gen_log.error("Cannot send error response after headers written") 1198 if not self._finished: 1199 # If we get an error between writing headers and finishing, 1200 # we are unlikely to be able to finish due to a 1201 # Content-Length mismatch. Try anyway to release the 1202 # socket. 1203 try: 1204 self.finish() 1205 except Exception: 1206 gen_log.error("Failed to flush partial response", exc_info=True) 1207 return 1208 self.clear() 1209 1210 reason = kwargs.get("reason") 1211 if "exc_info" in kwargs: 1212 exception = kwargs["exc_info"][1] 1213 if isinstance(exception, HTTPError) and exception.reason: 1214 reason = exception.reason 1215 self.set_status(status_code, reason=reason) 1216 try: 1217 self.write_error(status_code, **kwargs) 1218 except Exception: 1219 app_log.error("Uncaught exception in write_error", exc_info=True) 1220 if not self._finished: 1221 self.finish() 1222 1223 def write_error(self, status_code: int, **kwargs: Any) -> None: 1224 """Override to implement custom error pages. 1225 1226 ``write_error`` may call `write`, `render`, `set_header`, etc 1227 to produce output as usual. 1228 1229 If this error was caused by an uncaught exception (including 1230 HTTPError), an ``exc_info`` triple will be available as 1231 ``kwargs["exc_info"]``. Note that this exception may not be 1232 the "current" exception for purposes of methods like 1233 ``sys.exc_info()`` or ``traceback.format_exc``. 1234 """ 1235 if self.settings.get("serve_traceback") and "exc_info" in kwargs: 1236 # in debug mode, try to send a traceback 1237 self.set_header("Content-Type", "text/plain") 1238 for line in traceback.format_exception(*kwargs["exc_info"]): 1239 self.write(line) 1240 self.finish() 1241 else: 1242 self.finish( 1243 "<html><title>%(code)d: %(message)s</title>" 1244 "<body>%(code)d: %(message)s</body></html>" 1245 % {"code": status_code, "message": self._reason} 1246 ) 1247 1248 @property 1249 def locale(self) -> tornado.locale.Locale: 1250 """The locale for the current session. 1251 1252 Determined by either `get_user_locale`, which you can override to 1253 set the locale based on, e.g., a user preference stored in a 1254 database, or `get_browser_locale`, which uses the ``Accept-Language`` 1255 header. 1256 1257 .. versionchanged: 4.1 1258 Added a property setter. 1259 """ 1260 if not hasattr(self, "_locale"): 1261 loc = self.get_user_locale() 1262 if loc is not None: 1263 self._locale = loc 1264 else: 1265 self._locale = self.get_browser_locale() 1266 assert self._locale 1267 return self._locale 1268 1269 @locale.setter 1270 def locale(self, value: tornado.locale.Locale) -> None: 1271 self._locale = value 1272 1273 def get_user_locale(self) -> Optional[tornado.locale.Locale]: 1274 """Override to determine the locale from the authenticated user. 1275 1276 If None is returned, we fall back to `get_browser_locale()`. 1277 1278 This method should return a `tornado.locale.Locale` object, 1279 most likely obtained via a call like ``tornado.locale.get("en")`` 1280 """ 1281 return None 1282 1283 def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale: 1284 """Determines the user's locale from ``Accept-Language`` header. 1285 1286 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4 1287 """ 1288 if "Accept-Language" in self.request.headers: 1289 languages = self.request.headers["Accept-Language"].split(",") 1290 locales = [] 1291 for language in languages: 1292 parts = language.strip().split(";") 1293 if len(parts) > 1 and parts[1].startswith("q="): 1294 try: 1295 score = float(parts[1][2:]) 1296 except (ValueError, TypeError): 1297 score = 0.0 1298 else: 1299 score = 1.0 1300 locales.append((parts[0], score)) 1301 if locales: 1302 locales.sort(key=lambda pair: pair[1], reverse=True) 1303 codes = [loc[0] for loc in locales] 1304 return locale.get(*codes) 1305 return locale.get(default) 1306 1307 @property 1308 def current_user(self) -> Any: 1309 """The authenticated user for this request. 1310 1311 This is set in one of two ways: 1312 1313 * A subclass may override `get_current_user()`, which will be called 1314 automatically the first time ``self.current_user`` is accessed. 1315 `get_current_user()` will only be called once per request, 1316 and is cached for future access:: 1317 1318 def get_current_user(self): 1319 user_cookie = self.get_secure_cookie("user") 1320 if user_cookie: 1321 return json.loads(user_cookie) 1322 return None 1323 1324 * It may be set as a normal variable, typically from an overridden 1325 `prepare()`:: 1326 1327 @gen.coroutine 1328 def prepare(self): 1329 user_id_cookie = self.get_secure_cookie("user_id") 1330 if user_id_cookie: 1331 self.current_user = yield load_user(user_id_cookie) 1332 1333 Note that `prepare()` may be a coroutine while `get_current_user()` 1334 may not, so the latter form is necessary if loading the user requires 1335 asynchronous operations. 1336 1337 The user object may be any type of the application's choosing. 1338 """ 1339 if not hasattr(self, "_current_user"): 1340 self._current_user = self.get_current_user() 1341 return self._current_user 1342 1343 @current_user.setter 1344 def current_user(self, value: Any) -> None: 1345 self._current_user = value 1346 1347 def get_current_user(self) -> Any: 1348 """Override to determine the current user from, e.g., a cookie. 1349 1350 This method may not be a coroutine. 1351 """ 1352 return None 1353 1354 def get_login_url(self) -> str: 1355 """Override to customize the login URL based on the request. 1356 1357 By default, we use the ``login_url`` application setting. 1358 """ 1359 self.require_setting("login_url", "@tornado.web.authenticated") 1360 return self.application.settings["login_url"] 1361 1362 def get_template_path(self) -> Optional[str]: 1363 """Override to customize template path for each handler. 1364 1365 By default, we use the ``template_path`` application setting. 1366 Return None to load templates relative to the calling file. 1367 """ 1368 return self.application.settings.get("template_path") 1369 1370 @property 1371 def xsrf_token(self) -> bytes: 1372 """The XSRF-prevention token for the current user/session. 1373 1374 To prevent cross-site request forgery, we set an '_xsrf' cookie 1375 and include the same '_xsrf' value as an argument with all POST 1376 requests. If the two do not match, we reject the form submission 1377 as a potential forgery. 1378 1379 See http://en.wikipedia.org/wiki/Cross-site_request_forgery 1380 1381 This property is of type `bytes`, but it contains only ASCII 1382 characters. If a character string is required, there is no 1383 need to base64-encode it; just decode the byte string as 1384 UTF-8. 1385 1386 .. versionchanged:: 3.2.2 1387 The xsrf token will now be have a random mask applied in every 1388 request, which makes it safe to include the token in pages 1389 that are compressed. See http://breachattack.com for more 1390 information on the issue fixed by this change. Old (version 1) 1391 cookies will be converted to version 2 when this method is called 1392 unless the ``xsrf_cookie_version`` `Application` setting is 1393 set to 1. 1394 1395 .. versionchanged:: 4.3 1396 The ``xsrf_cookie_kwargs`` `Application` setting may be 1397 used to supply additional cookie options (which will be 1398 passed directly to `set_cookie`). For example, 1399 ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)`` 1400 will set the ``secure`` and ``httponly`` flags on the 1401 ``_xsrf`` cookie. 1402 """ 1403 if not hasattr(self, "_xsrf_token"): 1404 version, token, timestamp = self._get_raw_xsrf_token() 1405 output_version = self.settings.get("xsrf_cookie_version", 2) 1406 cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {}) 1407 if output_version == 1: 1408 self._xsrf_token = binascii.b2a_hex(token) 1409 elif output_version == 2: 1410 mask = os.urandom(4) 1411 self._xsrf_token = b"|".join( 1412 [ 1413 b"2", 1414 binascii.b2a_hex(mask), 1415 binascii.b2a_hex(_websocket_mask(mask, token)), 1416 utf8(str(int(timestamp))), 1417 ] 1418 ) 1419 else: 1420 raise ValueError("unknown xsrf cookie version %d", output_version) 1421 if version is None: 1422 if self.current_user and "expires_days" not in cookie_kwargs: 1423 cookie_kwargs["expires_days"] = 30 1424 self.set_cookie("_xsrf", self._xsrf_token, **cookie_kwargs) 1425 return self._xsrf_token 1426 1427 def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]: 1428 """Read or generate the xsrf token in its raw form. 1429 1430 The raw_xsrf_token is a tuple containing: 1431 1432 * version: the version of the cookie from which this token was read, 1433 or None if we generated a new token in this request. 1434 * token: the raw token data; random (non-ascii) bytes. 1435 * timestamp: the time this token was generated (will not be accurate 1436 for version 1 cookies) 1437 """ 1438 if not hasattr(self, "_raw_xsrf_token"): 1439 cookie = self.get_cookie("_xsrf") 1440 if cookie: 1441 version, token, timestamp = self._decode_xsrf_token(cookie) 1442 else: 1443 version, token, timestamp = None, None, None 1444 if token is None: 1445 version = None 1446 token = os.urandom(16) 1447 timestamp = time.time() 1448 assert token is not None 1449 assert timestamp is not None 1450 self._raw_xsrf_token = (version, token, timestamp) 1451 return self._raw_xsrf_token 1452 1453 def _decode_xsrf_token( 1454 self, cookie: str 1455 ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]: 1456 """Convert a cookie string into a the tuple form returned by 1457 _get_raw_xsrf_token. 1458 """ 1459 1460 try: 1461 m = _signed_value_version_re.match(utf8(cookie)) 1462 1463 if m: 1464 version = int(m.group(1)) 1465 if version == 2: 1466 _, mask_str, masked_token, timestamp_str = cookie.split("|") 1467 1468 mask = binascii.a2b_hex(utf8(mask_str)) 1469 token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token))) 1470 timestamp = int(timestamp_str) 1471 return version, token, timestamp 1472 else: 1473 # Treat unknown versions as not present instead of failing. 1474 raise Exception("Unknown xsrf cookie version") 1475 else: 1476 version = 1 1477 try: 1478 token = binascii.a2b_hex(utf8(cookie)) 1479 except (binascii.Error, TypeError): 1480 token = utf8(cookie) 1481 # We don't have a usable timestamp in older versions. 1482 timestamp = int(time.time()) 1483 return (version, token, timestamp) 1484 except Exception: 1485 # Catch exceptions and return nothing instead of failing. 1486 gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True) 1487 return None, None, None 1488 1489 def check_xsrf_cookie(self) -> None: 1490 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument. 1491 1492 To prevent cross-site request forgery, we set an ``_xsrf`` 1493 cookie and include the same value as a non-cookie 1494 field with all ``POST`` requests. If the two do not match, we 1495 reject the form submission as a potential forgery. 1496 1497 The ``_xsrf`` value may be set as either a form field named ``_xsrf`` 1498 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken`` 1499 (the latter is accepted for compatibility with Django). 1500 1501 See http://en.wikipedia.org/wiki/Cross-site_request_forgery 1502 1503 .. versionchanged:: 3.2.2 1504 Added support for cookie version 2. Both versions 1 and 2 are 1505 supported. 1506 """ 1507 # Prior to release 1.1.1, this check was ignored if the HTTP header 1508 # ``X-Requested-With: XMLHTTPRequest`` was present. This exception 1509 # has been shown to be insecure and has been removed. For more 1510 # information please see 1511 # http://www.djangoproject.com/weblog/2011/feb/08/security/ 1512 # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails 1513 token = ( 1514 self.get_argument("_xsrf", None) 1515 or self.request.headers.get("X-Xsrftoken") 1516 or self.request.headers.get("X-Csrftoken") 1517 ) 1518 if not token: 1519 raise HTTPError(403, "'_xsrf' argument missing from POST") 1520 _, token, _ = self._decode_xsrf_token(token) 1521 _, expected_token, _ = self._get_raw_xsrf_token() 1522 if not token: 1523 raise HTTPError(403, "'_xsrf' argument has invalid format") 1524 if not hmac.compare_digest(utf8(token), utf8(expected_token)): 1525 raise HTTPError(403, "XSRF cookie does not match POST argument") 1526 1527 def xsrf_form_html(self) -> str: 1528 """An HTML ``<input/>`` element to be included with all POST forms. 1529 1530 It defines the ``_xsrf`` input value, which we check on all POST 1531 requests to prevent cross-site request forgery. If you have set 1532 the ``xsrf_cookies`` application setting, you must include this 1533 HTML within all of your HTML forms. 1534 1535 In a template, this method should be called with ``{% module 1536 xsrf_form_html() %}`` 1537 1538 See `check_xsrf_cookie()` above for more information. 1539 """ 1540 return ( 1541 '<input type="hidden" name="_xsrf" value="' 1542 + escape.xhtml_escape(self.xsrf_token) 1543 + '"/>' 1544 ) 1545 1546 def static_url( 1547 self, path: str, include_host: Optional[bool] = None, **kwargs: Any 1548 ) -> str: 1549 """Returns a static URL for the given relative static file path. 1550 1551 This method requires you set the ``static_path`` setting in your 1552 application (which specifies the root directory of your static 1553 files). 1554 1555 This method returns a versioned url (by default appending 1556 ``?v=<signature>``), which allows the static files to be 1557 cached indefinitely. This can be disabled by passing 1558 ``include_version=False`` (in the default implementation; 1559 other static file implementations are not required to support 1560 this, but they may support other options). 1561 1562 By default this method returns URLs relative to the current 1563 host, but if ``include_host`` is true the URL returned will be 1564 absolute. If this handler has an ``include_host`` attribute, 1565 that value will be used as the default for all `static_url` 1566 calls that do not pass ``include_host`` as a keyword argument. 1567 1568 """ 1569 self.require_setting("static_path", "static_url") 1570 get_url = self.settings.get( 1571 "static_handler_class", StaticFileHandler 1572 ).make_static_url 1573 1574 if include_host is None: 1575 include_host = getattr(self, "include_host", False) 1576 1577 if include_host: 1578 base = self.request.protocol + "://" + self.request.host 1579 else: 1580 base = "" 1581 1582 return base + get_url(self.settings, path, **kwargs) 1583 1584 def require_setting(self, name: str, feature: str = "this feature") -> None: 1585 """Raises an exception if the given app setting is not defined.""" 1586 if not self.application.settings.get(name): 1587 raise Exception( 1588 "You must define the '%s' setting in your " 1589 "application to use %s" % (name, feature) 1590 ) 1591 1592 def reverse_url(self, name: str, *args: Any) -> str: 1593 """Alias for `Application.reverse_url`.""" 1594 return self.application.reverse_url(name, *args) 1595 1596 def compute_etag(self) -> Optional[str]: 1597 """Computes the etag header to be used for this request. 1598 1599 By default uses a hash of the content written so far. 1600 1601 May be overridden to provide custom etag implementations, 1602 or may return None to disable tornado's default etag support. 1603 """ 1604 hasher = hashlib.sha1() 1605 for part in self._write_buffer: 1606 hasher.update(part) 1607 return '"%s"' % hasher.hexdigest() 1608 1609 def set_etag_header(self) -> None: 1610 """Sets the response's Etag header using ``self.compute_etag()``. 1611 1612 Note: no header will be set if ``compute_etag()`` returns ``None``. 1613 1614 This method is called automatically when the request is finished. 1615 """ 1616 etag = self.compute_etag() 1617 if etag is not None: 1618 self.set_header("Etag", etag) 1619 1620 def check_etag_header(self) -> bool: 1621 """Checks the ``Etag`` header against requests's ``If-None-Match``. 1622 1623 Returns ``True`` if the request's Etag matches and a 304 should be 1624 returned. For example:: 1625 1626 self.set_etag_header() 1627 if self.check_etag_header(): 1628 self.set_status(304) 1629 return 1630 1631 This method is called automatically when the request is finished, 1632 but may be called earlier for applications that override 1633 `compute_etag` and want to do an early check for ``If-None-Match`` 1634 before completing the request. The ``Etag`` header should be set 1635 (perhaps with `set_etag_header`) before calling this method. 1636 """ 1637 computed_etag = utf8(self._headers.get("Etag", "")) 1638 # Find all weak and strong etag values from If-None-Match header 1639 # because RFC 7232 allows multiple etag values in a single header. 1640 etags = re.findall( 1641 br'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", "")) 1642 ) 1643 if not computed_etag or not etags: 1644 return False 1645 1646 match = False 1647 if etags[0] == b"*": 1648 match = True 1649 else: 1650 # Use a weak comparison when comparing entity-tags. 1651 def val(x: bytes) -> bytes: 1652 return x[2:] if x.startswith(b"W/") else x 1653 1654 for etag in etags: 1655 if val(etag) == val(computed_etag): 1656 match = True 1657 break 1658 return match 1659 1660 async def _execute( 1661 self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes 1662 ) -> None: 1663 """Executes this request with the given output transforms.""" 1664 self._transforms = transforms 1665 try: 1666 if self.request.method not in self.SUPPORTED_METHODS: 1667 raise HTTPError(405) 1668 self.path_args = [self.decode_argument(arg) for arg in args] 1669 self.path_kwargs = dict( 1670 (k, self.decode_argument(v, name=k)) for (k, v) in kwargs.items() 1671 ) 1672 # If XSRF cookies are turned on, reject form submissions without 1673 # the proper cookie 1674 if self.request.method not in ( 1675 "GET", 1676 "HEAD", 1677 "OPTIONS", 1678 ) and self.application.settings.get("xsrf_cookies"): 1679 self.check_xsrf_cookie() 1680 1681 result = self.prepare() 1682 if result is not None: 1683 result = await result 1684 if self._prepared_future is not None: 1685 # Tell the Application we've finished with prepare() 1686 # and are ready for the body to arrive. 1687 future_set_result_unless_cancelled(self._prepared_future, None) 1688 if self._finished: 1689 return 1690 1691 if _has_stream_request_body(self.__class__): 1692 # In streaming mode request.body is a Future that signals 1693 # the body has been completely received. The Future has no 1694 # result; the data has been passed to self.data_received 1695 # instead. 1696 try: 1697 await self.request._body_future 1698 except iostream.StreamClosedError: 1699 return 1700 1701 method = getattr(self, self.request.method.lower()) 1702 result = method(*self.path_args, **self.path_kwargs) 1703 if result is not None: 1704 result = await result 1705 if self._auto_finish and not self._finished: 1706 self.finish() 1707 except Exception as e: 1708 try: 1709 self._handle_request_exception(e) 1710 except Exception: 1711 app_log.error("Exception in exception handler", exc_info=True) 1712 finally: 1713 # Unset result to avoid circular references 1714 result = None 1715 if self._prepared_future is not None and not self._prepared_future.done(): 1716 # In case we failed before setting _prepared_future, do it 1717 # now (to unblock the HTTP server). Note that this is not 1718 # in a finally block to avoid GC issues prior to Python 3.4. 1719 self._prepared_future.set_result(None) 1720 1721 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]: 1722 """Implement this method to handle streamed request data. 1723 1724 Requires the `.stream_request_body` decorator. 1725 1726 May be a coroutine for flow control. 1727 """ 1728 raise NotImplementedError() 1729 1730 def _log(self) -> None: 1731 """Logs the current request. 1732 1733 Sort of deprecated since this functionality was moved to the 1734 Application, but left in place for the benefit of existing apps 1735 that have overridden this method. 1736 """ 1737 self.application.log_request(self) 1738 1739 def _request_summary(self) -> str: 1740 return "%s %s (%s)" % ( 1741 self.request.method, 1742 self.request.uri, 1743 self.request.remote_ip, 1744 ) 1745 1746 def _handle_request_exception(self, e: BaseException) -> None: 1747 if isinstance(e, Finish): 1748 # Not an error; just finish the request without logging. 1749 if not self._finished: 1750 self.finish(*e.args) 1751 return 1752 try: 1753 self.log_exception(*sys.exc_info()) 1754 except Exception: 1755 # An error here should still get a best-effort send_error() 1756 # to avoid leaking the connection. 1757 app_log.error("Error in exception logger", exc_info=True) 1758 if self._finished: 1759 # Extra errors after the request has been finished should 1760 # be logged, but there is no reason to continue to try and 1761 # send a response. 1762 return 1763 if isinstance(e, HTTPError): 1764 self.send_error(e.status_code, exc_info=sys.exc_info()) 1765 else: 1766 self.send_error(500, exc_info=sys.exc_info()) 1767 1768 def log_exception( 1769 self, 1770 typ: "Optional[Type[BaseException]]", 1771 value: Optional[BaseException], 1772 tb: Optional[TracebackType], 1773 ) -> None: 1774 """Override to customize logging of uncaught exceptions. 1775 1776 By default logs instances of `HTTPError` as warnings without 1777 stack traces (on the ``tornado.general`` logger), and all 1778 other exceptions as errors with stack traces (on the 1779 ``tornado.application`` logger). 1780 1781 .. versionadded:: 3.1 1782 """ 1783 if isinstance(value, HTTPError): 1784 if value.log_message: 1785 format = "%d %s: " + value.log_message 1786 args = [value.status_code, self._request_summary()] + list(value.args) 1787 gen_log.warning(format, *args) 1788 else: 1789 app_log.error( 1790 "Uncaught exception %s\n%r", 1791 self._request_summary(), 1792 self.request, 1793 exc_info=(typ, value, tb), # type: ignore 1794 ) 1795 1796 def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]: 1797 def render(*args, **kwargs) -> str: # type: ignore 1798 if not hasattr(self, "_active_modules"): 1799 self._active_modules = {} # type: Dict[str, UIModule] 1800 if name not in self._active_modules: 1801 self._active_modules[name] = module(self) 1802 rendered = self._active_modules[name].render(*args, **kwargs) 1803 return rendered 1804 1805 return render 1806 1807 def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]: 1808 return lambda *args, **kwargs: method(self, *args, **kwargs) 1809 1810 def _clear_representation_headers(self) -> None: 1811 # 304 responses should not contain representation metadata 1812 # headers (defined in 1813 # https://tools.ietf.org/html/rfc7231#section-3.1) 1814 # not explicitly allowed by 1815 # https://tools.ietf.org/html/rfc7232#section-4.1 1816 headers = ["Content-Encoding", "Content-Language", "Content-Type"] 1817 for h in headers: 1818 self.clear_header(h) 1819 1820 1821def stream_request_body(cls: Type[RequestHandler]) -> Type[RequestHandler]: 1822 """Apply to `RequestHandler` subclasses to enable streaming body support. 1823 1824 This decorator implies the following changes: 1825 1826 * `.HTTPServerRequest.body` is undefined, and body arguments will not 1827 be included in `RequestHandler.get_argument`. 1828 * `RequestHandler.prepare` is called when the request headers have been 1829 read instead of after the entire body has been read. 1830 * The subclass must define a method ``data_received(self, data):``, which 1831 will be called zero or more times as data is available. Note that 1832 if the request has an empty body, ``data_received`` may not be called. 1833 * ``prepare`` and ``data_received`` may return Futures (such as via 1834 ``@gen.coroutine``, in which case the next method will not be called 1835 until those futures have completed. 1836 * The regular HTTP method (``post``, ``put``, etc) will be called after 1837 the entire body has been read. 1838 1839 See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/master/demos/file_upload/>`_ 1840 for example usage. 1841 """ # noqa: E501 1842 if not issubclass(cls, RequestHandler): 1843 raise TypeError("expected subclass of RequestHandler, got %r", cls) 1844 cls._stream_request_body = True 1845 return cls 1846 1847 1848def _has_stream_request_body(cls: Type[RequestHandler]) -> bool: 1849 if not issubclass(cls, RequestHandler): 1850 raise TypeError("expected subclass of RequestHandler, got %r", cls) 1851 return cls._stream_request_body 1852 1853 1854def removeslash( 1855 method: Callable[..., Optional[Awaitable[None]]] 1856) -> Callable[..., Optional[Awaitable[None]]]: 1857 """Use this decorator to remove trailing slashes from the request path. 1858 1859 For example, a request to ``/foo/`` would redirect to ``/foo`` with this 1860 decorator. Your request handler mapping should use a regular expression 1861 like ``r'/foo/*'`` in conjunction with using the decorator. 1862 """ 1863 1864 @functools.wraps(method) 1865 def wrapper( # type: ignore 1866 self: RequestHandler, *args, **kwargs 1867 ) -> Optional[Awaitable[None]]: 1868 if self.request.path.endswith("/"): 1869 if self.request.method in ("GET", "HEAD"): 1870 uri = self.request.path.rstrip("/") 1871 if uri: # don't try to redirect '/' to '' 1872 if self.request.query: 1873 uri += "?" + self.request.query 1874 self.redirect(uri, permanent=True) 1875 return None 1876 else: 1877 raise HTTPError(404) 1878 return method(self, *args, **kwargs) 1879 1880 return wrapper 1881 1882 1883def addslash( 1884 method: Callable[..., Optional[Awaitable[None]]] 1885) -> Callable[..., Optional[Awaitable[None]]]: 1886 """Use this decorator to add a missing trailing slash to the request path. 1887 1888 For example, a request to ``/foo`` would redirect to ``/foo/`` with this 1889 decorator. Your request handler mapping should use a regular expression 1890 like ``r'/foo/?'`` in conjunction with using the decorator. 1891 """ 1892 1893 @functools.wraps(method) 1894 def wrapper( # type: ignore 1895 self: RequestHandler, *args, **kwargs 1896 ) -> Optional[Awaitable[None]]: 1897 if not self.request.path.endswith("/"): 1898 if self.request.method in ("GET", "HEAD"): 1899 uri = self.request.path + "/" 1900 if self.request.query: 1901 uri += "?" + self.request.query 1902 self.redirect(uri, permanent=True) 1903 return None 1904 raise HTTPError(404) 1905 return method(self, *args, **kwargs) 1906 1907 return wrapper 1908 1909 1910class _ApplicationRouter(ReversibleRuleRouter): 1911 """Routing implementation used internally by `Application`. 1912 1913 Provides a binding between `Application` and `RequestHandler`. 1914 This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways: 1915 * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and 1916 * it allows to use a list/tuple of rules as `~.routing.Rule` target. 1917 ``process_rule`` implementation will substitute this list with an appropriate 1918 `_ApplicationRouter` instance. 1919 """ 1920 1921 def __init__( 1922 self, application: "Application", rules: Optional[_RuleList] = None 1923 ) -> None: 1924 assert isinstance(application, Application) 1925 self.application = application 1926 super().__init__(rules) 1927 1928 def process_rule(self, rule: Rule) -> Rule: 1929 rule = super().process_rule(rule) 1930 1931 if isinstance(rule.target, (list, tuple)): 1932 rule.target = _ApplicationRouter( 1933 self.application, rule.target # type: ignore 1934 ) 1935 1936 return rule 1937 1938 def get_target_delegate( 1939 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any 1940 ) -> Optional[httputil.HTTPMessageDelegate]: 1941 if isclass(target) and issubclass(target, RequestHandler): 1942 return self.application.get_handler_delegate( 1943 request, target, **target_params 1944 ) 1945 1946 return super().get_target_delegate(target, request, **target_params) 1947 1948 1949class Application(ReversibleRouter): 1950 r"""A collection of request handlers that make up a web application. 1951 1952 Instances of this class are callable and can be passed directly to 1953 HTTPServer to serve the application:: 1954 1955 application = web.Application([ 1956 (r"/", MainPageHandler), 1957 ]) 1958 http_server = httpserver.HTTPServer(application) 1959 http_server.listen(8080) 1960 ioloop.IOLoop.current().start() 1961 1962 The constructor for this class takes in a list of `~.routing.Rule` 1963 objects or tuples of values corresponding to the arguments of 1964 `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``, 1965 the values in square brackets being optional. The default matcher is 1966 `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used 1967 instead of ``(PathMatches(regexp), target)``. 1968 1969 A common routing target is a `RequestHandler` subclass, but you can also 1970 use lists of rules as a target, which create a nested routing configuration:: 1971 1972 application = web.Application([ 1973 (HostMatches("example.com"), [ 1974 (r"/", MainPageHandler), 1975 (r"/feed", FeedHandler), 1976 ]), 1977 ]) 1978 1979 In addition to this you can use nested `~.routing.Router` instances, 1980 `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets 1981 (see `~.routing` module docs for more information). 1982 1983 When we receive requests, we iterate over the list in order and 1984 instantiate an instance of the first request class whose regexp 1985 matches the request path. The request class can be specified as 1986 either a class object or a (fully-qualified) name. 1987 1988 A dictionary may be passed as the third element (``target_kwargs``) 1989 of the tuple, which will be used as keyword arguments to the handler's 1990 constructor and `~RequestHandler.initialize` method. This pattern 1991 is used for the `StaticFileHandler` in this example (note that a 1992 `StaticFileHandler` can be installed automatically with the 1993 static_path setting described below):: 1994 1995 application = web.Application([ 1996 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 1997 ]) 1998 1999 We support virtual hosts with the `add_handlers` method, which takes in 2000 a host regular expression as the first argument:: 2001 2002 application.add_handlers(r"www\.myhost\.com", [ 2003 (r"/article/([0-9]+)", ArticleHandler), 2004 ]) 2005 2006 If there's no match for the current request's host, then ``default_host`` 2007 parameter value is matched against host regular expressions. 2008 2009 2010 .. warning:: 2011 2012 Applications that do not use TLS may be vulnerable to :ref:`DNS 2013 rebinding <dnsrebinding>` attacks. This attack is especially 2014 relevant to applications that only listen on ``127.0.0.1`` or 2015 other private networks. Appropriate host patterns must be used 2016 (instead of the default of ``r'.*'``) to prevent this risk. The 2017 ``default_host`` argument must not be used in applications that 2018 may be vulnerable to DNS rebinding. 2019 2020 You can serve static files by sending the ``static_path`` setting 2021 as a keyword argument. We will serve those files from the 2022 ``/static/`` URI (this is configurable with the 2023 ``static_url_prefix`` setting), and we will serve ``/favicon.ico`` 2024 and ``/robots.txt`` from the same directory. A custom subclass of 2025 `StaticFileHandler` can be specified with the 2026 ``static_handler_class`` setting. 2027 2028 .. versionchanged:: 4.5 2029 Integration with the new `tornado.routing` module. 2030 2031 """ 2032 2033 def __init__( 2034 self, 2035 handlers: Optional[_RuleList] = None, 2036 default_host: Optional[str] = None, 2037 transforms: Optional[List[Type["OutputTransform"]]] = None, 2038 **settings: Any 2039 ) -> None: 2040 if transforms is None: 2041 self.transforms = [] # type: List[Type[OutputTransform]] 2042 if settings.get("compress_response") or settings.get("gzip"): 2043 self.transforms.append(GZipContentEncoding) 2044 else: 2045 self.transforms = transforms 2046 self.default_host = default_host 2047 self.settings = settings 2048 self.ui_modules = { 2049 "linkify": _linkify, 2050 "xsrf_form_html": _xsrf_form_html, 2051 "Template": TemplateModule, 2052 } 2053 self.ui_methods = {} # type: Dict[str, Callable[..., str]] 2054 self._load_ui_modules(settings.get("ui_modules", {})) 2055 self._load_ui_methods(settings.get("ui_methods", {})) 2056 if self.settings.get("static_path"): 2057 path = self.settings["static_path"] 2058 handlers = list(handlers or []) 2059 static_url_prefix = settings.get("static_url_prefix", "/static/") 2060 static_handler_class = settings.get( 2061 "static_handler_class", StaticFileHandler 2062 ) 2063 static_handler_args = settings.get("static_handler_args", {}) 2064 static_handler_args["path"] = path 2065 for pattern in [ 2066 re.escape(static_url_prefix) + r"(.*)", 2067 r"/(favicon\.ico)", 2068 r"/(robots\.txt)", 2069 ]: 2070 handlers.insert(0, (pattern, static_handler_class, static_handler_args)) 2071 2072 if self.settings.get("debug"): 2073 self.settings.setdefault("autoreload", True) 2074 self.settings.setdefault("compiled_template_cache", False) 2075 self.settings.setdefault("static_hash_cache", False) 2076 self.settings.setdefault("serve_traceback", True) 2077 2078 self.wildcard_router = _ApplicationRouter(self, handlers) 2079 self.default_router = _ApplicationRouter( 2080 self, [Rule(AnyMatches(), self.wildcard_router)] 2081 ) 2082 2083 # Automatically reload modified modules 2084 if self.settings.get("autoreload"): 2085 from tornado import autoreload 2086 2087 autoreload.start() 2088 2089 def listen(self, port: int, address: str = "", **kwargs: Any) -> HTTPServer: 2090 """Starts an HTTP server for this application on the given port. 2091 2092 This is a convenience alias for creating an `.HTTPServer` 2093 object and calling its listen method. Keyword arguments not 2094 supported by `HTTPServer.listen <.TCPServer.listen>` are passed to the 2095 `.HTTPServer` constructor. For advanced uses 2096 (e.g. multi-process mode), do not use this method; create an 2097 `.HTTPServer` and call its 2098 `.TCPServer.bind`/`.TCPServer.start` methods directly. 2099 2100 Note that after calling this method you still need to call 2101 ``IOLoop.current().start()`` to start the server. 2102 2103 Returns the `.HTTPServer` object. 2104 2105 .. versionchanged:: 4.3 2106 Now returns the `.HTTPServer` object. 2107 """ 2108 server = HTTPServer(self, **kwargs) 2109 server.listen(port, address) 2110 return server 2111 2112 def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None: 2113 """Appends the given handlers to our handler list. 2114 2115 Host patterns are processed sequentially in the order they were 2116 added. All matching patterns will be considered. 2117 """ 2118 host_matcher = HostMatches(host_pattern) 2119 rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers)) 2120 2121 self.default_router.rules.insert(-1, rule) 2122 2123 if self.default_host is not None: 2124 self.wildcard_router.add_rules( 2125 [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)] 2126 ) 2127 2128 def add_transform(self, transform_class: Type["OutputTransform"]) -> None: 2129 self.transforms.append(transform_class) 2130 2131 def _load_ui_methods(self, methods: Any) -> None: 2132 if isinstance(methods, types.ModuleType): 2133 self._load_ui_methods(dict((n, getattr(methods, n)) for n in dir(methods))) 2134 elif isinstance(methods, list): 2135 for m in methods: 2136 self._load_ui_methods(m) 2137 else: 2138 for name, fn in methods.items(): 2139 if ( 2140 not name.startswith("_") 2141 and hasattr(fn, "__call__") 2142 and name[0].lower() == name[0] 2143 ): 2144 self.ui_methods[name] = fn 2145 2146 def _load_ui_modules(self, modules: Any) -> None: 2147 if isinstance(modules, types.ModuleType): 2148 self._load_ui_modules(dict((n, getattr(modules, n)) for n in dir(modules))) 2149 elif isinstance(modules, list): 2150 for m in modules: 2151 self._load_ui_modules(m) 2152 else: 2153 assert isinstance(modules, dict) 2154 for name, cls in modules.items(): 2155 try: 2156 if issubclass(cls, UIModule): 2157 self.ui_modules[name] = cls 2158 except TypeError: 2159 pass 2160 2161 def __call__( 2162 self, request: httputil.HTTPServerRequest 2163 ) -> Optional[Awaitable[None]]: 2164 # Legacy HTTPServer interface 2165 dispatcher = self.find_handler(request) 2166 return dispatcher.execute() 2167 2168 def find_handler( 2169 self, request: httputil.HTTPServerRequest, **kwargs: Any 2170 ) -> "_HandlerDelegate": 2171 route = self.default_router.find_handler(request) 2172 if route is not None: 2173 return cast("_HandlerDelegate", route) 2174 2175 if self.settings.get("default_handler_class"): 2176 return self.get_handler_delegate( 2177 request, 2178 self.settings["default_handler_class"], 2179 self.settings.get("default_handler_args", {}), 2180 ) 2181 2182 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404}) 2183 2184 def get_handler_delegate( 2185 self, 2186 request: httputil.HTTPServerRequest, 2187 target_class: Type[RequestHandler], 2188 target_kwargs: Optional[Dict[str, Any]] = None, 2189 path_args: Optional[List[bytes]] = None, 2190 path_kwargs: Optional[Dict[str, bytes]] = None, 2191 ) -> "_HandlerDelegate": 2192 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request 2193 for application and `RequestHandler` subclass. 2194 2195 :arg httputil.HTTPServerRequest request: current HTTP request. 2196 :arg RequestHandler target_class: a `RequestHandler` class. 2197 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor. 2198 :arg list path_args: positional arguments for ``target_class`` HTTP method that 2199 will be executed while handling a request (``get``, ``post`` or any other). 2200 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method. 2201 """ 2202 return _HandlerDelegate( 2203 self, request, target_class, target_kwargs, path_args, path_kwargs 2204 ) 2205 2206 def reverse_url(self, name: str, *args: Any) -> str: 2207 """Returns a URL path for handler named ``name`` 2208 2209 The handler must be added to the application as a named `URLSpec`. 2210 2211 Args will be substituted for capturing groups in the `URLSpec` regex. 2212 They will be converted to strings if necessary, encoded as utf8, 2213 and url-escaped. 2214 """ 2215 reversed_url = self.default_router.reverse_url(name, *args) 2216 if reversed_url is not None: 2217 return reversed_url 2218 2219 raise KeyError("%s not found in named urls" % name) 2220 2221 def log_request(self, handler: RequestHandler) -> None: 2222 """Writes a completed HTTP request to the logs. 2223 2224 By default writes to the python root logger. To change 2225 this behavior either subclass Application and override this method, 2226 or pass a function in the application settings dictionary as 2227 ``log_function``. 2228 """ 2229 if "log_function" in self.settings: 2230 self.settings["log_function"](handler) 2231 return 2232 if handler.get_status() < 400: 2233 log_method = access_log.info 2234 elif handler.get_status() < 500: 2235 log_method = access_log.warning 2236 else: 2237 log_method = access_log.error 2238 request_time = 1000.0 * handler.request.request_time() 2239 log_method( 2240 "%d %s %.2fms", 2241 handler.get_status(), 2242 handler._request_summary(), 2243 request_time, 2244 ) 2245 2246 2247class _HandlerDelegate(httputil.HTTPMessageDelegate): 2248 def __init__( 2249 self, 2250 application: Application, 2251 request: httputil.HTTPServerRequest, 2252 handler_class: Type[RequestHandler], 2253 handler_kwargs: Optional[Dict[str, Any]], 2254 path_args: Optional[List[bytes]], 2255 path_kwargs: Optional[Dict[str, bytes]], 2256 ) -> None: 2257 self.application = application 2258 self.connection = request.connection 2259 self.request = request 2260 self.handler_class = handler_class 2261 self.handler_kwargs = handler_kwargs or {} 2262 self.path_args = path_args or [] 2263 self.path_kwargs = path_kwargs or {} 2264 self.chunks = [] # type: List[bytes] 2265 self.stream_request_body = _has_stream_request_body(self.handler_class) 2266 2267 def headers_received( 2268 self, 2269 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine], 2270 headers: httputil.HTTPHeaders, 2271 ) -> Optional[Awaitable[None]]: 2272 if self.stream_request_body: 2273 self.request._body_future = Future() 2274 return self.execute() 2275 return None 2276 2277 def data_received(self, data: bytes) -> Optional[Awaitable[None]]: 2278 if self.stream_request_body: 2279 return self.handler.data_received(data) 2280 else: 2281 self.chunks.append(data) 2282 return None 2283 2284 def finish(self) -> None: 2285 if self.stream_request_body: 2286 future_set_result_unless_cancelled(self.request._body_future, None) 2287 else: 2288 self.request.body = b"".join(self.chunks) 2289 self.request._parse_body() 2290 self.execute() 2291 2292 def on_connection_close(self) -> None: 2293 if self.stream_request_body: 2294 self.handler.on_connection_close() 2295 else: 2296 self.chunks = None # type: ignore 2297 2298 def execute(self) -> Optional[Awaitable[None]]: 2299 # If template cache is disabled (usually in the debug mode), 2300 # re-compile templates and reload static files on every 2301 # request so you don't need to restart to see changes 2302 if not self.application.settings.get("compiled_template_cache", True): 2303 with RequestHandler._template_loader_lock: 2304 for loader in RequestHandler._template_loaders.values(): 2305 loader.reset() 2306 if not self.application.settings.get("static_hash_cache", True): 2307 StaticFileHandler.reset() 2308 2309 self.handler = self.handler_class( 2310 self.application, self.request, **self.handler_kwargs 2311 ) 2312 transforms = [t(self.request) for t in self.application.transforms] 2313 2314 if self.stream_request_body: 2315 self.handler._prepared_future = Future() 2316 # Note that if an exception escapes handler._execute it will be 2317 # trapped in the Future it returns (which we are ignoring here, 2318 # leaving it to be logged when the Future is GC'd). 2319 # However, that shouldn't happen because _execute has a blanket 2320 # except handler, and we cannot easily access the IOLoop here to 2321 # call add_future (because of the requirement to remain compatible 2322 # with WSGI) 2323 fut = gen.convert_yielded( 2324 self.handler._execute(transforms, *self.path_args, **self.path_kwargs) 2325 ) 2326 fut.add_done_callback(lambda f: f.result()) 2327 # If we are streaming the request body, then execute() is finished 2328 # when the handler has prepared to receive the body. If not, 2329 # it doesn't matter when execute() finishes (so we return None) 2330 return self.handler._prepared_future 2331 2332 2333class HTTPError(Exception): 2334 """An exception that will turn into an HTTP error response. 2335 2336 Raising an `HTTPError` is a convenient alternative to calling 2337 `RequestHandler.send_error` since it automatically ends the 2338 current function. 2339 2340 To customize the response sent with an `HTTPError`, override 2341 `RequestHandler.write_error`. 2342 2343 :arg int status_code: HTTP status code. Must be listed in 2344 `httplib.responses <http.client.responses>` unless the ``reason`` 2345 keyword argument is given. 2346 :arg str log_message: Message to be written to the log for this error 2347 (will not be shown to the user unless the `Application` is in debug 2348 mode). May contain ``%s``-style placeholders, which will be filled 2349 in with remaining positional parameters. 2350 :arg str reason: Keyword-only argument. The HTTP "reason" phrase 2351 to pass in the status line along with ``status_code``. Normally 2352 determined automatically from ``status_code``, but can be used 2353 to use a non-standard numeric code. 2354 """ 2355 2356 def __init__( 2357 self, 2358 status_code: int = 500, 2359 log_message: Optional[str] = None, 2360 *args: Any, 2361 **kwargs: Any 2362 ) -> None: 2363 self.status_code = status_code 2364 self.log_message = log_message 2365 self.args = args 2366 self.reason = kwargs.get("reason", None) 2367 if log_message and not args: 2368 self.log_message = log_message.replace("%", "%%") 2369 2370 def __str__(self) -> str: 2371 message = "HTTP %d: %s" % ( 2372 self.status_code, 2373 self.reason or httputil.responses.get(self.status_code, "Unknown"), 2374 ) 2375 if self.log_message: 2376 return message + " (" + (self.log_message % self.args) + ")" 2377 else: 2378 return message 2379 2380 2381class Finish(Exception): 2382 """An exception that ends the request without producing an error response. 2383 2384 When `Finish` is raised in a `RequestHandler`, the request will 2385 end (calling `RequestHandler.finish` if it hasn't already been 2386 called), but the error-handling methods (including 2387 `RequestHandler.write_error`) will not be called. 2388 2389 If `Finish()` was created with no arguments, the pending response 2390 will be sent as-is. If `Finish()` was given an argument, that 2391 argument will be passed to `RequestHandler.finish()`. 2392 2393 This can be a more convenient way to implement custom error pages 2394 than overriding ``write_error`` (especially in library code):: 2395 2396 if self.current_user is None: 2397 self.set_status(401) 2398 self.set_header('WWW-Authenticate', 'Basic realm="something"') 2399 raise Finish() 2400 2401 .. versionchanged:: 4.3 2402 Arguments passed to ``Finish()`` will be passed on to 2403 `RequestHandler.finish`. 2404 """ 2405 2406 pass 2407 2408 2409class MissingArgumentError(HTTPError): 2410 """Exception raised by `RequestHandler.get_argument`. 2411 2412 This is a subclass of `HTTPError`, so if it is uncaught a 400 response 2413 code will be used instead of 500 (and a stack trace will not be logged). 2414 2415 .. versionadded:: 3.1 2416 """ 2417 2418 def __init__(self, arg_name: str) -> None: 2419 super().__init__(400, "Missing argument %s" % arg_name) 2420 self.arg_name = arg_name 2421 2422 2423class ErrorHandler(RequestHandler): 2424 """Generates an error response with ``status_code`` for all requests.""" 2425 2426 def initialize(self, status_code: int) -> None: 2427 self.set_status(status_code) 2428 2429 def prepare(self) -> None: 2430 raise HTTPError(self._status_code) 2431 2432 def check_xsrf_cookie(self) -> None: 2433 # POSTs to an ErrorHandler don't actually have side effects, 2434 # so we don't need to check the xsrf token. This allows POSTs 2435 # to the wrong url to return a 404 instead of 403. 2436 pass 2437 2438 2439class RedirectHandler(RequestHandler): 2440 """Redirects the client to the given URL for all GET requests. 2441 2442 You should provide the keyword argument ``url`` to the handler, e.g.:: 2443 2444 application = web.Application([ 2445 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}), 2446 ]) 2447 2448 `RedirectHandler` supports regular expression substitutions. E.g., to 2449 swap the first and second parts of a path while preserving the remainder:: 2450 2451 application = web.Application([ 2452 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}), 2453 ]) 2454 2455 The final URL is formatted with `str.format` and the substrings that match 2456 the capturing groups. In the above example, a request to "/a/b/c" would be 2457 formatted like:: 2458 2459 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c" 2460 2461 Use Python's :ref:`format string syntax <formatstrings>` to customize how 2462 values are substituted. 2463 2464 .. versionchanged:: 4.5 2465 Added support for substitutions into the destination URL. 2466 2467 .. versionchanged:: 5.0 2468 If any query arguments are present, they will be copied to the 2469 destination URL. 2470 """ 2471 2472 def initialize(self, url: str, permanent: bool = True) -> None: 2473 self._url = url 2474 self._permanent = permanent 2475 2476 def get(self, *args: Any, **kwargs: Any) -> None: 2477 to_url = self._url.format(*args, **kwargs) 2478 if self.request.query_arguments: 2479 # TODO: figure out typing for the next line. 2480 to_url = httputil.url_concat( 2481 to_url, 2482 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore 2483 ) 2484 self.redirect(to_url, permanent=self._permanent) 2485 2486 2487class StaticFileHandler(RequestHandler): 2488 """A simple handler that can serve static content from a directory. 2489 2490 A `StaticFileHandler` is configured automatically if you pass the 2491 ``static_path`` keyword argument to `Application`. This handler 2492 can be customized with the ``static_url_prefix``, ``static_handler_class``, 2493 and ``static_handler_args`` settings. 2494 2495 To map an additional path to this handler for a static data directory 2496 you would add a line to your application like:: 2497 2498 application = web.Application([ 2499 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}), 2500 ]) 2501 2502 The handler constructor requires a ``path`` argument, which specifies the 2503 local root directory of the content to be served. 2504 2505 Note that a capture group in the regex is required to parse the value for 2506 the ``path`` argument to the get() method (different than the constructor 2507 argument above); see `URLSpec` for details. 2508 2509 To serve a file like ``index.html`` automatically when a directory is 2510 requested, set ``static_handler_args=dict(default_filename="index.html")`` 2511 in your application settings, or add ``default_filename`` as an initializer 2512 argument for your ``StaticFileHandler``. 2513 2514 To maximize the effectiveness of browser caching, this class supports 2515 versioned urls (by default using the argument ``?v=``). If a version 2516 is given, we instruct the browser to cache this file indefinitely. 2517 `make_static_url` (also available as `RequestHandler.static_url`) can 2518 be used to construct a versioned url. 2519 2520 This handler is intended primarily for use in development and light-duty 2521 file serving; for heavy traffic it will be more efficient to use 2522 a dedicated static file server (such as nginx or Apache). We support 2523 the HTTP ``Accept-Ranges`` mechanism to return partial content (because 2524 some browsers require this functionality to be present to seek in 2525 HTML5 audio or video). 2526 2527 **Subclassing notes** 2528 2529 This class is designed to be extensible by subclassing, but because 2530 of the way static urls are generated with class methods rather than 2531 instance methods, the inheritance patterns are somewhat unusual. 2532 Be sure to use the ``@classmethod`` decorator when overriding a 2533 class method. Instance methods may use the attributes ``self.path`` 2534 ``self.absolute_path``, and ``self.modified``. 2535 2536 Subclasses should only override methods discussed in this section; 2537 overriding other methods is error-prone. Overriding 2538 ``StaticFileHandler.get`` is particularly problematic due to the 2539 tight coupling with ``compute_etag`` and other methods. 2540 2541 To change the way static urls are generated (e.g. to match the behavior 2542 of another server or CDN), override `make_static_url`, `parse_url_path`, 2543 `get_cache_time`, and/or `get_version`. 2544 2545 To replace all interaction with the filesystem (e.g. to serve 2546 static content from a database), override `get_content`, 2547 `get_content_size`, `get_modified_time`, `get_absolute_path`, and 2548 `validate_absolute_path`. 2549 2550 .. versionchanged:: 3.1 2551 Many of the methods for subclasses were added in Tornado 3.1. 2552 """ 2553 2554 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years 2555 2556 _static_hashes = {} # type: Dict[str, Optional[str]] 2557 _lock = threading.Lock() # protects _static_hashes 2558 2559 def initialize(self, path: str, default_filename: Optional[str] = None) -> None: 2560 self.root = path 2561 self.default_filename = default_filename 2562 2563 @classmethod 2564 def reset(cls) -> None: 2565 with cls._lock: 2566 cls._static_hashes = {} 2567 2568 def head(self, path: str) -> Awaitable[None]: 2569 return self.get(path, include_body=False) 2570 2571 async def get(self, path: str, include_body: bool = True) -> None: 2572 # Set up our path instance variables. 2573 self.path = self.parse_url_path(path) 2574 del path # make sure we don't refer to path instead of self.path again 2575 absolute_path = self.get_absolute_path(self.root, self.path) 2576 self.absolute_path = self.validate_absolute_path(self.root, absolute_path) 2577 if self.absolute_path is None: 2578 return 2579 2580 self.modified = self.get_modified_time() 2581 self.set_headers() 2582 2583 if self.should_return_304(): 2584 self.set_status(304) 2585 return 2586 2587 request_range = None 2588 range_header = self.request.headers.get("Range") 2589 if range_header: 2590 # As per RFC 2616 14.16, if an invalid Range header is specified, 2591 # the request will be treated as if the header didn't exist. 2592 request_range = httputil._parse_request_range(range_header) 2593 2594 size = self.get_content_size() 2595 if request_range: 2596 start, end = request_range 2597 if start is not None and start < 0: 2598 start += size 2599 if start < 0: 2600 start = 0 2601 if ( 2602 start is not None 2603 and (start >= size or (end is not None and start >= end)) 2604 ) or end == 0: 2605 # As per RFC 2616 14.35.1, a range is not satisfiable only: if 2606 # the first requested byte is equal to or greater than the 2607 # content, or when a suffix with length 0 is specified. 2608 # https://tools.ietf.org/html/rfc7233#section-2.1 2609 # A byte-range-spec is invalid if the last-byte-pos value is present 2610 # and less than the first-byte-pos. 2611 self.set_status(416) # Range Not Satisfiable 2612 self.set_header("Content-Type", "text/plain") 2613 self.set_header("Content-Range", "bytes */%s" % (size,)) 2614 return 2615 if end is not None and end > size: 2616 # Clients sometimes blindly use a large range to limit their 2617 # download size; cap the endpoint at the actual file size. 2618 end = size 2619 # Note: only return HTTP 206 if less than the entire range has been 2620 # requested. Not only is this semantically correct, but Chrome 2621 # refuses to play audio if it gets an HTTP 206 in response to 2622 # ``Range: bytes=0-``. 2623 if size != (end or size) - (start or 0): 2624 self.set_status(206) # Partial Content 2625 self.set_header( 2626 "Content-Range", httputil._get_content_range(start, end, size) 2627 ) 2628 else: 2629 start = end = None 2630 2631 if start is not None and end is not None: 2632 content_length = end - start 2633 elif end is not None: 2634 content_length = end 2635 elif start is not None: 2636 content_length = size - start 2637 else: 2638 content_length = size 2639 self.set_header("Content-Length", content_length) 2640 2641 if include_body: 2642 content = self.get_content(self.absolute_path, start, end) 2643 if isinstance(content, bytes): 2644 content = [content] 2645 for chunk in content: 2646 try: 2647 self.write(chunk) 2648 await self.flush() 2649 except iostream.StreamClosedError: 2650 return 2651 else: 2652 assert self.request.method == "HEAD" 2653 2654 def compute_etag(self) -> Optional[str]: 2655 """Sets the ``Etag`` header based on static url version. 2656 2657 This allows efficient ``If-None-Match`` checks against cached 2658 versions, and sends the correct ``Etag`` for a partial response 2659 (i.e. the same ``Etag`` as the full file). 2660 2661 .. versionadded:: 3.1 2662 """ 2663 assert self.absolute_path is not None 2664 version_hash = self._get_cached_version(self.absolute_path) 2665 if not version_hash: 2666 return None 2667 return '"%s"' % (version_hash,) 2668 2669 def set_headers(self) -> None: 2670 """Sets the content and caching headers on the response. 2671 2672 .. versionadded:: 3.1 2673 """ 2674 self.set_header("Accept-Ranges", "bytes") 2675 self.set_etag_header() 2676 2677 if self.modified is not None: 2678 self.set_header("Last-Modified", self.modified) 2679 2680 content_type = self.get_content_type() 2681 if content_type: 2682 self.set_header("Content-Type", content_type) 2683 2684 cache_time = self.get_cache_time(self.path, self.modified, content_type) 2685 if cache_time > 0: 2686 self.set_header( 2687 "Expires", 2688 datetime.datetime.utcnow() + datetime.timedelta(seconds=cache_time), 2689 ) 2690 self.set_header("Cache-Control", "max-age=" + str(cache_time)) 2691 2692 self.set_extra_headers(self.path) 2693 2694 def should_return_304(self) -> bool: 2695 """Returns True if the headers indicate that we should return 304. 2696 2697 .. versionadded:: 3.1 2698 """ 2699 # If client sent If-None-Match, use it, ignore If-Modified-Since 2700 if self.request.headers.get("If-None-Match"): 2701 return self.check_etag_header() 2702 2703 # Check the If-Modified-Since, and don't send the result if the 2704 # content has not been modified 2705 ims_value = self.request.headers.get("If-Modified-Since") 2706 if ims_value is not None: 2707 date_tuple = email.utils.parsedate(ims_value) 2708 if date_tuple is not None: 2709 if_since = datetime.datetime(*date_tuple[:6]) 2710 assert self.modified is not None 2711 if if_since >= self.modified: 2712 return True 2713 2714 return False 2715 2716 @classmethod 2717 def get_absolute_path(cls, root: str, path: str) -> str: 2718 """Returns the absolute location of ``path`` relative to ``root``. 2719 2720 ``root`` is the path configured for this `StaticFileHandler` 2721 (in most cases the ``static_path`` `Application` setting). 2722 2723 This class method may be overridden in subclasses. By default 2724 it returns a filesystem path, but other strings may be used 2725 as long as they are unique and understood by the subclass's 2726 overridden `get_content`. 2727 2728 .. versionadded:: 3.1 2729 """ 2730 abspath = os.path.abspath(os.path.join(root, path)) 2731 return abspath 2732 2733 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]: 2734 """Validate and return the absolute path. 2735 2736 ``root`` is the configured path for the `StaticFileHandler`, 2737 and ``path`` is the result of `get_absolute_path` 2738 2739 This is an instance method called during request processing, 2740 so it may raise `HTTPError` or use methods like 2741 `RequestHandler.redirect` (return None after redirecting to 2742 halt further processing). This is where 404 errors for missing files 2743 are generated. 2744 2745 This method may modify the path before returning it, but note that 2746 any such modifications will not be understood by `make_static_url`. 2747 2748 In instance methods, this method's result is available as 2749 ``self.absolute_path``. 2750 2751 .. versionadded:: 3.1 2752 """ 2753 # os.path.abspath strips a trailing /. 2754 # We must add it back to `root` so that we only match files 2755 # in a directory named `root` instead of files starting with 2756 # that prefix. 2757 root = os.path.abspath(root) 2758 if not root.endswith(os.path.sep): 2759 # abspath always removes a trailing slash, except when 2760 # root is '/'. This is an unusual case, but several projects 2761 # have independently discovered this technique to disable 2762 # Tornado's path validation and (hopefully) do their own, 2763 # so we need to support it. 2764 root += os.path.sep 2765 # The trailing slash also needs to be temporarily added back 2766 # the requested path so a request to root/ will match. 2767 if not (absolute_path + os.path.sep).startswith(root): 2768 raise HTTPError(403, "%s is not in root static directory", self.path) 2769 if os.path.isdir(absolute_path) and self.default_filename is not None: 2770 # need to look at the request.path here for when path is empty 2771 # but there is some prefix to the path that was already 2772 # trimmed by the routing 2773 if not self.request.path.endswith("/"): 2774 self.redirect(self.request.path + "/", permanent=True) 2775 return None 2776 absolute_path = os.path.join(absolute_path, self.default_filename) 2777 if not os.path.exists(absolute_path): 2778 raise HTTPError(404) 2779 if not os.path.isfile(absolute_path): 2780 raise HTTPError(403, "%s is not a file", self.path) 2781 return absolute_path 2782 2783 @classmethod 2784 def get_content( 2785 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None 2786 ) -> Generator[bytes, None, None]: 2787 """Retrieve the content of the requested resource which is located 2788 at the given absolute path. 2789 2790 This class method may be overridden by subclasses. Note that its 2791 signature is different from other overridable class methods 2792 (no ``settings`` argument); this is deliberate to ensure that 2793 ``abspath`` is able to stand on its own as a cache key. 2794 2795 This method should either return a byte string or an iterator 2796 of byte strings. The latter is preferred for large files 2797 as it helps reduce memory fragmentation. 2798 2799 .. versionadded:: 3.1 2800 """ 2801 with open(abspath, "rb") as file: 2802 if start is not None: 2803 file.seek(start) 2804 if end is not None: 2805 remaining = end - (start or 0) # type: Optional[int] 2806 else: 2807 remaining = None 2808 while True: 2809 chunk_size = 64 * 1024 2810 if remaining is not None and remaining < chunk_size: 2811 chunk_size = remaining 2812 chunk = file.read(chunk_size) 2813 if chunk: 2814 if remaining is not None: 2815 remaining -= len(chunk) 2816 yield chunk 2817 else: 2818 if remaining is not None: 2819 assert remaining == 0 2820 return 2821 2822 @classmethod 2823 def get_content_version(cls, abspath: str) -> str: 2824 """Returns a version string for the resource at the given path. 2825 2826 This class method may be overridden by subclasses. The 2827 default implementation is a SHA-512 hash of the file's contents. 2828 2829 .. versionadded:: 3.1 2830 """ 2831 data = cls.get_content(abspath) 2832 hasher = hashlib.sha512() 2833 if isinstance(data, bytes): 2834 hasher.update(data) 2835 else: 2836 for chunk in data: 2837 hasher.update(chunk) 2838 return hasher.hexdigest() 2839 2840 def _stat(self) -> os.stat_result: 2841 assert self.absolute_path is not None 2842 if not hasattr(self, "_stat_result"): 2843 self._stat_result = os.stat(self.absolute_path) 2844 return self._stat_result 2845 2846 def get_content_size(self) -> int: 2847 """Retrieve the total size of the resource at the given path. 2848 2849 This method may be overridden by subclasses. 2850 2851 .. versionadded:: 3.1 2852 2853 .. versionchanged:: 4.0 2854 This method is now always called, instead of only when 2855 partial results are requested. 2856 """ 2857 stat_result = self._stat() 2858 return stat_result.st_size 2859 2860 def get_modified_time(self) -> Optional[datetime.datetime]: 2861 """Returns the time that ``self.absolute_path`` was last modified. 2862 2863 May be overridden in subclasses. Should return a `~datetime.datetime` 2864 object or None. 2865 2866 .. versionadded:: 3.1 2867 """ 2868 stat_result = self._stat() 2869 # NOTE: Historically, this used stat_result[stat.ST_MTIME], 2870 # which truncates the fractional portion of the timestamp. It 2871 # was changed from that form to stat_result.st_mtime to 2872 # satisfy mypy (which disallows the bracket operator), but the 2873 # latter form returns a float instead of an int. For 2874 # consistency with the past (and because we have a unit test 2875 # that relies on this), we truncate the float here, although 2876 # I'm not sure that's the right thing to do. 2877 modified = datetime.datetime.utcfromtimestamp(int(stat_result.st_mtime)) 2878 return modified 2879 2880 def get_content_type(self) -> str: 2881 """Returns the ``Content-Type`` header to be used for this request. 2882 2883 .. versionadded:: 3.1 2884 """ 2885 assert self.absolute_path is not None 2886 mime_type, encoding = mimetypes.guess_type(self.absolute_path) 2887 # per RFC 6713, use the appropriate type for a gzip compressed file 2888 if encoding == "gzip": 2889 return "application/gzip" 2890 # As of 2015-07-21 there is no bzip2 encoding defined at 2891 # http://www.iana.org/assignments/media-types/media-types.xhtml 2892 # So for that (and any other encoding), use octet-stream. 2893 elif encoding is not None: 2894 return "application/octet-stream" 2895 elif mime_type is not None: 2896 return mime_type 2897 # if mime_type not detected, use application/octet-stream 2898 else: 2899 return "application/octet-stream" 2900 2901 def set_extra_headers(self, path: str) -> None: 2902 """For subclass to add extra headers to the response""" 2903 pass 2904 2905 def get_cache_time( 2906 self, path: str, modified: Optional[datetime.datetime], mime_type: str 2907 ) -> int: 2908 """Override to customize cache control behavior. 2909 2910 Return a positive number of seconds to make the result 2911 cacheable for that amount of time or 0 to mark resource as 2912 cacheable for an unspecified amount of time (subject to 2913 browser heuristics). 2914 2915 By default returns cache expiry of 10 years for resources requested 2916 with ``v`` argument. 2917 """ 2918 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0 2919 2920 @classmethod 2921 def make_static_url( 2922 cls, settings: Dict[str, Any], path: str, include_version: bool = True 2923 ) -> str: 2924 """Constructs a versioned url for the given path. 2925 2926 This method may be overridden in subclasses (but note that it 2927 is a class method rather than an instance method). Subclasses 2928 are only required to implement the signature 2929 ``make_static_url(cls, settings, path)``; other keyword 2930 arguments may be passed through `~RequestHandler.static_url` 2931 but are not standard. 2932 2933 ``settings`` is the `Application.settings` dictionary. ``path`` 2934 is the static path being requested. The url returned should be 2935 relative to the current host. 2936 2937 ``include_version`` determines whether the generated URL should 2938 include the query string containing the version hash of the 2939 file corresponding to the given ``path``. 2940 2941 """ 2942 url = settings.get("static_url_prefix", "/static/") + path 2943 if not include_version: 2944 return url 2945 2946 version_hash = cls.get_version(settings, path) 2947 if not version_hash: 2948 return url 2949 2950 return "%s?v=%s" % (url, version_hash) 2951 2952 def parse_url_path(self, url_path: str) -> str: 2953 """Converts a static URL path into a filesystem path. 2954 2955 ``url_path`` is the path component of the URL with 2956 ``static_url_prefix`` removed. The return value should be 2957 filesystem path relative to ``static_path``. 2958 2959 This is the inverse of `make_static_url`. 2960 """ 2961 if os.path.sep != "/": 2962 url_path = url_path.replace("/", os.path.sep) 2963 return url_path 2964 2965 @classmethod 2966 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]: 2967 """Generate the version string to be used in static URLs. 2968 2969 ``settings`` is the `Application.settings` dictionary and ``path`` 2970 is the relative location of the requested asset on the filesystem. 2971 The returned value should be a string, or ``None`` if no version 2972 could be determined. 2973 2974 .. versionchanged:: 3.1 2975 This method was previously recommended for subclasses to override; 2976 `get_content_version` is now preferred as it allows the base 2977 class to handle caching of the result. 2978 """ 2979 abs_path = cls.get_absolute_path(settings["static_path"], path) 2980 return cls._get_cached_version(abs_path) 2981 2982 @classmethod 2983 def _get_cached_version(cls, abs_path: str) -> Optional[str]: 2984 with cls._lock: 2985 hashes = cls._static_hashes 2986 if abs_path not in hashes: 2987 try: 2988 hashes[abs_path] = cls.get_content_version(abs_path) 2989 except Exception: 2990 gen_log.error("Could not open static file %r", abs_path) 2991 hashes[abs_path] = None 2992 hsh = hashes.get(abs_path) 2993 if hsh: 2994 return hsh 2995 return None 2996 2997 2998class FallbackHandler(RequestHandler): 2999 """A `RequestHandler` that wraps another HTTP server callback. 3000 3001 The fallback is a callable object that accepts an 3002 `~.httputil.HTTPServerRequest`, such as an `Application` or 3003 `tornado.wsgi.WSGIContainer`. This is most useful to use both 3004 Tornado ``RequestHandlers`` and WSGI in the same server. Typical 3005 usage:: 3006 3007 wsgi_app = tornado.wsgi.WSGIContainer( 3008 django.core.handlers.wsgi.WSGIHandler()) 3009 application = tornado.web.Application([ 3010 (r"/foo", FooHandler), 3011 (r".*", FallbackHandler, dict(fallback=wsgi_app), 3012 ]) 3013 """ 3014 3015 def initialize( 3016 self, fallback: Callable[[httputil.HTTPServerRequest], None] 3017 ) -> None: 3018 self.fallback = fallback 3019 3020 def prepare(self) -> None: 3021 self.fallback(self.request) 3022 self._finished = True 3023 self.on_finish() 3024 3025 3026class OutputTransform(object): 3027 """A transform modifies the result of an HTTP request (e.g., GZip encoding) 3028 3029 Applications are not expected to create their own OutputTransforms 3030 or interact with them directly; the framework chooses which transforms 3031 (if any) to apply. 3032 """ 3033 3034 def __init__(self, request: httputil.HTTPServerRequest) -> None: 3035 pass 3036 3037 def transform_first_chunk( 3038 self, 3039 status_code: int, 3040 headers: httputil.HTTPHeaders, 3041 chunk: bytes, 3042 finishing: bool, 3043 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 3044 return status_code, headers, chunk 3045 3046 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 3047 return chunk 3048 3049 3050class GZipContentEncoding(OutputTransform): 3051 """Applies the gzip content encoding to the response. 3052 3053 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11 3054 3055 .. versionchanged:: 4.0 3056 Now compresses all mime types beginning with ``text/``, instead 3057 of just a whitelist. (the whitelist is still used for certain 3058 non-text mime types). 3059 """ 3060 3061 # Whitelist of compressible mime types (in addition to any types 3062 # beginning with "text/"). 3063 CONTENT_TYPES = set( 3064 [ 3065 "application/javascript", 3066 "application/x-javascript", 3067 "application/xml", 3068 "application/atom+xml", 3069 "application/json", 3070 "application/xhtml+xml", 3071 "image/svg+xml", 3072 ] 3073 ) 3074 # Python's GzipFile defaults to level 9, while most other gzip 3075 # tools (including gzip itself) default to 6, which is probably a 3076 # better CPU/size tradeoff. 3077 GZIP_LEVEL = 6 3078 # Responses that are too short are unlikely to benefit from gzipping 3079 # after considering the "Content-Encoding: gzip" header and the header 3080 # inside the gzip encoding. 3081 # Note that responses written in multiple chunks will be compressed 3082 # regardless of size. 3083 MIN_LENGTH = 1024 3084 3085 def __init__(self, request: httputil.HTTPServerRequest) -> None: 3086 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "") 3087 3088 def _compressible_type(self, ctype: str) -> bool: 3089 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES 3090 3091 def transform_first_chunk( 3092 self, 3093 status_code: int, 3094 headers: httputil.HTTPHeaders, 3095 chunk: bytes, 3096 finishing: bool, 3097 ) -> Tuple[int, httputil.HTTPHeaders, bytes]: 3098 # TODO: can/should this type be inherited from the superclass? 3099 if "Vary" in headers: 3100 headers["Vary"] += ", Accept-Encoding" 3101 else: 3102 headers["Vary"] = "Accept-Encoding" 3103 if self._gzipping: 3104 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0] 3105 self._gzipping = ( 3106 self._compressible_type(ctype) 3107 and (not finishing or len(chunk) >= self.MIN_LENGTH) 3108 and ("Content-Encoding" not in headers) 3109 ) 3110 if self._gzipping: 3111 headers["Content-Encoding"] = "gzip" 3112 self._gzip_value = BytesIO() 3113 self._gzip_file = gzip.GzipFile( 3114 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL 3115 ) 3116 chunk = self.transform_chunk(chunk, finishing) 3117 if "Content-Length" in headers: 3118 # The original content length is no longer correct. 3119 # If this is the last (and only) chunk, we can set the new 3120 # content-length; otherwise we remove it and fall back to 3121 # chunked encoding. 3122 if finishing: 3123 headers["Content-Length"] = str(len(chunk)) 3124 else: 3125 del headers["Content-Length"] 3126 return status_code, headers, chunk 3127 3128 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes: 3129 if self._gzipping: 3130 self._gzip_file.write(chunk) 3131 if finishing: 3132 self._gzip_file.close() 3133 else: 3134 self._gzip_file.flush() 3135 chunk = self._gzip_value.getvalue() 3136 self._gzip_value.truncate(0) 3137 self._gzip_value.seek(0) 3138 return chunk 3139 3140 3141def authenticated( 3142 method: Callable[..., Optional[Awaitable[None]]] 3143) -> Callable[..., Optional[Awaitable[None]]]: 3144 """Decorate methods with this to require that the user be logged in. 3145 3146 If the user is not logged in, they will be redirected to the configured 3147 `login url <RequestHandler.get_login_url>`. 3148 3149 If you configure a login url with a query parameter, Tornado will 3150 assume you know what you're doing and use it as-is. If not, it 3151 will add a `next` parameter so the login page knows where to send 3152 you once you're logged in. 3153 """ 3154 3155 @functools.wraps(method) 3156 def wrapper( # type: ignore 3157 self: RequestHandler, *args, **kwargs 3158 ) -> Optional[Awaitable[None]]: 3159 if not self.current_user: 3160 if self.request.method in ("GET", "HEAD"): 3161 url = self.get_login_url() 3162 if "?" not in url: 3163 if urllib.parse.urlsplit(url).scheme: 3164 # if login url is absolute, make next absolute too 3165 next_url = self.request.full_url() 3166 else: 3167 assert self.request.uri is not None 3168 next_url = self.request.uri 3169 url += "?" + urlencode(dict(next=next_url)) 3170 self.redirect(url) 3171 return None 3172 raise HTTPError(403) 3173 return method(self, *args, **kwargs) 3174 3175 return wrapper 3176 3177 3178class UIModule(object): 3179 """A re-usable, modular UI unit on a page. 3180 3181 UI modules often execute additional queries, and they can include 3182 additional CSS and JavaScript that will be included in the output 3183 page, which is automatically inserted on page render. 3184 3185 Subclasses of UIModule must override the `render` method. 3186 """ 3187 3188 def __init__(self, handler: RequestHandler) -> None: 3189 self.handler = handler 3190 self.request = handler.request 3191 self.ui = handler.ui 3192 self.locale = handler.locale 3193 3194 @property 3195 def current_user(self) -> Any: 3196 return self.handler.current_user 3197 3198 def render(self, *args: Any, **kwargs: Any) -> str: 3199 """Override in subclasses to return this module's output.""" 3200 raise NotImplementedError() 3201 3202 def embedded_javascript(self) -> Optional[str]: 3203 """Override to return a JavaScript string 3204 to be embedded in the page.""" 3205 return None 3206 3207 def javascript_files(self) -> Optional[Iterable[str]]: 3208 """Override to return a list of JavaScript files needed by this module. 3209 3210 If the return values are relative paths, they will be passed to 3211 `RequestHandler.static_url`; otherwise they will be used as-is. 3212 """ 3213 return None 3214 3215 def embedded_css(self) -> Optional[str]: 3216 """Override to return a CSS string 3217 that will be embedded in the page.""" 3218 return None 3219 3220 def css_files(self) -> Optional[Iterable[str]]: 3221 """Override to returns a list of CSS files required by this module. 3222 3223 If the return values are relative paths, they will be passed to 3224 `RequestHandler.static_url`; otherwise they will be used as-is. 3225 """ 3226 return None 3227 3228 def html_head(self) -> Optional[str]: 3229 """Override to return an HTML string that will be put in the <head/> 3230 element. 3231 """ 3232 return None 3233 3234 def html_body(self) -> Optional[str]: 3235 """Override to return an HTML string that will be put at the end of 3236 the <body/> element. 3237 """ 3238 return None 3239 3240 def render_string(self, path: str, **kwargs: Any) -> bytes: 3241 """Renders a template and returns it as a string.""" 3242 return self.handler.render_string(path, **kwargs) 3243 3244 3245class _linkify(UIModule): 3246 def render(self, text: str, **kwargs: Any) -> str: # type: ignore 3247 return escape.linkify(text, **kwargs) 3248 3249 3250class _xsrf_form_html(UIModule): 3251 def render(self) -> str: # type: ignore 3252 return self.handler.xsrf_form_html() 3253 3254 3255class TemplateModule(UIModule): 3256 """UIModule that simply renders the given template. 3257 3258 {% module Template("foo.html") %} is similar to {% include "foo.html" %}, 3259 but the module version gets its own namespace (with kwargs passed to 3260 Template()) instead of inheriting the outer template's namespace. 3261 3262 Templates rendered through this module also get access to UIModule's 3263 automatic JavaScript/CSS features. Simply call set_resources 3264 inside the template and give it keyword arguments corresponding to 3265 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }} 3266 Note that these resources are output once per template file, not once 3267 per instantiation of the template, so they must not depend on 3268 any arguments to the template. 3269 """ 3270 3271 def __init__(self, handler: RequestHandler) -> None: 3272 super().__init__(handler) 3273 # keep resources in both a list and a dict to preserve order 3274 self._resource_list = [] # type: List[Dict[str, Any]] 3275 self._resource_dict = {} # type: Dict[str, Dict[str, Any]] 3276 3277 def render(self, path: str, **kwargs: Any) -> bytes: # type: ignore 3278 def set_resources(**kwargs) -> str: # type: ignore 3279 if path not in self._resource_dict: 3280 self._resource_list.append(kwargs) 3281 self._resource_dict[path] = kwargs 3282 else: 3283 if self._resource_dict[path] != kwargs: 3284 raise ValueError( 3285 "set_resources called with different " 3286 "resources for the same template" 3287 ) 3288 return "" 3289 3290 return self.render_string(path, set_resources=set_resources, **kwargs) 3291 3292 def _get_resources(self, key: str) -> Iterable[str]: 3293 return (r[key] for r in self._resource_list if key in r) 3294 3295 def embedded_javascript(self) -> str: 3296 return "\n".join(self._get_resources("embedded_javascript")) 3297 3298 def javascript_files(self) -> Iterable[str]: 3299 result = [] 3300 for f in self._get_resources("javascript_files"): 3301 if isinstance(f, (unicode_type, bytes)): 3302 result.append(f) 3303 else: 3304 result.extend(f) 3305 return result 3306 3307 def embedded_css(self) -> str: 3308 return "\n".join(self._get_resources("embedded_css")) 3309 3310 def css_files(self) -> Iterable[str]: 3311 result = [] 3312 for f in self._get_resources("css_files"): 3313 if isinstance(f, (unicode_type, bytes)): 3314 result.append(f) 3315 else: 3316 result.extend(f) 3317 return result 3318 3319 def html_head(self) -> str: 3320 return "".join(self._get_resources("html_head")) 3321 3322 def html_body(self) -> str: 3323 return "".join(self._get_resources("html_body")) 3324 3325 3326class _UIModuleNamespace(object): 3327 """Lazy namespace which creates UIModule proxies bound to a handler.""" 3328 3329 def __init__( 3330 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]] 3331 ) -> None: 3332 self.handler = handler 3333 self.ui_modules = ui_modules 3334 3335 def __getitem__(self, key: str) -> Callable[..., str]: 3336 return self.handler._ui_module(key, self.ui_modules[key]) 3337 3338 def __getattr__(self, key: str) -> Callable[..., str]: 3339 try: 3340 return self[key] 3341 except KeyError as e: 3342 raise AttributeError(str(e)) 3343 3344 3345def create_signed_value( 3346 secret: _CookieSecretTypes, 3347 name: str, 3348 value: Union[str, bytes], 3349 version: Optional[int] = None, 3350 clock: Optional[Callable[[], float]] = None, 3351 key_version: Optional[int] = None, 3352) -> bytes: 3353 if version is None: 3354 version = DEFAULT_SIGNED_VALUE_VERSION 3355 if clock is None: 3356 clock = time.time 3357 3358 timestamp = utf8(str(int(clock()))) 3359 value = base64.b64encode(utf8(value)) 3360 if version == 1: 3361 assert not isinstance(secret, dict) 3362 signature = _create_signature_v1(secret, name, value, timestamp) 3363 value = b"|".join([value, timestamp, signature]) 3364 return value 3365 elif version == 2: 3366 # The v2 format consists of a version number and a series of 3367 # length-prefixed fields "%d:%s", the last of which is a 3368 # signature, all separated by pipes. All numbers are in 3369 # decimal format with no leading zeros. The signature is an 3370 # HMAC-SHA256 of the whole string up to that point, including 3371 # the final pipe. 3372 # 3373 # The fields are: 3374 # - format version (i.e. 2; no length prefix) 3375 # - key version (integer, default is 0) 3376 # - timestamp (integer seconds since epoch) 3377 # - name (not encoded; assumed to be ~alphanumeric) 3378 # - value (base64-encoded) 3379 # - signature (hex-encoded; no length prefix) 3380 def format_field(s: Union[str, bytes]) -> bytes: 3381 return utf8("%d:" % len(s)) + utf8(s) 3382 3383 to_sign = b"|".join( 3384 [ 3385 b"2", 3386 format_field(str(key_version or 0)), 3387 format_field(timestamp), 3388 format_field(name), 3389 format_field(value), 3390 b"", 3391 ] 3392 ) 3393 3394 if isinstance(secret, dict): 3395 assert ( 3396 key_version is not None 3397 ), "Key version must be set when sign key dict is used" 3398 assert version >= 2, "Version must be at least 2 for key version support" 3399 secret = secret[key_version] 3400 3401 signature = _create_signature_v2(secret, to_sign) 3402 return to_sign + signature 3403 else: 3404 raise ValueError("Unsupported version %d" % version) 3405 3406 3407# A leading version number in decimal 3408# with no leading zeros, followed by a pipe. 3409_signed_value_version_re = re.compile(br"^([1-9][0-9]*)\|(.*)$") 3410 3411 3412def _get_version(value: bytes) -> int: 3413 # Figures out what version value is. Version 1 did not include an 3414 # explicit version field and started with arbitrary base64 data, 3415 # which makes this tricky. 3416 m = _signed_value_version_re.match(value) 3417 if m is None: 3418 version = 1 3419 else: 3420 try: 3421 version = int(m.group(1)) 3422 if version > 999: 3423 # Certain payloads from the version-less v1 format may 3424 # be parsed as valid integers. Due to base64 padding 3425 # restrictions, this can only happen for numbers whose 3426 # length is a multiple of 4, so we can treat all 3427 # numbers up to 999 as versions, and for the rest we 3428 # fall back to v1 format. 3429 version = 1 3430 except ValueError: 3431 version = 1 3432 return version 3433 3434 3435def decode_signed_value( 3436 secret: _CookieSecretTypes, 3437 name: str, 3438 value: Union[None, str, bytes], 3439 max_age_days: float = 31, 3440 clock: Optional[Callable[[], float]] = None, 3441 min_version: Optional[int] = None, 3442) -> Optional[bytes]: 3443 if clock is None: 3444 clock = time.time 3445 if min_version is None: 3446 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION 3447 if min_version > 2: 3448 raise ValueError("Unsupported min_version %d" % min_version) 3449 if not value: 3450 return None 3451 3452 value = utf8(value) 3453 version = _get_version(value) 3454 3455 if version < min_version: 3456 return None 3457 if version == 1: 3458 assert not isinstance(secret, dict) 3459 return _decode_signed_value_v1(secret, name, value, max_age_days, clock) 3460 elif version == 2: 3461 return _decode_signed_value_v2(secret, name, value, max_age_days, clock) 3462 else: 3463 return None 3464 3465 3466def _decode_signed_value_v1( 3467 secret: Union[str, bytes], 3468 name: str, 3469 value: bytes, 3470 max_age_days: float, 3471 clock: Callable[[], float], 3472) -> Optional[bytes]: 3473 parts = utf8(value).split(b"|") 3474 if len(parts) != 3: 3475 return None 3476 signature = _create_signature_v1(secret, name, parts[0], parts[1]) 3477 if not hmac.compare_digest(parts[2], signature): 3478 gen_log.warning("Invalid cookie signature %r", value) 3479 return None 3480 timestamp = int(parts[1]) 3481 if timestamp < clock() - max_age_days * 86400: 3482 gen_log.warning("Expired cookie %r", value) 3483 return None 3484 if timestamp > clock() + 31 * 86400: 3485 # _cookie_signature does not hash a delimiter between the 3486 # parts of the cookie, so an attacker could transfer trailing 3487 # digits from the payload to the timestamp without altering the 3488 # signature. For backwards compatibility, sanity-check timestamp 3489 # here instead of modifying _cookie_signature. 3490 gen_log.warning("Cookie timestamp in future; possible tampering %r", value) 3491 return None 3492 if parts[1].startswith(b"0"): 3493 gen_log.warning("Tampered cookie %r", value) 3494 return None 3495 try: 3496 return base64.b64decode(parts[0]) 3497 except Exception: 3498 return None 3499 3500 3501def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]: 3502 def _consume_field(s: bytes) -> Tuple[bytes, bytes]: 3503 length, _, rest = s.partition(b":") 3504 n = int(length) 3505 field_value = rest[:n] 3506 # In python 3, indexing bytes returns small integers; we must 3507 # use a slice to get a byte string as in python 2. 3508 if rest[n : n + 1] != b"|": 3509 raise ValueError("malformed v2 signed value field") 3510 rest = rest[n + 1 :] 3511 return field_value, rest 3512 3513 rest = value[2:] # remove version number 3514 key_version, rest = _consume_field(rest) 3515 timestamp, rest = _consume_field(rest) 3516 name_field, rest = _consume_field(rest) 3517 value_field, passed_sig = _consume_field(rest) 3518 return int(key_version), timestamp, name_field, value_field, passed_sig 3519 3520 3521def _decode_signed_value_v2( 3522 secret: _CookieSecretTypes, 3523 name: str, 3524 value: bytes, 3525 max_age_days: float, 3526 clock: Callable[[], float], 3527) -> Optional[bytes]: 3528 try: 3529 ( 3530 key_version, 3531 timestamp_bytes, 3532 name_field, 3533 value_field, 3534 passed_sig, 3535 ) = _decode_fields_v2(value) 3536 except ValueError: 3537 return None 3538 signed_string = value[: -len(passed_sig)] 3539 3540 if isinstance(secret, dict): 3541 try: 3542 secret = secret[key_version] 3543 except KeyError: 3544 return None 3545 3546 expected_sig = _create_signature_v2(secret, signed_string) 3547 if not hmac.compare_digest(passed_sig, expected_sig): 3548 return None 3549 if name_field != utf8(name): 3550 return None 3551 timestamp = int(timestamp_bytes) 3552 if timestamp < clock() - max_age_days * 86400: 3553 # The signature has expired. 3554 return None 3555 try: 3556 return base64.b64decode(value_field) 3557 except Exception: 3558 return None 3559 3560 3561def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]: 3562 value = utf8(value) 3563 version = _get_version(value) 3564 if version < 2: 3565 return None 3566 try: 3567 key_version, _, _, _, _ = _decode_fields_v2(value) 3568 except ValueError: 3569 return None 3570 3571 return key_version 3572 3573 3574def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes: 3575 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1) 3576 for part in parts: 3577 hash.update(utf8(part)) 3578 return utf8(hash.hexdigest()) 3579 3580 3581def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes: 3582 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256) 3583 hash.update(utf8(s)) 3584 return utf8(hash.hexdigest()) 3585 3586 3587def is_absolute(path: str) -> bool: 3588 return any(path.startswith(x) for x in ["/", "http:", "https:"]) 3589