Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/tornado/web.py: 21%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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.now(datetime.timezone.utc) + datetime.timedelta(
651 days=expires_days
652 )
653 if expires:
654 morsel["expires"] = httputil.format_timestamp(expires)
655 if path:
656 morsel["path"] = path
657 if max_age:
658 # Note change from _ to -.
659 morsel["max-age"] = str(max_age)
660 if httponly:
661 # Note that SimpleCookie ignores the value here. The presense of an
662 # httponly (or secure) key is treated as true.
663 morsel["httponly"] = True
664 if secure:
665 morsel["secure"] = True
666 if samesite:
667 morsel["samesite"] = samesite
668 if kwargs:
669 # The setitem interface is case-insensitive, so continue to support
670 # kwargs for backwards compatibility until we can remove deprecated
671 # features.
672 for k, v in kwargs.items():
673 morsel[k] = v
674 warnings.warn(
675 f"Deprecated arguments to set_cookie: {set(kwargs.keys())} "
676 "(should be lowercase)",
677 DeprecationWarning,
678 )
680 def clear_cookie(self, name: str, **kwargs: Any) -> None:
681 """Deletes the cookie with the given name.
683 This method accepts the same arguments as `set_cookie`, except for
684 ``expires`` and ``max_age``. Clearing a cookie requires the same
685 ``domain`` and ``path`` arguments as when it was set. In some cases the
686 ``samesite`` and ``secure`` arguments are also required to match. Other
687 arguments are ignored.
689 Similar to `set_cookie`, the effect of this method will not be
690 seen until the following request.
692 .. versionchanged:: 6.3
694 Now accepts all keyword arguments that ``set_cookie`` does.
695 The ``samesite`` and ``secure`` flags have recently become
696 required for clearing ``samesite="none"`` cookies.
697 """
698 for excluded_arg in ["expires", "max_age"]:
699 if excluded_arg in kwargs:
700 raise TypeError(
701 f"clear_cookie() got an unexpected keyword argument '{excluded_arg}'"
702 )
703 expires = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(
704 days=365
705 )
706 self.set_cookie(name, value="", expires=expires, **kwargs)
708 def clear_all_cookies(self, **kwargs: Any) -> None:
709 """Attempt to delete all the cookies the user sent with this request.
711 See `clear_cookie` for more information on keyword arguments. Due to
712 limitations of the cookie protocol, it is impossible to determine on the
713 server side which values are necessary for the ``domain``, ``path``,
714 ``samesite``, or ``secure`` arguments, this method can only be
715 successful if you consistently use the same values for these arguments
716 when setting cookies.
718 Similar to `set_cookie`, the effect of this method will not be seen
719 until the following request.
721 .. versionchanged:: 3.2
723 Added the ``path`` and ``domain`` parameters.
725 .. versionchanged:: 6.3
727 Now accepts all keyword arguments that ``set_cookie`` does.
729 .. deprecated:: 6.3
731 The increasingly complex rules governing cookies have made it
732 impossible for a ``clear_all_cookies`` method to work reliably
733 since all we know about cookies are their names. Applications
734 should generally use ``clear_cookie`` one at a time instead.
735 """
736 for name in self.request.cookies:
737 self.clear_cookie(name, **kwargs)
739 def set_signed_cookie(
740 self,
741 name: str,
742 value: Union[str, bytes],
743 expires_days: Optional[float] = 30,
744 version: Optional[int] = None,
745 **kwargs: Any,
746 ) -> None:
747 """Signs and timestamps a cookie so it cannot be forged.
749 You must specify the ``cookie_secret`` setting in your Application
750 to use this method. It should be a long, random sequence of bytes
751 to be used as the HMAC secret for the signature.
753 To read a cookie set with this method, use `get_signed_cookie()`.
755 Note that the ``expires_days`` parameter sets the lifetime of the
756 cookie in the browser, but is independent of the ``max_age_days``
757 parameter to `get_signed_cookie`.
758 A value of None limits the lifetime to the current browser session.
760 Secure cookies may contain arbitrary byte values, not just unicode
761 strings (unlike regular cookies)
763 Similar to `set_cookie`, the effect of this method will not be
764 seen until the following request.
766 .. versionchanged:: 3.2.1
768 Added the ``version`` argument. Introduced cookie version 2
769 and made it the default.
771 .. versionchanged:: 6.3
773 Renamed from ``set_secure_cookie`` to ``set_signed_cookie`` to
774 avoid confusion with other uses of "secure" in cookie attributes
775 and prefixes. The old name remains as an alias.
776 """
777 self.set_cookie(
778 name,
779 self.create_signed_value(name, value, version=version),
780 expires_days=expires_days,
781 **kwargs,
782 )
784 set_secure_cookie = set_signed_cookie
786 def create_signed_value(
787 self, name: str, value: Union[str, bytes], version: Optional[int] = None
788 ) -> bytes:
789 """Signs and timestamps a string so it cannot be forged.
791 Normally used via set_signed_cookie, but provided as a separate
792 method for non-cookie uses. To decode a value not stored
793 as a cookie use the optional value argument to get_signed_cookie.
795 .. versionchanged:: 3.2.1
797 Added the ``version`` argument. Introduced cookie version 2
798 and made it the default.
799 """
800 self.require_setting("cookie_secret", "secure cookies")
801 secret = self.application.settings["cookie_secret"]
802 key_version = None
803 if isinstance(secret, dict):
804 if self.application.settings.get("key_version") is None:
805 raise Exception("key_version setting must be used for secret_key dicts")
806 key_version = self.application.settings["key_version"]
808 return create_signed_value(
809 secret, name, value, version=version, key_version=key_version
810 )
812 def get_signed_cookie(
813 self,
814 name: str,
815 value: Optional[str] = None,
816 max_age_days: float = 31,
817 min_version: Optional[int] = None,
818 ) -> Optional[bytes]:
819 """Returns the given signed cookie if it validates, or None.
821 The decoded cookie value is returned as a byte string (unlike
822 `get_cookie`).
824 Similar to `get_cookie`, this method only returns cookies that
825 were present in the request. It does not see outgoing cookies set by
826 `set_signed_cookie` in this handler.
828 .. versionchanged:: 3.2.1
830 Added the ``min_version`` argument. Introduced cookie version 2;
831 both versions 1 and 2 are accepted by default.
833 .. versionchanged:: 6.3
835 Renamed from ``get_secure_cookie`` to ``get_signed_cookie`` to
836 avoid confusion with other uses of "secure" in cookie attributes
837 and prefixes. The old name remains as an alias.
839 """
840 self.require_setting("cookie_secret", "secure cookies")
841 if value is None:
842 value = self.get_cookie(name)
843 return decode_signed_value(
844 self.application.settings["cookie_secret"],
845 name,
846 value,
847 max_age_days=max_age_days,
848 min_version=min_version,
849 )
851 get_secure_cookie = get_signed_cookie
853 def get_signed_cookie_key_version(
854 self, name: str, value: Optional[str] = None
855 ) -> Optional[int]:
856 """Returns the signing key version of the secure cookie.
858 The version is returned as int.
860 .. versionchanged:: 6.3
862 Renamed from ``get_secure_cookie_key_version`` to
863 ``set_signed_cookie_key_version`` to avoid confusion with other
864 uses of "secure" in cookie attributes and prefixes. The old name
865 remains as an alias.
867 """
868 self.require_setting("cookie_secret", "secure cookies")
869 if value is None:
870 value = self.get_cookie(name)
871 if value is None:
872 return None
873 return get_signature_key_version(value)
875 get_secure_cookie_key_version = get_signed_cookie_key_version
877 def redirect(
878 self, url: str, permanent: bool = False, status: Optional[int] = None
879 ) -> None:
880 """Sends a redirect to the given (optionally relative) URL.
882 If the ``status`` argument is specified, that value is used as the
883 HTTP status code; otherwise either 301 (permanent) or 302
884 (temporary) is chosen based on the ``permanent`` argument.
885 The default is 302 (temporary).
886 """
887 if self._headers_written:
888 raise Exception("Cannot redirect after headers have been written")
889 if status is None:
890 status = 301 if permanent else 302
891 else:
892 assert isinstance(status, int) and 300 <= status <= 399
893 self.set_status(status)
894 self.set_header("Location", utf8(url))
895 self.finish()
897 def write(self, chunk: Union[str, bytes, dict]) -> None:
898 """Writes the given chunk to the output buffer.
900 To write the output to the network, use the `flush()` method below.
902 If the given chunk is a dictionary, we write it as JSON and set
903 the Content-Type of the response to be ``application/json``.
904 (if you want to send JSON as a different ``Content-Type``, call
905 ``set_header`` *after* calling ``write()``).
907 Note that lists are not converted to JSON because of a potential
908 cross-site security vulnerability. All JSON output should be
909 wrapped in a dictionary. More details at
910 http://haacked.com/archive/2009/06/25/json-hijacking.aspx/ and
911 https://github.com/facebook/tornado/issues/1009
912 """
913 if self._finished:
914 raise RuntimeError("Cannot write() after finish()")
915 if not isinstance(chunk, (bytes, unicode_type, dict)):
916 message = "write() only accepts bytes, unicode, and dict objects"
917 if isinstance(chunk, list):
918 message += (
919 ". Lists not accepted for security reasons; see "
920 + "http://www.tornadoweb.org/en/stable/web.html#tornado.web.RequestHandler.write" # noqa: E501
921 )
922 raise TypeError(message)
923 if isinstance(chunk, dict):
924 chunk = escape.json_encode(chunk)
925 self.set_header("Content-Type", "application/json; charset=UTF-8")
926 chunk = utf8(chunk)
927 self._write_buffer.append(chunk)
929 def render(self, template_name: str, **kwargs: Any) -> "Future[None]":
930 """Renders the template with the given arguments as the response.
932 ``render()`` calls ``finish()``, so no other output methods can be called
933 after it.
935 Returns a `.Future` with the same semantics as the one returned by `finish`.
936 Awaiting this `.Future` is optional.
938 .. versionchanged:: 5.1
940 Now returns a `.Future` instead of ``None``.
941 """
942 if self._finished:
943 raise RuntimeError("Cannot render() after finish()")
944 html = self.render_string(template_name, **kwargs)
946 # Insert the additional JS and CSS added by the modules on the page
947 js_embed = []
948 js_files = []
949 css_embed = []
950 css_files = []
951 html_heads = []
952 html_bodies = []
953 for module in getattr(self, "_active_modules", {}).values():
954 embed_part = module.embedded_javascript()
955 if embed_part:
956 js_embed.append(utf8(embed_part))
957 file_part = module.javascript_files()
958 if file_part:
959 if isinstance(file_part, (unicode_type, bytes)):
960 js_files.append(_unicode(file_part))
961 else:
962 js_files.extend(file_part)
963 embed_part = module.embedded_css()
964 if embed_part:
965 css_embed.append(utf8(embed_part))
966 file_part = module.css_files()
967 if file_part:
968 if isinstance(file_part, (unicode_type, bytes)):
969 css_files.append(_unicode(file_part))
970 else:
971 css_files.extend(file_part)
972 head_part = module.html_head()
973 if head_part:
974 html_heads.append(utf8(head_part))
975 body_part = module.html_body()
976 if body_part:
977 html_bodies.append(utf8(body_part))
979 if js_files:
980 # Maintain order of JavaScript files given by modules
981 js = self.render_linked_js(js_files)
982 sloc = html.rindex(b"</body>")
983 html = html[:sloc] + utf8(js) + b"\n" + html[sloc:]
984 if js_embed:
985 js_bytes = self.render_embed_js(js_embed)
986 sloc = html.rindex(b"</body>")
987 html = html[:sloc] + js_bytes + b"\n" + html[sloc:]
988 if css_files:
989 css = self.render_linked_css(css_files)
990 hloc = html.index(b"</head>")
991 html = html[:hloc] + utf8(css) + b"\n" + html[hloc:]
992 if css_embed:
993 css_bytes = self.render_embed_css(css_embed)
994 hloc = html.index(b"</head>")
995 html = html[:hloc] + css_bytes + b"\n" + html[hloc:]
996 if html_heads:
997 hloc = html.index(b"</head>")
998 html = html[:hloc] + b"".join(html_heads) + b"\n" + html[hloc:]
999 if html_bodies:
1000 hloc = html.index(b"</body>")
1001 html = html[:hloc] + b"".join(html_bodies) + b"\n" + html[hloc:]
1002 return self.finish(html)
1004 def render_linked_js(self, js_files: Iterable[str]) -> str:
1005 """Default method used to render the final js links for the
1006 rendered webpage.
1008 Override this method in a sub-classed controller to change the output.
1009 """
1010 paths = []
1011 unique_paths = set() # type: Set[str]
1013 for path in js_files:
1014 if not is_absolute(path):
1015 path = self.static_url(path)
1016 if path not in unique_paths:
1017 paths.append(path)
1018 unique_paths.add(path)
1020 return "".join(
1021 '<script src="'
1022 + escape.xhtml_escape(p)
1023 + '" type="text/javascript"></script>'
1024 for p in paths
1025 )
1027 def render_embed_js(self, js_embed: Iterable[bytes]) -> bytes:
1028 """Default method used to render the final embedded js for the
1029 rendered webpage.
1031 Override this method in a sub-classed controller to change the output.
1032 """
1033 return (
1034 b'<script type="text/javascript">\n//<![CDATA[\n'
1035 + b"\n".join(js_embed)
1036 + b"\n//]]>\n</script>"
1037 )
1039 def render_linked_css(self, css_files: Iterable[str]) -> str:
1040 """Default method used to render the final css links for the
1041 rendered webpage.
1043 Override this method in a sub-classed controller to change the output.
1044 """
1045 paths = []
1046 unique_paths = set() # type: Set[str]
1048 for path in css_files:
1049 if not is_absolute(path):
1050 path = self.static_url(path)
1051 if path not in unique_paths:
1052 paths.append(path)
1053 unique_paths.add(path)
1055 return "".join(
1056 '<link href="' + escape.xhtml_escape(p) + '" '
1057 'type="text/css" rel="stylesheet"/>'
1058 for p in paths
1059 )
1061 def render_embed_css(self, css_embed: Iterable[bytes]) -> bytes:
1062 """Default method used to render the final embedded css for the
1063 rendered webpage.
1065 Override this method in a sub-classed controller to change the output.
1066 """
1067 return b'<style type="text/css">\n' + b"\n".join(css_embed) + b"\n</style>"
1069 def render_string(self, template_name: str, **kwargs: Any) -> bytes:
1070 """Generate the given template with the given arguments.
1072 We return the generated byte string (in utf8). To generate and
1073 write a template as a response, use render() above.
1074 """
1075 # If no template_path is specified, use the path of the calling file
1076 template_path = self.get_template_path()
1077 if not template_path:
1078 frame = sys._getframe(0)
1079 web_file = frame.f_code.co_filename
1080 while frame.f_code.co_filename == web_file and frame.f_back is not None:
1081 frame = frame.f_back
1082 assert frame.f_code.co_filename is not None
1083 template_path = os.path.dirname(frame.f_code.co_filename)
1084 with RequestHandler._template_loader_lock:
1085 if template_path not in RequestHandler._template_loaders:
1086 loader = self.create_template_loader(template_path)
1087 RequestHandler._template_loaders[template_path] = loader
1088 else:
1089 loader = RequestHandler._template_loaders[template_path]
1090 t = loader.load(template_name)
1091 namespace = self.get_template_namespace()
1092 namespace.update(kwargs)
1093 return t.generate(**namespace)
1095 def get_template_namespace(self) -> Dict[str, Any]:
1096 """Returns a dictionary to be used as the default template namespace.
1098 May be overridden by subclasses to add or modify values.
1100 The results of this method will be combined with additional
1101 defaults in the `tornado.template` module and keyword arguments
1102 to `render` or `render_string`.
1103 """
1104 namespace = dict(
1105 handler=self,
1106 request=self.request,
1107 current_user=self.current_user,
1108 locale=self.locale,
1109 _=self.locale.translate,
1110 pgettext=self.locale.pgettext,
1111 static_url=self.static_url,
1112 xsrf_form_html=self.xsrf_form_html,
1113 reverse_url=self.reverse_url,
1114 )
1115 namespace.update(self.ui)
1116 return namespace
1118 def create_template_loader(self, template_path: str) -> template.BaseLoader:
1119 """Returns a new template loader for the given path.
1121 May be overridden by subclasses. By default returns a
1122 directory-based loader on the given path, using the
1123 ``autoescape`` and ``template_whitespace`` application
1124 settings. If a ``template_loader`` application setting is
1125 supplied, uses that instead.
1126 """
1127 settings = self.application.settings
1128 if "template_loader" in settings:
1129 return settings["template_loader"]
1130 kwargs = {}
1131 if "autoescape" in settings:
1132 # autoescape=None means "no escaping", so we have to be sure
1133 # to only pass this kwarg if the user asked for it.
1134 kwargs["autoescape"] = settings["autoescape"]
1135 if "template_whitespace" in settings:
1136 kwargs["whitespace"] = settings["template_whitespace"]
1137 return template.Loader(template_path, **kwargs)
1139 def flush(self, include_footers: bool = False) -> "Future[None]":
1140 """Flushes the current output buffer to the network.
1142 .. versionchanged:: 4.0
1143 Now returns a `.Future` if no callback is given.
1145 .. versionchanged:: 6.0
1147 The ``callback`` argument was removed.
1148 """
1149 assert self.request.connection is not None
1150 chunk = b"".join(self._write_buffer)
1151 self._write_buffer = []
1152 if not self._headers_written:
1153 self._headers_written = True
1154 for transform in self._transforms:
1155 assert chunk is not None
1156 (
1157 self._status_code,
1158 self._headers,
1159 chunk,
1160 ) = transform.transform_first_chunk(
1161 self._status_code, self._headers, chunk, include_footers
1162 )
1163 # Ignore the chunk and only write the headers for HEAD requests
1164 if self.request.method == "HEAD":
1165 chunk = b""
1167 # Finalize the cookie headers (which have been stored in a side
1168 # object so an outgoing cookie could be overwritten before it
1169 # is sent).
1170 if hasattr(self, "_new_cookie"):
1171 for cookie in self._new_cookie.values():
1172 self.add_header("Set-Cookie", cookie.OutputString(None))
1174 start_line = httputil.ResponseStartLine("", self._status_code, self._reason)
1175 return self.request.connection.write_headers(
1176 start_line, self._headers, chunk
1177 )
1178 else:
1179 for transform in self._transforms:
1180 chunk = transform.transform_chunk(chunk, include_footers)
1181 # Ignore the chunk and only write the headers for HEAD requests
1182 if self.request.method != "HEAD":
1183 return self.request.connection.write(chunk)
1184 else:
1185 future = Future() # type: Future[None]
1186 future.set_result(None)
1187 return future
1189 def finish(self, chunk: Optional[Union[str, bytes, dict]] = None) -> "Future[None]":
1190 """Finishes this response, ending the HTTP request.
1192 Passing a ``chunk`` to ``finish()`` is equivalent to passing that
1193 chunk to ``write()`` and then calling ``finish()`` with no arguments.
1195 Returns a `.Future` which may optionally be awaited to track the sending
1196 of the response to the client. This `.Future` resolves when all the response
1197 data has been sent, and raises an error if the connection is closed before all
1198 data can be sent.
1200 .. versionchanged:: 5.1
1202 Now returns a `.Future` instead of ``None``.
1203 """
1204 if self._finished:
1205 raise RuntimeError("finish() called twice")
1207 if chunk is not None:
1208 self.write(chunk)
1210 # Automatically support ETags and add the Content-Length header if
1211 # we have not flushed any content yet.
1212 if not self._headers_written:
1213 if (
1214 self._status_code == 200
1215 and self.request.method in ("GET", "HEAD")
1216 and "Etag" not in self._headers
1217 ):
1218 self.set_etag_header()
1219 if self.check_etag_header():
1220 self._write_buffer = []
1221 self.set_status(304)
1222 if self._status_code in (204, 304) or (100 <= self._status_code < 200):
1223 assert not self._write_buffer, (
1224 "Cannot send body with %s" % self._status_code
1225 )
1226 self._clear_representation_headers()
1227 elif "Content-Length" not in self._headers:
1228 content_length = sum(len(part) for part in self._write_buffer)
1229 self.set_header("Content-Length", content_length)
1231 assert self.request.connection is not None
1232 # Now that the request is finished, clear the callback we
1233 # set on the HTTPConnection (which would otherwise prevent the
1234 # garbage collection of the RequestHandler when there
1235 # are keepalive connections)
1236 self.request.connection.set_close_callback(None) # type: ignore
1238 future = self.flush(include_footers=True)
1239 self.request.connection.finish()
1240 self._log()
1241 self._finished = True
1242 self.on_finish()
1243 self._break_cycles()
1244 return future
1246 def detach(self) -> iostream.IOStream:
1247 """Take control of the underlying stream.
1249 Returns the underlying `.IOStream` object and stops all
1250 further HTTP processing. Intended for implementing protocols
1251 like websockets that tunnel over an HTTP handshake.
1253 This method is only supported when HTTP/1.1 is used.
1255 .. versionadded:: 5.1
1256 """
1257 self._finished = True
1258 # TODO: add detach to HTTPConnection?
1259 return self.request.connection.detach() # type: ignore
1261 def _break_cycles(self) -> None:
1262 # Break up a reference cycle between this handler and the
1263 # _ui_module closures to allow for faster GC on CPython.
1264 self.ui = None # type: ignore
1266 def send_error(self, status_code: int = 500, **kwargs: Any) -> None:
1267 """Sends the given HTTP error code to the browser.
1269 If `flush()` has already been called, it is not possible to send
1270 an error, so this method will simply terminate the response.
1271 If output has been written but not yet flushed, it will be discarded
1272 and replaced with the error page.
1274 Override `write_error()` to customize the error page that is returned.
1275 Additional keyword arguments are passed through to `write_error`.
1276 """
1277 if self._headers_written:
1278 gen_log.error("Cannot send error response after headers written")
1279 if not self._finished:
1280 # If we get an error between writing headers and finishing,
1281 # we are unlikely to be able to finish due to a
1282 # Content-Length mismatch. Try anyway to release the
1283 # socket.
1284 try:
1285 self.finish()
1286 except Exception:
1287 gen_log.error("Failed to flush partial response", exc_info=True)
1288 return
1289 self.clear()
1291 reason = kwargs.get("reason")
1292 if "exc_info" in kwargs:
1293 exception = kwargs["exc_info"][1]
1294 if isinstance(exception, HTTPError) and exception.reason:
1295 reason = exception.reason
1296 self.set_status(status_code, reason=reason)
1297 try:
1298 self.write_error(status_code, **kwargs)
1299 except Exception:
1300 app_log.error("Uncaught exception in write_error", exc_info=True)
1301 if not self._finished:
1302 self.finish()
1304 def write_error(self, status_code: int, **kwargs: Any) -> None:
1305 """Override to implement custom error pages.
1307 ``write_error`` may call `write`, `render`, `set_header`, etc
1308 to produce output as usual.
1310 If this error was caused by an uncaught exception (including
1311 HTTPError), an ``exc_info`` triple will be available as
1312 ``kwargs["exc_info"]``. Note that this exception may not be
1313 the "current" exception for purposes of methods like
1314 ``sys.exc_info()`` or ``traceback.format_exc``.
1315 """
1316 if self.settings.get("serve_traceback") and "exc_info" in kwargs:
1317 # in debug mode, try to send a traceback
1318 self.set_header("Content-Type", "text/plain")
1319 for line in traceback.format_exception(*kwargs["exc_info"]):
1320 self.write(line)
1321 self.finish()
1322 else:
1323 self.finish(
1324 "<html><title>%(code)d: %(message)s</title>"
1325 "<body>%(code)d: %(message)s</body></html>"
1326 % {"code": status_code, "message": self._reason}
1327 )
1329 @property
1330 def locale(self) -> tornado.locale.Locale:
1331 """The locale for the current session.
1333 Determined by either `get_user_locale`, which you can override to
1334 set the locale based on, e.g., a user preference stored in a
1335 database, or `get_browser_locale`, which uses the ``Accept-Language``
1336 header.
1338 .. versionchanged: 4.1
1339 Added a property setter.
1340 """
1341 if not hasattr(self, "_locale"):
1342 loc = self.get_user_locale()
1343 if loc is not None:
1344 self._locale = loc
1345 else:
1346 self._locale = self.get_browser_locale()
1347 assert self._locale
1348 return self._locale
1350 @locale.setter
1351 def locale(self, value: tornado.locale.Locale) -> None:
1352 self._locale = value
1354 def get_user_locale(self) -> Optional[tornado.locale.Locale]:
1355 """Override to determine the locale from the authenticated user.
1357 If None is returned, we fall back to `get_browser_locale()`.
1359 This method should return a `tornado.locale.Locale` object,
1360 most likely obtained via a call like ``tornado.locale.get("en")``
1361 """
1362 return None
1364 def get_browser_locale(self, default: str = "en_US") -> tornado.locale.Locale:
1365 """Determines the user's locale from ``Accept-Language`` header.
1367 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4
1368 """
1369 if "Accept-Language" in self.request.headers:
1370 languages = self.request.headers["Accept-Language"].split(",")
1371 locales = []
1372 for language in languages:
1373 parts = language.strip().split(";")
1374 if len(parts) > 1 and parts[1].strip().startswith("q="):
1375 try:
1376 score = float(parts[1].strip()[2:])
1377 if score < 0:
1378 raise ValueError()
1379 except (ValueError, TypeError):
1380 score = 0.0
1381 else:
1382 score = 1.0
1383 if score > 0:
1384 locales.append((parts[0], score))
1385 if locales:
1386 locales.sort(key=lambda pair: pair[1], reverse=True)
1387 codes = [loc[0] for loc in locales]
1388 return locale.get(*codes)
1389 return locale.get(default)
1391 @property
1392 def current_user(self) -> Any:
1393 """The authenticated user for this request.
1395 This is set in one of two ways:
1397 * A subclass may override `get_current_user()`, which will be called
1398 automatically the first time ``self.current_user`` is accessed.
1399 `get_current_user()` will only be called once per request,
1400 and is cached for future access::
1402 def get_current_user(self):
1403 user_cookie = self.get_signed_cookie("user")
1404 if user_cookie:
1405 return json.loads(user_cookie)
1406 return None
1408 * It may be set as a normal variable, typically from an overridden
1409 `prepare()`::
1411 @gen.coroutine
1412 def prepare(self):
1413 user_id_cookie = self.get_signed_cookie("user_id")
1414 if user_id_cookie:
1415 self.current_user = yield load_user(user_id_cookie)
1417 Note that `prepare()` may be a coroutine while `get_current_user()`
1418 may not, so the latter form is necessary if loading the user requires
1419 asynchronous operations.
1421 The user object may be any type of the application's choosing.
1422 """
1423 if not hasattr(self, "_current_user"):
1424 self._current_user = self.get_current_user()
1425 return self._current_user
1427 @current_user.setter
1428 def current_user(self, value: Any) -> None:
1429 self._current_user = value
1431 def get_current_user(self) -> Any:
1432 """Override to determine the current user from, e.g., a cookie.
1434 This method may not be a coroutine.
1435 """
1436 return None
1438 def get_login_url(self) -> str:
1439 """Override to customize the login URL based on the request.
1441 By default, we use the ``login_url`` application setting.
1442 """
1443 self.require_setting("login_url", "@tornado.web.authenticated")
1444 return self.application.settings["login_url"]
1446 def get_template_path(self) -> Optional[str]:
1447 """Override to customize template path for each handler.
1449 By default, we use the ``template_path`` application setting.
1450 Return None to load templates relative to the calling file.
1451 """
1452 return self.application.settings.get("template_path")
1454 @property
1455 def xsrf_token(self) -> bytes:
1456 """The XSRF-prevention token for the current user/session.
1458 To prevent cross-site request forgery, we set an '_xsrf' cookie
1459 and include the same '_xsrf' value as an argument with all POST
1460 requests. If the two do not match, we reject the form submission
1461 as a potential forgery.
1463 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1465 This property is of type `bytes`, but it contains only ASCII
1466 characters. If a character string is required, there is no
1467 need to base64-encode it; just decode the byte string as
1468 UTF-8.
1470 .. versionchanged:: 3.2.2
1471 The xsrf token will now be have a random mask applied in every
1472 request, which makes it safe to include the token in pages
1473 that are compressed. See http://breachattack.com for more
1474 information on the issue fixed by this change. Old (version 1)
1475 cookies will be converted to version 2 when this method is called
1476 unless the ``xsrf_cookie_version`` `Application` setting is
1477 set to 1.
1479 .. versionchanged:: 4.3
1480 The ``xsrf_cookie_kwargs`` `Application` setting may be
1481 used to supply additional cookie options (which will be
1482 passed directly to `set_cookie`). For example,
1483 ``xsrf_cookie_kwargs=dict(httponly=True, secure=True)``
1484 will set the ``secure`` and ``httponly`` flags on the
1485 ``_xsrf`` cookie.
1486 """
1487 if not hasattr(self, "_xsrf_token"):
1488 version, token, timestamp = self._get_raw_xsrf_token()
1489 output_version = self.settings.get("xsrf_cookie_version", 2)
1490 cookie_kwargs = self.settings.get("xsrf_cookie_kwargs", {})
1491 if output_version == 1:
1492 self._xsrf_token = binascii.b2a_hex(token)
1493 elif output_version == 2:
1494 mask = os.urandom(4)
1495 self._xsrf_token = b"|".join(
1496 [
1497 b"2",
1498 binascii.b2a_hex(mask),
1499 binascii.b2a_hex(_websocket_mask(mask, token)),
1500 utf8(str(int(timestamp))),
1501 ]
1502 )
1503 else:
1504 raise ValueError("unknown xsrf cookie version %d", output_version)
1505 if version is None:
1506 if self.current_user and "expires_days" not in cookie_kwargs:
1507 cookie_kwargs["expires_days"] = 30
1508 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1509 self.set_cookie(cookie_name, self._xsrf_token, **cookie_kwargs)
1510 return self._xsrf_token
1512 def _get_raw_xsrf_token(self) -> Tuple[Optional[int], bytes, float]:
1513 """Read or generate the xsrf token in its raw form.
1515 The raw_xsrf_token is a tuple containing:
1517 * version: the version of the cookie from which this token was read,
1518 or None if we generated a new token in this request.
1519 * token: the raw token data; random (non-ascii) bytes.
1520 * timestamp: the time this token was generated (will not be accurate
1521 for version 1 cookies)
1522 """
1523 if not hasattr(self, "_raw_xsrf_token"):
1524 cookie_name = self.settings.get("xsrf_cookie_name", "_xsrf")
1525 cookie = self.get_cookie(cookie_name)
1526 if cookie:
1527 version, token, timestamp = self._decode_xsrf_token(cookie)
1528 else:
1529 version, token, timestamp = None, None, None
1530 if token is None:
1531 version = None
1532 token = os.urandom(16)
1533 timestamp = time.time()
1534 assert token is not None
1535 assert timestamp is not None
1536 self._raw_xsrf_token = (version, token, timestamp)
1537 return self._raw_xsrf_token
1539 def _decode_xsrf_token(
1540 self, cookie: str
1541 ) -> Tuple[Optional[int], Optional[bytes], Optional[float]]:
1542 """Convert a cookie string into a the tuple form returned by
1543 _get_raw_xsrf_token.
1544 """
1546 try:
1547 m = _signed_value_version_re.match(utf8(cookie))
1549 if m:
1550 version = int(m.group(1))
1551 if version == 2:
1552 _, mask_str, masked_token, timestamp_str = cookie.split("|")
1554 mask = binascii.a2b_hex(utf8(mask_str))
1555 token = _websocket_mask(mask, binascii.a2b_hex(utf8(masked_token)))
1556 timestamp = int(timestamp_str)
1557 return version, token, timestamp
1558 else:
1559 # Treat unknown versions as not present instead of failing.
1560 raise Exception("Unknown xsrf cookie version")
1561 else:
1562 version = 1
1563 try:
1564 token = binascii.a2b_hex(utf8(cookie))
1565 except (binascii.Error, TypeError):
1566 token = utf8(cookie)
1567 # We don't have a usable timestamp in older versions.
1568 timestamp = int(time.time())
1569 return (version, token, timestamp)
1570 except Exception:
1571 # Catch exceptions and return nothing instead of failing.
1572 gen_log.debug("Uncaught exception in _decode_xsrf_token", exc_info=True)
1573 return None, None, None
1575 def check_xsrf_cookie(self) -> None:
1576 """Verifies that the ``_xsrf`` cookie matches the ``_xsrf`` argument.
1578 To prevent cross-site request forgery, we set an ``_xsrf``
1579 cookie and include the same value as a non-cookie
1580 field with all ``POST`` requests. If the two do not match, we
1581 reject the form submission as a potential forgery.
1583 The ``_xsrf`` value may be set as either a form field named ``_xsrf``
1584 or in a custom HTTP header named ``X-XSRFToken`` or ``X-CSRFToken``
1585 (the latter is accepted for compatibility with Django).
1587 See http://en.wikipedia.org/wiki/Cross-site_request_forgery
1589 .. versionchanged:: 3.2.2
1590 Added support for cookie version 2. Both versions 1 and 2 are
1591 supported.
1592 """
1593 # Prior to release 1.1.1, this check was ignored if the HTTP header
1594 # ``X-Requested-With: XMLHTTPRequest`` was present. This exception
1595 # has been shown to be insecure and has been removed. For more
1596 # information please see
1597 # http://www.djangoproject.com/weblog/2011/feb/08/security/
1598 # http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
1599 token = (
1600 self.get_argument("_xsrf", None)
1601 or self.request.headers.get("X-Xsrftoken")
1602 or self.request.headers.get("X-Csrftoken")
1603 )
1604 if not token:
1605 raise HTTPError(403, "'_xsrf' argument missing from POST")
1606 _, token, _ = self._decode_xsrf_token(token)
1607 _, expected_token, _ = self._get_raw_xsrf_token()
1608 if not token:
1609 raise HTTPError(403, "'_xsrf' argument has invalid format")
1610 if not hmac.compare_digest(utf8(token), utf8(expected_token)):
1611 raise HTTPError(403, "XSRF cookie does not match POST argument")
1613 def xsrf_form_html(self) -> str:
1614 """An HTML ``<input/>`` element to be included with all POST forms.
1616 It defines the ``_xsrf`` input value, which we check on all POST
1617 requests to prevent cross-site request forgery. If you have set
1618 the ``xsrf_cookies`` application setting, you must include this
1619 HTML within all of your HTML forms.
1621 In a template, this method should be called with ``{% module
1622 xsrf_form_html() %}``
1624 See `check_xsrf_cookie()` above for more information.
1625 """
1626 return (
1627 '<input type="hidden" name="_xsrf" value="'
1628 + escape.xhtml_escape(self.xsrf_token)
1629 + '"/>'
1630 )
1632 def static_url(
1633 self, path: str, include_host: Optional[bool] = None, **kwargs: Any
1634 ) -> str:
1635 """Returns a static URL for the given relative static file path.
1637 This method requires you set the ``static_path`` setting in your
1638 application (which specifies the root directory of your static
1639 files).
1641 This method returns a versioned url (by default appending
1642 ``?v=<signature>``), which allows the static files to be
1643 cached indefinitely. This can be disabled by passing
1644 ``include_version=False`` (in the default implementation;
1645 other static file implementations are not required to support
1646 this, but they may support other options).
1648 By default this method returns URLs relative to the current
1649 host, but if ``include_host`` is true the URL returned will be
1650 absolute. If this handler has an ``include_host`` attribute,
1651 that value will be used as the default for all `static_url`
1652 calls that do not pass ``include_host`` as a keyword argument.
1654 """
1655 self.require_setting("static_path", "static_url")
1656 get_url = self.settings.get(
1657 "static_handler_class", StaticFileHandler
1658 ).make_static_url
1660 if include_host is None:
1661 include_host = getattr(self, "include_host", False)
1663 if include_host:
1664 base = self.request.protocol + "://" + self.request.host
1665 else:
1666 base = ""
1668 return base + get_url(self.settings, path, **kwargs)
1670 def require_setting(self, name: str, feature: str = "this feature") -> None:
1671 """Raises an exception if the given app setting is not defined."""
1672 if not self.application.settings.get(name):
1673 raise Exception(
1674 "You must define the '%s' setting in your "
1675 "application to use %s" % (name, feature)
1676 )
1678 def reverse_url(self, name: str, *args: Any) -> str:
1679 """Alias for `Application.reverse_url`."""
1680 return self.application.reverse_url(name, *args)
1682 def compute_etag(self) -> Optional[str]:
1683 """Computes the etag header to be used for this request.
1685 By default uses a hash of the content written so far.
1687 May be overridden to provide custom etag implementations,
1688 or may return None to disable tornado's default etag support.
1689 """
1690 hasher = hashlib.sha1()
1691 for part in self._write_buffer:
1692 hasher.update(part)
1693 return '"%s"' % hasher.hexdigest()
1695 def set_etag_header(self) -> None:
1696 """Sets the response's Etag header using ``self.compute_etag()``.
1698 Note: no header will be set if ``compute_etag()`` returns ``None``.
1700 This method is called automatically when the request is finished.
1701 """
1702 etag = self.compute_etag()
1703 if etag is not None:
1704 self.set_header("Etag", etag)
1706 def check_etag_header(self) -> bool:
1707 """Checks the ``Etag`` header against requests's ``If-None-Match``.
1709 Returns ``True`` if the request's Etag matches and a 304 should be
1710 returned. For example::
1712 self.set_etag_header()
1713 if self.check_etag_header():
1714 self.set_status(304)
1715 return
1717 This method is called automatically when the request is finished,
1718 but may be called earlier for applications that override
1719 `compute_etag` and want to do an early check for ``If-None-Match``
1720 before completing the request. The ``Etag`` header should be set
1721 (perhaps with `set_etag_header`) before calling this method.
1722 """
1723 computed_etag = utf8(self._headers.get("Etag", ""))
1724 # Find all weak and strong etag values from If-None-Match header
1725 # because RFC 7232 allows multiple etag values in a single header.
1726 etags = re.findall(
1727 rb'\*|(?:W/)?"[^"]*"', utf8(self.request.headers.get("If-None-Match", ""))
1728 )
1729 if not computed_etag or not etags:
1730 return False
1732 match = False
1733 if etags[0] == b"*":
1734 match = True
1735 else:
1736 # Use a weak comparison when comparing entity-tags.
1737 def val(x: bytes) -> bytes:
1738 return x[2:] if x.startswith(b"W/") else x
1740 for etag in etags:
1741 if val(etag) == val(computed_etag):
1742 match = True
1743 break
1744 return match
1746 async def _execute(
1747 self, transforms: List["OutputTransform"], *args: bytes, **kwargs: bytes
1748 ) -> None:
1749 """Executes this request with the given output transforms."""
1750 self._transforms = transforms
1751 try:
1752 if self.request.method not in self.SUPPORTED_METHODS:
1753 raise HTTPError(405)
1754 self.path_args = [self.decode_argument(arg) for arg in args]
1755 self.path_kwargs = dict(
1756 (k, self.decode_argument(v, name=k)) for (k, v) in kwargs.items()
1757 )
1758 # If XSRF cookies are turned on, reject form submissions without
1759 # the proper cookie
1760 if self.request.method not in (
1761 "GET",
1762 "HEAD",
1763 "OPTIONS",
1764 ) and self.application.settings.get("xsrf_cookies"):
1765 self.check_xsrf_cookie()
1767 result = self.prepare()
1768 if result is not None:
1769 result = await result # type: ignore
1770 if self._prepared_future is not None:
1771 # Tell the Application we've finished with prepare()
1772 # and are ready for the body to arrive.
1773 future_set_result_unless_cancelled(self._prepared_future, None)
1774 if self._finished:
1775 return
1777 if _has_stream_request_body(self.__class__):
1778 # In streaming mode request.body is a Future that signals
1779 # the body has been completely received. The Future has no
1780 # result; the data has been passed to self.data_received
1781 # instead.
1782 try:
1783 await self.request._body_future
1784 except iostream.StreamClosedError:
1785 return
1787 method = getattr(self, self.request.method.lower())
1788 result = method(*self.path_args, **self.path_kwargs)
1789 if result is not None:
1790 result = await result
1791 if self._auto_finish and not self._finished:
1792 self.finish()
1793 except Exception as e:
1794 try:
1795 self._handle_request_exception(e)
1796 except Exception:
1797 app_log.error("Exception in exception handler", exc_info=True)
1798 finally:
1799 # Unset result to avoid circular references
1800 result = None
1801 if self._prepared_future is not None and not self._prepared_future.done():
1802 # In case we failed before setting _prepared_future, do it
1803 # now (to unblock the HTTP server). Note that this is not
1804 # in a finally block to avoid GC issues prior to Python 3.4.
1805 self._prepared_future.set_result(None)
1807 def data_received(self, chunk: bytes) -> Optional[Awaitable[None]]:
1808 """Implement this method to handle streamed request data.
1810 Requires the `.stream_request_body` decorator.
1812 May be a coroutine for flow control.
1813 """
1814 raise NotImplementedError()
1816 def _log(self) -> None:
1817 """Logs the current request.
1819 Sort of deprecated since this functionality was moved to the
1820 Application, but left in place for the benefit of existing apps
1821 that have overridden this method.
1822 """
1823 self.application.log_request(self)
1825 def _request_summary(self) -> str:
1826 return "%s %s (%s)" % (
1827 self.request.method,
1828 self.request.uri,
1829 self.request.remote_ip,
1830 )
1832 def _handle_request_exception(self, e: BaseException) -> None:
1833 if isinstance(e, Finish):
1834 # Not an error; just finish the request without logging.
1835 if not self._finished:
1836 self.finish(*e.args)
1837 return
1838 try:
1839 self.log_exception(*sys.exc_info())
1840 except Exception:
1841 # An error here should still get a best-effort send_error()
1842 # to avoid leaking the connection.
1843 app_log.error("Error in exception logger", exc_info=True)
1844 if self._finished:
1845 # Extra errors after the request has been finished should
1846 # be logged, but there is no reason to continue to try and
1847 # send a response.
1848 return
1849 if isinstance(e, HTTPError):
1850 self.send_error(e.status_code, exc_info=sys.exc_info())
1851 else:
1852 self.send_error(500, exc_info=sys.exc_info())
1854 def log_exception(
1855 self,
1856 typ: "Optional[Type[BaseException]]",
1857 value: Optional[BaseException],
1858 tb: Optional[TracebackType],
1859 ) -> None:
1860 """Override to customize logging of uncaught exceptions.
1862 By default logs instances of `HTTPError` as warnings without
1863 stack traces (on the ``tornado.general`` logger), and all
1864 other exceptions as errors with stack traces (on the
1865 ``tornado.application`` logger).
1867 .. versionadded:: 3.1
1868 """
1869 if isinstance(value, HTTPError):
1870 if value.log_message:
1871 format = "%d %s: " + value.log_message
1872 args = [value.status_code, self._request_summary()] + list(value.args)
1873 gen_log.warning(format, *args)
1874 else:
1875 app_log.error(
1876 "Uncaught exception %s\n%r",
1877 self._request_summary(),
1878 self.request,
1879 exc_info=(typ, value, tb), # type: ignore
1880 )
1882 def _ui_module(self, name: str, module: Type["UIModule"]) -> Callable[..., str]:
1883 def render(*args, **kwargs) -> str: # type: ignore
1884 if not hasattr(self, "_active_modules"):
1885 self._active_modules = {} # type: Dict[str, UIModule]
1886 if name not in self._active_modules:
1887 self._active_modules[name] = module(self)
1888 rendered = self._active_modules[name].render(*args, **kwargs)
1889 return rendered
1891 return render
1893 def _ui_method(self, method: Callable[..., str]) -> Callable[..., str]:
1894 return lambda *args, **kwargs: method(self, *args, **kwargs)
1896 def _clear_representation_headers(self) -> None:
1897 # 304 responses should not contain representation metadata
1898 # headers (defined in
1899 # https://tools.ietf.org/html/rfc7231#section-3.1)
1900 # not explicitly allowed by
1901 # https://tools.ietf.org/html/rfc7232#section-4.1
1902 headers = ["Content-Encoding", "Content-Language", "Content-Type"]
1903 for h in headers:
1904 self.clear_header(h)
1907_RequestHandlerType = TypeVar("_RequestHandlerType", bound=RequestHandler)
1910def stream_request_body(cls: Type[_RequestHandlerType]) -> Type[_RequestHandlerType]:
1911 """Apply to `RequestHandler` subclasses to enable streaming body support.
1913 This decorator implies the following changes:
1915 * `.HTTPServerRequest.body` is undefined, and body arguments will not
1916 be included in `RequestHandler.get_argument`.
1917 * `RequestHandler.prepare` is called when the request headers have been
1918 read instead of after the entire body has been read.
1919 * The subclass must define a method ``data_received(self, data):``, which
1920 will be called zero or more times as data is available. Note that
1921 if the request has an empty body, ``data_received`` may not be called.
1922 * ``prepare`` and ``data_received`` may return Futures (such as via
1923 ``@gen.coroutine``, in which case the next method will not be called
1924 until those futures have completed.
1925 * The regular HTTP method (``post``, ``put``, etc) will be called after
1926 the entire body has been read.
1928 See the `file receiver demo <https://github.com/tornadoweb/tornado/tree/stable/demos/file_upload/>`_
1929 for example usage.
1930 """ # noqa: E501
1931 if not issubclass(cls, RequestHandler):
1932 raise TypeError("expected subclass of RequestHandler, got %r", cls)
1933 cls._stream_request_body = True
1934 return cls
1937def _has_stream_request_body(cls: Type[RequestHandler]) -> bool:
1938 if not issubclass(cls, RequestHandler):
1939 raise TypeError("expected subclass of RequestHandler, got %r", cls)
1940 return cls._stream_request_body
1943def removeslash(
1944 method: Callable[..., Optional[Awaitable[None]]]
1945) -> Callable[..., Optional[Awaitable[None]]]:
1946 """Use this decorator to remove trailing slashes from the request path.
1948 For example, a request to ``/foo/`` would redirect to ``/foo`` with this
1949 decorator. Your request handler mapping should use a regular expression
1950 like ``r'/foo/*'`` in conjunction with using the decorator.
1951 """
1953 @functools.wraps(method)
1954 def wrapper( # type: ignore
1955 self: RequestHandler, *args, **kwargs
1956 ) -> Optional[Awaitable[None]]:
1957 if self.request.path.endswith("/"):
1958 if self.request.method in ("GET", "HEAD"):
1959 uri = self.request.path.rstrip("/")
1960 if uri: # don't try to redirect '/' to ''
1961 if self.request.query:
1962 uri += "?" + self.request.query
1963 self.redirect(uri, permanent=True)
1964 return None
1965 else:
1966 raise HTTPError(404)
1967 return method(self, *args, **kwargs)
1969 return wrapper
1972def addslash(
1973 method: Callable[..., Optional[Awaitable[None]]]
1974) -> Callable[..., Optional[Awaitable[None]]]:
1975 """Use this decorator to add a missing trailing slash to the request path.
1977 For example, a request to ``/foo`` would redirect to ``/foo/`` with this
1978 decorator. Your request handler mapping should use a regular expression
1979 like ``r'/foo/?'`` in conjunction with using the decorator.
1980 """
1982 @functools.wraps(method)
1983 def wrapper( # type: ignore
1984 self: RequestHandler, *args, **kwargs
1985 ) -> Optional[Awaitable[None]]:
1986 if not self.request.path.endswith("/"):
1987 if self.request.method in ("GET", "HEAD"):
1988 uri = self.request.path + "/"
1989 if self.request.query:
1990 uri += "?" + self.request.query
1991 self.redirect(uri, permanent=True)
1992 return None
1993 raise HTTPError(404)
1994 return method(self, *args, **kwargs)
1996 return wrapper
1999class _ApplicationRouter(ReversibleRuleRouter):
2000 """Routing implementation used internally by `Application`.
2002 Provides a binding between `Application` and `RequestHandler`.
2003 This implementation extends `~.routing.ReversibleRuleRouter` in a couple of ways:
2004 * it allows to use `RequestHandler` subclasses as `~.routing.Rule` target and
2005 * it allows to use a list/tuple of rules as `~.routing.Rule` target.
2006 ``process_rule`` implementation will substitute this list with an appropriate
2007 `_ApplicationRouter` instance.
2008 """
2010 def __init__(
2011 self, application: "Application", rules: Optional[_RuleList] = None
2012 ) -> None:
2013 assert isinstance(application, Application)
2014 self.application = application
2015 super().__init__(rules)
2017 def process_rule(self, rule: Rule) -> Rule:
2018 rule = super().process_rule(rule)
2020 if isinstance(rule.target, (list, tuple)):
2021 rule.target = _ApplicationRouter(
2022 self.application, rule.target # type: ignore
2023 )
2025 return rule
2027 def get_target_delegate(
2028 self, target: Any, request: httputil.HTTPServerRequest, **target_params: Any
2029 ) -> Optional[httputil.HTTPMessageDelegate]:
2030 if isclass(target) and issubclass(target, RequestHandler):
2031 return self.application.get_handler_delegate(
2032 request, target, **target_params
2033 )
2035 return super().get_target_delegate(target, request, **target_params)
2038class Application(ReversibleRouter):
2039 r"""A collection of request handlers that make up a web application.
2041 Instances of this class are callable and can be passed directly to
2042 HTTPServer to serve the application::
2044 application = web.Application([
2045 (r"/", MainPageHandler),
2046 ])
2047 http_server = httpserver.HTTPServer(application)
2048 http_server.listen(8080)
2050 The constructor for this class takes in a list of `~.routing.Rule`
2051 objects or tuples of values corresponding to the arguments of
2052 `~.routing.Rule` constructor: ``(matcher, target, [target_kwargs], [name])``,
2053 the values in square brackets being optional. The default matcher is
2054 `~.routing.PathMatches`, so ``(regexp, target)`` tuples can also be used
2055 instead of ``(PathMatches(regexp), target)``.
2057 A common routing target is a `RequestHandler` subclass, but you can also
2058 use lists of rules as a target, which create a nested routing configuration::
2060 application = web.Application([
2061 (HostMatches("example.com"), [
2062 (r"/", MainPageHandler),
2063 (r"/feed", FeedHandler),
2064 ]),
2065 ])
2067 In addition to this you can use nested `~.routing.Router` instances,
2068 `~.httputil.HTTPMessageDelegate` subclasses and callables as routing targets
2069 (see `~.routing` module docs for more information).
2071 When we receive requests, we iterate over the list in order and
2072 instantiate an instance of the first request class whose regexp
2073 matches the request path. The request class can be specified as
2074 either a class object or a (fully-qualified) name.
2076 A dictionary may be passed as the third element (``target_kwargs``)
2077 of the tuple, which will be used as keyword arguments to the handler's
2078 constructor and `~RequestHandler.initialize` method. This pattern
2079 is used for the `StaticFileHandler` in this example (note that a
2080 `StaticFileHandler` can be installed automatically with the
2081 static_path setting described below)::
2083 application = web.Application([
2084 (r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2085 ])
2087 We support virtual hosts with the `add_handlers` method, which takes in
2088 a host regular expression as the first argument::
2090 application.add_handlers(r"www\.myhost\.com", [
2091 (r"/article/([0-9]+)", ArticleHandler),
2092 ])
2094 If there's no match for the current request's host, then ``default_host``
2095 parameter value is matched against host regular expressions.
2098 .. warning::
2100 Applications that do not use TLS may be vulnerable to :ref:`DNS
2101 rebinding <dnsrebinding>` attacks. This attack is especially
2102 relevant to applications that only listen on ``127.0.0.1`` or
2103 other private networks. Appropriate host patterns must be used
2104 (instead of the default of ``r'.*'``) to prevent this risk. The
2105 ``default_host`` argument must not be used in applications that
2106 may be vulnerable to DNS rebinding.
2108 You can serve static files by sending the ``static_path`` setting
2109 as a keyword argument. We will serve those files from the
2110 ``/static/`` URI (this is configurable with the
2111 ``static_url_prefix`` setting), and we will serve ``/favicon.ico``
2112 and ``/robots.txt`` from the same directory. A custom subclass of
2113 `StaticFileHandler` can be specified with the
2114 ``static_handler_class`` setting.
2116 .. versionchanged:: 4.5
2117 Integration with the new `tornado.routing` module.
2119 """
2121 def __init__(
2122 self,
2123 handlers: Optional[_RuleList] = None,
2124 default_host: Optional[str] = None,
2125 transforms: Optional[List[Type["OutputTransform"]]] = None,
2126 **settings: Any,
2127 ) -> None:
2128 if transforms is None:
2129 self.transforms = [] # type: List[Type[OutputTransform]]
2130 if settings.get("compress_response") or settings.get("gzip"):
2131 self.transforms.append(GZipContentEncoding)
2132 else:
2133 self.transforms = transforms
2134 self.default_host = default_host
2135 self.settings = settings
2136 self.ui_modules = {
2137 "linkify": _linkify,
2138 "xsrf_form_html": _xsrf_form_html,
2139 "Template": TemplateModule,
2140 }
2141 self.ui_methods = {} # type: Dict[str, Callable[..., str]]
2142 self._load_ui_modules(settings.get("ui_modules", {}))
2143 self._load_ui_methods(settings.get("ui_methods", {}))
2144 if self.settings.get("static_path"):
2145 path = self.settings["static_path"]
2146 handlers = list(handlers or [])
2147 static_url_prefix = settings.get("static_url_prefix", "/static/")
2148 static_handler_class = settings.get(
2149 "static_handler_class", StaticFileHandler
2150 )
2151 static_handler_args = settings.get("static_handler_args", {})
2152 static_handler_args["path"] = path
2153 for pattern in [
2154 re.escape(static_url_prefix) + r"(.*)",
2155 r"/(favicon\.ico)",
2156 r"/(robots\.txt)",
2157 ]:
2158 handlers.insert(0, (pattern, static_handler_class, static_handler_args))
2160 if self.settings.get("debug"):
2161 self.settings.setdefault("autoreload", True)
2162 self.settings.setdefault("compiled_template_cache", False)
2163 self.settings.setdefault("static_hash_cache", False)
2164 self.settings.setdefault("serve_traceback", True)
2166 self.wildcard_router = _ApplicationRouter(self, handlers)
2167 self.default_router = _ApplicationRouter(
2168 self, [Rule(AnyMatches(), self.wildcard_router)]
2169 )
2171 # Automatically reload modified modules
2172 if self.settings.get("autoreload"):
2173 from tornado import autoreload
2175 autoreload.start()
2177 def listen(
2178 self,
2179 port: int,
2180 address: Optional[str] = None,
2181 *,
2182 family: socket.AddressFamily = socket.AF_UNSPEC,
2183 backlog: int = tornado.netutil._DEFAULT_BACKLOG,
2184 flags: Optional[int] = None,
2185 reuse_port: bool = False,
2186 **kwargs: Any,
2187 ) -> HTTPServer:
2188 """Starts an HTTP server for this application on the given port.
2190 This is a convenience alias for creating an `.HTTPServer` object and
2191 calling its listen method. Keyword arguments not supported by
2192 `HTTPServer.listen <.TCPServer.listen>` are passed to the `.HTTPServer`
2193 constructor. For advanced uses (e.g. multi-process mode), do not use
2194 this method; create an `.HTTPServer` and call its
2195 `.TCPServer.bind`/`.TCPServer.start` methods directly.
2197 Note that after calling this method you still need to call
2198 ``IOLoop.current().start()`` (or run within ``asyncio.run``) to start
2199 the server.
2201 Returns the `.HTTPServer` object.
2203 .. versionchanged:: 4.3
2204 Now returns the `.HTTPServer` object.
2206 .. versionchanged:: 6.2
2207 Added support for new keyword arguments in `.TCPServer.listen`,
2208 including ``reuse_port``.
2209 """
2210 server = HTTPServer(self, **kwargs)
2211 server.listen(
2212 port,
2213 address=address,
2214 family=family,
2215 backlog=backlog,
2216 flags=flags,
2217 reuse_port=reuse_port,
2218 )
2219 return server
2221 def add_handlers(self, host_pattern: str, host_handlers: _RuleList) -> None:
2222 """Appends the given handlers to our handler list.
2224 Host patterns are processed sequentially in the order they were
2225 added. All matching patterns will be considered.
2226 """
2227 host_matcher = HostMatches(host_pattern)
2228 rule = Rule(host_matcher, _ApplicationRouter(self, host_handlers))
2230 self.default_router.rules.insert(-1, rule)
2232 if self.default_host is not None:
2233 self.wildcard_router.add_rules(
2234 [(DefaultHostMatches(self, host_matcher.host_pattern), host_handlers)]
2235 )
2237 def add_transform(self, transform_class: Type["OutputTransform"]) -> None:
2238 self.transforms.append(transform_class)
2240 def _load_ui_methods(self, methods: Any) -> None:
2241 if isinstance(methods, types.ModuleType):
2242 self._load_ui_methods(dict((n, getattr(methods, n)) for n in dir(methods)))
2243 elif isinstance(methods, list):
2244 for m in methods:
2245 self._load_ui_methods(m)
2246 else:
2247 for name, fn in methods.items():
2248 if (
2249 not name.startswith("_")
2250 and hasattr(fn, "__call__")
2251 and name[0].lower() == name[0]
2252 ):
2253 self.ui_methods[name] = fn
2255 def _load_ui_modules(self, modules: Any) -> None:
2256 if isinstance(modules, types.ModuleType):
2257 self._load_ui_modules(dict((n, getattr(modules, n)) for n in dir(modules)))
2258 elif isinstance(modules, list):
2259 for m in modules:
2260 self._load_ui_modules(m)
2261 else:
2262 assert isinstance(modules, dict)
2263 for name, cls in modules.items():
2264 try:
2265 if issubclass(cls, UIModule):
2266 self.ui_modules[name] = cls
2267 except TypeError:
2268 pass
2270 def __call__(
2271 self, request: httputil.HTTPServerRequest
2272 ) -> Optional[Awaitable[None]]:
2273 # Legacy HTTPServer interface
2274 dispatcher = self.find_handler(request)
2275 return dispatcher.execute()
2277 def find_handler(
2278 self, request: httputil.HTTPServerRequest, **kwargs: Any
2279 ) -> "_HandlerDelegate":
2280 route = self.default_router.find_handler(request)
2281 if route is not None:
2282 return cast("_HandlerDelegate", route)
2284 if self.settings.get("default_handler_class"):
2285 return self.get_handler_delegate(
2286 request,
2287 self.settings["default_handler_class"],
2288 self.settings.get("default_handler_args", {}),
2289 )
2291 return self.get_handler_delegate(request, ErrorHandler, {"status_code": 404})
2293 def get_handler_delegate(
2294 self,
2295 request: httputil.HTTPServerRequest,
2296 target_class: Type[RequestHandler],
2297 target_kwargs: Optional[Dict[str, Any]] = None,
2298 path_args: Optional[List[bytes]] = None,
2299 path_kwargs: Optional[Dict[str, bytes]] = None,
2300 ) -> "_HandlerDelegate":
2301 """Returns `~.httputil.HTTPMessageDelegate` that can serve a request
2302 for application and `RequestHandler` subclass.
2304 :arg httputil.HTTPServerRequest request: current HTTP request.
2305 :arg RequestHandler target_class: a `RequestHandler` class.
2306 :arg dict target_kwargs: keyword arguments for ``target_class`` constructor.
2307 :arg list path_args: positional arguments for ``target_class`` HTTP method that
2308 will be executed while handling a request (``get``, ``post`` or any other).
2309 :arg dict path_kwargs: keyword arguments for ``target_class`` HTTP method.
2310 """
2311 return _HandlerDelegate(
2312 self, request, target_class, target_kwargs, path_args, path_kwargs
2313 )
2315 def reverse_url(self, name: str, *args: Any) -> str:
2316 """Returns a URL path for handler named ``name``
2318 The handler must be added to the application as a named `URLSpec`.
2320 Args will be substituted for capturing groups in the `URLSpec` regex.
2321 They will be converted to strings if necessary, encoded as utf8,
2322 and url-escaped.
2323 """
2324 reversed_url = self.default_router.reverse_url(name, *args)
2325 if reversed_url is not None:
2326 return reversed_url
2328 raise KeyError("%s not found in named urls" % name)
2330 def log_request(self, handler: RequestHandler) -> None:
2331 """Writes a completed HTTP request to the logs.
2333 By default writes to the python root logger. To change
2334 this behavior either subclass Application and override this method,
2335 or pass a function in the application settings dictionary as
2336 ``log_function``.
2337 """
2338 if "log_function" in self.settings:
2339 self.settings["log_function"](handler)
2340 return
2341 if handler.get_status() < 400:
2342 log_method = access_log.info
2343 elif handler.get_status() < 500:
2344 log_method = access_log.warning
2345 else:
2346 log_method = access_log.error
2347 request_time = 1000.0 * handler.request.request_time()
2348 log_method(
2349 "%d %s %.2fms",
2350 handler.get_status(),
2351 handler._request_summary(),
2352 request_time,
2353 )
2356class _HandlerDelegate(httputil.HTTPMessageDelegate):
2357 def __init__(
2358 self,
2359 application: Application,
2360 request: httputil.HTTPServerRequest,
2361 handler_class: Type[RequestHandler],
2362 handler_kwargs: Optional[Dict[str, Any]],
2363 path_args: Optional[List[bytes]],
2364 path_kwargs: Optional[Dict[str, bytes]],
2365 ) -> None:
2366 self.application = application
2367 self.connection = request.connection
2368 self.request = request
2369 self.handler_class = handler_class
2370 self.handler_kwargs = handler_kwargs or {}
2371 self.path_args = path_args or []
2372 self.path_kwargs = path_kwargs or {}
2373 self.chunks = [] # type: List[bytes]
2374 self.stream_request_body = _has_stream_request_body(self.handler_class)
2376 def headers_received(
2377 self,
2378 start_line: Union[httputil.RequestStartLine, httputil.ResponseStartLine],
2379 headers: httputil.HTTPHeaders,
2380 ) -> Optional[Awaitable[None]]:
2381 if self.stream_request_body:
2382 self.request._body_future = Future()
2383 return self.execute()
2384 return None
2386 def data_received(self, data: bytes) -> Optional[Awaitable[None]]:
2387 if self.stream_request_body:
2388 return self.handler.data_received(data)
2389 else:
2390 self.chunks.append(data)
2391 return None
2393 def finish(self) -> None:
2394 if self.stream_request_body:
2395 future_set_result_unless_cancelled(self.request._body_future, None)
2396 else:
2397 self.request.body = b"".join(self.chunks)
2398 self.request._parse_body()
2399 self.execute()
2401 def on_connection_close(self) -> None:
2402 if self.stream_request_body:
2403 self.handler.on_connection_close()
2404 else:
2405 self.chunks = None # type: ignore
2407 def execute(self) -> Optional[Awaitable[None]]:
2408 # If template cache is disabled (usually in the debug mode),
2409 # re-compile templates and reload static files on every
2410 # request so you don't need to restart to see changes
2411 if not self.application.settings.get("compiled_template_cache", True):
2412 with RequestHandler._template_loader_lock:
2413 for loader in RequestHandler._template_loaders.values():
2414 loader.reset()
2415 if not self.application.settings.get("static_hash_cache", True):
2416 static_handler_class = self.application.settings.get(
2417 "static_handler_class", StaticFileHandler
2418 )
2419 static_handler_class.reset()
2421 self.handler = self.handler_class(
2422 self.application, self.request, **self.handler_kwargs
2423 )
2424 transforms = [t(self.request) for t in self.application.transforms]
2426 if self.stream_request_body:
2427 self.handler._prepared_future = Future()
2428 # Note that if an exception escapes handler._execute it will be
2429 # trapped in the Future it returns (which we are ignoring here,
2430 # leaving it to be logged when the Future is GC'd).
2431 # However, that shouldn't happen because _execute has a blanket
2432 # except handler, and we cannot easily access the IOLoop here to
2433 # call add_future (because of the requirement to remain compatible
2434 # with WSGI)
2435 fut = gen.convert_yielded(
2436 self.handler._execute(transforms, *self.path_args, **self.path_kwargs)
2437 )
2438 fut.add_done_callback(lambda f: f.result())
2439 # If we are streaming the request body, then execute() is finished
2440 # when the handler has prepared to receive the body. If not,
2441 # it doesn't matter when execute() finishes (so we return None)
2442 return self.handler._prepared_future
2445class HTTPError(Exception):
2446 """An exception that will turn into an HTTP error response.
2448 Raising an `HTTPError` is a convenient alternative to calling
2449 `RequestHandler.send_error` since it automatically ends the
2450 current function.
2452 To customize the response sent with an `HTTPError`, override
2453 `RequestHandler.write_error`.
2455 :arg int status_code: HTTP status code. Must be listed in
2456 `httplib.responses <http.client.responses>` unless the ``reason``
2457 keyword argument is given.
2458 :arg str log_message: Message to be written to the log for this error
2459 (will not be shown to the user unless the `Application` is in debug
2460 mode). May contain ``%s``-style placeholders, which will be filled
2461 in with remaining positional parameters.
2462 :arg str reason: Keyword-only argument. The HTTP "reason" phrase
2463 to pass in the status line along with ``status_code``. Normally
2464 determined automatically from ``status_code``, but can be used
2465 to use a non-standard numeric code.
2466 """
2468 def __init__(
2469 self,
2470 status_code: int = 500,
2471 log_message: Optional[str] = None,
2472 *args: Any,
2473 **kwargs: Any,
2474 ) -> None:
2475 self.status_code = status_code
2476 self.log_message = log_message
2477 self.args = args
2478 self.reason = kwargs.get("reason", None)
2479 if log_message and not args:
2480 self.log_message = log_message.replace("%", "%%")
2482 def __str__(self) -> str:
2483 message = "HTTP %d: %s" % (
2484 self.status_code,
2485 self.reason or httputil.responses.get(self.status_code, "Unknown"),
2486 )
2487 if self.log_message:
2488 return message + " (" + (self.log_message % self.args) + ")"
2489 else:
2490 return message
2493class Finish(Exception):
2494 """An exception that ends the request without producing an error response.
2496 When `Finish` is raised in a `RequestHandler`, the request will
2497 end (calling `RequestHandler.finish` if it hasn't already been
2498 called), but the error-handling methods (including
2499 `RequestHandler.write_error`) will not be called.
2501 If `Finish()` was created with no arguments, the pending response
2502 will be sent as-is. If `Finish()` was given an argument, that
2503 argument will be passed to `RequestHandler.finish()`.
2505 This can be a more convenient way to implement custom error pages
2506 than overriding ``write_error`` (especially in library code)::
2508 if self.current_user is None:
2509 self.set_status(401)
2510 self.set_header('WWW-Authenticate', 'Basic realm="something"')
2511 raise Finish()
2513 .. versionchanged:: 4.3
2514 Arguments passed to ``Finish()`` will be passed on to
2515 `RequestHandler.finish`.
2516 """
2518 pass
2521class MissingArgumentError(HTTPError):
2522 """Exception raised by `RequestHandler.get_argument`.
2524 This is a subclass of `HTTPError`, so if it is uncaught a 400 response
2525 code will be used instead of 500 (and a stack trace will not be logged).
2527 .. versionadded:: 3.1
2528 """
2530 def __init__(self, arg_name: str) -> None:
2531 super().__init__(400, "Missing argument %s" % arg_name)
2532 self.arg_name = arg_name
2535class ErrorHandler(RequestHandler):
2536 """Generates an error response with ``status_code`` for all requests."""
2538 def initialize(self, status_code: int) -> None:
2539 self.set_status(status_code)
2541 def prepare(self) -> None:
2542 raise HTTPError(self._status_code)
2544 def check_xsrf_cookie(self) -> None:
2545 # POSTs to an ErrorHandler don't actually have side effects,
2546 # so we don't need to check the xsrf token. This allows POSTs
2547 # to the wrong url to return a 404 instead of 403.
2548 pass
2551class RedirectHandler(RequestHandler):
2552 """Redirects the client to the given URL for all GET requests.
2554 You should provide the keyword argument ``url`` to the handler, e.g.::
2556 application = web.Application([
2557 (r"/oldpath", web.RedirectHandler, {"url": "/newpath"}),
2558 ])
2560 `RedirectHandler` supports regular expression substitutions. E.g., to
2561 swap the first and second parts of a path while preserving the remainder::
2563 application = web.Application([
2564 (r"/(.*?)/(.*?)/(.*)", web.RedirectHandler, {"url": "/{1}/{0}/{2}"}),
2565 ])
2567 The final URL is formatted with `str.format` and the substrings that match
2568 the capturing groups. In the above example, a request to "/a/b/c" would be
2569 formatted like::
2571 str.format("/{1}/{0}/{2}", "a", "b", "c") # -> "/b/a/c"
2573 Use Python's :ref:`format string syntax <formatstrings>` to customize how
2574 values are substituted.
2576 .. versionchanged:: 4.5
2577 Added support for substitutions into the destination URL.
2579 .. versionchanged:: 5.0
2580 If any query arguments are present, they will be copied to the
2581 destination URL.
2582 """
2584 def initialize(self, url: str, permanent: bool = True) -> None:
2585 self._url = url
2586 self._permanent = permanent
2588 def get(self, *args: Any, **kwargs: Any) -> None:
2589 to_url = self._url.format(*args, **kwargs)
2590 if self.request.query_arguments:
2591 # TODO: figure out typing for the next line.
2592 to_url = httputil.url_concat(
2593 to_url,
2594 list(httputil.qs_to_qsl(self.request.query_arguments)), # type: ignore
2595 )
2596 self.redirect(to_url, permanent=self._permanent)
2599class StaticFileHandler(RequestHandler):
2600 """A simple handler that can serve static content from a directory.
2602 A `StaticFileHandler` is configured automatically if you pass the
2603 ``static_path`` keyword argument to `Application`. This handler
2604 can be customized with the ``static_url_prefix``, ``static_handler_class``,
2605 and ``static_handler_args`` settings.
2607 To map an additional path to this handler for a static data directory
2608 you would add a line to your application like::
2610 application = web.Application([
2611 (r"/content/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
2612 ])
2614 The handler constructor requires a ``path`` argument, which specifies the
2615 local root directory of the content to be served.
2617 Note that a capture group in the regex is required to parse the value for
2618 the ``path`` argument to the get() method (different than the constructor
2619 argument above); see `URLSpec` for details.
2621 To serve a file like ``index.html`` automatically when a directory is
2622 requested, set ``static_handler_args=dict(default_filename="index.html")``
2623 in your application settings, or add ``default_filename`` as an initializer
2624 argument for your ``StaticFileHandler``.
2626 To maximize the effectiveness of browser caching, this class supports
2627 versioned urls (by default using the argument ``?v=``). If a version
2628 is given, we instruct the browser to cache this file indefinitely.
2629 `make_static_url` (also available as `RequestHandler.static_url`) can
2630 be used to construct a versioned url.
2632 This handler is intended primarily for use in development and light-duty
2633 file serving; for heavy traffic it will be more efficient to use
2634 a dedicated static file server (such as nginx or Apache). We support
2635 the HTTP ``Accept-Ranges`` mechanism to return partial content (because
2636 some browsers require this functionality to be present to seek in
2637 HTML5 audio or video).
2639 **Subclassing notes**
2641 This class is designed to be extensible by subclassing, but because
2642 of the way static urls are generated with class methods rather than
2643 instance methods, the inheritance patterns are somewhat unusual.
2644 Be sure to use the ``@classmethod`` decorator when overriding a
2645 class method. Instance methods may use the attributes ``self.path``
2646 ``self.absolute_path``, and ``self.modified``.
2648 Subclasses should only override methods discussed in this section;
2649 overriding other methods is error-prone. Overriding
2650 ``StaticFileHandler.get`` is particularly problematic due to the
2651 tight coupling with ``compute_etag`` and other methods.
2653 To change the way static urls are generated (e.g. to match the behavior
2654 of another server or CDN), override `make_static_url`, `parse_url_path`,
2655 `get_cache_time`, and/or `get_version`.
2657 To replace all interaction with the filesystem (e.g. to serve
2658 static content from a database), override `get_content`,
2659 `get_content_size`, `get_modified_time`, `get_absolute_path`, and
2660 `validate_absolute_path`.
2662 .. versionchanged:: 3.1
2663 Many of the methods for subclasses were added in Tornado 3.1.
2664 """
2666 CACHE_MAX_AGE = 86400 * 365 * 10 # 10 years
2668 _static_hashes = {} # type: Dict[str, Optional[str]]
2669 _lock = threading.Lock() # protects _static_hashes
2671 def initialize(self, path: str, default_filename: Optional[str] = None) -> None:
2672 self.root = path
2673 self.default_filename = default_filename
2675 @classmethod
2676 def reset(cls) -> None:
2677 with cls._lock:
2678 cls._static_hashes = {}
2680 def head(self, path: str) -> Awaitable[None]:
2681 return self.get(path, include_body=False)
2683 async def get(self, path: str, include_body: bool = True) -> None:
2684 # Set up our path instance variables.
2685 self.path = self.parse_url_path(path)
2686 del path # make sure we don't refer to path instead of self.path again
2687 absolute_path = self.get_absolute_path(self.root, self.path)
2688 self.absolute_path = self.validate_absolute_path(self.root, absolute_path)
2689 if self.absolute_path is None:
2690 return
2692 self.modified = self.get_modified_time()
2693 self.set_headers()
2695 if self.should_return_304():
2696 self.set_status(304)
2697 return
2699 request_range = None
2700 range_header = self.request.headers.get("Range")
2701 if range_header:
2702 # As per RFC 2616 14.16, if an invalid Range header is specified,
2703 # the request will be treated as if the header didn't exist.
2704 request_range = httputil._parse_request_range(range_header)
2706 size = self.get_content_size()
2707 if request_range:
2708 start, end = request_range
2709 if start is not None and start < 0:
2710 start += size
2711 if start < 0:
2712 start = 0
2713 if (
2714 start is not None
2715 and (start >= size or (end is not None and start >= end))
2716 ) or end == 0:
2717 # As per RFC 2616 14.35.1, a range is not satisfiable only: if
2718 # the first requested byte is equal to or greater than the
2719 # content, or when a suffix with length 0 is specified.
2720 # https://tools.ietf.org/html/rfc7233#section-2.1
2721 # A byte-range-spec is invalid if the last-byte-pos value is present
2722 # and less than the first-byte-pos.
2723 self.set_status(416) # Range Not Satisfiable
2724 self.set_header("Content-Type", "text/plain")
2725 self.set_header("Content-Range", "bytes */%s" % (size,))
2726 return
2727 if end is not None and end > size:
2728 # Clients sometimes blindly use a large range to limit their
2729 # download size; cap the endpoint at the actual file size.
2730 end = size
2731 # Note: only return HTTP 206 if less than the entire range has been
2732 # requested. Not only is this semantically correct, but Chrome
2733 # refuses to play audio if it gets an HTTP 206 in response to
2734 # ``Range: bytes=0-``.
2735 if size != (end or size) - (start or 0):
2736 self.set_status(206) # Partial Content
2737 self.set_header(
2738 "Content-Range", httputil._get_content_range(start, end, size)
2739 )
2740 else:
2741 start = end = None
2743 if start is not None and end is not None:
2744 content_length = end - start
2745 elif end is not None:
2746 content_length = end
2747 elif start is not None:
2748 content_length = size - start
2749 else:
2750 content_length = size
2751 self.set_header("Content-Length", content_length)
2753 if include_body:
2754 content = self.get_content(self.absolute_path, start, end)
2755 if isinstance(content, bytes):
2756 content = [content]
2757 for chunk in content:
2758 try:
2759 self.write(chunk)
2760 await self.flush()
2761 except iostream.StreamClosedError:
2762 return
2763 else:
2764 assert self.request.method == "HEAD"
2766 def compute_etag(self) -> Optional[str]:
2767 """Sets the ``Etag`` header based on static url version.
2769 This allows efficient ``If-None-Match`` checks against cached
2770 versions, and sends the correct ``Etag`` for a partial response
2771 (i.e. the same ``Etag`` as the full file).
2773 .. versionadded:: 3.1
2774 """
2775 assert self.absolute_path is not None
2776 version_hash = self._get_cached_version(self.absolute_path)
2777 if not version_hash:
2778 return None
2779 return '"%s"' % (version_hash,)
2781 def set_headers(self) -> None:
2782 """Sets the content and caching headers on the response.
2784 .. versionadded:: 3.1
2785 """
2786 self.set_header("Accept-Ranges", "bytes")
2787 self.set_etag_header()
2789 if self.modified is not None:
2790 self.set_header("Last-Modified", self.modified)
2792 content_type = self.get_content_type()
2793 if content_type:
2794 self.set_header("Content-Type", content_type)
2796 cache_time = self.get_cache_time(self.path, self.modified, content_type)
2797 if cache_time > 0:
2798 self.set_header(
2799 "Expires",
2800 datetime.datetime.now(datetime.timezone.utc)
2801 + datetime.timedelta(seconds=cache_time),
2802 )
2803 self.set_header("Cache-Control", "max-age=" + str(cache_time))
2805 self.set_extra_headers(self.path)
2807 def should_return_304(self) -> bool:
2808 """Returns True if the headers indicate that we should return 304.
2810 .. versionadded:: 3.1
2811 """
2812 # If client sent If-None-Match, use it, ignore If-Modified-Since
2813 if self.request.headers.get("If-None-Match"):
2814 return self.check_etag_header()
2816 # Check the If-Modified-Since, and don't send the result if the
2817 # content has not been modified
2818 ims_value = self.request.headers.get("If-Modified-Since")
2819 if ims_value is not None:
2820 if_since = email.utils.parsedate_to_datetime(ims_value)
2821 if if_since.tzinfo is None:
2822 if_since = if_since.replace(tzinfo=datetime.timezone.utc)
2823 assert self.modified is not None
2824 if if_since >= self.modified:
2825 return True
2827 return False
2829 @classmethod
2830 def get_absolute_path(cls, root: str, path: str) -> str:
2831 """Returns the absolute location of ``path`` relative to ``root``.
2833 ``root`` is the path configured for this `StaticFileHandler`
2834 (in most cases the ``static_path`` `Application` setting).
2836 This class method may be overridden in subclasses. By default
2837 it returns a filesystem path, but other strings may be used
2838 as long as they are unique and understood by the subclass's
2839 overridden `get_content`.
2841 .. versionadded:: 3.1
2842 """
2843 abspath = os.path.abspath(os.path.join(root, path))
2844 return abspath
2846 def validate_absolute_path(self, root: str, absolute_path: str) -> Optional[str]:
2847 """Validate and return the absolute path.
2849 ``root`` is the configured path for the `StaticFileHandler`,
2850 and ``path`` is the result of `get_absolute_path`
2852 This is an instance method called during request processing,
2853 so it may raise `HTTPError` or use methods like
2854 `RequestHandler.redirect` (return None after redirecting to
2855 halt further processing). This is where 404 errors for missing files
2856 are generated.
2858 This method may modify the path before returning it, but note that
2859 any such modifications will not be understood by `make_static_url`.
2861 In instance methods, this method's result is available as
2862 ``self.absolute_path``.
2864 .. versionadded:: 3.1
2865 """
2866 # os.path.abspath strips a trailing /.
2867 # We must add it back to `root` so that we only match files
2868 # in a directory named `root` instead of files starting with
2869 # that prefix.
2870 root = os.path.abspath(root)
2871 if not root.endswith(os.path.sep):
2872 # abspath always removes a trailing slash, except when
2873 # root is '/'. This is an unusual case, but several projects
2874 # have independently discovered this technique to disable
2875 # Tornado's path validation and (hopefully) do their own,
2876 # so we need to support it.
2877 root += os.path.sep
2878 # The trailing slash also needs to be temporarily added back
2879 # the requested path so a request to root/ will match.
2880 if not (absolute_path + os.path.sep).startswith(root):
2881 raise HTTPError(403, "%s is not in root static directory", self.path)
2882 if os.path.isdir(absolute_path) and self.default_filename is not None:
2883 # need to look at the request.path here for when path is empty
2884 # but there is some prefix to the path that was already
2885 # trimmed by the routing
2886 if not self.request.path.endswith("/"):
2887 if self.request.path.startswith("//"):
2888 # A redirect with two initial slashes is a "protocol-relative" URL.
2889 # This means the next path segment is treated as a hostname instead
2890 # of a part of the path, making this effectively an open redirect.
2891 # Reject paths starting with two slashes to prevent this.
2892 # This is only reachable under certain configurations.
2893 raise HTTPError(
2894 403, "cannot redirect path with two initial slashes"
2895 )
2896 self.redirect(self.request.path + "/", permanent=True)
2897 return None
2898 absolute_path = os.path.join(absolute_path, self.default_filename)
2899 if not os.path.exists(absolute_path):
2900 raise HTTPError(404)
2901 if not os.path.isfile(absolute_path):
2902 raise HTTPError(403, "%s is not a file", self.path)
2903 return absolute_path
2905 @classmethod
2906 def get_content(
2907 cls, abspath: str, start: Optional[int] = None, end: Optional[int] = None
2908 ) -> Generator[bytes, None, None]:
2909 """Retrieve the content of the requested resource which is located
2910 at the given absolute path.
2912 This class method may be overridden by subclasses. Note that its
2913 signature is different from other overridable class methods
2914 (no ``settings`` argument); this is deliberate to ensure that
2915 ``abspath`` is able to stand on its own as a cache key.
2917 This method should either return a byte string or an iterator
2918 of byte strings. The latter is preferred for large files
2919 as it helps reduce memory fragmentation.
2921 .. versionadded:: 3.1
2922 """
2923 with open(abspath, "rb") as file:
2924 if start is not None:
2925 file.seek(start)
2926 if end is not None:
2927 remaining = end - (start or 0) # type: Optional[int]
2928 else:
2929 remaining = None
2930 while True:
2931 chunk_size = 64 * 1024
2932 if remaining is not None and remaining < chunk_size:
2933 chunk_size = remaining
2934 chunk = file.read(chunk_size)
2935 if chunk:
2936 if remaining is not None:
2937 remaining -= len(chunk)
2938 yield chunk
2939 else:
2940 if remaining is not None:
2941 assert remaining == 0
2942 return
2944 @classmethod
2945 def get_content_version(cls, abspath: str) -> str:
2946 """Returns a version string for the resource at the given path.
2948 This class method may be overridden by subclasses. The
2949 default implementation is a SHA-512 hash of the file's contents.
2951 .. versionadded:: 3.1
2952 """
2953 data = cls.get_content(abspath)
2954 hasher = hashlib.sha512()
2955 if isinstance(data, bytes):
2956 hasher.update(data)
2957 else:
2958 for chunk in data:
2959 hasher.update(chunk)
2960 return hasher.hexdigest()
2962 def _stat(self) -> os.stat_result:
2963 assert self.absolute_path is not None
2964 if not hasattr(self, "_stat_result"):
2965 self._stat_result = os.stat(self.absolute_path)
2966 return self._stat_result
2968 def get_content_size(self) -> int:
2969 """Retrieve the total size of the resource at the given path.
2971 This method may be overridden by subclasses.
2973 .. versionadded:: 3.1
2975 .. versionchanged:: 4.0
2976 This method is now always called, instead of only when
2977 partial results are requested.
2978 """
2979 stat_result = self._stat()
2980 return stat_result.st_size
2982 def get_modified_time(self) -> Optional[datetime.datetime]:
2983 """Returns the time that ``self.absolute_path`` was last modified.
2985 May be overridden in subclasses. Should return a `~datetime.datetime`
2986 object or None.
2988 .. versionadded:: 3.1
2990 .. versionchanged:: 6.4
2991 Now returns an aware datetime object instead of a naive one.
2992 Subclasses that override this method may return either kind.
2993 """
2994 stat_result = self._stat()
2995 # NOTE: Historically, this used stat_result[stat.ST_MTIME],
2996 # which truncates the fractional portion of the timestamp. It
2997 # was changed from that form to stat_result.st_mtime to
2998 # satisfy mypy (which disallows the bracket operator), but the
2999 # latter form returns a float instead of an int. For
3000 # consistency with the past (and because we have a unit test
3001 # that relies on this), we truncate the float here, although
3002 # I'm not sure that's the right thing to do.
3003 modified = datetime.datetime.fromtimestamp(
3004 int(stat_result.st_mtime), datetime.timezone.utc
3005 )
3006 return modified
3008 def get_content_type(self) -> str:
3009 """Returns the ``Content-Type`` header to be used for this request.
3011 .. versionadded:: 3.1
3012 """
3013 assert self.absolute_path is not None
3014 mime_type, encoding = mimetypes.guess_type(self.absolute_path)
3015 # per RFC 6713, use the appropriate type for a gzip compressed file
3016 if encoding == "gzip":
3017 return "application/gzip"
3018 # As of 2015-07-21 there is no bzip2 encoding defined at
3019 # http://www.iana.org/assignments/media-types/media-types.xhtml
3020 # So for that (and any other encoding), use octet-stream.
3021 elif encoding is not None:
3022 return "application/octet-stream"
3023 elif mime_type is not None:
3024 return mime_type
3025 # if mime_type not detected, use application/octet-stream
3026 else:
3027 return "application/octet-stream"
3029 def set_extra_headers(self, path: str) -> None:
3030 """For subclass to add extra headers to the response"""
3031 pass
3033 def get_cache_time(
3034 self, path: str, modified: Optional[datetime.datetime], mime_type: str
3035 ) -> int:
3036 """Override to customize cache control behavior.
3038 Return a positive number of seconds to make the result
3039 cacheable for that amount of time or 0 to mark resource as
3040 cacheable for an unspecified amount of time (subject to
3041 browser heuristics).
3043 By default returns cache expiry of 10 years for resources requested
3044 with ``v`` argument.
3045 """
3046 return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
3048 @classmethod
3049 def make_static_url(
3050 cls, settings: Dict[str, Any], path: str, include_version: bool = True
3051 ) -> str:
3052 """Constructs a versioned url for the given path.
3054 This method may be overridden in subclasses (but note that it
3055 is a class method rather than an instance method). Subclasses
3056 are only required to implement the signature
3057 ``make_static_url(cls, settings, path)``; other keyword
3058 arguments may be passed through `~RequestHandler.static_url`
3059 but are not standard.
3061 ``settings`` is the `Application.settings` dictionary. ``path``
3062 is the static path being requested. The url returned should be
3063 relative to the current host.
3065 ``include_version`` determines whether the generated URL should
3066 include the query string containing the version hash of the
3067 file corresponding to the given ``path``.
3069 """
3070 url = settings.get("static_url_prefix", "/static/") + path
3071 if not include_version:
3072 return url
3074 version_hash = cls.get_version(settings, path)
3075 if not version_hash:
3076 return url
3078 return "%s?v=%s" % (url, version_hash)
3080 def parse_url_path(self, url_path: str) -> str:
3081 """Converts a static URL path into a filesystem path.
3083 ``url_path`` is the path component of the URL with
3084 ``static_url_prefix`` removed. The return value should be
3085 filesystem path relative to ``static_path``.
3087 This is the inverse of `make_static_url`.
3088 """
3089 if os.path.sep != "/":
3090 url_path = url_path.replace("/", os.path.sep)
3091 return url_path
3093 @classmethod
3094 def get_version(cls, settings: Dict[str, Any], path: str) -> Optional[str]:
3095 """Generate the version string to be used in static URLs.
3097 ``settings`` is the `Application.settings` dictionary and ``path``
3098 is the relative location of the requested asset on the filesystem.
3099 The returned value should be a string, or ``None`` if no version
3100 could be determined.
3102 .. versionchanged:: 3.1
3103 This method was previously recommended for subclasses to override;
3104 `get_content_version` is now preferred as it allows the base
3105 class to handle caching of the result.
3106 """
3107 abs_path = cls.get_absolute_path(settings["static_path"], path)
3108 return cls._get_cached_version(abs_path)
3110 @classmethod
3111 def _get_cached_version(cls, abs_path: str) -> Optional[str]:
3112 with cls._lock:
3113 hashes = cls._static_hashes
3114 if abs_path not in hashes:
3115 try:
3116 hashes[abs_path] = cls.get_content_version(abs_path)
3117 except Exception:
3118 gen_log.error("Could not open static file %r", abs_path)
3119 hashes[abs_path] = None
3120 hsh = hashes.get(abs_path)
3121 if hsh:
3122 return hsh
3123 return None
3126class FallbackHandler(RequestHandler):
3127 """A `RequestHandler` that wraps another HTTP server callback.
3129 The fallback is a callable object that accepts an
3130 `~.httputil.HTTPServerRequest`, such as an `Application` or
3131 `tornado.wsgi.WSGIContainer`. This is most useful to use both
3132 Tornado ``RequestHandlers`` and WSGI in the same server. Typical
3133 usage::
3135 wsgi_app = tornado.wsgi.WSGIContainer(
3136 django.core.handlers.wsgi.WSGIHandler())
3137 application = tornado.web.Application([
3138 (r"/foo", FooHandler),
3139 (r".*", FallbackHandler, dict(fallback=wsgi_app)),
3140 ])
3141 """
3143 def initialize(
3144 self, fallback: Callable[[httputil.HTTPServerRequest], None]
3145 ) -> None:
3146 self.fallback = fallback
3148 def prepare(self) -> None:
3149 self.fallback(self.request)
3150 self._finished = True
3151 self.on_finish()
3154class OutputTransform(object):
3155 """A transform modifies the result of an HTTP request (e.g., GZip encoding)
3157 Applications are not expected to create their own OutputTransforms
3158 or interact with them directly; the framework chooses which transforms
3159 (if any) to apply.
3160 """
3162 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3163 pass
3165 def transform_first_chunk(
3166 self,
3167 status_code: int,
3168 headers: httputil.HTTPHeaders,
3169 chunk: bytes,
3170 finishing: bool,
3171 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3172 return status_code, headers, chunk
3174 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3175 return chunk
3178class GZipContentEncoding(OutputTransform):
3179 """Applies the gzip content encoding to the response.
3181 See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
3183 .. versionchanged:: 4.0
3184 Now compresses all mime types beginning with ``text/``, instead
3185 of just a whitelist. (the whitelist is still used for certain
3186 non-text mime types).
3187 """
3189 # Whitelist of compressible mime types (in addition to any types
3190 # beginning with "text/").
3191 CONTENT_TYPES = set(
3192 [
3193 "application/javascript",
3194 "application/x-javascript",
3195 "application/xml",
3196 "application/atom+xml",
3197 "application/json",
3198 "application/xhtml+xml",
3199 "image/svg+xml",
3200 ]
3201 )
3202 # Python's GzipFile defaults to level 9, while most other gzip
3203 # tools (including gzip itself) default to 6, which is probably a
3204 # better CPU/size tradeoff.
3205 GZIP_LEVEL = 6
3206 # Responses that are too short are unlikely to benefit from gzipping
3207 # after considering the "Content-Encoding: gzip" header and the header
3208 # inside the gzip encoding.
3209 # Note that responses written in multiple chunks will be compressed
3210 # regardless of size.
3211 MIN_LENGTH = 1024
3213 def __init__(self, request: httputil.HTTPServerRequest) -> None:
3214 self._gzipping = "gzip" in request.headers.get("Accept-Encoding", "")
3216 def _compressible_type(self, ctype: str) -> bool:
3217 return ctype.startswith("text/") or ctype in self.CONTENT_TYPES
3219 def transform_first_chunk(
3220 self,
3221 status_code: int,
3222 headers: httputil.HTTPHeaders,
3223 chunk: bytes,
3224 finishing: bool,
3225 ) -> Tuple[int, httputil.HTTPHeaders, bytes]:
3226 # TODO: can/should this type be inherited from the superclass?
3227 if "Vary" in headers:
3228 headers["Vary"] += ", Accept-Encoding"
3229 else:
3230 headers["Vary"] = "Accept-Encoding"
3231 if self._gzipping:
3232 ctype = _unicode(headers.get("Content-Type", "")).split(";")[0]
3233 self._gzipping = (
3234 self._compressible_type(ctype)
3235 and (not finishing or len(chunk) >= self.MIN_LENGTH)
3236 and ("Content-Encoding" not in headers)
3237 )
3238 if self._gzipping:
3239 headers["Content-Encoding"] = "gzip"
3240 self._gzip_value = BytesIO()
3241 self._gzip_file = gzip.GzipFile(
3242 mode="w", fileobj=self._gzip_value, compresslevel=self.GZIP_LEVEL
3243 )
3244 chunk = self.transform_chunk(chunk, finishing)
3245 if "Content-Length" in headers:
3246 # The original content length is no longer correct.
3247 # If this is the last (and only) chunk, we can set the new
3248 # content-length; otherwise we remove it and fall back to
3249 # chunked encoding.
3250 if finishing:
3251 headers["Content-Length"] = str(len(chunk))
3252 else:
3253 del headers["Content-Length"]
3254 return status_code, headers, chunk
3256 def transform_chunk(self, chunk: bytes, finishing: bool) -> bytes:
3257 if self._gzipping:
3258 self._gzip_file.write(chunk)
3259 if finishing:
3260 self._gzip_file.close()
3261 else:
3262 self._gzip_file.flush()
3263 chunk = self._gzip_value.getvalue()
3264 self._gzip_value.truncate(0)
3265 self._gzip_value.seek(0)
3266 return chunk
3269def authenticated(
3270 method: Callable[..., Optional[Awaitable[None]]]
3271) -> Callable[..., Optional[Awaitable[None]]]:
3272 """Decorate methods with this to require that the user be logged in.
3274 If the user is not logged in, they will be redirected to the configured
3275 `login url <RequestHandler.get_login_url>`.
3277 If you configure a login url with a query parameter, Tornado will
3278 assume you know what you're doing and use it as-is. If not, it
3279 will add a `next` parameter so the login page knows where to send
3280 you once you're logged in.
3281 """
3283 @functools.wraps(method)
3284 def wrapper( # type: ignore
3285 self: RequestHandler, *args, **kwargs
3286 ) -> Optional[Awaitable[None]]:
3287 if not self.current_user:
3288 if self.request.method in ("GET", "HEAD"):
3289 url = self.get_login_url()
3290 if "?" not in url:
3291 if urllib.parse.urlsplit(url).scheme:
3292 # if login url is absolute, make next absolute too
3293 next_url = self.request.full_url()
3294 else:
3295 assert self.request.uri is not None
3296 next_url = self.request.uri
3297 url += "?" + urlencode(dict(next=next_url))
3298 self.redirect(url)
3299 return None
3300 raise HTTPError(403)
3301 return method(self, *args, **kwargs)
3303 return wrapper
3306class UIModule(object):
3307 """A re-usable, modular UI unit on a page.
3309 UI modules often execute additional queries, and they can include
3310 additional CSS and JavaScript that will be included in the output
3311 page, which is automatically inserted on page render.
3313 Subclasses of UIModule must override the `render` method.
3314 """
3316 def __init__(self, handler: RequestHandler) -> None:
3317 self.handler = handler
3318 self.request = handler.request
3319 self.ui = handler.ui
3320 self.locale = handler.locale
3322 @property
3323 def current_user(self) -> Any:
3324 return self.handler.current_user
3326 def render(self, *args: Any, **kwargs: Any) -> str:
3327 """Override in subclasses to return this module's output."""
3328 raise NotImplementedError()
3330 def embedded_javascript(self) -> Optional[str]:
3331 """Override to return a JavaScript string
3332 to be embedded in the page."""
3333 return None
3335 def javascript_files(self) -> Optional[Iterable[str]]:
3336 """Override to return a list of JavaScript files needed by this module.
3338 If the return values are relative paths, they will be passed to
3339 `RequestHandler.static_url`; otherwise they will be used as-is.
3340 """
3341 return None
3343 def embedded_css(self) -> Optional[str]:
3344 """Override to return a CSS string
3345 that will be embedded in the page."""
3346 return None
3348 def css_files(self) -> Optional[Iterable[str]]:
3349 """Override to returns a list of CSS files required by this module.
3351 If the return values are relative paths, they will be passed to
3352 `RequestHandler.static_url`; otherwise they will be used as-is.
3353 """
3354 return None
3356 def html_head(self) -> Optional[str]:
3357 """Override to return an HTML string that will be put in the <head/>
3358 element.
3359 """
3360 return None
3362 def html_body(self) -> Optional[str]:
3363 """Override to return an HTML string that will be put at the end of
3364 the <body/> element.
3365 """
3366 return None
3368 def render_string(self, path: str, **kwargs: Any) -> bytes:
3369 """Renders a template and returns it as a string."""
3370 return self.handler.render_string(path, **kwargs)
3373class _linkify(UIModule):
3374 def render(self, text: str, **kwargs: Any) -> str: # type: ignore
3375 return escape.linkify(text, **kwargs)
3378class _xsrf_form_html(UIModule):
3379 def render(self) -> str: # type: ignore
3380 return self.handler.xsrf_form_html()
3383class TemplateModule(UIModule):
3384 """UIModule that simply renders the given template.
3386 {% module Template("foo.html") %} is similar to {% include "foo.html" %},
3387 but the module version gets its own namespace (with kwargs passed to
3388 Template()) instead of inheriting the outer template's namespace.
3390 Templates rendered through this module also get access to UIModule's
3391 automatic JavaScript/CSS features. Simply call set_resources
3392 inside the template and give it keyword arguments corresponding to
3393 the methods on UIModule: {{ set_resources(js_files=static_url("my.js")) }}
3394 Note that these resources are output once per template file, not once
3395 per instantiation of the template, so they must not depend on
3396 any arguments to the template.
3397 """
3399 def __init__(self, handler: RequestHandler) -> None:
3400 super().__init__(handler)
3401 # keep resources in both a list and a dict to preserve order
3402 self._resource_list = [] # type: List[Dict[str, Any]]
3403 self._resource_dict = {} # type: Dict[str, Dict[str, Any]]
3405 def render(self, path: str, **kwargs: Any) -> bytes: # type: ignore
3406 def set_resources(**kwargs) -> str: # type: ignore
3407 if path not in self._resource_dict:
3408 self._resource_list.append(kwargs)
3409 self._resource_dict[path] = kwargs
3410 else:
3411 if self._resource_dict[path] != kwargs:
3412 raise ValueError(
3413 "set_resources called with different "
3414 "resources for the same template"
3415 )
3416 return ""
3418 return self.render_string(path, set_resources=set_resources, **kwargs)
3420 def _get_resources(self, key: str) -> Iterable[str]:
3421 return (r[key] for r in self._resource_list if key in r)
3423 def embedded_javascript(self) -> str:
3424 return "\n".join(self._get_resources("embedded_javascript"))
3426 def javascript_files(self) -> Iterable[str]:
3427 result = []
3428 for f in self._get_resources("javascript_files"):
3429 if isinstance(f, (unicode_type, bytes)):
3430 result.append(f)
3431 else:
3432 result.extend(f)
3433 return result
3435 def embedded_css(self) -> str:
3436 return "\n".join(self._get_resources("embedded_css"))
3438 def css_files(self) -> Iterable[str]:
3439 result = []
3440 for f in self._get_resources("css_files"):
3441 if isinstance(f, (unicode_type, bytes)):
3442 result.append(f)
3443 else:
3444 result.extend(f)
3445 return result
3447 def html_head(self) -> str:
3448 return "".join(self._get_resources("html_head"))
3450 def html_body(self) -> str:
3451 return "".join(self._get_resources("html_body"))
3454class _UIModuleNamespace(object):
3455 """Lazy namespace which creates UIModule proxies bound to a handler."""
3457 def __init__(
3458 self, handler: RequestHandler, ui_modules: Dict[str, Type[UIModule]]
3459 ) -> None:
3460 self.handler = handler
3461 self.ui_modules = ui_modules
3463 def __getitem__(self, key: str) -> Callable[..., str]:
3464 return self.handler._ui_module(key, self.ui_modules[key])
3466 def __getattr__(self, key: str) -> Callable[..., str]:
3467 try:
3468 return self[key]
3469 except KeyError as e:
3470 raise AttributeError(str(e))
3473def create_signed_value(
3474 secret: _CookieSecretTypes,
3475 name: str,
3476 value: Union[str, bytes],
3477 version: Optional[int] = None,
3478 clock: Optional[Callable[[], float]] = None,
3479 key_version: Optional[int] = None,
3480) -> bytes:
3481 if version is None:
3482 version = DEFAULT_SIGNED_VALUE_VERSION
3483 if clock is None:
3484 clock = time.time
3486 timestamp = utf8(str(int(clock())))
3487 value = base64.b64encode(utf8(value))
3488 if version == 1:
3489 assert not isinstance(secret, dict)
3490 signature = _create_signature_v1(secret, name, value, timestamp)
3491 value = b"|".join([value, timestamp, signature])
3492 return value
3493 elif version == 2:
3494 # The v2 format consists of a version number and a series of
3495 # length-prefixed fields "%d:%s", the last of which is a
3496 # signature, all separated by pipes. All numbers are in
3497 # decimal format with no leading zeros. The signature is an
3498 # HMAC-SHA256 of the whole string up to that point, including
3499 # the final pipe.
3500 #
3501 # The fields are:
3502 # - format version (i.e. 2; no length prefix)
3503 # - key version (integer, default is 0)
3504 # - timestamp (integer seconds since epoch)
3505 # - name (not encoded; assumed to be ~alphanumeric)
3506 # - value (base64-encoded)
3507 # - signature (hex-encoded; no length prefix)
3508 def format_field(s: Union[str, bytes]) -> bytes:
3509 return utf8("%d:" % len(s)) + utf8(s)
3511 to_sign = b"|".join(
3512 [
3513 b"2",
3514 format_field(str(key_version or 0)),
3515 format_field(timestamp),
3516 format_field(name),
3517 format_field(value),
3518 b"",
3519 ]
3520 )
3522 if isinstance(secret, dict):
3523 assert (
3524 key_version is not None
3525 ), "Key version must be set when sign key dict is used"
3526 assert version >= 2, "Version must be at least 2 for key version support"
3527 secret = secret[key_version]
3529 signature = _create_signature_v2(secret, to_sign)
3530 return to_sign + signature
3531 else:
3532 raise ValueError("Unsupported version %d" % version)
3535# A leading version number in decimal
3536# with no leading zeros, followed by a pipe.
3537_signed_value_version_re = re.compile(rb"^([1-9][0-9]*)\|(.*)$")
3540def _get_version(value: bytes) -> int:
3541 # Figures out what version value is. Version 1 did not include an
3542 # explicit version field and started with arbitrary base64 data,
3543 # which makes this tricky.
3544 m = _signed_value_version_re.match(value)
3545 if m is None:
3546 version = 1
3547 else:
3548 try:
3549 version = int(m.group(1))
3550 if version > 999:
3551 # Certain payloads from the version-less v1 format may
3552 # be parsed as valid integers. Due to base64 padding
3553 # restrictions, this can only happen for numbers whose
3554 # length is a multiple of 4, so we can treat all
3555 # numbers up to 999 as versions, and for the rest we
3556 # fall back to v1 format.
3557 version = 1
3558 except ValueError:
3559 version = 1
3560 return version
3563def decode_signed_value(
3564 secret: _CookieSecretTypes,
3565 name: str,
3566 value: Union[None, str, bytes],
3567 max_age_days: float = 31,
3568 clock: Optional[Callable[[], float]] = None,
3569 min_version: Optional[int] = None,
3570) -> Optional[bytes]:
3571 if clock is None:
3572 clock = time.time
3573 if min_version is None:
3574 min_version = DEFAULT_SIGNED_VALUE_MIN_VERSION
3575 if min_version > 2:
3576 raise ValueError("Unsupported min_version %d" % min_version)
3577 if not value:
3578 return None
3580 value = utf8(value)
3581 version = _get_version(value)
3583 if version < min_version:
3584 return None
3585 if version == 1:
3586 assert not isinstance(secret, dict)
3587 return _decode_signed_value_v1(secret, name, value, max_age_days, clock)
3588 elif version == 2:
3589 return _decode_signed_value_v2(secret, name, value, max_age_days, clock)
3590 else:
3591 return None
3594def _decode_signed_value_v1(
3595 secret: Union[str, bytes],
3596 name: str,
3597 value: bytes,
3598 max_age_days: float,
3599 clock: Callable[[], float],
3600) -> Optional[bytes]:
3601 parts = utf8(value).split(b"|")
3602 if len(parts) != 3:
3603 return None
3604 signature = _create_signature_v1(secret, name, parts[0], parts[1])
3605 if not hmac.compare_digest(parts[2], signature):
3606 gen_log.warning("Invalid cookie signature %r", value)
3607 return None
3608 timestamp = int(parts[1])
3609 if timestamp < clock() - max_age_days * 86400:
3610 gen_log.warning("Expired cookie %r", value)
3611 return None
3612 if timestamp > clock() + 31 * 86400:
3613 # _cookie_signature does not hash a delimiter between the
3614 # parts of the cookie, so an attacker could transfer trailing
3615 # digits from the payload to the timestamp without altering the
3616 # signature. For backwards compatibility, sanity-check timestamp
3617 # here instead of modifying _cookie_signature.
3618 gen_log.warning("Cookie timestamp in future; possible tampering %r", value)
3619 return None
3620 if parts[1].startswith(b"0"):
3621 gen_log.warning("Tampered cookie %r", value)
3622 return None
3623 try:
3624 return base64.b64decode(parts[0])
3625 except Exception:
3626 return None
3629def _decode_fields_v2(value: bytes) -> Tuple[int, bytes, bytes, bytes, bytes]:
3630 def _consume_field(s: bytes) -> Tuple[bytes, bytes]:
3631 length, _, rest = s.partition(b":")
3632 n = int(length)
3633 field_value = rest[:n]
3634 # In python 3, indexing bytes returns small integers; we must
3635 # use a slice to get a byte string as in python 2.
3636 if rest[n : n + 1] != b"|":
3637 raise ValueError("malformed v2 signed value field")
3638 rest = rest[n + 1 :]
3639 return field_value, rest
3641 rest = value[2:] # remove version number
3642 key_version, rest = _consume_field(rest)
3643 timestamp, rest = _consume_field(rest)
3644 name_field, rest = _consume_field(rest)
3645 value_field, passed_sig = _consume_field(rest)
3646 return int(key_version), timestamp, name_field, value_field, passed_sig
3649def _decode_signed_value_v2(
3650 secret: _CookieSecretTypes,
3651 name: str,
3652 value: bytes,
3653 max_age_days: float,
3654 clock: Callable[[], float],
3655) -> Optional[bytes]:
3656 try:
3657 (
3658 key_version,
3659 timestamp_bytes,
3660 name_field,
3661 value_field,
3662 passed_sig,
3663 ) = _decode_fields_v2(value)
3664 except ValueError:
3665 return None
3666 signed_string = value[: -len(passed_sig)]
3668 if isinstance(secret, dict):
3669 try:
3670 secret = secret[key_version]
3671 except KeyError:
3672 return None
3674 expected_sig = _create_signature_v2(secret, signed_string)
3675 if not hmac.compare_digest(passed_sig, expected_sig):
3676 return None
3677 if name_field != utf8(name):
3678 return None
3679 timestamp = int(timestamp_bytes)
3680 if timestamp < clock() - max_age_days * 86400:
3681 # The signature has expired.
3682 return None
3683 try:
3684 return base64.b64decode(value_field)
3685 except Exception:
3686 return None
3689def get_signature_key_version(value: Union[str, bytes]) -> Optional[int]:
3690 value = utf8(value)
3691 version = _get_version(value)
3692 if version < 2:
3693 return None
3694 try:
3695 key_version, _, _, _, _ = _decode_fields_v2(value)
3696 except ValueError:
3697 return None
3699 return key_version
3702def _create_signature_v1(secret: Union[str, bytes], *parts: Union[str, bytes]) -> bytes:
3703 hash = hmac.new(utf8(secret), digestmod=hashlib.sha1)
3704 for part in parts:
3705 hash.update(utf8(part))
3706 return utf8(hash.hexdigest())
3709def _create_signature_v2(secret: Union[str, bytes], s: bytes) -> bytes:
3710 hash = hmac.new(utf8(secret), digestmod=hashlib.sha256)
3711 hash.update(utf8(s))
3712 return utf8(hash.hexdigest())
3715def is_absolute(path: str) -> bool:
3716 return any(path.startswith(x) for x in ["/", "http:", "https:"])