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