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