diff options
Diffstat (limited to 'python/werkzeug/contrib')
-rw-r--r-- | python/werkzeug/contrib/__init__.py | 16 | ||||
-rw-r--r-- | python/werkzeug/contrib/atom.py | 362 | ||||
-rw-r--r-- | python/werkzeug/contrib/cache.py | 933 | ||||
-rw-r--r-- | python/werkzeug/contrib/fixers.py | 262 | ||||
-rw-r--r-- | python/werkzeug/contrib/iterio.py | 358 | ||||
-rw-r--r-- | python/werkzeug/contrib/lint.py | 11 | ||||
-rw-r--r-- | python/werkzeug/contrib/profiler.py | 42 | ||||
-rw-r--r-- | python/werkzeug/contrib/securecookie.py | 362 | ||||
-rw-r--r-- | python/werkzeug/contrib/sessions.py | 389 | ||||
-rw-r--r-- | python/werkzeug/contrib/wrappers.py | 385 |
10 files changed, 0 insertions, 3120 deletions
diff --git a/python/werkzeug/contrib/__init__.py b/python/werkzeug/contrib/__init__.py deleted file mode 100644 index 0e741f0..0000000 --- a/python/werkzeug/contrib/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 -*- -""" - werkzeug.contrib - ~~~~~~~~~~~~~~~~ - - Contains user-submitted code that other users may find useful, but which - is not part of the Werkzeug core. Anyone can write code for inclusion in - the `contrib` package. All modules in this package are distributed as an - add-on library and thus are not part of Werkzeug itself. - - This file itself is mostly for informational purposes and to tell the - Python interpreter that `contrib` is a package. - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" diff --git a/python/werkzeug/contrib/atom.py b/python/werkzeug/contrib/atom.py deleted file mode 100644 index d079d2b..0000000 --- a/python/werkzeug/contrib/atom.py +++ /dev/null @@ -1,362 +0,0 @@ -# -*- coding: utf-8 -*- -""" - werkzeug.contrib.atom - ~~~~~~~~~~~~~~~~~~~~~ - - This module provides a class called :class:`AtomFeed` which can be - used to generate feeds in the Atom syndication format (see :rfc:`4287`). - - Example:: - - def atom_feed(request): - feed = AtomFeed("My Blog", feed_url=request.url, - url=request.host_url, - subtitle="My example blog for a feed test.") - for post in Post.query.limit(10).all(): - feed.add(post.title, post.body, content_type='html', - author=post.author, url=post.url, id=post.uid, - updated=post.last_update, published=post.pub_date) - return feed.get_response() - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -import warnings -from datetime import datetime - -from .._compat import implements_to_string -from .._compat import string_types -from ..utils import escape -from ..wrappers import BaseResponse - -warnings.warn( - "'werkzeug.contrib.atom' is deprecated as of version 0.15 and will" - " be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, -) - -XHTML_NAMESPACE = "http://www.w3.org/1999/xhtml" - - -def _make_text_block(name, content, content_type=None): - """Helper function for the builder that creates an XML text block.""" - if content_type == "xhtml": - return u'<%s type="xhtml"><div xmlns="%s">%s</div></%s>\n' % ( - name, - XHTML_NAMESPACE, - content, - name, - ) - if not content_type: - return u"<%s>%s</%s>\n" % (name, escape(content), name) - return u'<%s type="%s">%s</%s>\n' % (name, content_type, escape(content), name) - - -def format_iso8601(obj): - """Format a datetime object for iso8601""" - iso8601 = obj.isoformat() - if obj.tzinfo: - return iso8601 - return iso8601 + "Z" - - -@implements_to_string -class AtomFeed(object): - - """A helper class that creates Atom feeds. - - :param title: the title of the feed. Required. - :param title_type: the type attribute for the title element. One of - ``'html'``, ``'text'`` or ``'xhtml'``. - :param url: the url for the feed (not the url *of* the feed) - :param id: a globally unique id for the feed. Must be an URI. If - not present the `feed_url` is used, but one of both is - required. - :param updated: the time the feed was modified the last time. Must - be a :class:`datetime.datetime` object. If not - present the latest entry's `updated` is used. - Treated as UTC if naive datetime. - :param feed_url: the URL to the feed. Should be the URL that was - requested. - :param author: the author of the feed. Must be either a string (the - name) or a dict with name (required) and uri or - email (both optional). Can be a list of (may be - mixed, too) strings and dicts, too, if there are - multiple authors. Required if not every entry has an - author element. - :param icon: an icon for the feed. - :param logo: a logo for the feed. - :param rights: copyright information for the feed. - :param rights_type: the type attribute for the rights element. One of - ``'html'``, ``'text'`` or ``'xhtml'``. Default is - ``'text'``. - :param subtitle: a short description of the feed. - :param subtitle_type: the type attribute for the subtitle element. - One of ``'text'``, ``'html'``, ``'text'`` - or ``'xhtml'``. Default is ``'text'``. - :param links: additional links. Must be a list of dictionaries with - href (required) and rel, type, hreflang, title, length - (all optional) - :param generator: the software that generated this feed. This must be - a tuple in the form ``(name, url, version)``. If - you don't want to specify one of them, set the item - to `None`. - :param entries: a list with the entries for the feed. Entries can also - be added later with :meth:`add`. - - For more information on the elements see - http://www.atomenabled.org/developers/syndication/ - - Everywhere where a list is demanded, any iterable can be used. - """ - - default_generator = ("Werkzeug", None, None) - - def __init__(self, title=None, entries=None, **kwargs): - self.title = title - self.title_type = kwargs.get("title_type", "text") - self.url = kwargs.get("url") - self.feed_url = kwargs.get("feed_url", self.url) - self.id = kwargs.get("id", self.feed_url) - self.updated = kwargs.get("updated") - self.author = kwargs.get("author", ()) - self.icon = kwargs.get("icon") - self.logo = kwargs.get("logo") - self.rights = kwargs.get("rights") - self.rights_type = kwargs.get("rights_type") - self.subtitle = kwargs.get("subtitle") - self.subtitle_type = kwargs.get("subtitle_type", "text") - self.generator = kwargs.get("generator") - if self.generator is None: - self.generator = self.default_generator - self.links = kwargs.get("links", []) - self.entries = list(entries) if entries else [] - - if not hasattr(self.author, "__iter__") or isinstance( - self.author, string_types + (dict,) - ): - self.author = [self.author] - for i, author in enumerate(self.author): - if not isinstance(author, dict): - self.author[i] = {"name": author} - - if not self.title: - raise ValueError("title is required") - if not self.id: - raise ValueError("id is required") - for author in self.author: - if "name" not in author: - raise TypeError("author must contain at least a name") - - def add(self, *args, **kwargs): - """Add a new entry to the feed. This function can either be called - with a :class:`FeedEntry` or some keyword and positional arguments - that are forwarded to the :class:`FeedEntry` constructor. - """ - if len(args) == 1 and not kwargs and isinstance(args[0], FeedEntry): - self.entries.append(args[0]) - else: - kwargs["feed_url"] = self.feed_url - self.entries.append(FeedEntry(*args, **kwargs)) - - def __repr__(self): - return "<%s %r (%d entries)>" % ( - self.__class__.__name__, - self.title, - len(self.entries), - ) - - def generate(self): - """Return a generator that yields pieces of XML.""" - # atom demands either an author element in every entry or a global one - if not self.author: - if any(not e.author for e in self.entries): - self.author = ({"name": "Unknown author"},) - - if not self.updated: - dates = sorted([entry.updated for entry in self.entries]) - self.updated = dates[-1] if dates else datetime.utcnow() - - yield u'<?xml version="1.0" encoding="utf-8"?>\n' - yield u'<feed xmlns="http://www.w3.org/2005/Atom">\n' - yield " " + _make_text_block("title", self.title, self.title_type) - yield u" <id>%s</id>\n" % escape(self.id) - yield u" <updated>%s</updated>\n" % format_iso8601(self.updated) - if self.url: - yield u' <link href="%s" />\n' % escape(self.url) - if self.feed_url: - yield u' <link href="%s" rel="self" />\n' % escape(self.feed_url) - for link in self.links: - yield u" <link %s/>\n" % "".join( - '%s="%s" ' % (k, escape(link[k])) for k in link - ) - for author in self.author: - yield u" <author>\n" - yield u" <name>%s</name>\n" % escape(author["name"]) - if "uri" in author: - yield u" <uri>%s</uri>\n" % escape(author["uri"]) - if "email" in author: - yield " <email>%s</email>\n" % escape(author["email"]) - yield " </author>\n" - if self.subtitle: - yield " " + _make_text_block("subtitle", self.subtitle, self.subtitle_type) - if self.icon: - yield u" <icon>%s</icon>\n" % escape(self.icon) - if self.logo: - yield u" <logo>%s</logo>\n" % escape(self.logo) - if self.rights: - yield " " + _make_text_block("rights", self.rights, self.rights_type) - generator_name, generator_url, generator_version = self.generator - if generator_name or generator_url or generator_version: - tmp = [u" <generator"] - if generator_url: - tmp.append(u' uri="%s"' % escape(generator_url)) - if generator_version: - tmp.append(u' version="%s"' % escape(generator_version)) - tmp.append(u">%s</generator>\n" % escape(generator_name)) - yield u"".join(tmp) - for entry in self.entries: - for line in entry.generate(): - yield u" " + line - yield u"</feed>\n" - - def to_string(self): - """Convert the feed into a string.""" - return u"".join(self.generate()) - - def get_response(self): - """Return a response object for the feed.""" - return BaseResponse(self.to_string(), mimetype="application/atom+xml") - - def __call__(self, environ, start_response): - """Use the class as WSGI response object.""" - return self.get_response()(environ, start_response) - - def __str__(self): - return self.to_string() - - -@implements_to_string -class FeedEntry(object): - - """Represents a single entry in a feed. - - :param title: the title of the entry. Required. - :param title_type: the type attribute for the title element. One of - ``'html'``, ``'text'`` or ``'xhtml'``. - :param content: the content of the entry. - :param content_type: the type attribute for the content element. One - of ``'html'``, ``'text'`` or ``'xhtml'``. - :param summary: a summary of the entry's content. - :param summary_type: the type attribute for the summary element. One - of ``'html'``, ``'text'`` or ``'xhtml'``. - :param url: the url for the entry. - :param id: a globally unique id for the entry. Must be an URI. If - not present the URL is used, but one of both is required. - :param updated: the time the entry was modified the last time. Must - be a :class:`datetime.datetime` object. Treated as - UTC if naive datetime. Required. - :param author: the author of the entry. Must be either a string (the - name) or a dict with name (required) and uri or - email (both optional). Can be a list of (may be - mixed, too) strings and dicts, too, if there are - multiple authors. Required if the feed does not have an - author element. - :param published: the time the entry was initially published. Must - be a :class:`datetime.datetime` object. Treated as - UTC if naive datetime. - :param rights: copyright information for the entry. - :param rights_type: the type attribute for the rights element. One of - ``'html'``, ``'text'`` or ``'xhtml'``. Default is - ``'text'``. - :param links: additional links. Must be a list of dictionaries with - href (required) and rel, type, hreflang, title, length - (all optional) - :param categories: categories for the entry. Must be a list of dictionaries - with term (required), scheme and label (all optional) - :param xml_base: The xml base (url) for this feed item. If not provided - it will default to the item url. - - For more information on the elements see - http://www.atomenabled.org/developers/syndication/ - - Everywhere where a list is demanded, any iterable can be used. - """ - - def __init__(self, title=None, content=None, feed_url=None, **kwargs): - self.title = title - self.title_type = kwargs.get("title_type", "text") - self.content = content - self.content_type = kwargs.get("content_type", "html") - self.url = kwargs.get("url") - self.id = kwargs.get("id", self.url) - self.updated = kwargs.get("updated") - self.summary = kwargs.get("summary") - self.summary_type = kwargs.get("summary_type", "html") - self.author = kwargs.get("author", ()) - self.published = kwargs.get("published") - self.rights = kwargs.get("rights") - self.links = kwargs.get("links", []) - self.categories = kwargs.get("categories", []) - self.xml_base = kwargs.get("xml_base", feed_url) - - if not hasattr(self.author, "__iter__") or isinstance( - self.author, string_types + (dict,) - ): - self.author = [self.author] - for i, author in enumerate(self.author): - if not isinstance(author, dict): - self.author[i] = {"name": author} - - if not self.title: - raise ValueError("title is required") - if not self.id: - raise ValueError("id is required") - if not self.updated: - raise ValueError("updated is required") - - def __repr__(self): - return "<%s %r>" % (self.__class__.__name__, self.title) - - def generate(self): - """Yields pieces of ATOM XML.""" - base = "" - if self.xml_base: - base = ' xml:base="%s"' % escape(self.xml_base) - yield u"<entry%s>\n" % base - yield u" " + _make_text_block("title", self.title, self.title_type) - yield u" <id>%s</id>\n" % escape(self.id) - yield u" <updated>%s</updated>\n" % format_iso8601(self.updated) - if self.published: - yield u" <published>%s</published>\n" % format_iso8601(self.published) - if self.url: - yield u' <link href="%s" />\n' % escape(self.url) - for author in self.author: - yield u" <author>\n" - yield u" <name>%s</name>\n" % escape(author["name"]) - if "uri" in author: - yield u" <uri>%s</uri>\n" % escape(author["uri"]) - if "email" in author: - yield u" <email>%s</email>\n" % escape(author["email"]) - yield u" </author>\n" - for link in self.links: - yield u" <link %s/>\n" % "".join( - '%s="%s" ' % (k, escape(link[k])) for k in link - ) - for category in self.categories: - yield u" <category %s/>\n" % "".join( - '%s="%s" ' % (k, escape(category[k])) for k in category - ) - if self.summary: - yield u" " + _make_text_block("summary", self.summary, self.summary_type) - if self.content: - yield u" " + _make_text_block("content", self.content, self.content_type) - yield u"</entry>\n" - - def to_string(self): - """Convert the feed item into a unicode object.""" - return u"".join(self.generate()) - - def __str__(self): - return self.to_string() diff --git a/python/werkzeug/contrib/cache.py b/python/werkzeug/contrib/cache.py deleted file mode 100644 index 79c749b..0000000 --- a/python/werkzeug/contrib/cache.py +++ /dev/null @@ -1,933 +0,0 @@ -# -*- coding: utf-8 -*- -""" - werkzeug.contrib.cache - ~~~~~~~~~~~~~~~~~~~~~~ - - The main problem with dynamic Web sites is, well, they're dynamic. Each - time a user requests a page, the webserver executes a lot of code, queries - the database, renders templates until the visitor gets the page he sees. - - This is a lot more expensive than just loading a file from the file system - and sending it to the visitor. - - For most Web applications, this overhead isn't a big deal but once it - becomes, you will be glad to have a cache system in place. - - How Caching Works - ================= - - Caching is pretty simple. Basically you have a cache object lurking around - somewhere that is connected to a remote cache or the file system or - something else. When the request comes in you check if the current page - is already in the cache and if so, you're returning it from the cache. - Otherwise you generate the page and put it into the cache. (Or a fragment - of the page, you don't have to cache the full thing) - - Here is a simple example of how to cache a sidebar for 5 minutes:: - - def get_sidebar(user): - identifier = 'sidebar_for/user%d' % user.id - value = cache.get(identifier) - if value is not None: - return value - value = generate_sidebar_for(user=user) - cache.set(identifier, value, timeout=60 * 5) - return value - - Creating a Cache Object - ======================= - - To create a cache object you just import the cache system of your choice - from the cache module and instantiate it. Then you can start working - with that object: - - >>> from werkzeug.contrib.cache import SimpleCache - >>> c = SimpleCache() - >>> c.set("foo", "value") - >>> c.get("foo") - 'value' - >>> c.get("missing") is None - True - - Please keep in mind that you have to create the cache and put it somewhere - you have access to it (either as a module global you can import or you just - put it into your WSGI application). - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -import errno -import os -import platform -import re -import tempfile -import warnings -from hashlib import md5 -from time import time - -from .._compat import integer_types -from .._compat import iteritems -from .._compat import string_types -from .._compat import text_type -from .._compat import to_native -from ..posixemulation import rename - -try: - import cPickle as pickle -except ImportError: # pragma: no cover - import pickle - -warnings.warn( - "'werkzeug.contrib.cache' is deprecated as of version 0.15 and will" - " be removed in version 1.0. It has moved to https://github.com" - "/pallets/cachelib.", - DeprecationWarning, - stacklevel=2, -) - - -def _items(mappingorseq): - """Wrapper for efficient iteration over mappings represented by dicts - or sequences:: - - >>> for k, v in _items((i, i*i) for i in xrange(5)): - ... assert k*k == v - - >>> for k, v in _items(dict((i, i*i) for i in xrange(5))): - ... assert k*k == v - - """ - if hasattr(mappingorseq, "items"): - return iteritems(mappingorseq) - return mappingorseq - - -class BaseCache(object): - """Baseclass for the cache systems. All the cache systems implement this - API or a superset of it. - - :param default_timeout: the default timeout (in seconds) that is used if - no timeout is specified on :meth:`set`. A timeout - of 0 indicates that the cache never expires. - """ - - def __init__(self, default_timeout=300): - self.default_timeout = default_timeout - - def _normalize_timeout(self, timeout): - if timeout is None: - timeout = self.default_timeout - return timeout - - def get(self, key): - """Look up key in the cache and return the value for it. - - :param key: the key to be looked up. - :returns: The value if it exists and is readable, else ``None``. - """ - return None - - def delete(self, key): - """Delete `key` from the cache. - - :param key: the key to delete. - :returns: Whether the key existed and has been deleted. - :rtype: boolean - """ - return True - - def get_many(self, *keys): - """Returns a list of values for the given keys. - For each key an item in the list is created:: - - foo, bar = cache.get_many("foo", "bar") - - Has the same error handling as :meth:`get`. - - :param keys: The function accepts multiple keys as positional - arguments. - """ - return [self.get(k) for k in keys] - - def get_dict(self, *keys): - """Like :meth:`get_many` but return a dict:: - - d = cache.get_dict("foo", "bar") - foo = d["foo"] - bar = d["bar"] - - :param keys: The function accepts multiple keys as positional - arguments. - """ - return dict(zip(keys, self.get_many(*keys))) - - def set(self, key, value, timeout=None): - """Add a new key/value to the cache (overwrites value, if key already - exists in the cache). - - :param key: the key to set - :param value: the value for the key - :param timeout: the cache timeout for the key in seconds (if not - specified, it uses the default timeout). A timeout of - 0 idicates that the cache never expires. - :returns: ``True`` if key has been updated, ``False`` for backend - errors. Pickling errors, however, will raise a subclass of - ``pickle.PickleError``. - :rtype: boolean - """ - return True - - def add(self, key, value, timeout=None): - """Works like :meth:`set` but does not overwrite the values of already - existing keys. - - :param key: the key to set - :param value: the value for the key - :param timeout: the cache timeout for the key in seconds (if not - specified, it uses the default timeout). A timeout of - 0 idicates that the cache never expires. - :returns: Same as :meth:`set`, but also ``False`` for already - existing keys. - :rtype: boolean - """ - return True - - def set_many(self, mapping, timeout=None): - """Sets multiple keys and values from a mapping. - - :param mapping: a mapping with the keys/values to set. - :param timeout: the cache timeout for the key in seconds (if not - specified, it uses the default timeout). A timeout of - 0 idicates that the cache never expires. - :returns: Whether all given keys have been set. - :rtype: boolean - """ - rv = True - for key, value in _items(mapping): - if not self.set(key, value, timeout): - rv = False - return rv - - def delete_many(self, *keys): - """Deletes multiple keys at once. - - :param keys: The function accepts multiple keys as positional - arguments. - :returns: Whether all given keys have been deleted. - :rtype: boolean - """ - return all(self.delete(key) for key in keys) - - def has(self, key): - """Checks if a key exists in the cache without returning it. This is a - cheap operation that bypasses loading the actual data on the backend. - - This method is optional and may not be implemented on all caches. - - :param key: the key to check - """ - raise NotImplementedError( - "%s doesn't have an efficient implementation of `has`. That " - "means it is impossible to check whether a key exists without " - "fully loading the key's data. Consider using `self.get` " - "explicitly if you don't care about performance." - ) - - def clear(self): - """Clears the cache. Keep in mind that not all caches support - completely clearing the cache. - - :returns: Whether the cache has been cleared. - :rtype: boolean - """ - return True - - def inc(self, key, delta=1): - """Increments the value of a key by `delta`. If the key does - not yet exist it is initialized with `delta`. - - For supporting caches this is an atomic operation. - - :param key: the key to increment. - :param delta: the delta to add. - :returns: The new value or ``None`` for backend errors. - """ - value = (self.get(key) or 0) + delta - return value if self.set(key, value) else None - - def dec(self, key, delta=1): - """Decrements the value of a key by `delta`. If the key does - not yet exist it is initialized with `-delta`. - - For supporting caches this is an atomic operation. - - :param key: the key to increment. - :param delta: the delta to subtract. - :returns: The new value or `None` for backend errors. - """ - value = (self.get(key) or 0) - delta - return value if self.set(key, value) else None - - -class NullCache(BaseCache): - """A cache that doesn't cache. This can be useful for unit testing. - - :param default_timeout: a dummy parameter that is ignored but exists - for API compatibility with other caches. - """ - - def has(self, key): - return False - - -class SimpleCache(BaseCache): - """Simple memory cache for single process environments. This class exists - mainly for the development server and is not 100% thread safe. It tries - to use as many atomic operations as possible and no locks for simplicity - but it could happen under heavy load that keys are added multiple times. - - :param threshold: the maximum number of items the cache stores before - it starts deleting some. - :param default_timeout: the default timeout that is used if no timeout is - specified on :meth:`~BaseCache.set`. A timeout of - 0 indicates that the cache never expires. - """ - - def __init__(self, threshold=500, default_timeout=300): - BaseCache.__init__(self, default_timeout) - self._cache = {} - self.clear = self._cache.clear - self._threshold = threshold - - def _prune(self): - if len(self._cache) > self._threshold: - now = time() - toremove = [] - for idx, (key, (expires, _)) in enumerate(self._cache.items()): - if (expires != 0 and expires <= now) or idx % 3 == 0: - toremove.append(key) - for key in toremove: - self._cache.pop(key, None) - - def _normalize_timeout(self, timeout): - timeout = BaseCache._normalize_timeout(self, timeout) - if timeout > 0: - timeout = time() + timeout - return timeout - - def get(self, key): - try: - expires, value = self._cache[key] - if expires == 0 or expires > time(): - return pickle.loads(value) - except (KeyError, pickle.PickleError): - return None - - def set(self, key, value, timeout=None): - expires = self._normalize_timeout(timeout) - self._prune() - self._cache[key] = (expires, pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) - return True - - def add(self, key, value, timeout=None): - expires = self._normalize_timeout(timeout) - self._prune() - item = (expires, pickle.dumps(value, pickle.HIGHEST_PROTOCOL)) - if key in self._cache: - return False - self._cache.setdefault(key, item) - return True - - def delete(self, key): - return self._cache.pop(key, None) is not None - - def has(self, key): - try: - expires, value = self._cache[key] - return expires == 0 or expires > time() - except KeyError: - return False - - -_test_memcached_key = re.compile(r"[^\x00-\x21\xff]{1,250}$").match - - -class MemcachedCache(BaseCache): - """A cache that uses memcached as backend. - - The first argument can either be an object that resembles the API of a - :class:`memcache.Client` or a tuple/list of server addresses. In the - event that a tuple/list is passed, Werkzeug tries to import the best - available memcache library. - - This cache looks into the following packages/modules to find bindings for - memcached: - - - ``pylibmc`` - - ``google.appengine.api.memcached`` - - ``memcached`` - - ``libmc`` - - Implementation notes: This cache backend works around some limitations in - memcached to simplify the interface. For example unicode keys are encoded - to utf-8 on the fly. Methods such as :meth:`~BaseCache.get_dict` return - the keys in the same format as passed. Furthermore all get methods - silently ignore key errors to not cause problems when untrusted user data - is passed to the get methods which is often the case in web applications. - - :param servers: a list or tuple of server addresses or alternatively - a :class:`memcache.Client` or a compatible client. - :param default_timeout: the default timeout that is used if no timeout is - specified on :meth:`~BaseCache.set`. A timeout of - 0 indicates that the cache never expires. - :param key_prefix: a prefix that is added before all keys. This makes it - possible to use the same memcached server for different - applications. Keep in mind that - :meth:`~BaseCache.clear` will also clear keys with a - different prefix. - """ - - def __init__(self, servers=None, default_timeout=300, key_prefix=None): - BaseCache.__init__(self, default_timeout) - if servers is None or isinstance(servers, (list, tuple)): - if servers is None: - servers = ["127.0.0.1:11211"] - self._client = self.import_preferred_memcache_lib(servers) - if self._client is None: - raise RuntimeError("no memcache module found") - else: - # NOTE: servers is actually an already initialized memcache - # client. - self._client = servers - - self.key_prefix = to_native(key_prefix) - - def _normalize_key(self, key): - key = to_native(key, "utf-8") - if self.key_prefix: - key = self.key_prefix + key - return key - - def _normalize_timeout(self, timeout): - timeout = BaseCache._normalize_timeout(self, timeout) - if timeout > 0: - timeout = int(time()) + timeout - return timeout - - def get(self, key): - key = self._normalize_key(key) - # memcached doesn't support keys longer than that. Because often - # checks for so long keys can occur because it's tested from user - # submitted data etc we fail silently for getting. - if _test_memcached_key(key): - return self._client.get(key) - - def get_dict(self, *keys): - key_mapping = {} - have_encoded_keys = False - for key in keys: - encoded_key = self._normalize_key(key) - if not isinstance(key, str): - have_encoded_keys = True - if _test_memcached_key(key): - key_mapping[encoded_key] = key - _keys = list(key_mapping) - d = rv = self._client.get_multi(_keys) - if have_encoded_keys or self.key_prefix: - rv = {} - for key, value in iteritems(d): - rv[key_mapping[key]] = value - if len(rv) < len(keys): - for key in keys: - if key not in rv: - rv[key] = None - return rv - - def add(self, key, value, timeout=None): - key = self._normalize_key(key) - timeout = self._normalize_timeout(timeout) - return self._client.add(key, value, timeout) - - def set(self, key, value, timeout=None): - key = self._normalize_key(key) - timeout = self._normalize_timeout(timeout) - return self._client.set(key, value, timeout) - - def get_many(self, *keys): - d = self.get_dict(*keys) - return [d[key] for key in keys] - - def set_many(self, mapping, timeout=None): - new_mapping = {} - for key, value in _items(mapping): - key = self._normalize_key(key) - new_mapping[key] = value - - timeout = self._normalize_timeout(timeout) - failed_keys = self._client.set_multi(new_mapping, timeout) - return not failed_keys - - def delete(self, key): - key = self._normalize_key(key) - if _test_memcached_key(key): - return self._client.delete(key) - - def delete_many(self, *keys): - new_keys = [] - for key in keys: - key = self._normalize_key(key) - if _test_memcached_key(key): - new_keys.append(key) - return self._client.delete_multi(new_keys) - - def has(self, key): - key = self._normalize_key(key) - if _test_memcached_key(key): - return self._client.append(key, "") - return False - - def clear(self): - return self._client.flush_all() - - def inc(self, key, delta=1): - key = self._normalize_key(key) - return self._client.incr(key, delta) - - def dec(self, key, delta=1): - key = self._normalize_key(key) - return self._client.decr(key, delta) - - def import_preferred_memcache_lib(self, servers): - """Returns an initialized memcache client. Used by the constructor.""" - try: - import pylibmc - except ImportError: - pass - else: - return pylibmc.Client(servers) - - try: - from google.appengine.api import memcache - except ImportError: - pass - else: - return memcache.Client() - - try: - import memcache - except ImportError: - pass - else: - return memcache.Client(servers) - - try: - import libmc - except ImportError: - pass - else: - return libmc.Client(servers) - - -# backwards compatibility -GAEMemcachedCache = MemcachedCache - - -class RedisCache(BaseCache): - """Uses the Redis key-value store as a cache backend. - - The first argument can be either a string denoting address of the Redis - server or an object resembling an instance of a redis.Redis class. - - Note: Python Redis API already takes care of encoding unicode strings on - the fly. - - .. versionadded:: 0.7 - - .. versionadded:: 0.8 - `key_prefix` was added. - - .. versionchanged:: 0.8 - This cache backend now properly serializes objects. - - .. versionchanged:: 0.8.3 - This cache backend now supports password authentication. - - .. versionchanged:: 0.10 - ``**kwargs`` is now passed to the redis object. - - :param host: address of the Redis server or an object which API is - compatible with the official Python Redis client (redis-py). - :param port: port number on which Redis server listens for connections. - :param password: password authentication for the Redis server. - :param db: db (zero-based numeric index) on Redis Server to connect. - :param default_timeout: the default timeout that is used if no timeout is - specified on :meth:`~BaseCache.set`. A timeout of - 0 indicates that the cache never expires. - :param key_prefix: A prefix that should be added to all keys. - - Any additional keyword arguments will be passed to ``redis.Redis``. - """ - - def __init__( - self, - host="localhost", - port=6379, - password=None, - db=0, - default_timeout=300, - key_prefix=None, - **kwargs - ): - BaseCache.__init__(self, default_timeout) - if host is None: - raise ValueError("RedisCache host parameter may not be None") - if isinstance(host, string_types): - try: - import redis - except ImportError: - raise RuntimeError("no redis module found") - if kwargs.get("decode_responses", None): - raise ValueError("decode_responses is not supported by RedisCache.") - self._client = redis.Redis( - host=host, port=port, password=password, db=db, **kwargs - ) - else: - self._client = host - self.key_prefix = key_prefix or "" - - def _normalize_timeout(self, timeout): - timeout = BaseCache._normalize_timeout(self, timeout) - if timeout == 0: - timeout = -1 - return timeout - - def dump_object(self, value): - """Dumps an object into a string for redis. By default it serializes - integers as regular string and pickle dumps everything else. - """ - t = type(value) - if t in integer_types: - return str(value).encode("ascii") - return b"!" + pickle.dumps(value) - - def load_object(self, value): - """The reversal of :meth:`dump_object`. This might be called with - None. - """ - if value is None: - return None - if value.startswith(b"!"): - try: - return pickle.loads(value[1:]) - except pickle.PickleError: - return None - try: - return int(value) - except ValueError: - # before 0.8 we did not have serialization. Still support that. - return value - - def get(self, key): - return self.load_object(self._client.get(self.key_prefix + key)) - - def get_many(self, *keys): - if self.key_prefix: - keys = [self.key_prefix + key for key in keys] - return [self.load_object(x) for x in self._client.mget(keys)] - - def set(self, key, value, timeout=None): - timeout = self._normalize_timeout(timeout) - dump = self.dump_object(value) - if timeout == -1: - result = self._client.set(name=self.key_prefix + key, value=dump) - else: - result = self._client.setex( - name=self.key_prefix + key, value=dump, time=timeout - ) - return result - - def add(self, key, value, timeout=None): - timeout = self._normalize_timeout(timeout) - dump = self.dump_object(value) - return self._client.setnx( - name=self.key_prefix + key, value=dump - ) and self._client.expire(name=self.key_prefix + key, time=timeout) - - def set_many(self, mapping, timeout=None): - timeout = self._normalize_timeout(timeout) - # Use transaction=False to batch without calling redis MULTI - # which is not supported by twemproxy - pipe = self._client.pipeline(transaction=False) - - for key, value in _items(mapping): - dump = self.dump_object(value) - if timeout == -1: - pipe.set(name=self.key_prefix + key, value=dump) - else: - pipe.setex(name=self.key_prefix + key, value=dump, time=timeout) - return pipe.execute() - - def delete(self, key): - return self._client.delete(self.key_prefix + key) - - def delete_many(self, *keys): - if not keys: - return - if self.key_prefix: - keys = [self.key_prefix + key for key in keys] - return self._client.delete(*keys) - - def has(self, key): - return self._client.exists(self.key_prefix + key) - - def clear(self): - status = False - if self.key_prefix: - keys = self._client.keys(self.key_prefix + "*") - if keys: - status = self._client.delete(*keys) - else: - status = self._client.flushdb() - return status - - def inc(self, key, delta=1): - return self._client.incr(name=self.key_prefix + key, amount=delta) - - def dec(self, key, delta=1): - return self._client.decr(name=self.key_prefix + key, amount=delta) - - -class FileSystemCache(BaseCache): - """A cache that stores the items on the file system. This cache depends - on being the only user of the `cache_dir`. Make absolutely sure that - nobody but this cache stores files there or otherwise the cache will - randomly delete files therein. - - :param cache_dir: the directory where cache files are stored. - :param threshold: the maximum number of items the cache stores before - it starts deleting some. A threshold value of 0 - indicates no threshold. - :param default_timeout: the default timeout that is used if no timeout is - specified on :meth:`~BaseCache.set`. A timeout of - 0 indicates that the cache never expires. - :param mode: the file mode wanted for the cache files, default 0600 - """ - - #: used for temporary files by the FileSystemCache - _fs_transaction_suffix = ".__wz_cache" - #: keep amount of files in a cache element - _fs_count_file = "__wz_cache_count" - - def __init__(self, cache_dir, threshold=500, default_timeout=300, mode=0o600): - BaseCache.__init__(self, default_timeout) - self._path = cache_dir - self._threshold = threshold - self._mode = mode - - try: - os.makedirs(self._path) - except OSError as ex: - if ex.errno != errno.EEXIST: - raise - - self._update_count(value=len(self._list_dir())) - - @property - def _file_count(self): - return self.get(self._fs_count_file) or 0 - - def _update_count(self, delta=None, value=None): - # If we have no threshold, don't count files - if self._threshold == 0: - return - - if delta: - new_count = self._file_count + delta - else: - new_count = value or 0 - self.set(self._fs_count_file, new_count, mgmt_element=True) - - def _normalize_timeout(self, timeout): - timeout = BaseCache._normalize_timeout(self, timeout) - if timeout != 0: - timeout = time() + timeout - return int(timeout) - - def _list_dir(self): - """return a list of (fully qualified) cache filenames - """ - mgmt_files = [ - self._get_filename(name).split("/")[-1] for name in (self._fs_count_file,) - ] - return [ - os.path.join(self._path, fn) - for fn in os.listdir(self._path) - if not fn.endswith(self._fs_transaction_suffix) and fn not in mgmt_files - ] - - def _prune(self): - if self._threshold == 0 or not self._file_count > self._threshold: - return - - entries = self._list_dir() - now = time() - for idx, fname in enumerate(entries): - try: - remove = False - with open(fname, "rb") as f: - expires = pickle.load(f) - remove = (expires != 0 and expires <= now) or idx % 3 == 0 - - if remove: - os.remove(fname) - except (IOError, OSError): - pass - self._update_count(value=len(self._list_dir())) - - def clear(self): - for fname in self._list_dir(): - try: - os.remove(fname) - except (IOError, OSError): - self._update_count(value=len(self._list_dir())) - return False - self._update_count(value=0) - return True - - def _get_filename(self, key): - if isinstance(key, text_type): - key = key.encode("utf-8") # XXX unicode review - hash = md5(key).hexdigest() - return os.path.join(self._path, hash) - - def get(self, key): - filename = self._get_filename(key) - try: - with open(filename, "rb") as f: - pickle_time = pickle.load(f) - if pickle_time == 0 or pickle_time >= time(): - return pickle.load(f) - else: - os.remove(filename) - return None - except (IOError, OSError, pickle.PickleError): - return None - - def add(self, key, value, timeout=None): - filename = self._get_filename(key) - if not os.path.exists(filename): - return self.set(key, value, timeout) - return False - - def set(self, key, value, timeout=None, mgmt_element=False): - # Management elements have no timeout - if mgmt_element: - timeout = 0 - - # Don't prune on management element update, to avoid loop - else: - self._prune() - - timeout = self._normalize_timeout(timeout) - filename = self._get_filename(key) - try: - fd, tmp = tempfile.mkstemp( - suffix=self._fs_transaction_suffix, dir=self._path - ) - with os.fdopen(fd, "wb") as f: - pickle.dump(timeout, f, 1) - pickle.dump(value, f, pickle.HIGHEST_PROTOCOL) - rename(tmp, filename) - os.chmod(filename, self._mode) - except (IOError, OSError): - return False - else: - # Management elements should not count towards threshold - if not mgmt_element: - self._update_count(delta=1) - return True - - def delete(self, key, mgmt_element=False): - try: - os.remove(self._get_filename(key)) - except (IOError, OSError): - return False - else: - # Management elements should not count towards threshold - if not mgmt_element: - self._update_count(delta=-1) - return True - - def has(self, key): - filename = self._get_filename(key) - try: - with open(filename, "rb") as f: - pickle_time = pickle.load(f) - if pickle_time == 0 or pickle_time >= time(): - return True - else: - os.remove(filename) - return False - except (IOError, OSError, pickle.PickleError): - return False - - -class UWSGICache(BaseCache): - """Implements the cache using uWSGI's caching framework. - - .. note:: - This class cannot be used when running under PyPy, because the uWSGI - API implementation for PyPy is lacking the needed functionality. - - :param default_timeout: The default timeout in seconds. - :param cache: The name of the caching instance to connect to, for - example: mycache@localhost:3031, defaults to an empty string, which - means uWSGI will cache in the local instance. If the cache is in the - same instance as the werkzeug app, you only have to provide the name of - the cache. - """ - - def __init__(self, default_timeout=300, cache=""): - BaseCache.__init__(self, default_timeout) - - if platform.python_implementation() == "PyPy": - raise RuntimeError( - "uWSGI caching does not work under PyPy, see " - "the docs for more details." - ) - - try: - import uwsgi - - self._uwsgi = uwsgi - except ImportError: - raise RuntimeError( - "uWSGI could not be imported, are you running under uWSGI?" - ) - - self.cache = cache - - def get(self, key): - rv = self._uwsgi.cache_get(key, self.cache) - if rv is None: - return - return pickle.loads(rv) - - def delete(self, key): - return self._uwsgi.cache_del(key, self.cache) - - def set(self, key, value, timeout=None): - return self._uwsgi.cache_update( - key, pickle.dumps(value), self._normalize_timeout(timeout), self.cache - ) - - def add(self, key, value, timeout=None): - return self._uwsgi.cache_set( - key, pickle.dumps(value), self._normalize_timeout(timeout), self.cache - ) - - def clear(self): - return self._uwsgi.cache_clear(self.cache) - - def has(self, key): - return self._uwsgi.cache_exists(key, self.cache) is not None diff --git a/python/werkzeug/contrib/fixers.py b/python/werkzeug/contrib/fixers.py deleted file mode 100644 index 8df0afd..0000000 --- a/python/werkzeug/contrib/fixers.py +++ /dev/null @@ -1,262 +0,0 @@ -""" -Fixers -====== - -.. warning:: - .. deprecated:: 0.15 - ``ProxyFix`` has moved to :mod:`werkzeug.middleware.proxy_fix`. - All other code in this module is deprecated and will be removed - in version 1.0. - -.. versionadded:: 0.5 - -This module includes various helpers that fix web server behavior. - -.. autoclass:: ProxyFix - :members: - -.. autoclass:: CGIRootFix - -.. autoclass:: PathInfoFromRequestUriFix - -.. autoclass:: HeaderRewriterFix - -.. autoclass:: InternetExplorerFix - -:copyright: 2007 Pallets -:license: BSD-3-Clause -""" -import warnings - -from ..datastructures import Headers -from ..datastructures import ResponseCacheControl -from ..http import parse_cache_control_header -from ..http import parse_options_header -from ..http import parse_set_header -from ..middleware.proxy_fix import ProxyFix as _ProxyFix -from ..useragents import UserAgent - -try: - from urllib.parse import unquote -except ImportError: - from urllib import unquote - - -class CGIRootFix(object): - """Wrap the application in this middleware if you are using FastCGI - or CGI and you have problems with your app root being set to the CGI - script's path instead of the path users are going to visit. - - :param app: the WSGI application - :param app_root: Defaulting to ``'/'``, you can set this to - something else if your app is mounted somewhere else. - - .. deprecated:: 0.15 - This middleware will be removed in version 1.0. - - .. versionchanged:: 0.9 - Added `app_root` parameter and renamed from - ``LighttpdCGIRootFix``. - """ - - def __init__(self, app, app_root="/"): - warnings.warn( - "'CGIRootFix' is deprecated as of version 0.15 and will be" - " removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - self.app = app - self.app_root = app_root.strip("/") - - def __call__(self, environ, start_response): - environ["SCRIPT_NAME"] = self.app_root - return self.app(environ, start_response) - - -class LighttpdCGIRootFix(CGIRootFix): - def __init__(self, *args, **kwargs): - warnings.warn( - "'LighttpdCGIRootFix' is renamed 'CGIRootFix'. Both will be" - " removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(LighttpdCGIRootFix, self).__init__(*args, **kwargs) - - -class PathInfoFromRequestUriFix(object): - """On windows environment variables are limited to the system charset - which makes it impossible to store the `PATH_INFO` variable in the - environment without loss of information on some systems. - - This is for example a problem for CGI scripts on a Windows Apache. - - This fixer works by recreating the `PATH_INFO` from `REQUEST_URI`, - `REQUEST_URL`, or `UNENCODED_URL` (whatever is available). Thus the - fix can only be applied if the webserver supports either of these - variables. - - :param app: the WSGI application - - .. deprecated:: 0.15 - This middleware will be removed in version 1.0. - """ - - def __init__(self, app): - warnings.warn( - "'PathInfoFromRequestUriFix' is deprecated as of version" - " 0.15 and will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - self.app = app - - def __call__(self, environ, start_response): - for key in "REQUEST_URL", "REQUEST_URI", "UNENCODED_URL": - if key not in environ: - continue - request_uri = unquote(environ[key]) - script_name = unquote(environ.get("SCRIPT_NAME", "")) - if request_uri.startswith(script_name): - environ["PATH_INFO"] = request_uri[len(script_name) :].split("?", 1)[0] - break - return self.app(environ, start_response) - - -class ProxyFix(_ProxyFix): - """ - .. deprecated:: 0.15 - ``werkzeug.contrib.fixers.ProxyFix`` has moved to - :mod:`werkzeug.middleware.proxy_fix`. This import will be - removed in 1.0. - """ - - def __init__(self, *args, **kwargs): - warnings.warn( - "'werkzeug.contrib.fixers.ProxyFix' has moved to 'werkzeug" - ".middleware.proxy_fix.ProxyFix'. This import is deprecated" - " as of version 0.15 and will be removed in 1.0.", - DeprecationWarning, - stacklevel=2, - ) - super(ProxyFix, self).__init__(*args, **kwargs) - - -class HeaderRewriterFix(object): - """This middleware can remove response headers and add others. This - is for example useful to remove the `Date` header from responses if you - are using a server that adds that header, no matter if it's present or - not or to add `X-Powered-By` headers:: - - app = HeaderRewriterFix(app, remove_headers=['Date'], - add_headers=[('X-Powered-By', 'WSGI')]) - - :param app: the WSGI application - :param remove_headers: a sequence of header keys that should be - removed. - :param add_headers: a sequence of ``(key, value)`` tuples that should - be added. - - .. deprecated:: 0.15 - This middleware will be removed in 1.0. - """ - - def __init__(self, app, remove_headers=None, add_headers=None): - warnings.warn( - "'HeaderRewriterFix' is deprecated as of version 0.15 and" - " will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - self.app = app - self.remove_headers = set(x.lower() for x in (remove_headers or ())) - self.add_headers = list(add_headers or ()) - - def __call__(self, environ, start_response): - def rewriting_start_response(status, headers, exc_info=None): - new_headers = [] - for key, value in headers: - if key.lower() not in self.remove_headers: - new_headers.append((key, value)) - new_headers += self.add_headers - return start_response(status, new_headers, exc_info) - - return self.app(environ, rewriting_start_response) - - -class InternetExplorerFix(object): - """This middleware fixes a couple of bugs with Microsoft Internet - Explorer. Currently the following fixes are applied: - - - removing of `Vary` headers for unsupported mimetypes which - causes troubles with caching. Can be disabled by passing - ``fix_vary=False`` to the constructor. - see: https://support.microsoft.com/en-us/help/824847 - - - removes offending headers to work around caching bugs in - Internet Explorer if `Content-Disposition` is set. Can be - disabled by passing ``fix_attach=False`` to the constructor. - - If it does not detect affected Internet Explorer versions it won't touch - the request / response. - - .. deprecated:: 0.15 - This middleware will be removed in 1.0. - """ - - # This code was inspired by Django fixers for the same bugs. The - # fix_vary and fix_attach fixers were originally implemented in Django - # by Michael Axiak and is available as part of the Django project: - # https://code.djangoproject.com/ticket/4148 - - def __init__(self, app, fix_vary=True, fix_attach=True): - warnings.warn( - "'InternetExplorerFix' is deprecated as of version 0.15 and" - " will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - self.app = app - self.fix_vary = fix_vary - self.fix_attach = fix_attach - - def fix_headers(self, environ, headers, status=None): - if self.fix_vary: - header = headers.get("content-type", "") - mimetype, options = parse_options_header(header) - if mimetype not in ("text/html", "text/plain", "text/sgml"): - headers.pop("vary", None) - - if self.fix_attach and "content-disposition" in headers: - pragma = parse_set_header(headers.get("pragma", "")) - pragma.discard("no-cache") - header = pragma.to_header() - if not header: - headers.pop("pragma", "") - else: - headers["Pragma"] = header - header = headers.get("cache-control", "") - if header: - cc = parse_cache_control_header(header, cls=ResponseCacheControl) - cc.no_cache = None - cc.no_store = False - header = cc.to_header() - if not header: - headers.pop("cache-control", "") - else: - headers["Cache-Control"] = header - - def run_fixed(self, environ, start_response): - def fixing_start_response(status, headers, exc_info=None): - headers = Headers(headers) - self.fix_headers(environ, headers, status) - return start_response(status, headers.to_wsgi_list(), exc_info) - - return self.app(environ, fixing_start_response) - - def __call__(self, environ, start_response): - ua = UserAgent(environ) - if ua.browser != "msie": - return self.app(environ, start_response) - return self.run_fixed(environ, start_response) diff --git a/python/werkzeug/contrib/iterio.py b/python/werkzeug/contrib/iterio.py deleted file mode 100644 index b672454..0000000 --- a/python/werkzeug/contrib/iterio.py +++ /dev/null @@ -1,358 +0,0 @@ -# -*- coding: utf-8 -*- -r""" - werkzeug.contrib.iterio - ~~~~~~~~~~~~~~~~~~~~~~~ - - This module implements a :class:`IterIO` that converts an iterator into - a stream object and the other way round. Converting streams into - iterators requires the `greenlet`_ module. - - To convert an iterator into a stream all you have to do is to pass it - directly to the :class:`IterIO` constructor. In this example we pass it - a newly created generator:: - - def foo(): - yield "something\n" - yield "otherthings" - stream = IterIO(foo()) - print stream.read() # read the whole iterator - - The other way round works a bit different because we have to ensure that - the code execution doesn't take place yet. An :class:`IterIO` call with a - callable as first argument does two things. The function itself is passed - an :class:`IterIO` stream it can feed. The object returned by the - :class:`IterIO` constructor on the other hand is not an stream object but - an iterator:: - - def foo(stream): - stream.write("some") - stream.write("thing") - stream.flush() - stream.write("otherthing") - iterator = IterIO(foo) - print iterator.next() # prints something - print iterator.next() # prints otherthing - iterator.next() # raises StopIteration - - .. _greenlet: https://github.com/python-greenlet/greenlet - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -import warnings - -from .._compat import implements_iterator - -try: - import greenlet -except ImportError: - greenlet = None - -warnings.warn( - "'werkzeug.contrib.iterio' is deprecated as of version 0.15 and" - " will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, -) - - -def _mixed_join(iterable, sentinel): - """concatenate any string type in an intelligent way.""" - iterator = iter(iterable) - first_item = next(iterator, sentinel) - if isinstance(first_item, bytes): - return first_item + b"".join(iterator) - return first_item + u"".join(iterator) - - -def _newline(reference_string): - if isinstance(reference_string, bytes): - return b"\n" - return u"\n" - - -@implements_iterator -class IterIO(object): - """Instances of this object implement an interface compatible with the - standard Python :class:`file` object. Streams are either read-only or - write-only depending on how the object is created. - - If the first argument is an iterable a file like object is returned that - returns the contents of the iterable. In case the iterable is empty - read operations will return the sentinel value. - - If the first argument is a callable then the stream object will be - created and passed to that function. The caller itself however will - not receive a stream but an iterable. The function will be executed - step by step as something iterates over the returned iterable. Each - call to :meth:`flush` will create an item for the iterable. If - :meth:`flush` is called without any writes in-between the sentinel - value will be yielded. - - Note for Python 3: due to the incompatible interface of bytes and - streams you should set the sentinel value explicitly to an empty - bytestring (``b''``) if you are expecting to deal with bytes as - otherwise the end of the stream is marked with the wrong sentinel - value. - - .. versionadded:: 0.9 - `sentinel` parameter was added. - """ - - def __new__(cls, obj, sentinel=""): - try: - iterator = iter(obj) - except TypeError: - return IterI(obj, sentinel) - return IterO(iterator, sentinel) - - def __iter__(self): - return self - - def tell(self): - if self.closed: - raise ValueError("I/O operation on closed file") - return self.pos - - def isatty(self): - if self.closed: - raise ValueError("I/O operation on closed file") - return False - - def seek(self, pos, mode=0): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def truncate(self, size=None): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def write(self, s): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def writelines(self, list): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def read(self, n=-1): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def readlines(self, sizehint=0): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def readline(self, length=None): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def flush(self): - if self.closed: - raise ValueError("I/O operation on closed file") - raise IOError(9, "Bad file descriptor") - - def __next__(self): - if self.closed: - raise StopIteration() - line = self.readline() - if not line: - raise StopIteration() - return line - - -class IterI(IterIO): - """Convert an stream into an iterator.""" - - def __new__(cls, func, sentinel=""): - if greenlet is None: - raise RuntimeError("IterI requires greenlet support") - stream = object.__new__(cls) - stream._parent = greenlet.getcurrent() - stream._buffer = [] - stream.closed = False - stream.sentinel = sentinel - stream.pos = 0 - - def run(): - func(stream) - stream.close() - - g = greenlet.greenlet(run, stream._parent) - while 1: - rv = g.switch() - if not rv: - return - yield rv[0] - - def close(self): - if not self.closed: - self.closed = True - self._flush_impl() - - def write(self, s): - if self.closed: - raise ValueError("I/O operation on closed file") - if s: - self.pos += len(s) - self._buffer.append(s) - - def writelines(self, list): - for item in list: - self.write(item) - - def flush(self): - if self.closed: - raise ValueError("I/O operation on closed file") - self._flush_impl() - - def _flush_impl(self): - data = _mixed_join(self._buffer, self.sentinel) - self._buffer = [] - if not data and self.closed: - self._parent.switch() - else: - self._parent.switch((data,)) - - -class IterO(IterIO): - """Iter output. Wrap an iterator and give it a stream like interface.""" - - def __new__(cls, gen, sentinel=""): - self = object.__new__(cls) - self._gen = gen - self._buf = None - self.sentinel = sentinel - self.closed = False - self.pos = 0 - return self - - def __iter__(self): - return self - - def _buf_append(self, string): - """Replace string directly without appending to an empty string, - avoiding type issues.""" - if not self._buf: - self._buf = string - else: - self._buf += string - - def close(self): - if not self.closed: - self.closed = True - if hasattr(self._gen, "close"): - self._gen.close() - - def seek(self, pos, mode=0): - if self.closed: - raise ValueError("I/O operation on closed file") - if mode == 1: - pos += self.pos - elif mode == 2: - self.read() - self.pos = min(self.pos, self.pos + pos) - return - elif mode != 0: - raise IOError("Invalid argument") - buf = [] - try: - tmp_end_pos = len(self._buf or "") - while pos > tmp_end_pos: - item = next(self._gen) - tmp_end_pos += len(item) - buf.append(item) - except StopIteration: - pass - if buf: - self._buf_append(_mixed_join(buf, self.sentinel)) - self.pos = max(0, pos) - - def read(self, n=-1): - if self.closed: - raise ValueError("I/O operation on closed file") - if n < 0: - self._buf_append(_mixed_join(self._gen, self.sentinel)) - result = self._buf[self.pos :] - self.pos += len(result) - return result - new_pos = self.pos + n - buf = [] - try: - tmp_end_pos = 0 if self._buf is None else len(self._buf) - while new_pos > tmp_end_pos or (self._buf is None and not buf): - item = next(self._gen) - tmp_end_pos += len(item) - buf.append(item) - except StopIteration: - pass - if buf: - self._buf_append(_mixed_join(buf, self.sentinel)) - - if self._buf is None: - return self.sentinel - - new_pos = max(0, new_pos) - try: - return self._buf[self.pos : new_pos] - finally: - self.pos = min(new_pos, len(self._buf)) - - def readline(self, length=None): - if self.closed: - raise ValueError("I/O operation on closed file") - - nl_pos = -1 - if self._buf: - nl_pos = self._buf.find(_newline(self._buf), self.pos) - buf = [] - try: - if self._buf is None: - pos = self.pos - else: - pos = len(self._buf) - while nl_pos < 0: - item = next(self._gen) - local_pos = item.find(_newline(item)) - buf.append(item) - if local_pos >= 0: - nl_pos = pos + local_pos - break - pos += len(item) - except StopIteration: - pass - if buf: - self._buf_append(_mixed_join(buf, self.sentinel)) - - if self._buf is None: - return self.sentinel - - if nl_pos < 0: - new_pos = len(self._buf) - else: - new_pos = nl_pos + 1 - if length is not None and self.pos + length < new_pos: - new_pos = self.pos + length - try: - return self._buf[self.pos : new_pos] - finally: - self.pos = min(new_pos, len(self._buf)) - - def readlines(self, sizehint=0): - total = 0 - lines = [] - line = self.readline() - while line: - lines.append(line) - total += len(line) - if 0 < sizehint <= total: - break - line = self.readline() - return lines diff --git a/python/werkzeug/contrib/lint.py b/python/werkzeug/contrib/lint.py deleted file mode 100644 index 8bd8b8a..0000000 --- a/python/werkzeug/contrib/lint.py +++ /dev/null @@ -1,11 +0,0 @@ -import warnings - -from ..middleware.lint import * # noqa: F401, F403 - -warnings.warn( - "'werkzeug.contrib.lint' has moved to 'werkzeug.middleware.lint'." - " This import is deprecated as of version 0.15 and will be removed" - " in version 1.0.", - DeprecationWarning, - stacklevel=2, -) diff --git a/python/werkzeug/contrib/profiler.py b/python/werkzeug/contrib/profiler.py deleted file mode 100644 index b79fe56..0000000 --- a/python/werkzeug/contrib/profiler.py +++ /dev/null @@ -1,42 +0,0 @@ -import warnings - -from ..middleware.profiler import * # noqa: F401, F403 - -warnings.warn( - "'werkzeug.contrib.profiler' has moved to" - "'werkzeug.middleware.profiler'. This import is deprecated as of" - "version 0.15 and will be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, -) - - -class MergeStream(object): - """An object that redirects ``write`` calls to multiple streams. - Use this to log to both ``sys.stdout`` and a file:: - - f = open('profiler.log', 'w') - stream = MergeStream(sys.stdout, f) - profiler = ProfilerMiddleware(app, stream) - - .. deprecated:: 0.15 - Use the ``tee`` command in your terminal instead. This class - will be removed in 1.0. - """ - - def __init__(self, *streams): - warnings.warn( - "'MergeStream' is deprecated as of version 0.15 and will be removed in" - " version 1.0. Use your terminal's 'tee' command instead.", - DeprecationWarning, - stacklevel=2, - ) - - if not streams: - raise TypeError("At least one stream must be given.") - - self.streams = streams - - def write(self, data): - for stream in self.streams: - stream.write(data) diff --git a/python/werkzeug/contrib/securecookie.py b/python/werkzeug/contrib/securecookie.py deleted file mode 100644 index c4c9eee..0000000 --- a/python/werkzeug/contrib/securecookie.py +++ /dev/null @@ -1,362 +0,0 @@ -# -*- coding: utf-8 -*- -r""" - werkzeug.contrib.securecookie - ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module implements a cookie that is not alterable from the client - because it adds a checksum the server checks for. You can use it as - session replacement if all you have is a user id or something to mark - a logged in user. - - Keep in mind that the data is still readable from the client as a - normal cookie is. However you don't have to store and flush the - sessions you have at the server. - - Example usage: - - >>> from werkzeug.contrib.securecookie import SecureCookie - >>> x = SecureCookie({"foo": 42, "baz": (1, 2, 3)}, "deadbeef") - - Dumping into a string so that one can store it in a cookie: - - >>> value = x.serialize() - - Loading from that string again: - - >>> x = SecureCookie.unserialize(value, "deadbeef") - >>> x["baz"] - (1, 2, 3) - - If someone modifies the cookie and the checksum is wrong the unserialize - method will fail silently and return a new empty `SecureCookie` object. - - Keep in mind that the values will be visible in the cookie so do not - store data in a cookie you don't want the user to see. - - Application Integration - ======================= - - If you are using the werkzeug request objects you could integrate the - secure cookie into your application like this:: - - from werkzeug.utils import cached_property - from werkzeug.wrappers import BaseRequest - from werkzeug.contrib.securecookie import SecureCookie - - # don't use this key but a different one; you could just use - # os.urandom(20) to get something random - SECRET_KEY = '\xfa\xdd\xb8z\xae\xe0}4\x8b\xea' - - class Request(BaseRequest): - - @cached_property - def client_session(self): - data = self.cookies.get('session_data') - if not data: - return SecureCookie(secret_key=SECRET_KEY) - return SecureCookie.unserialize(data, SECRET_KEY) - - def application(environ, start_response): - request = Request(environ) - - # get a response object here - response = ... - - if request.client_session.should_save: - session_data = request.client_session.serialize() - response.set_cookie('session_data', session_data, - httponly=True) - return response(environ, start_response) - - A less verbose integration can be achieved by using shorthand methods:: - - class Request(BaseRequest): - - @cached_property - def client_session(self): - return SecureCookie.load_cookie(self, secret_key=COOKIE_SECRET) - - def application(environ, start_response): - request = Request(environ) - - # get a response object here - response = ... - - request.client_session.save_cookie(response) - return response(environ, start_response) - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -import base64 -import pickle -import warnings -from hashlib import sha1 as _default_hash -from hmac import new as hmac -from time import time - -from .._compat import iteritems -from .._compat import text_type -from .._compat import to_bytes -from .._compat import to_native -from .._internal import _date_to_unix -from ..contrib.sessions import ModificationTrackingDict -from ..security import safe_str_cmp -from ..urls import url_quote_plus -from ..urls import url_unquote_plus - -warnings.warn( - "'werkzeug.contrib.securecookie' is deprecated as of version 0.15" - " and will be removed in version 1.0. It has moved to" - " https://github.com/pallets/secure-cookie.", - DeprecationWarning, - stacklevel=2, -) - - -class UnquoteError(Exception): - """Internal exception used to signal failures on quoting.""" - - -class SecureCookie(ModificationTrackingDict): - """Represents a secure cookie. You can subclass this class and provide - an alternative mac method. The import thing is that the mac method - is a function with a similar interface to the hashlib. Required - methods are update() and digest(). - - Example usage: - - >>> x = SecureCookie({"foo": 42, "baz": (1, 2, 3)}, "deadbeef") - >>> x["foo"] - 42 - >>> x["baz"] - (1, 2, 3) - >>> x["blafasel"] = 23 - >>> x.should_save - True - - :param data: the initial data. Either a dict, list of tuples or `None`. - :param secret_key: the secret key. If not set `None` or not specified - it has to be set before :meth:`serialize` is called. - :param new: The initial value of the `new` flag. - """ - - #: The hash method to use. This has to be a module with a new function - #: or a function that creates a hashlib object. Such as `hashlib.md5` - #: Subclasses can override this attribute. The default hash is sha1. - #: Make sure to wrap this in staticmethod() if you store an arbitrary - #: function there such as hashlib.sha1 which might be implemented - #: as a function. - hash_method = staticmethod(_default_hash) - - #: The module used for serialization. Should have a ``dumps`` and a - #: ``loads`` method that takes bytes. The default is :mod:`pickle`. - #: - #: .. versionchanged:: 0.15 - #: The default of ``pickle`` will change to :mod:`json` in 1.0. - serialization_method = pickle - - #: if the contents should be base64 quoted. This can be disabled if the - #: serialization process returns cookie safe strings only. - quote_base64 = True - - def __init__(self, data=None, secret_key=None, new=True): - ModificationTrackingDict.__init__(self, data or ()) - # explicitly convert it into a bytestring because python 2.6 - # no longer performs an implicit string conversion on hmac - if secret_key is not None: - secret_key = to_bytes(secret_key, "utf-8") - self.secret_key = secret_key - self.new = new - - if self.serialization_method is pickle: - warnings.warn( - "The default 'SecureCookie.serialization_method' will" - " change from pickle to json in version 1.0. To upgrade" - " existing tokens, override 'unquote' to try pickle if" - " json fails.", - stacklevel=2, - ) - - def __repr__(self): - return "<%s %s%s>" % ( - self.__class__.__name__, - dict.__repr__(self), - "*" if self.should_save else "", - ) - - @property - def should_save(self): - """True if the session should be saved. By default this is only true - for :attr:`modified` cookies, not :attr:`new`. - """ - return self.modified - - @classmethod - def quote(cls, value): - """Quote the value for the cookie. This can be any object supported - by :attr:`serialization_method`. - - :param value: the value to quote. - """ - if cls.serialization_method is not None: - value = cls.serialization_method.dumps(value) - if cls.quote_base64: - value = b"".join( - base64.b64encode(to_bytes(value, "utf8")).splitlines() - ).strip() - return value - - @classmethod - def unquote(cls, value): - """Unquote the value for the cookie. If unquoting does not work a - :exc:`UnquoteError` is raised. - - :param value: the value to unquote. - """ - try: - if cls.quote_base64: - value = base64.b64decode(value) - if cls.serialization_method is not None: - value = cls.serialization_method.loads(value) - return value - except Exception: - # unfortunately pickle and other serialization modules can - # cause pretty every error here. if we get one we catch it - # and convert it into an UnquoteError - raise UnquoteError() - - def serialize(self, expires=None): - """Serialize the secure cookie into a string. - - If expires is provided, the session will be automatically invalidated - after expiration when you unseralize it. This provides better - protection against session cookie theft. - - :param expires: an optional expiration date for the cookie (a - :class:`datetime.datetime` object) - """ - if self.secret_key is None: - raise RuntimeError("no secret key defined") - if expires: - self["_expires"] = _date_to_unix(expires) - result = [] - mac = hmac(self.secret_key, None, self.hash_method) - for key, value in sorted(self.items()): - result.append( - ( - "%s=%s" % (url_quote_plus(key), self.quote(value).decode("ascii")) - ).encode("ascii") - ) - mac.update(b"|" + result[-1]) - return b"?".join([base64.b64encode(mac.digest()).strip(), b"&".join(result)]) - - @classmethod - def unserialize(cls, string, secret_key): - """Load the secure cookie from a serialized string. - - :param string: the cookie value to unserialize. - :param secret_key: the secret key used to serialize the cookie. - :return: a new :class:`SecureCookie`. - """ - if isinstance(string, text_type): - string = string.encode("utf-8", "replace") - if isinstance(secret_key, text_type): - secret_key = secret_key.encode("utf-8", "replace") - try: - base64_hash, data = string.split(b"?", 1) - except (ValueError, IndexError): - items = () - else: - items = {} - mac = hmac(secret_key, None, cls.hash_method) - for item in data.split(b"&"): - mac.update(b"|" + item) - if b"=" not in item: - items = None - break - key, value = item.split(b"=", 1) - # try to make the key a string - key = url_unquote_plus(key.decode("ascii")) - try: - key = to_native(key) - except UnicodeError: - pass - items[key] = value - - # no parsing error and the mac looks okay, we can now - # sercurely unpickle our cookie. - try: - client_hash = base64.b64decode(base64_hash) - except TypeError: - items = client_hash = None - if items is not None and safe_str_cmp(client_hash, mac.digest()): - try: - for key, value in iteritems(items): - items[key] = cls.unquote(value) - except UnquoteError: - items = () - else: - if "_expires" in items: - if time() > items["_expires"]: - items = () - else: - del items["_expires"] - else: - items = () - return cls(items, secret_key, False) - - @classmethod - def load_cookie(cls, request, key="session", secret_key=None): - """Loads a :class:`SecureCookie` from a cookie in request. If the - cookie is not set, a new :class:`SecureCookie` instanced is - returned. - - :param request: a request object that has a `cookies` attribute - which is a dict of all cookie values. - :param key: the name of the cookie. - :param secret_key: the secret key used to unquote the cookie. - Always provide the value even though it has - no default! - """ - data = request.cookies.get(key) - if not data: - return cls(secret_key=secret_key) - return cls.unserialize(data, secret_key) - - def save_cookie( - self, - response, - key="session", - expires=None, - session_expires=None, - max_age=None, - path="/", - domain=None, - secure=None, - httponly=False, - force=False, - ): - """Saves the SecureCookie in a cookie on response object. All - parameters that are not described here are forwarded directly - to :meth:`~BaseResponse.set_cookie`. - - :param response: a response object that has a - :meth:`~BaseResponse.set_cookie` method. - :param key: the name of the cookie. - :param session_expires: the expiration date of the secure cookie - stored information. If this is not provided - the cookie `expires` date is used instead. - """ - if force or self.should_save: - data = self.serialize(session_expires or expires) - response.set_cookie( - key, - data, - expires=expires, - max_age=max_age, - path=path, - domain=domain, - secure=secure, - httponly=httponly, - ) diff --git a/python/werkzeug/contrib/sessions.py b/python/werkzeug/contrib/sessions.py deleted file mode 100644 index 866e827..0000000 --- a/python/werkzeug/contrib/sessions.py +++ /dev/null @@ -1,389 +0,0 @@ -# -*- coding: utf-8 -*- -r""" - werkzeug.contrib.sessions - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - This module contains some helper classes that help one to add session - support to a python WSGI application. For full client-side session - storage see :mod:`~werkzeug.contrib.securecookie` which implements a - secure, client-side session storage. - - - Application Integration - ======================= - - :: - - from werkzeug.contrib.sessions import SessionMiddleware, \ - FilesystemSessionStore - - app = SessionMiddleware(app, FilesystemSessionStore()) - - The current session will then appear in the WSGI environment as - `werkzeug.session`. However it's recommended to not use the middleware - but the stores directly in the application. However for very simple - scripts a middleware for sessions could be sufficient. - - This module does not implement methods or ways to check if a session is - expired. That should be done by a cronjob and storage specific. For - example to prune unused filesystem sessions one could check the modified - time of the files. If sessions are stored in the database the new() - method should add an expiration timestamp for the session. - - For better flexibility it's recommended to not use the middleware but the - store and session object directly in the application dispatching:: - - session_store = FilesystemSessionStore() - - def application(environ, start_response): - request = Request(environ) - sid = request.cookies.get('cookie_name') - if sid is None: - request.session = session_store.new() - else: - request.session = session_store.get(sid) - response = get_the_response_object(request) - if request.session.should_save: - session_store.save(request.session) - response.set_cookie('cookie_name', request.session.sid) - return response(environ, start_response) - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -import os -import re -import tempfile -import warnings -from hashlib import sha1 -from os import path -from pickle import dump -from pickle import HIGHEST_PROTOCOL -from pickle import load -from random import random -from time import time - -from .._compat import PY2 -from .._compat import text_type -from ..datastructures import CallbackDict -from ..filesystem import get_filesystem_encoding -from ..posixemulation import rename -from ..utils import dump_cookie -from ..utils import parse_cookie -from ..wsgi import ClosingIterator - -warnings.warn( - "'werkzeug.contrib.sessions' is deprecated as of version 0.15 and" - " will be removed in version 1.0. It has moved to" - " https://github.com/pallets/secure-cookie.", - DeprecationWarning, - stacklevel=2, -) - -_sha1_re = re.compile(r"^[a-f0-9]{40}$") - - -def _urandom(): - if hasattr(os, "urandom"): - return os.urandom(30) - return text_type(random()).encode("ascii") - - -def generate_key(salt=None): - if salt is None: - salt = repr(salt).encode("ascii") - return sha1(b"".join([salt, str(time()).encode("ascii"), _urandom()])).hexdigest() - - -class ModificationTrackingDict(CallbackDict): - __slots__ = ("modified",) - - def __init__(self, *args, **kwargs): - def on_update(self): - self.modified = True - - self.modified = False - CallbackDict.__init__(self, on_update=on_update) - dict.update(self, *args, **kwargs) - - def copy(self): - """Create a flat copy of the dict.""" - missing = object() - result = object.__new__(self.__class__) - for name in self.__slots__: - val = getattr(self, name, missing) - if val is not missing: - setattr(result, name, val) - return result - - def __copy__(self): - return self.copy() - - -class Session(ModificationTrackingDict): - """Subclass of a dict that keeps track of direct object changes. Changes - in mutable structures are not tracked, for those you have to set - `modified` to `True` by hand. - """ - - __slots__ = ModificationTrackingDict.__slots__ + ("sid", "new") - - def __init__(self, data, sid, new=False): - ModificationTrackingDict.__init__(self, data) - self.sid = sid - self.new = new - - def __repr__(self): - return "<%s %s%s>" % ( - self.__class__.__name__, - dict.__repr__(self), - "*" if self.should_save else "", - ) - - @property - def should_save(self): - """True if the session should be saved. - - .. versionchanged:: 0.6 - By default the session is now only saved if the session is - modified, not if it is new like it was before. - """ - return self.modified - - -class SessionStore(object): - """Baseclass for all session stores. The Werkzeug contrib module does not - implement any useful stores besides the filesystem store, application - developers are encouraged to create their own stores. - - :param session_class: The session class to use. Defaults to - :class:`Session`. - """ - - def __init__(self, session_class=None): - if session_class is None: - session_class = Session - self.session_class = session_class - - def is_valid_key(self, key): - """Check if a key has the correct format.""" - return _sha1_re.match(key) is not None - - def generate_key(self, salt=None): - """Simple function that generates a new session key.""" - return generate_key(salt) - - def new(self): - """Generate a new session.""" - return self.session_class({}, self.generate_key(), True) - - def save(self, session): - """Save a session.""" - - def save_if_modified(self, session): - """Save if a session class wants an update.""" - if session.should_save: - self.save(session) - - def delete(self, session): - """Delete a session.""" - - def get(self, sid): - """Get a session for this sid or a new session object. This method - has to check if the session key is valid and create a new session if - that wasn't the case. - """ - return self.session_class({}, sid, True) - - -#: used for temporary files by the filesystem session store -_fs_transaction_suffix = ".__wz_sess" - - -class FilesystemSessionStore(SessionStore): - """Simple example session store that saves sessions on the filesystem. - This store works best on POSIX systems and Windows Vista / Windows - Server 2008 and newer. - - .. versionchanged:: 0.6 - `renew_missing` was added. Previously this was considered `True`, - now the default changed to `False` and it can be explicitly - deactivated. - - :param path: the path to the folder used for storing the sessions. - If not provided the default temporary directory is used. - :param filename_template: a string template used to give the session - a filename. ``%s`` is replaced with the - session id. - :param session_class: The session class to use. Defaults to - :class:`Session`. - :param renew_missing: set to `True` if you want the store to - give the user a new sid if the session was - not yet saved. - """ - - def __init__( - self, - path=None, - filename_template="werkzeug_%s.sess", - session_class=None, - renew_missing=False, - mode=0o644, - ): - SessionStore.__init__(self, session_class) - if path is None: - path = tempfile.gettempdir() - self.path = path - if isinstance(filename_template, text_type) and PY2: - filename_template = filename_template.encode(get_filesystem_encoding()) - assert not filename_template.endswith(_fs_transaction_suffix), ( - "filename templates may not end with %s" % _fs_transaction_suffix - ) - self.filename_template = filename_template - self.renew_missing = renew_missing - self.mode = mode - - def get_session_filename(self, sid): - # out of the box, this should be a strict ASCII subset but - # you might reconfigure the session object to have a more - # arbitrary string. - if isinstance(sid, text_type) and PY2: - sid = sid.encode(get_filesystem_encoding()) - return path.join(self.path, self.filename_template % sid) - - def save(self, session): - fn = self.get_session_filename(session.sid) - fd, tmp = tempfile.mkstemp(suffix=_fs_transaction_suffix, dir=self.path) - f = os.fdopen(fd, "wb") - try: - dump(dict(session), f, HIGHEST_PROTOCOL) - finally: - f.close() - try: - rename(tmp, fn) - os.chmod(fn, self.mode) - except (IOError, OSError): - pass - - def delete(self, session): - fn = self.get_session_filename(session.sid) - try: - os.unlink(fn) - except OSError: - pass - - def get(self, sid): - if not self.is_valid_key(sid): - return self.new() - try: - f = open(self.get_session_filename(sid), "rb") - except IOError: - if self.renew_missing: - return self.new() - data = {} - else: - try: - try: - data = load(f) - except Exception: - data = {} - finally: - f.close() - return self.session_class(data, sid, False) - - def list(self): - """Lists all sessions in the store. - - .. versionadded:: 0.6 - """ - before, after = self.filename_template.split("%s", 1) - filename_re = re.compile( - r"%s(.{5,})%s$" % (re.escape(before), re.escape(after)) - ) - result = [] - for filename in os.listdir(self.path): - #: this is a session that is still being saved. - if filename.endswith(_fs_transaction_suffix): - continue - match = filename_re.match(filename) - if match is not None: - result.append(match.group(1)) - return result - - -class SessionMiddleware(object): - """A simple middleware that puts the session object of a store provided - into the WSGI environ. It automatically sets cookies and restores - sessions. - - However a middleware is not the preferred solution because it won't be as - fast as sessions managed by the application itself and will put a key into - the WSGI environment only relevant for the application which is against - the concept of WSGI. - - The cookie parameters are the same as for the :func:`~dump_cookie` - function just prefixed with ``cookie_``. Additionally `max_age` is - called `cookie_age` and not `cookie_max_age` because of backwards - compatibility. - """ - - def __init__( - self, - app, - store, - cookie_name="session_id", - cookie_age=None, - cookie_expires=None, - cookie_path="/", - cookie_domain=None, - cookie_secure=None, - cookie_httponly=False, - cookie_samesite="Lax", - environ_key="werkzeug.session", - ): - self.app = app - self.store = store - self.cookie_name = cookie_name - self.cookie_age = cookie_age - self.cookie_expires = cookie_expires - self.cookie_path = cookie_path - self.cookie_domain = cookie_domain - self.cookie_secure = cookie_secure - self.cookie_httponly = cookie_httponly - self.cookie_samesite = cookie_samesite - self.environ_key = environ_key - - def __call__(self, environ, start_response): - cookie = parse_cookie(environ.get("HTTP_COOKIE", "")) - sid = cookie.get(self.cookie_name, None) - if sid is None: - session = self.store.new() - else: - session = self.store.get(sid) - environ[self.environ_key] = session - - def injecting_start_response(status, headers, exc_info=None): - if session.should_save: - self.store.save(session) - headers.append( - ( - "Set-Cookie", - dump_cookie( - self.cookie_name, - session.sid, - self.cookie_age, - self.cookie_expires, - self.cookie_path, - self.cookie_domain, - self.cookie_secure, - self.cookie_httponly, - samesite=self.cookie_samesite, - ), - ) - ) - return start_response(status, headers, exc_info) - - return ClosingIterator( - self.app(environ, injecting_start_response), - lambda: self.store.save_if_modified(session), - ) diff --git a/python/werkzeug/contrib/wrappers.py b/python/werkzeug/contrib/wrappers.py deleted file mode 100644 index 49b82a7..0000000 --- a/python/werkzeug/contrib/wrappers.py +++ /dev/null @@ -1,385 +0,0 @@ -# -*- coding: utf-8 -*- -""" - werkzeug.contrib.wrappers - ~~~~~~~~~~~~~~~~~~~~~~~~~ - - Extra wrappers or mixins contributed by the community. These wrappers can - be mixed in into request objects to add extra functionality. - - Example:: - - from werkzeug.wrappers import Request as RequestBase - from werkzeug.contrib.wrappers import JSONRequestMixin - - class Request(RequestBase, JSONRequestMixin): - pass - - Afterwards this request object provides the extra functionality of the - :class:`JSONRequestMixin`. - - :copyright: 2007 Pallets - :license: BSD-3-Clause -""" -import codecs -import warnings - -from .._compat import wsgi_decoding_dance -from ..exceptions import BadRequest -from ..http import dump_options_header -from ..http import parse_options_header -from ..utils import cached_property -from ..wrappers.json import JSONMixin as _JSONMixin - - -def is_known_charset(charset): - """Checks if the given charset is known to Python.""" - try: - codecs.lookup(charset) - except LookupError: - return False - return True - - -class JSONRequestMixin(_JSONMixin): - """ - .. deprecated:: 0.15 - Moved to :class:`werkzeug.wrappers.json.JSONMixin`. This old - import will be removed in version 1.0. - """ - - @property - def json(self): - warnings.warn( - "'werkzeug.contrib.wrappers.JSONRequestMixin' has moved to" - " 'werkzeug.wrappers.json.JSONMixin'. This old import will" - " be removed in version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - return super(JSONRequestMixin, self).json - - -class ProtobufRequestMixin(object): - - """Add protobuf parsing method to a request object. This will parse the - input data through `protobuf`_ if possible. - - :exc:`~werkzeug.exceptions.BadRequest` will be raised if the content-type - is not protobuf or if the data itself cannot be parsed property. - - .. _protobuf: https://github.com/protocolbuffers/protobuf - - .. deprecated:: 0.15 - This mixin will be removed in version 1.0. - """ - - #: by default the :class:`ProtobufRequestMixin` will raise a - #: :exc:`~werkzeug.exceptions.BadRequest` if the object is not - #: initialized. You can bypass that check by setting this - #: attribute to `False`. - protobuf_check_initialization = True - - def parse_protobuf(self, proto_type): - """Parse the data into an instance of proto_type.""" - warnings.warn( - "'werkzeug.contrib.wrappers.ProtobufRequestMixin' is" - " deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - if "protobuf" not in self.environ.get("CONTENT_TYPE", ""): - raise BadRequest("Not a Protobuf request") - - obj = proto_type() - try: - obj.ParseFromString(self.data) - except Exception: - raise BadRequest("Unable to parse Protobuf request") - - # Fail if not all required fields are set - if self.protobuf_check_initialization and not obj.IsInitialized(): - raise BadRequest("Partial Protobuf request") - - return obj - - -class RoutingArgsRequestMixin(object): - - """This request mixin adds support for the wsgiorg routing args - `specification`_. - - .. _specification: https://wsgi.readthedocs.io/en/latest/ - specifications/routing_args.html - - .. deprecated:: 0.15 - This mixin will be removed in version 1.0. - """ - - def _get_routing_args(self): - warnings.warn( - "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is" - " deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - return self.environ.get("wsgiorg.routing_args", (()))[0] - - def _set_routing_args(self, value): - warnings.warn( - "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is" - " deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - if self.shallow: - raise RuntimeError( - "A shallow request tried to modify the WSGI " - "environment. If you really want to do that, " - "set `shallow` to False." - ) - self.environ["wsgiorg.routing_args"] = (value, self.routing_vars) - - routing_args = property( - _get_routing_args, - _set_routing_args, - doc=""" - The positional URL arguments as `tuple`.""", - ) - del _get_routing_args, _set_routing_args - - def _get_routing_vars(self): - warnings.warn( - "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is" - " deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - rv = self.environ.get("wsgiorg.routing_args") - if rv is not None: - return rv[1] - rv = {} - if not self.shallow: - self.routing_vars = rv - return rv - - def _set_routing_vars(self, value): - warnings.warn( - "'werkzeug.contrib.wrappers.RoutingArgsRequestMixin' is" - " deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - if self.shallow: - raise RuntimeError( - "A shallow request tried to modify the WSGI " - "environment. If you really want to do that, " - "set `shallow` to False." - ) - self.environ["wsgiorg.routing_args"] = (self.routing_args, value) - - routing_vars = property( - _get_routing_vars, - _set_routing_vars, - doc=""" - The keyword URL arguments as `dict`.""", - ) - del _get_routing_vars, _set_routing_vars - - -class ReverseSlashBehaviorRequestMixin(object): - - """This mixin reverses the trailing slash behavior of :attr:`script_root` - and :attr:`path`. This makes it possible to use :func:`~urlparse.urljoin` - directly on the paths. - - Because it changes the behavior or :class:`Request` this class has to be - mixed in *before* the actual request class:: - - class MyRequest(ReverseSlashBehaviorRequestMixin, Request): - pass - - This example shows the differences (for an application mounted on - `/application` and the request going to `/application/foo/bar`): - - +---------------+-------------------+---------------------+ - | | normal behavior | reverse behavior | - +===============+===================+=====================+ - | `script_root` | ``/application`` | ``/application/`` | - +---------------+-------------------+---------------------+ - | `path` | ``/foo/bar`` | ``foo/bar`` | - +---------------+-------------------+---------------------+ - - .. deprecated:: 0.15 - This mixin will be removed in version 1.0. - """ - - @cached_property - def path(self): - """Requested path as unicode. This works a bit like the regular path - info in the WSGI environment but will not include a leading slash. - """ - warnings.warn( - "'werkzeug.contrib.wrappers.ReverseSlashBehaviorRequestMixin'" - " is deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - path = wsgi_decoding_dance( - self.environ.get("PATH_INFO") or "", self.charset, self.encoding_errors - ) - return path.lstrip("/") - - @cached_property - def script_root(self): - """The root path of the script includling a trailing slash.""" - warnings.warn( - "'werkzeug.contrib.wrappers.ReverseSlashBehaviorRequestMixin'" - " is deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - path = wsgi_decoding_dance( - self.environ.get("SCRIPT_NAME") or "", self.charset, self.encoding_errors - ) - return path.rstrip("/") + "/" - - -class DynamicCharsetRequestMixin(object): - - """"If this mixin is mixed into a request class it will provide - a dynamic `charset` attribute. This means that if the charset is - transmitted in the content type headers it's used from there. - - Because it changes the behavior or :class:`Request` this class has - to be mixed in *before* the actual request class:: - - class MyRequest(DynamicCharsetRequestMixin, Request): - pass - - By default the request object assumes that the URL charset is the - same as the data charset. If the charset varies on each request - based on the transmitted data it's not a good idea to let the URLs - change based on that. Most browsers assume either utf-8 or latin1 - for the URLs if they have troubles figuring out. It's strongly - recommended to set the URL charset to utf-8:: - - class MyRequest(DynamicCharsetRequestMixin, Request): - url_charset = 'utf-8' - - .. deprecated:: 0.15 - This mixin will be removed in version 1.0. - - .. versionadded:: 0.6 - """ - - #: the default charset that is assumed if the content type header - #: is missing or does not contain a charset parameter. The default - #: is latin1 which is what HTTP specifies as default charset. - #: You may however want to set this to utf-8 to better support - #: browsers that do not transmit a charset for incoming data. - default_charset = "latin1" - - def unknown_charset(self, charset): - """Called if a charset was provided but is not supported by - the Python codecs module. By default latin1 is assumed then - to not lose any information, you may override this method to - change the behavior. - - :param charset: the charset that was not found. - :return: the replacement charset. - """ - return "latin1" - - @cached_property - def charset(self): - """The charset from the content type.""" - warnings.warn( - "'werkzeug.contrib.wrappers.DynamicCharsetRequestMixin'" - " is deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - header = self.environ.get("CONTENT_TYPE") - if header: - ct, options = parse_options_header(header) - charset = options.get("charset") - if charset: - if is_known_charset(charset): - return charset - return self.unknown_charset(charset) - return self.default_charset - - -class DynamicCharsetResponseMixin(object): - - """If this mixin is mixed into a response class it will provide - a dynamic `charset` attribute. This means that if the charset is - looked up and stored in the `Content-Type` header and updates - itself automatically. This also means a small performance hit but - can be useful if you're working with different charsets on - responses. - - Because the charset attribute is no a property at class-level, the - default value is stored in `default_charset`. - - Because it changes the behavior or :class:`Response` this class has - to be mixed in *before* the actual response class:: - - class MyResponse(DynamicCharsetResponseMixin, Response): - pass - - .. deprecated:: 0.15 - This mixin will be removed in version 1.0. - - .. versionadded:: 0.6 - """ - - #: the default charset. - default_charset = "utf-8" - - def _get_charset(self): - warnings.warn( - "'werkzeug.contrib.wrappers.DynamicCharsetResponseMixin'" - " is deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - header = self.headers.get("content-type") - if header: - charset = parse_options_header(header)[1].get("charset") - if charset: - return charset - return self.default_charset - - def _set_charset(self, charset): - warnings.warn( - "'werkzeug.contrib.wrappers.DynamicCharsetResponseMixin'" - " is deprecated as of version 0.15 and will be removed in" - " version 1.0.", - DeprecationWarning, - stacklevel=2, - ) - header = self.headers.get("content-type") - ct, options = parse_options_header(header) - if not ct: - raise TypeError("Cannot set charset if Content-Type header is missing.") - options["charset"] = charset - self.headers["Content-Type"] = dump_options_header(ct, options) - - charset = property( - _get_charset, - _set_charset, - doc=""" - The charset for the response. It's stored inside the - Content-Type header as a parameter.""", - ) - del _get_charset, _set_charset |