123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778 |
- # -*- coding: utf-8 -*-
- """
- werkzeug.utils
- ~~~~~~~~~~~~~~
- This module implements various utilities for WSGI applications. Most of
- them are used by the request and response wrappers but especially for
- middleware development it makes sense to use them without the wrappers.
- :copyright: 2007 Pallets
- :license: BSD-3-Clause
- """
- import codecs
- import os
- import pkgutil
- import re
- import sys
- from ._compat import iteritems
- from ._compat import PY2
- from ._compat import reraise
- from ._compat import string_types
- from ._compat import text_type
- from ._compat import unichr
- from ._internal import _DictAccessorProperty
- from ._internal import _missing
- from ._internal import _parse_signature
- try:
- from html.entities import name2codepoint
- except ImportError:
- from htmlentitydefs import name2codepoint
- _format_re = re.compile(r"\$(?:(%s)|\{(%s)\})" % (("[a-zA-Z_][a-zA-Z0-9_]*",) * 2))
- _entity_re = re.compile(r"&([^;]+);")
- _filename_ascii_strip_re = re.compile(r"[^A-Za-z0-9_.-]")
- _windows_device_files = (
- "CON",
- "AUX",
- "COM1",
- "COM2",
- "COM3",
- "COM4",
- "LPT1",
- "LPT2",
- "LPT3",
- "PRN",
- "NUL",
- )
- class cached_property(property):
- """A decorator that converts a function into a lazy property. The
- function wrapped is called the first time to retrieve the result
- and then that calculated result is used the next time you access
- the value::
- class Foo(object):
- @cached_property
- def foo(self):
- # calculate something important here
- return 42
- The class has to have a `__dict__` in order for this property to
- work.
- """
- # implementation detail: A subclass of python's builtin property
- # decorator, we override __get__ to check for a cached value. If one
- # chooses to invoke __get__ by hand the property will still work as
- # expected because the lookup logic is replicated in __get__ for
- # manual invocation.
- def __init__(self, func, name=None, doc=None):
- self.__name__ = name or func.__name__
- self.__module__ = func.__module__
- self.__doc__ = doc or func.__doc__
- self.func = func
- def __set__(self, obj, value):
- obj.__dict__[self.__name__] = value
- def __get__(self, obj, type=None):
- if obj is None:
- return self
- value = obj.__dict__.get(self.__name__, _missing)
- if value is _missing:
- value = self.func(obj)
- obj.__dict__[self.__name__] = value
- return value
- def invalidate_cached_property(obj, name):
- """Invalidates the cache for a :class:`cached_property`:
- >>> class Test(object):
- ... @cached_property
- ... def magic_number(self):
- ... print("recalculating...")
- ... return 42
- ...
- >>> var = Test()
- >>> var.magic_number
- recalculating...
- 42
- >>> var.magic_number
- 42
- >>> invalidate_cached_property(var, "magic_number")
- >>> var.magic_number
- recalculating...
- 42
- You must pass the name of the cached property as the second argument.
- """
- if not isinstance(getattr(obj.__class__, name, None), cached_property):
- raise TypeError(
- "Attribute {} of object {} is not a cached_property, "
- "cannot be invalidated".format(name, obj)
- )
- obj.__dict__[name] = _missing
- class environ_property(_DictAccessorProperty):
- """Maps request attributes to environment variables. This works not only
- for the Werzeug request object, but also any other class with an
- environ attribute:
- >>> class Test(object):
- ... environ = {'key': 'value'}
- ... test = environ_property('key')
- >>> var = Test()
- >>> var.test
- 'value'
- If you pass it a second value it's used as default if the key does not
- exist, the third one can be a converter that takes a value and converts
- it. If it raises :exc:`ValueError` or :exc:`TypeError` the default value
- is used. If no default value is provided `None` is used.
- Per default the property is read only. You have to explicitly enable it
- by passing ``read_only=False`` to the constructor.
- """
- read_only = True
- def lookup(self, obj):
- return obj.environ
- class header_property(_DictAccessorProperty):
- """Like `environ_property` but for headers."""
- def lookup(self, obj):
- return obj.headers
- class HTMLBuilder(object):
- """Helper object for HTML generation.
- Per default there are two instances of that class. The `html` one, and
- the `xhtml` one for those two dialects. The class uses keyword parameters
- and positional parameters to generate small snippets of HTML.
- Keyword parameters are converted to XML/SGML attributes, positional
- arguments are used as children. Because Python accepts positional
- arguments before keyword arguments it's a good idea to use a list with the
- star-syntax for some children:
- >>> html.p(class_='foo', *[html.a('foo', href='foo.html'), ' ',
- ... html.a('bar', href='bar.html')])
- u'<p class="foo"><a href="foo.html">foo</a> <a href="bar.html">bar</a></p>'
- This class works around some browser limitations and can not be used for
- arbitrary SGML/XML generation. For that purpose lxml and similar
- libraries exist.
- Calling the builder escapes the string passed:
- >>> html.p(html("<foo>"))
- u'<p><foo></p>'
- """
- _entity_re = re.compile(r"&([^;]+);")
- _entities = name2codepoint.copy()
- _entities["apos"] = 39
- _empty_elements = {
- "area",
- "base",
- "basefont",
- "br",
- "col",
- "command",
- "embed",
- "frame",
- "hr",
- "img",
- "input",
- "keygen",
- "isindex",
- "link",
- "meta",
- "param",
- "source",
- "wbr",
- }
- _boolean_attributes = {
- "selected",
- "checked",
- "compact",
- "declare",
- "defer",
- "disabled",
- "ismap",
- "multiple",
- "nohref",
- "noresize",
- "noshade",
- "nowrap",
- }
- _plaintext_elements = {"textarea"}
- _c_like_cdata = {"script", "style"}
- def __init__(self, dialect):
- self._dialect = dialect
- def __call__(self, s):
- return escape(s)
- def __getattr__(self, tag):
- if tag[:2] == "__":
- raise AttributeError(tag)
- def proxy(*children, **arguments):
- buffer = "<" + tag
- for key, value in iteritems(arguments):
- if value is None:
- continue
- if key[-1] == "_":
- key = key[:-1]
- if key in self._boolean_attributes:
- if not value:
- continue
- if self._dialect == "xhtml":
- value = '="' + key + '"'
- else:
- value = ""
- else:
- value = '="' + escape(value) + '"'
- buffer += " " + key + value
- if not children and tag in self._empty_elements:
- if self._dialect == "xhtml":
- buffer += " />"
- else:
- buffer += ">"
- return buffer
- buffer += ">"
- children_as_string = "".join(
- [text_type(x) for x in children if x is not None]
- )
- if children_as_string:
- if tag in self._plaintext_elements:
- children_as_string = escape(children_as_string)
- elif tag in self._c_like_cdata and self._dialect == "xhtml":
- children_as_string = (
- "/*<![CDATA[*/" + children_as_string + "/*]]>*/"
- )
- buffer += children_as_string + "</" + tag + ">"
- return buffer
- return proxy
- def __repr__(self):
- return "<%s for %r>" % (self.__class__.__name__, self._dialect)
- html = HTMLBuilder("html")
- xhtml = HTMLBuilder("xhtml")
- # https://cgit.freedesktop.org/xdg/shared-mime-info/tree/freedesktop.org.xml.in
- # https://www.iana.org/assignments/media-types/media-types.xhtml
- # Types listed in the XDG mime info that have a charset in the IANA registration.
- _charset_mimetypes = {
- "application/ecmascript",
- "application/javascript",
- "application/sql",
- "application/xml",
- "application/xml-dtd",
- "application/xml-external-parsed-entity",
- }
- def get_content_type(mimetype, charset):
- """Returns the full content type string with charset for a mimetype.
- If the mimetype represents text, the charset parameter will be
- appended, otherwise the mimetype is returned unchanged.
- :param mimetype: The mimetype to be used as content type.
- :param charset: The charset to be appended for text mimetypes.
- :return: The content type.
- .. versionchanged:: 0.15
- Any type that ends with ``+xml`` gets a charset, not just those
- that start with ``application/``. Known text types such as
- ``application/javascript`` are also given charsets.
- """
- if (
- mimetype.startswith("text/")
- or mimetype in _charset_mimetypes
- or mimetype.endswith("+xml")
- ):
- mimetype += "; charset=" + charset
- return mimetype
- def detect_utf_encoding(data):
- """Detect which UTF encoding was used to encode the given bytes.
- The latest JSON standard (:rfc:`8259`) suggests that only UTF-8 is
- accepted. Older documents allowed 8, 16, or 32. 16 and 32 can be big
- or little endian. Some editors or libraries may prepend a BOM.
- :internal:
- :param data: Bytes in unknown UTF encoding.
- :return: UTF encoding name
- .. versionadded:: 0.15
- """
- head = data[:4]
- if head[:3] == codecs.BOM_UTF8:
- return "utf-8-sig"
- if b"\x00" not in head:
- return "utf-8"
- if head in (codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE):
- return "utf-32"
- if head[:2] in (codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE):
- return "utf-16"
- if len(head) == 4:
- if head[:3] == b"\x00\x00\x00":
- return "utf-32-be"
- if head[::2] == b"\x00\x00":
- return "utf-16-be"
- if head[1:] == b"\x00\x00\x00":
- return "utf-32-le"
- if head[1::2] == b"\x00\x00":
- return "utf-16-le"
- if len(head) == 2:
- return "utf-16-be" if head.startswith(b"\x00") else "utf-16-le"
- return "utf-8"
- def format_string(string, context):
- """String-template format a string:
- >>> format_string('$foo and ${foo}s', dict(foo=42))
- '42 and 42s'
- This does not do any attribute lookup etc. For more advanced string
- formattings have a look at the `werkzeug.template` module.
- :param string: the format string.
- :param context: a dict with the variables to insert.
- """
- def lookup_arg(match):
- x = context[match.group(1) or match.group(2)]
- if not isinstance(x, string_types):
- x = type(string)(x)
- return x
- return _format_re.sub(lookup_arg, string)
- def secure_filename(filename):
- r"""Pass it a filename and it will return a secure version of it. This
- filename can then safely be stored on a regular file system and passed
- to :func:`os.path.join`. The filename returned is an ASCII only string
- for maximum portability.
- On windows systems the function also makes sure that the file is not
- named after one of the special device files.
- >>> secure_filename("My cool movie.mov")
- 'My_cool_movie.mov'
- >>> secure_filename("../../../etc/passwd")
- 'etc_passwd'
- >>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
- 'i_contain_cool_umlauts.txt'
- The function might return an empty filename. It's your responsibility
- to ensure that the filename is unique and that you abort or
- generate a random filename if the function returned an empty one.
- .. versionadded:: 0.5
- :param filename: the filename to secure
- """
- if isinstance(filename, text_type):
- from unicodedata import normalize
- filename = normalize("NFKD", filename).encode("ascii", "ignore")
- if not PY2:
- filename = filename.decode("ascii")
- for sep in os.path.sep, os.path.altsep:
- if sep:
- filename = filename.replace(sep, " ")
- filename = str(_filename_ascii_strip_re.sub("", "_".join(filename.split()))).strip(
- "._"
- )
- # on nt a couple of special files are present in each folder. We
- # have to ensure that the target file is not such a filename. In
- # this case we prepend an underline
- if (
- os.name == "nt"
- and filename
- and filename.split(".")[0].upper() in _windows_device_files
- ):
- filename = "_" + filename
- return filename
- def escape(s):
- """Replace special characters "&", "<", ">" and (") to HTML-safe sequences.
- There is a special handling for `None` which escapes to an empty string.
- .. versionchanged:: 0.9
- `quote` is now implicitly on.
- :param s: the string to escape.
- :param quote: ignored.
- """
- if s is None:
- return ""
- elif hasattr(s, "__html__"):
- return text_type(s.__html__())
- if not isinstance(s, string_types):
- s = text_type(s)
- return (
- s.replace("&", "&")
- .replace("<", "<")
- .replace(">", ">")
- .replace('"', """)
- )
- def unescape(s):
- """The reverse function of `escape`. This unescapes all the HTML
- entities, not only the XML entities inserted by `escape`.
- :param s: the string to unescape.
- """
- def handle_match(m):
- name = m.group(1)
- if name in HTMLBuilder._entities:
- return unichr(HTMLBuilder._entities[name])
- try:
- if name[:2] in ("#x", "#X"):
- return unichr(int(name[2:], 16))
- elif name.startswith("#"):
- return unichr(int(name[1:]))
- except ValueError:
- pass
- return u""
- return _entity_re.sub(handle_match, s)
- def redirect(location, code=302, Response=None):
- """Returns a response object (a WSGI application) that, if called,
- redirects the client to the target location. Supported codes are
- 301, 302, 303, 305, 307, and 308. 300 is not supported because
- it's not a real redirect and 304 because it's the answer for a
- request with a request with defined If-Modified-Since headers.
- .. versionadded:: 0.6
- The location can now be a unicode string that is encoded using
- the :func:`iri_to_uri` function.
- .. versionadded:: 0.10
- The class used for the Response object can now be passed in.
- :param location: the location the response should redirect to.
- :param code: the redirect status code. defaults to 302.
- :param class Response: a Response class to use when instantiating a
- response. The default is :class:`werkzeug.wrappers.Response` if
- unspecified.
- """
- if Response is None:
- from .wrappers import Response
- display_location = escape(location)
- if isinstance(location, text_type):
- # Safe conversion is necessary here as we might redirect
- # to a broken URI scheme (for instance itms-services).
- from .urls import iri_to_uri
- location = iri_to_uri(location, safe_conversion=True)
- response = Response(
- '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">\n'
- "<title>Redirecting...</title>\n"
- "<h1>Redirecting...</h1>\n"
- "<p>You should be redirected automatically to target URL: "
- '<a href="%s">%s</a>. If not click the link.'
- % (escape(location), display_location),
- code,
- mimetype="text/html",
- )
- response.headers["Location"] = location
- return response
- def append_slash_redirect(environ, code=301):
- """Redirects to the same URL but with a slash appended. The behavior
- of this function is undefined if the path ends with a slash already.
- :param environ: the WSGI environment for the request that triggers
- the redirect.
- :param code: the status code for the redirect.
- """
- new_path = environ["PATH_INFO"].strip("/") + "/"
- query_string = environ.get("QUERY_STRING")
- if query_string:
- new_path += "?" + query_string
- return redirect(new_path, code)
- def import_string(import_name, silent=False):
- """Imports an object based on a string. This is useful if you want to
- use import paths as endpoints or something similar. An import path can
- be specified either in dotted notation (``xml.sax.saxutils.escape``)
- or with a colon as object delimiter (``xml.sax.saxutils:escape``).
- If `silent` is True the return value will be `None` if the import fails.
- :param import_name: the dotted name for the object to import.
- :param silent: if set to `True` import errors are ignored and
- `None` is returned instead.
- :return: imported object
- """
- # force the import name to automatically convert to strings
- # __import__ is not able to handle unicode strings in the fromlist
- # if the module is a package
- import_name = str(import_name).replace(":", ".")
- try:
- try:
- __import__(import_name)
- except ImportError:
- if "." not in import_name:
- raise
- else:
- return sys.modules[import_name]
- module_name, obj_name = import_name.rsplit(".", 1)
- module = __import__(module_name, globals(), locals(), [obj_name])
- try:
- return getattr(module, obj_name)
- except AttributeError as e:
- raise ImportError(e)
- except ImportError as e:
- if not silent:
- reraise(
- ImportStringError, ImportStringError(import_name, e), sys.exc_info()[2]
- )
- def find_modules(import_path, include_packages=False, recursive=False):
- """Finds all the modules below a package. This can be useful to
- automatically import all views / controllers so that their metaclasses /
- function decorators have a chance to register themselves on the
- application.
- Packages are not returned unless `include_packages` is `True`. This can
- also recursively list modules but in that case it will import all the
- packages to get the correct load path of that module.
- :param import_path: the dotted name for the package to find child modules.
- :param include_packages: set to `True` if packages should be returned, too.
- :param recursive: set to `True` if recursion should happen.
- :return: generator
- """
- module = import_string(import_path)
- path = getattr(module, "__path__", None)
- if path is None:
- raise ValueError("%r is not a package" % import_path)
- basename = module.__name__ + "."
- for _importer, modname, ispkg in pkgutil.iter_modules(path):
- modname = basename + modname
- if ispkg:
- if include_packages:
- yield modname
- if recursive:
- for item in find_modules(modname, include_packages, True):
- yield item
- else:
- yield modname
- def validate_arguments(func, args, kwargs, drop_extra=True):
- """Checks if the function accepts the arguments and keyword arguments.
- Returns a new ``(args, kwargs)`` tuple that can safely be passed to
- the function without causing a `TypeError` because the function signature
- is incompatible. If `drop_extra` is set to `True` (which is the default)
- any extra positional or keyword arguments are dropped automatically.
- The exception raised provides three attributes:
- `missing`
- A set of argument names that the function expected but where
- missing.
- `extra`
- A dict of keyword arguments that the function can not handle but
- where provided.
- `extra_positional`
- A list of values that where given by positional argument but the
- function cannot accept.
- This can be useful for decorators that forward user submitted data to
- a view function::
- from werkzeug.utils import ArgumentValidationError, validate_arguments
- def sanitize(f):
- def proxy(request):
- data = request.values.to_dict()
- try:
- args, kwargs = validate_arguments(f, (request,), data)
- except ArgumentValidationError:
- raise BadRequest('The browser failed to transmit all '
- 'the data expected.')
- return f(*args, **kwargs)
- return proxy
- :param func: the function the validation is performed against.
- :param args: a tuple of positional arguments.
- :param kwargs: a dict of keyword arguments.
- :param drop_extra: set to `False` if you don't want extra arguments
- to be silently dropped.
- :return: tuple in the form ``(args, kwargs)``.
- """
- parser = _parse_signature(func)
- args, kwargs, missing, extra, extra_positional = parser(args, kwargs)[:5]
- if missing:
- raise ArgumentValidationError(tuple(missing))
- elif (extra or extra_positional) and not drop_extra:
- raise ArgumentValidationError(None, extra, extra_positional)
- return tuple(args), kwargs
- def bind_arguments(func, args, kwargs):
- """Bind the arguments provided into a dict. When passed a function,
- a tuple of arguments and a dict of keyword arguments `bind_arguments`
- returns a dict of names as the function would see it. This can be useful
- to implement a cache decorator that uses the function arguments to build
- the cache key based on the values of the arguments.
- :param func: the function the arguments should be bound for.
- :param args: tuple of positional arguments.
- :param kwargs: a dict of keyword arguments.
- :return: a :class:`dict` of bound keyword arguments.
- """
- (
- args,
- kwargs,
- missing,
- extra,
- extra_positional,
- arg_spec,
- vararg_var,
- kwarg_var,
- ) = _parse_signature(func)(args, kwargs)
- values = {}
- for (name, _has_default, _default), value in zip(arg_spec, args):
- values[name] = value
- if vararg_var is not None:
- values[vararg_var] = tuple(extra_positional)
- elif extra_positional:
- raise TypeError("too many positional arguments")
- if kwarg_var is not None:
- multikw = set(extra) & set([x[0] for x in arg_spec])
- if multikw:
- raise TypeError(
- "got multiple values for keyword argument " + repr(next(iter(multikw)))
- )
- values[kwarg_var] = extra
- elif extra:
- raise TypeError("got unexpected keyword argument " + repr(next(iter(extra))))
- return values
- class ArgumentValidationError(ValueError):
- """Raised if :func:`validate_arguments` fails to validate"""
- def __init__(self, missing=None, extra=None, extra_positional=None):
- self.missing = set(missing or ())
- self.extra = extra or {}
- self.extra_positional = extra_positional or []
- ValueError.__init__(
- self,
- "function arguments invalid. (%d missing, %d additional)"
- % (len(self.missing), len(self.extra) + len(self.extra_positional)),
- )
- class ImportStringError(ImportError):
- """Provides information about a failed :func:`import_string` attempt."""
- #: String in dotted notation that failed to be imported.
- import_name = None
- #: Wrapped exception.
- exception = None
- def __init__(self, import_name, exception):
- self.import_name = import_name
- self.exception = exception
- msg = (
- "import_string() failed for %r. Possible reasons are:\n\n"
- "- missing __init__.py in a package;\n"
- "- package or module path not included in sys.path;\n"
- "- duplicated package or module name taking precedence in "
- "sys.path;\n"
- "- missing module, class, function or variable;\n\n"
- "Debugged import:\n\n%s\n\n"
- "Original exception:\n\n%s: %s"
- )
- name = ""
- tracked = []
- for part in import_name.replace(":", ".").split("."):
- name += (name and ".") + part
- imported = import_string(name, silent=True)
- if imported:
- tracked.append((name, getattr(imported, "__file__", None)))
- else:
- track = ["- %r found in %r." % (n, i) for n, i in tracked]
- track.append("- %r not found." % name)
- msg = msg % (
- import_name,
- "\n".join(track),
- exception.__class__.__name__,
- str(exception),
- )
- break
- ImportError.__init__(self, msg)
- def __repr__(self):
- return "<%s(%r, %r)>" % (
- self.__class__.__name__,
- self.import_name,
- self.exception,
- )
|