changeset 6129:7f12cf241d5e

update werkzeug to 0.12.1, update CHANGES
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Tue, 11 Apr 2017 22:42:23 +0200
parents 561b7a9c2bd9
children 7f0616feeae9
files MoinMoin/support/werkzeug/__init__.py MoinMoin/support/werkzeug/_internal.py MoinMoin/support/werkzeug/_reloader.py MoinMoin/support/werkzeug/contrib/atom.py MoinMoin/support/werkzeug/contrib/cache.py MoinMoin/support/werkzeug/contrib/fixers.py MoinMoin/support/werkzeug/contrib/iterio.py MoinMoin/support/werkzeug/contrib/lint.py MoinMoin/support/werkzeug/contrib/securecookie.py MoinMoin/support/werkzeug/contrib/sessions.py MoinMoin/support/werkzeug/contrib/wrappers.py MoinMoin/support/werkzeug/datastructures.py MoinMoin/support/werkzeug/debug/__init__.py MoinMoin/support/werkzeug/debug/console.py MoinMoin/support/werkzeug/debug/repr.py MoinMoin/support/werkzeug/debug/shared/debugger.js MoinMoin/support/werkzeug/debug/shared/style.css MoinMoin/support/werkzeug/debug/tbtools.py MoinMoin/support/werkzeug/exceptions.py MoinMoin/support/werkzeug/formparser.py MoinMoin/support/werkzeug/http.py MoinMoin/support/werkzeug/local.py MoinMoin/support/werkzeug/routing.py MoinMoin/support/werkzeug/script.py MoinMoin/support/werkzeug/security.py MoinMoin/support/werkzeug/serving.py MoinMoin/support/werkzeug/test.py MoinMoin/support/werkzeug/useragents.py MoinMoin/support/werkzeug/wrappers.py MoinMoin/support/werkzeug/wsgi.py docs/CHANGES docs/REQUIREMENTS
diffstat 32 files changed, 739 insertions(+), 199 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/support/werkzeug/__init__.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/__init__.py	Tue Apr 11 22:42:23 2017 +0200
@@ -19,8 +19,7 @@
 
 from werkzeug._compat import iteritems
 
-# the version.  Usually set automatically by a script.
-__version__ = '0.11.11'
+__version__ = '0.12.1'
 
 
 # This import magic raises concerns quite often which is why the implementation
--- a/MoinMoin/support/werkzeug/_internal.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/_internal.py	Tue Apr 11 22:42:23 2017 +0200
@@ -43,7 +43,7 @@
 _octal_re = re.compile(b'\\\\[0-3][0-7][0-7]')
 _quote_re = re.compile(b'[\\\\].')
 _legal_cookie_chars_re = b'[\w\d!#%&\'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]'
-_cookie_re = re.compile(b"""(?x)
+_cookie_re = re.compile(b"""
     (?P<key>[^=]+)
     \s*=\s*
     (?P<val>
@@ -51,7 +51,7 @@
          (?:.*?)
     )
     \s*;
-""")
+""", flags=re.VERBOSE)
 
 
 class _Missing(object):
@@ -98,7 +98,11 @@
         return parse
 
     # inspect the function signature and collect all the information
-    positional, vararg_var, kwarg_var, defaults = inspect.getargspec(func)
+    if hasattr(inspect, 'getfullargspec'):
+        tup = inspect.getfullargspec(func)
+    else:
+        tup = inspect.getargspec(func)
+    positional, vararg_var, kwarg_var, defaults = tup[:4]
     defaults = defaults or ()
     arg_count = len(positional)
     arguments = []
--- a/MoinMoin/support/werkzeug/_reloader.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/_reloader.py	Tue Apr 11 22:42:23 2017 +0200
@@ -50,6 +50,21 @@
     return _find_common_roots(rv)
 
 
+def _get_args_for_reloading():
+    """Returns the executable. This contains a workaround for windows
+    if the executable is incorrectly reported to not have the .exe
+    extension which can cause bugs on reloading.
+    """
+    rv = [sys.executable]
+    py_script = sys.argv[0]
+    if os.name == 'nt' and not os.path.exists(py_script) and \
+       os.path.exists(py_script + '.exe'):
+        py_script += '.exe'
+    rv.append(py_script)
+    rv.extend(sys.argv[1:])
+    return rv
+
+
 def _find_common_roots(paths):
     """Out of some paths it finds the common roots that need monitoring."""
     paths = [x.split(os.path.sep) for x in paths]
@@ -93,7 +108,7 @@
         """
         while 1:
             _log('info', ' * Restarting with %s' % self.name)
-            args = [sys.executable] + sys.argv
+            args = _get_args_for_reloading()
             new_environ = os.environ.copy()
             new_environ['WERKZEUG_RUN_MAIN'] = 'true'
 
--- a/MoinMoin/support/werkzeug/contrib/atom.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/atom.py	Tue Apr 11 22:42:23 2017 +0200
@@ -159,7 +159,7 @@
         """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 False in map(lambda e: bool(e.author), self.entries):
+            if any(not e.author for e in self.entries):
                 self.author = ({'name': 'Unknown author'},)
 
         if not self.updated:
--- a/MoinMoin/support/werkzeug/contrib/cache.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/cache.py	Tue Apr 11 22:42:23 2017 +0200
@@ -23,7 +23,7 @@
     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 a template::
+    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
@@ -60,6 +60,7 @@
 import re
 import errno
 import tempfile
+import platform
 from hashlib import md5
 from time import time
 try:
@@ -93,14 +94,19 @@
     """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.
+    :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.
 
@@ -149,9 +155,9 @@
 
         :param key: the key to set
         :param value: the value for the key
-        :param timeout: the cache timeout for the key (if not specified,
-                        it uses the default timeout). A timeout of 0 idicates
-                        that the cache never expires.
+        :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``.
@@ -165,9 +171,9 @@
 
         :param key: the key to set
         :param value: the value for the key
-        :param timeout: the cache timeout for the key or the default
-                        timeout if not specified. A timeout of 0 indicates
-                        that the cache never expires.
+        :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
@@ -178,9 +184,9 @@
         """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 (if not specified,
-                        it uses the default timeout). A timeout of 0
-                        indicates tht the cache never expires.
+        :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
         """
@@ -218,6 +224,7 @@
     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
         """
@@ -289,9 +296,8 @@
             for key in toremove:
                 self._cache.pop(key, None)
 
-    def _get_expiration(self, timeout):
-        if timeout is None:
-            timeout = self.default_timeout
+    def _normalize_timeout(self, timeout):
+        timeout = BaseCache._normalize_timeout(self, timeout)
         if timeout > 0:
             timeout = time() + timeout
         return timeout
@@ -305,14 +311,14 @@
             return None
 
     def set(self, key, value, timeout=None):
-        expires = self._get_expiration(timeout)
+        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._get_expiration(timeout)
+        expires = self._normalize_timeout(timeout)
         self._prune()
         item = (expires, pickle.dumps(value,
                                       pickle.HIGHEST_PROTOCOL))
@@ -391,8 +397,7 @@
         return key
 
     def _normalize_timeout(self, timeout):
-        if timeout is None:
-            timeout = self.default_timeout
+        timeout = BaseCache._normalize_timeout(self, timeout)
         if timeout > 0:
             timeout = int(time()) + timeout
         return timeout
@@ -561,9 +566,8 @@
             self._client = host
         self.key_prefix = key_prefix or ''
 
-    def _get_expiration(self, timeout):
-        if timeout is None:
-            timeout = self.default_timeout
+    def _normalize_timeout(self, timeout):
+        timeout = BaseCache._normalize_timeout(self, timeout)
         if timeout == 0:
             timeout = -1
         return timeout
@@ -603,7 +607,7 @@
         return [self.load_object(x) for x in self._client.mget(keys)]
 
     def set(self, key, value, timeout=None):
-        timeout = self._get_expiration(timeout)
+        timeout = self._normalize_timeout(timeout)
         dump = self.dump_object(value)
         if timeout == -1:
             result = self._client.set(name=self.key_prefix + key,
@@ -614,7 +618,7 @@
         return result
 
     def add(self, key, value, timeout=None):
-        timeout = self._get_expiration(timeout)
+        timeout = self._normalize_timeout(timeout)
         dump = self.dump_object(value)
         return (
             self._client.setnx(name=self.key_prefix + key, value=dump) and
@@ -622,7 +626,7 @@
         )
 
     def set_many(self, mapping, timeout=None):
-        timeout = self._get_expiration(timeout)
+        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)
@@ -698,6 +702,12 @@
             if ex.errno != errno.EEXIST:
                 raise
 
+    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
         """
@@ -708,8 +718,8 @@
         entries = self._list_dir()
         if len(entries) > self._threshold:
             now = time()
-            try:
-                for idx, fname in enumerate(entries):
+            for idx, fname in enumerate(entries):
+                try:
                     remove = False
                     with open(fname, 'rb') as f:
                         expires = pickle.load(f)
@@ -717,8 +727,8 @@
 
                     if remove:
                         os.remove(fname)
-            except (IOError, OSError):
-                pass
+                except (IOError, OSError):
+                    pass
 
     def clear(self):
         for fname in self._list_dir():
@@ -754,10 +764,7 @@
         return False
 
     def set(self, key, value, timeout=None):
-        if timeout is None:
-            timeout = int(time() + self.default_timeout)
-        elif timeout != 0:
-            timeout = int(time() + timeout)
+        timeout = self._normalize_timeout(timeout)
         filename = self._get_filename(key)
         self._prune()
         try:
@@ -793,3 +800,59 @@
                     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
--- a/MoinMoin/support/werkzeug/contrib/fixers.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/fixers.py	Tue Apr 11 22:42:23 2017 +0200
@@ -129,7 +129,7 @@
         .. versionadded:: 0.8
         """
         if len(forwarded_for) >= self.num_proxies:
-            return forwarded_for[-1 * self.num_proxies]
+            return forwarded_for[-self.num_proxies]
 
     def __call__(self, environ, start_response):
         getter = environ.get
--- a/MoinMoin/support/werkzeug/contrib/iterio.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/iterio.py	Tue Apr 11 22:42:23 2017 +0200
@@ -34,7 +34,7 @@
         print iterator.next()       # prints otherthing
         iterator.next()             # raises StopIteration
 
-    .. _greenlet: http://codespeak.net/py/dist/greenlet.html
+    .. _greenlet: https://github.com/python-greenlet/greenlet
 
     :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
     :license: BSD, see LICENSE for more details.
@@ -260,7 +260,7 @@
         try:
             tmp_end_pos = len(self._buf)
             while pos > tmp_end_pos:
-                item = self._gen.next()
+                item = next(self._gen)
                 tmp_end_pos += len(item)
                 buf.append(item)
         except StopIteration:
--- a/MoinMoin/support/werkzeug/contrib/lint.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/lint.py	Tue Apr 11 22:42:23 2017 +0200
@@ -19,7 +19,11 @@
     :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
     :license: BSD, see LICENSE for more details.
 """
-from urlparse import urlparse
+try:
+    from urllib.parse import urlparse
+except ImportError:
+    from urlparse import urlparse
+
 from warnings import warn
 
 from werkzeug.datastructures import Headers
--- a/MoinMoin/support/werkzeug/contrib/securecookie.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/securecookie.py	Tue Apr 11 22:42:23 2017 +0200
@@ -57,7 +57,7 @@
                 return SecureCookie.unserialize(data, SECRET_KEY)
 
         def application(environ, start_response):
-            request = Request(environ, start_response)
+            request = Request(environ)
 
             # get a response object here
             response = ...
@@ -77,7 +77,7 @@
                 return SecureCookie.load_cookie(self, secret_key=COOKIE_SECRET)
 
         def application(environ, start_response):
-            request = Request(environ, start_response)
+            request = Request(environ)
 
             # get a response object here
             response = ...
--- a/MoinMoin/support/werkzeug/contrib/sessions.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/sessions.py	Tue Apr 11 22:42:23 2017 +0200
@@ -27,7 +27,7 @@
     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.  It sessions are stored in the database the new()
+    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
--- a/MoinMoin/support/werkzeug/contrib/wrappers.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/contrib/wrappers.py	Tue Apr 11 22:42:23 2017 +0200
@@ -101,7 +101,7 @@
     """This request mixin adds support for the wsgiorg routing args
     `specification`_.
 
-    .. _specification: http://www.wsgi.org/wsgi/Specifications/routing_args
+    .. _specification: https://wsgi.readthedocs.io/en/latest/specifications/routing_args.html
     """
 
     def _get_routing_args(self):
--- a/MoinMoin/support/werkzeug/datastructures.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/datastructures.py	Tue Apr 11 22:42:23 2017 +0200
@@ -13,6 +13,7 @@
 import mimetypes
 from copy import deepcopy
 from itertools import repeat
+from collections import Container, Iterable, Mapping, MutableSet
 
 from werkzeug._internal import _missing, _empty_stream
 from werkzeug._compat import iterkeys, itervalues, iteritems, iterlists, \
@@ -51,7 +52,14 @@
     if not PY2:
         return lambda x: x
 
-    def setmethod(cls, name):
+    def setviewmethod(cls, name):
+        viewmethod_name = 'view%s' % name
+        viewmethod = lambda self, *a, **kw: ViewItems(self, name, 'view_%s' % name, *a, **kw)
+        viewmethod.__doc__ = \
+            '"""`%s()` object providing a view on %s"""' % (viewmethod_name, name)
+        setattr(cls, viewmethod_name, viewmethod)
+
+    def setitermethod(cls, name):
         itermethod = getattr(cls, name)
         setattr(cls, 'iter%s' % name, itermethod)
         listmethod = lambda self, *a, **kw: list(itermethod(self, *a, **kw))
@@ -61,7 +69,8 @@
 
     def wrap(cls):
         for name in names:
-            setmethod(cls, name)
+            setitermethod(cls, name)
+            setviewmethod(cls, name)
         return cls
     return wrap
 
@@ -89,9 +98,6 @@
     def __delitem__(self, key):
         is_immutable(self)
 
-    def __delslice__(self, i, j):
-        is_immutable(self)
-
     def __iadd__(self, other):
         is_immutable(self)
     __imul__ = __iadd__
@@ -99,9 +105,6 @@
     def __setitem__(self, key, value):
         is_immutable(self)
 
-    def __setslice__(self, i, j, value):
-        is_immutable(self)
-
     def append(self, item):
         is_immutable(self)
     remove = append
@@ -322,6 +325,25 @@
         return self
 
 
+class ViewItems(object):
+
+    def __init__(self, multi_dict, method, repr_name, *a, **kw):
+        self.__multi_dict = multi_dict
+        self.__method = method
+        self.__repr_name = repr_name
+        self.__a = a
+        self.__kw = kw
+
+    def __get_items(self):
+        return getattr(self.__multi_dict, self.__method)(*self.__a, **self.__kw)
+
+    def __repr__(self):
+        return '%s(%r)' % (self.__repr_name, list(self.__get_items()))
+
+    def __iter__(self):
+        return iter(self.__get_items())
+
+
 @native_itermethods(['keys', 'values', 'items', 'lists', 'listvalues'])
 class MultiDict(TypeConversionDict):
 
@@ -606,7 +628,12 @@
                         not in the dictionary.
         """
         try:
-            return dict.pop(self, key)[0]
+            lst = dict.pop(self, key)
+
+            if len(lst) == 0:
+                raise exceptions.BadRequestKeyError()
+
+            return lst[0]
         except KeyError as e:
             if default is not _missing:
                 return default
@@ -616,6 +643,10 @@
         """Pop an item from the dict."""
         try:
             item = dict.popitem(self)
+
+            if len(item[1]) == 0:
+                raise exceptions.BadRequestKeyError()
+
             return (item[0], item[1][0])
         except KeyError as e:
             raise exceptions.BadRequestKeyError(str(e))
@@ -728,6 +759,8 @@
                 return False
         return True
 
+    __hash__ = None
+
     def __ne__(self, other):
         return not self.__eq__(other)
 
@@ -935,6 +968,8 @@
         return other.__class__ is self.__class__ and \
             set(other._list) == set(self._list)
 
+    __hash__ = None
+
     def __ne__(self, other):
         return not self.__eq__(other)
 
@@ -1303,6 +1338,8 @@
     def __eq__(self, other):
         return self.environ is other.environ
 
+    __hash__ = None
+
     def __getitem__(self, key, _get_mode=False):
         # _get_mode is a no-op for this class as there is no index but
         # used because get() calls it.
@@ -1600,10 +1637,8 @@
             list.__init__(self, values)
         else:
             self.provided = True
-            values = [(a, b) for b, a in values]
-            values.sort()
-            values.reverse()
-            list.__init__(self, [(a, b) for b, a in values])
+            values = sorted(values, key=lambda x: (x[1], x[0]), reverse=True)
+            list.__init__(self, values)
 
     def _value_matches(self, value, item):
         """Check if a value matches a given accept item."""
@@ -1959,7 +1994,7 @@
         )
 
 
-class HeaderSet(object):
+class HeaderSet(MutableSet):
 
     """Similar to the :class:`ETags` class this implements a set-like structure.
     Unlike :class:`ETags` this is case insensitive and used for vary, allow, and
@@ -2113,7 +2148,7 @@
         )
 
 
-class ETags(object):
+class ETags(Container, Iterable):
 
     """A set that can be used to check if one etag is present in a collection
     of etags.
@@ -2275,6 +2310,17 @@
                 ranges.append('%s-%s' % (begin, end - 1))
         return '%s=%s' % (self.units, ','.join(ranges))
 
+    def to_content_range_header(self, length):
+        """Converts the object into `Content-Range` HTTP header,
+        based on given length
+        """
+        range_for_length = self.range_for_length(length)
+        if range_for_length is not None:
+            return '%s %d-%d/%d' % (self.units,
+                                    range_for_length[0],
+                                    range_for_length[1] - 1, length)
+        return None
+
     def __str__(self):
         return self.to_header()
 
@@ -2677,7 +2723,7 @@
         return getattr(self.stream, name)
 
     def __iter__(self):
-        return iter(self.readline, '')
+        return iter(self.stream)
 
     def __repr__(self):
         return '<%s: %r (%r)>' % (
--- a/MoinMoin/support/werkzeug/debug/__init__.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/debug/__init__.py	Tue Apr 11 22:42:23 2017 +0200
@@ -252,10 +252,10 @@
                pin_logging:
                 _log('warning', ' * Debugger is active!')
                 if self.pin is None:
-                    _log('warning', ' * Debugger pin disabled.  '
+                    _log('warning', ' * Debugger PIN disabled.  '
                          'DEBUGGER UNSECURED!')
                 else:
-                    _log('info', ' * Debugger pin code: %s' % self.pin)
+                    _log('info', ' * Debugger PIN: %s' % self.pin)
         else:
             self.pin = None
 
--- a/MoinMoin/support/werkzeug/debug/console.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/debug/console.py	Tue Apr 11 22:42:23 2017 +0200
@@ -173,7 +173,7 @@
                 del self.buffer[:]
         finally:
             output = ThreadedStream.fetch()
-        return prompt + source + output
+        return prompt + escape(source) + output
 
     def runcode(self, code):
         try:
--- a/MoinMoin/support/werkzeug/debug/repr.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/debug/repr.py	Tue Apr 11 22:42:23 2017 +0200
@@ -154,17 +154,16 @@
 
     def string_repr(self, obj, limit=70):
         buf = ['<span class="string">']
-        escaped = escape(obj)
-        a = repr(escaped[:limit])
-        b = repr(escaped[limit:])
+        a = repr(obj[:limit])
+        b = repr(obj[limit:])
         if isinstance(obj, text_type) and PY2:
             buf.append('u')
             a = a[1:]
             b = b[1:]
         if b != "''":
-            buf.extend((a[:-1], '<span class="extended">', b[1:], '</span>'))
+            buf.extend((escape(a[:-1]), '<span class="extended">', escape(b[1:]), '</span>'))
         else:
-            buf.append(a)
+            buf.append(escape(a))
         buf.append('</span>')
         return _add_subclass_info(u''.join(buf), obj, (bytes, text_type))
 
--- a/MoinMoin/support/werkzeug/debug/shared/debugger.js	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/debug/shared/debugger.js	Tue Apr 11 22:42:23 2017 +0200
@@ -182,7 +182,7 @@
     }).
     appendTo(consoleNode);
 
-  var command = $('<input type="text">')
+  var command = $('<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false">')
     .appendTo(form)
     .keydown(function(e) {
       if (e.charCode == 100 && e.ctrlKey) {
--- a/MoinMoin/support/werkzeug/debug/shared/style.css	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/debug/shared/style.css	Tue Apr 11 22:42:23 2017 +0200
@@ -24,7 +24,8 @@
 div.debugger { text-align: left; padding: 12px; margin: auto;
                background-color: white; }
 h1           { font-size: 36px; margin: 0 0 0.3em 0; }
-div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap; }
+div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap;
+               font-family: monospace; }
 div.explanation { margin: 20px 13px; font-size: 15px; color: #555; }
 div.footer   { font-size: 13px; text-align: right; margin: 30px 0;
                color: #86989B; }
--- a/MoinMoin/support/werkzeug/debug/tbtools.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/debug/tbtools.py	Tue Apr 11 22:42:23 2017 +0200
@@ -26,7 +26,7 @@
 
 
 _coding_re = re.compile(br'coding[:=]\s*([-\w.]+)')
-_line_re = re.compile(br'^(.*?)$(?m)')
+_line_re = re.compile(br'^(.*?)$', re.MULTILINE)
 _funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
 UTF8_COOKIE = b'\xef\xbb\xbf'
 
@@ -60,7 +60,7 @@
           SECRET = "%(secret)s";
     </script>
   </head>
-  <body>
+  <body style="background-color: #fff">
     <div class="debugger">
 '''
 FOOTER = u'''\
--- a/MoinMoin/support/werkzeug/exceptions.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/exceptions.py	Tue Apr 11 22:42:23 2017 +0200
@@ -156,10 +156,12 @@
         return response(environ, start_response)
 
     def __str__(self):
-        return '%d: %s' % (self.code, self.name)
+        code = self.code if self.code is not None else '???'
+        return '%s %s: %s' % (code, self.name, self.description)
 
     def __repr__(self):
-        return '<%s \'%s\'>' % (self.__class__.__name__, self)
+        code = self.code if self.code is not None else '???'
+        return "<%s '%s: %s'>" % (self.__class__.__name__, code, self.name)
 
 
 class BadRequest(HTTPException):
@@ -412,8 +414,7 @@
 
     """*416* `Requested Range Not Satisfiable`
 
-    The client asked for a part of the file that lies beyond the end
-    of the file.
+    The client asked for an invalid part of the file.
 
     .. versionadded:: 0.7
     """
@@ -422,6 +423,21 @@
         'The server cannot provide the requested range.'
     )
 
+    def __init__(self, length=None, units="bytes", description=None):
+        """Takes an optional `Content-Range` header value based on ``length``
+        parameter.
+        """
+        HTTPException.__init__(self, description)
+        self.length = length
+        self.units = units
+
+    def get_headers(self, environ):
+        headers = HTTPException.get_headers(self, environ)
+        if self.length is not None:
+            headers.append(
+                ('Content-Range', '%s */%d' % (self.units, self.length)))
+        return headers
+
 
 class ExpectationFailed(HTTPException):
 
@@ -466,6 +482,18 @@
     )
 
 
+class Locked(HTTPException):
+
+    """*423* `Locked`
+
+    Used if the resource that is being accessed is locked.
+    """
+    code = 423
+    description = (
+        'The resource that is being accessed is locked.'
+    )
+
+
 class PreconditionRequired(HTTPException):
 
     """*428* `Precondition Required`
@@ -515,6 +543,19 @@
     )
 
 
+class UnavailableForLegalReasons(HTTPException):
+
+    """*451* `Unavailable For Legal Reasons`
+
+    This status code indicates that the server is denying access to the
+    resource as a consequence of a legal demand.
+    """
+    code = 451
+    description = (
+        'Unavailable for legal reasons.'
+    )
+
+
 class InternalServerError(HTTPException):
 
     """*500* `Internal Server Error`
@@ -645,7 +686,27 @@
             raise LookupError('no exception for %r' % code)
         raise self.mapping[code](*args, **kwargs)
 
-abort = Aborter()
+
+def abort(status, *args, **kwargs):
+    '''
+    Raises an :py:exc:`HTTPException` for the given status code or WSGI
+    application::
+
+        abort(404)  # 404 Not Found
+        abort(Response('Hello World'))
+
+    Can be passed a WSGI application or a status code.  If a status code is
+    given it's looked up in the list of exceptions and will raise that
+    exception, if passed a WSGI application it will wrap it in a proxy WSGI
+    exception and raise that::
+
+       abort(404)
+       abort(Response('Hello World'))
+
+    '''
+    return _aborter(status, *args, **kwargs)
+
+_aborter = Aborter()
 
 
 #: an exception that is used internally to signal both a key error and a
--- a/MoinMoin/support/werkzeug/formparser.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/formparser.py	Tue Apr 11 22:42:23 2017 +0200
@@ -286,15 +286,11 @@
 
     def __init__(self, stream_factory=None, charset='utf-8', errors='replace',
                  max_form_memory_size=None, cls=None, buffer_size=64 * 1024):
-        self.stream_factory = stream_factory
         self.charset = charset
         self.errors = errors
         self.max_form_memory_size = max_form_memory_size
-        if stream_factory is None:
-            stream_factory = default_stream_factory
-        if cls is None:
-            cls = MultiDict
-        self.cls = cls
+        self.stream_factory = default_stream_factory if stream_factory is None else stream_factory
+        self.cls = MultiDict if cls is None else cls
 
         # make sure the buffer size is divisible by four so that we can base64
         # decode chunk by chunk
--- a/MoinMoin/support/werkzeug/http.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/http.py	Tue Apr 11 22:42:23 2017 +0200
@@ -23,9 +23,11 @@
 except ImportError:  # pragma: no cover
     from email.Utils import parsedate_tz
 try:
-    from urllib2 import parse_http_list as _parse_list_header
+    from urllib.request import parse_http_list as _parse_list_header
+    from urllib.parse import unquote_to_bytes as _unquote
 except ImportError:  # pragma: no cover
-    from urllib.request import parse_http_list as _parse_list_header
+    from urllib2 import parse_http_list as _parse_list_header, \
+        unquote as _unquote
 from datetime import datetime, timedelta
 from hashlib import md5
 import base64
@@ -61,7 +63,8 @@
 _unsafe_header_chars = set('()<>@,;:\"/[]?={} \t')
 _quoted_string_re = r'"[^"\\]*(?:\\.[^"\\]*)*"'
 _option_header_piece_re = re.compile(
-    r';\s*(%s|[^\s;,=]+)\s*(?:=\s*(%s|[^;,]+)?)?\s*' %
+    r';\s*(%s|[^\s;,=\*]+)\s*'
+    r'(?:\*?=\s*(?:([^\s]+?)\'([^\s]*?)\')?(%s|[^;,]+)?)?\s*' %
     (_quoted_string_re, _quoted_string_re)
 )
 _option_header_start_mime_type = re.compile(r',\s*([^;,\s]+)([;,]\s*.+)?')
@@ -125,6 +128,7 @@
     429:    'Too Many Requests',
     431:    'Request Header Fields Too Large',
     449:    'Retry With',  # proprietary MS extension
+    451:    'Unavailable For Legal Reasons',
     500:    'Internal Server Error',
     501:    'Not Implemented',
     502:    'Bad Gateway',
@@ -354,12 +358,14 @@
             optmatch = _option_header_piece_re.match(rest)
             if not optmatch:
                 break
-            option, option_value = optmatch.groups()
+            option, encoding, _, option_value = optmatch.groups()
             option = unquote_header_value(option)
             if option_value is not None:
                 option_value = unquote_header_value(
                     option_value,
                     option == 'filename')
+                if encoding is not None:
+                    option_value = _unquote(option_value).decode(encoding)
             options[option] = option_value
             rest = rest[optmatch.end():]
         result.append(options)
@@ -551,15 +557,24 @@
         if item.startswith('-'):
             if last_end < 0:
                 return None
-            begin = int(item)
+            try:
+                begin = int(item)
+            except ValueError:
+                return None
             end = None
             last_end = -1
         elif '-' in item:
             begin, end = item.split('-', 1)
+            begin = begin.strip()
+            end = end.strip()
+            if not begin.isdigit():
+                return None
             begin = int(begin)
             if begin < last_end or last_end < 0:
                 return None
             if end:
+                if not end.isdigit():
+                    return None
                 end = int(end) + 1
                 if begin >= end:
                     return None
@@ -766,7 +781,8 @@
     return _dump_date(timestamp, ' ')
 
 
-def is_resource_modified(environ, etag=None, data=None, last_modified=None):
+def is_resource_modified(environ, etag=None, data=None, last_modified=None,
+                         ignore_if_range=True):
     """Convenience method for conditional requests.
 
     :param environ: the WSGI environment of the request to be checked.
@@ -774,6 +790,8 @@
     :param data: or alternatively the data of the response to automatically
                  generate an etag using :func:`generate_etag`.
     :param last_modified: an optional date of the last modification.
+    :param ignore_if_range: If `False`, `If-Range` header will be taken into
+                            account.
     :return: `True` if the resource was modified, otherwise `False`.
     """
     if etag is None and data is not None:
@@ -792,18 +810,32 @@
     if last_modified is not None:
         last_modified = last_modified.replace(microsecond=0)
 
-    modified_since = parse_date(environ.get('HTTP_IF_MODIFIED_SINCE'))
+    if_range = None
+    if not ignore_if_range and 'HTTP_RANGE' in environ:
+        # http://tools.ietf.org/html/rfc7233#section-3.2
+        # A server MUST ignore an If-Range header field received in a request
+        # that does not contain a Range header field.
+        if_range = parse_if_range_header(environ.get('HTTP_IF_RANGE'))
+
+    if if_range is not None and if_range.date is not None:
+        modified_since = if_range.date
+    else:
+        modified_since = parse_date(environ.get('HTTP_IF_MODIFIED_SINCE'))
 
     if modified_since and last_modified and last_modified <= modified_since:
         unmodified = True
+
     if etag:
-        if_none_match = parse_etags(environ.get('HTTP_IF_NONE_MATCH'))
-        if if_none_match:
-            # http://tools.ietf.org/html/rfc7232#section-3.2
-            # "A recipient MUST use the weak comparison function when comparing
-            # entity-tags for If-None-Match"
-            etag, _ = unquote_etag(etag)
-            unmodified = if_none_match.contains_weak(etag)
+        etag, _ = unquote_etag(etag)
+        if if_range is not None and if_range.etag is not None:
+            unmodified = parse_etags(if_range.etag).contains(etag)
+        else:
+            if_none_match = parse_etags(environ.get('HTTP_IF_NONE_MATCH'))
+            if if_none_match:
+                # http://tools.ietf.org/html/rfc7232#section-3.2
+                # "A recipient MUST use the weak comparison function when comparing
+                # entity-tags for If-None-Match"
+                unmodified = if_none_match.contains_weak(etag)
 
     return not unmodified
 
@@ -855,7 +887,7 @@
     .. versionadded:: 0.5
 
     :param header: the header to test.
-    :return: `True` if it's an entity header, `False` otherwise.
+    :return: `True` if it's an HTTP/1.1 "Hop-by-Hop" header, `False` otherwise.
     """
     return header.lower() in _hop_by_hop_headers
 
--- a/MoinMoin/support/werkzeug/local.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/local.py	Tue Apr 11 22:42:23 2017 +0200
@@ -172,8 +172,8 @@
 
     """Local objects cannot manage themselves. For that you need a local
     manager.  You can pass a local manager multiple locals or add them later
-    by appending them to `manager.locals`.  Everytime the manager cleans up
-    it, will clean up all the data left in the locals for this context.
+    by appending them to `manager.locals`.  Every time the manager cleans up,
+    it will clean up all the data left in the locals for this context.
 
     The `ident_func` parameter can be added to override the default ident
     function for the wrapped locals.
@@ -285,13 +285,17 @@
         session = LocalProxy(lambda: get_current_request().session)
 
     .. versionchanged:: 0.6.1
-       The class can be instanciated with a callable as well now.
+       The class can be instantiated with a callable as well now.
     """
-    __slots__ = ('__local', '__dict__', '__name__')
+    __slots__ = ('__local', '__dict__', '__name__', '__wrapped__')
 
     def __init__(self, local, name=None):
         object.__setattr__(self, '_LocalProxy__local', local)
         object.__setattr__(self, '__name__', name)
+        if callable(local) and not hasattr(local, '__release_local__'):
+            # "local" is a callable that is not an instance of Local or
+            # LocalManager: mark it as a wrapped function.
+            object.__setattr__(self, '__wrapped__', local)
 
     def _get_current_object(self):
         """Return the current object.  This is useful if you want the real
--- a/MoinMoin/support/werkzeug/routing.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/routing.py	Tue Apr 11 22:42:23 2017 +0200
@@ -89,7 +89,7 @@
     If matching succeeded but the URL rule was incompatible to the given
     method (for example there were only rules for `GET` and `HEAD` and
     routing system tried to match a `POST` request) a `MethodNotAllowed`
-    method is raised.
+    exception is raised.
 
 
     :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
@@ -112,6 +112,7 @@
     text_type, string_types, native_string_result, \
     implements_to_string, wsgi_decoding_dance
 from werkzeug.datastructures import ImmutableDict, MultiDict
+from werkzeug.utils import cached_property
 
 
 _rule_re = re.compile(r'''
@@ -249,6 +250,7 @@
         self.matched_values = matched_values
 
 
+@implements_to_string
 class BuildError(RoutingException, LookupError):
 
     """Raised if the build system cannot find a URL for an endpoint with the
@@ -260,10 +262,14 @@
         self.endpoint = endpoint
         self.values = values
         self.method = method
-        self.suggested = self.closest_rule(adapter)
+        self.adapter = adapter
+
+    @cached_property
+    def suggested(self):
+        return self.closest_rule(self.adapter)
 
     def closest_rule(self, adapter):
-        def score_rule(rule):
+        def _score_rule(rule):
             return sum([
                 0.98 * difflib.SequenceMatcher(
                     None, rule.endpoint, self.endpoint
@@ -273,22 +279,20 @@
             ])
 
         if adapter and adapter.map._rules:
-            return max(adapter.map._rules, key=score_rule)
-        else:
-            return None
+            return max(adapter.map._rules, key=_score_rule)
 
     def __str__(self):
         message = []
-        message.append("Could not build url for endpoint %r" % self.endpoint)
+        message.append('Could not build url for endpoint %r' % self.endpoint)
         if self.method:
-            message.append(" (%r)" % self.method)
+            message.append(' (%r)' % self.method)
         if self.values:
-            message.append(" with values %r" % sorted(self.values.keys()))
-        message.append(".")
+            message.append(' with values %r' % sorted(self.values.keys()))
+        message.append('.')
         if self.suggested:
             if self.endpoint == self.suggested.endpoint:
                 if self.method and self.method not in self.suggested.methods:
-                    message.append(" Did you mean to use methods %r?" % sorted(
+                    message.append(' Did you mean to use methods %r?' % sorted(
                         self.suggested.methods
                     ))
                 missing_values = self.suggested.arguments.union(
@@ -296,14 +300,14 @@
                 ) - set(self.values.keys())
                 if missing_values:
                     message.append(
-                        " Did you forget to specify values %r?" %
+                        ' Did you forget to specify values %r?' %
                         sorted(missing_values)
                     )
             else:
                 message.append(
-                    " Did you mean %r instead?" % self.suggested.endpoint
+                    ' Did you mean %r instead?' % self.suggested.endpoint
                 )
-        return "".join(message)
+        return u''.join(message)
 
 
 class ValidationError(ValueError):
@@ -610,6 +614,8 @@
         if methods is None:
             self.methods = None
         else:
+            if isinstance(methods, str):
+                raise TypeError('param `methods` should be `Iterable[str]`, not `str`')
             self.methods = set([x.upper() for x in methods])
             if 'HEAD' not in self.methods and 'GET' in self.methods:
                 self.methods.add('HEAD')
@@ -741,9 +747,9 @@
         )
         self._regex = re.compile(regex, re.UNICODE)
 
-    def match(self, path):
+    def match(self, path, method=None):
         """Check if the rule matches a given path. Path is a string in the
-        form ``"subdomain|/path(method)"`` and is assembled by the map.  If
+        form ``"subdomain|/path"`` and is assembled by the map.  If
         the map is doing host matching the subdomain part will be the host
         instead.
 
@@ -761,7 +767,9 @@
                 # tells the map to redirect to the same url but with a
                 # trailing slash
                 if self.strict_slashes and not self.is_leaf and \
-                   not groups.pop('__suffix__'):
+                        not groups.pop('__suffix__') and \
+                        (method is None or self.methods is None or
+                         method in self.methods):
                     raise RequestSlash()
                 # if we are not in strict slashes mode we have to remove
                 # a __suffix__
@@ -881,6 +889,8 @@
         return self.__class__ is other.__class__ and \
             self._trace == other._trace
 
+    __hash__ = None
+
     def __ne__(self, other):
         return not self.__eq__(other)
 
@@ -1517,7 +1527,7 @@
         have_match_for = set()
         for rule in self.map._rules:
             try:
-                rv = rule.match(path)
+                rv = rule.match(path, method)
             except RequestSlash:
                 raise RequestRedirect(self.make_redirect_url(
                     url_quote(path_info, self.map.charset,
--- a/MoinMoin/support/werkzeug/script.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/script.py	Tue Apr 11 22:42:23 2017 +0200
@@ -75,6 +75,7 @@
 import sys
 import inspect
 import getopt
+from warnings import warn
 from os.path import basename
 from werkzeug._compat import iteritems
 
@@ -95,6 +96,11 @@
 }
 
 
+def _deprecated():
+    warn(DeprecationWarning('werkzeug.script is deprecated and '
+                            'will be removed soon'), stacklevel=2)
+
+
 def run(namespace=None, action_prefix='action_', args=None):
     """Run the script.  Participating actions are looked up in the caller's
     namespace if no namespace is given, otherwise in the dict provided.
@@ -109,6 +115,7 @@
     :param args: the arguments for the function.  If not specified
                  :data:`sys.argv` without the first argument is used.
     """
+    _deprecated()
     if namespace is None:
         namespace = sys._getframe(1).f_locals
     actions = find_actions(namespace, action_prefix)
@@ -179,12 +186,14 @@
 
 def fail(message, code=-1):
     """Fail with an error."""
+    _deprecated()
     print('Error: %s' % message, file=sys.stderr)
     sys.exit(code)
 
 
 def find_actions(namespace, action_prefix):
     """Find all the actions in the namespace."""
+    _deprecated()
     actions = {}
     for key, value in iteritems(namespace):
         if key.startswith(action_prefix):
@@ -194,6 +203,7 @@
 
 def print_usage(actions):
     """Print the usage information.  (Help screen)"""
+    _deprecated()
     actions = sorted(iteritems(actions))
     print('usage: %s <action> [<options>]' % basename(sys.argv[0]))
     print('       %s --help' % basename(sys.argv[0]))
@@ -220,6 +230,7 @@
 
 def analyse_action(func):
     """Analyse a function."""
+    _deprecated()
     description = inspect.getdoc(func) or 'undocumented action'
     arguments = []
     args, varargs, kwargs, defaults = inspect.getargspec(func)
@@ -255,6 +266,7 @@
                    not specified a generic banner is used instead.
     :param use_ipython: if set to `True` ipython is used if available.
     """
+    _deprecated()
     if banner is None:
         banner = 'Interactive Werkzeug Shell'
     if init_func is None:
@@ -267,14 +279,14 @@
             try:
                 try:
                     from IPython.frontend.terminal.embed import InteractiveShellEmbed
-                    sh = InteractiveShellEmbed(banner1=banner)
+                    sh = InteractiveShellEmbed.instance(banner1=banner)
                 except ImportError:
                     from IPython.Shell import IPShellEmbed
                     sh = IPShellEmbed(banner=banner)
             except ImportError:
                 pass
             else:
-                sh(global_ns={}, local_ns=namespace)
+                sh(local_ns=namespace)
                 return
         from code import interact
         interact(banner, local=namespace)
@@ -304,6 +316,8 @@
     :param extra_files: optional list of extra files to track for reloading.
     :param ssl_context: optional SSL context for running server in HTTPS mode.
     """
+    _deprecated()
+
     def action(hostname=('h', hostname), port=('p', port),
                reloader=use_reloader, debugger=use_debugger,
                evalex=use_evalex, threaded=threaded, processes=processes):
--- a/MoinMoin/support/werkzeug/security.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/security.py	Tue Apr 11 22:42:23 2017 +0200
@@ -23,7 +23,7 @@
 
 
 SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
-DEFAULT_PBKDF2_ITERATIONS = 1000
+DEFAULT_PBKDF2_ITERATIONS = 50000
 
 
 _pack_int = Struct('>I').pack
@@ -59,7 +59,7 @@
                    the digest size will be used.
     :param hashfunc: the hash function to use.  This can either be the
                      string name of a known hash function, or a function
-                     from the hashlib module.  Defaults to sha1.
+                     from the hashlib module.  Defaults to sha256.
     """
     rv = pbkdf2_bin(data, salt, iterations, keylen, hashfunc)
     return to_native(codecs.encode(rv, 'hex_codec'))
@@ -72,7 +72,7 @@
                keylen=None, hashfunc=None):
     """Returns a binary digest for the PBKDF2 hash algorithm of `data`
     with the given `salt`. It iterates `iterations` times and produces a
-    key of `keylen` bytes. By default, SHA-1 is used as hash function;
+    key of `keylen` bytes. By default, SHA-256 is used as hash function;
     a different hashlib `hashfunc` can be provided.
 
     .. versionadded:: 0.9
@@ -84,12 +84,12 @@
                    the digest size will be used.
     :param hashfunc: the hash function to use.  This can either be the
                      string name of a known hash function or a function
-                     from the hashlib module.  Defaults to sha1.
+                     from the hashlib module.  Defaults to sha256.
     """
     if isinstance(hashfunc, string_types):
         hashfunc = _hash_funcs[hashfunc]
     elif not hashfunc:
-        hashfunc = hashlib.sha1
+        hashfunc = hashlib.sha256
     data = to_bytes(data)
     salt = to_bytes(salt)
 
@@ -201,9 +201,9 @@
     return rv, actual_method
 
 
-def generate_password_hash(password, method='pbkdf2:sha1', salt_length=8):
-    """Hash a password with the given method and salt with with a string of
-    the given length.  The format of the string returned includes the method
+def generate_password_hash(password, method='pbkdf2:sha256', salt_length=8):
+    """Hash a password with the given method and salt with a string of
+    the given length. The format of the string returned includes the method
     that was used so that :func:`check_password_hash` can check the hash.
 
     The format for the hashed string looks like this::
@@ -211,14 +211,14 @@
         method$salt$hash
 
     This method can **not** generate unsalted passwords but it is possible
-    to set the method to plain to enforce plaintext passwords.  If a salt
-    is used, hmac is used internally to salt the password.
+    to set param method='plain' in order to enforce plaintext passwords.
+    If a salt is used, hmac is used internally to salt the password.
 
     If PBKDF2 is wanted it can be enabled by setting the method to
     ``pbkdf2:method:iterations`` where iterations is optional::
 
-        pbkdf2:sha1:2000$salt$hash
-        pbkdf2:sha1$salt$hash
+        pbkdf2:sha256:80000$salt$hash
+        pbkdf2:sha256$salt$hash
 
     :param password: the password to hash.
     :param method: the hash method to use (one that hashlib supports). Can
--- a/MoinMoin/support/werkzeug/serving.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/serving.py	Tue Apr 11 22:42:23 2017 +0200
@@ -42,6 +42,15 @@
 import sys
 import signal
 
+
+can_fork = hasattr(os, "fork")
+
+
+try:
+    import termcolor
+except ImportError:
+    termcolor = None
+
 try:
     import ssl
 except ImportError:
@@ -62,22 +71,30 @@
 
 
 try:
-    from SocketServer import ThreadingMixIn, ForkingMixIn
+    import SocketServer as socketserver
     from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
 except ImportError:
-    from socketserver import ThreadingMixIn, ForkingMixIn
+    import socketserver
     from http.server import HTTPServer, BaseHTTPRequestHandler
 
+ThreadingMixIn = socketserver.ThreadingMixIn
+
+if can_fork:
+    ForkingMixIn = socketserver.ForkingMixIn
+else:
+    class ForkingMixIn(object):
+        pass
+
 # important: do not use relative imports here or python -m will break
 import werkzeug
 from werkzeug._internal import _log
-from werkzeug._compat import PY2, reraise, wsgi_encoding_dance
+from werkzeug._compat import PY2, WIN, reraise, wsgi_encoding_dance
 from werkzeug.urls import url_parse, url_unquote
 from werkzeug.exceptions import InternalServerError
 
 
 LISTEN_QUEUE = 128
-can_open_by_fd = hasattr(socket, 'fromfd')
+can_open_by_fd = not WIN and hasattr(socket, 'fromfd')
 
 
 class WSGIRequestHandler(BaseHTTPRequestHandler, object):
@@ -111,8 +128,6 @@
             'SCRIPT_NAME':          '',
             'PATH_INFO':            wsgi_encoding_dance(path_info),
             'QUERY_STRING':         wsgi_encoding_dance(request_url.query),
-            'CONTENT_TYPE':         self.headers.get('Content-Type', ''),
-            'CONTENT_LENGTH':       self.headers.get('Content-Length', ''),
             'REMOTE_ADDR':          self.address_string(),
             'REMOTE_PORT':          self.port_integer(),
             'SERVER_NAME':          self.server.server_address[0],
@@ -121,9 +136,10 @@
         }
 
         for key, value in self.headers.items():
-            key = 'HTTP_' + key.upper().replace('-', '_')
-            if key not in ('HTTP_CONTENT_TYPE', 'HTTP_CONTENT_LENGTH'):
-                environ[key] = value
+            key = key.upper().replace('-', '_')
+            if key not in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
+                key = 'HTTP_' + key
+            environ[key] = value
 
         if request_url.scheme and request_url.netloc:
             environ['HTTP_HOST'] = request_url.netloc
@@ -269,7 +285,28 @@
         return self.client_address[1]
 
     def log_request(self, code='-', size='-'):
-        self.log('info', '"%s" %s %s', self.requestline, code, size)
+        msg = self.requestline
+        code = str(code)
+
+        if termcolor:
+            color = termcolor.colored
+
+            if code[0] == '1':    # 1xx - Informational
+                msg = color(msg, attrs=['bold'])
+            if code[0] == '2':    # 2xx - Success
+                msg = color(msg, color='white')
+            elif code == '304':   # 304 - Resource Not Modified
+                msg = color(msg, color='cyan')
+            elif code[0] == '3':  # 3xx - Redirection
+                msg = color(msg, color='green')
+            elif code == '404':   # 404 - Resource Not Found
+                msg = color(msg, color='yellow')
+            elif code[0] == '4':  # 4xx - Client Error
+                msg = color(msg, color='red', attrs=['bold'])
+            else:                 # 5xx, or any other response
+                msg = color(msg, color='magenta', attrs=['bold'])
+
+        self.log('info', '"%s" %s %s', msg, code, size)
 
     def log_error(self, *args):
         self.log('error', *args)
@@ -309,9 +346,9 @@
     issuer.O = 'Self-Signed'
 
     pkey = crypto.PKey()
-    pkey.generate_key(crypto.TYPE_RSA, 1024)
+    pkey.generate_key(crypto.TYPE_RSA, 2048)
     cert.set_pubkey(pkey)
-    cert.sign(pkey, 'md5')
+    cert.sign(pkey, 'sha256')
 
     return cert, pkey
 
@@ -466,7 +503,7 @@
         self.passthrough_errors = passthrough_errors
         self.shutdown_signal = False
         self.host = host
-        self.port = port
+        self.port = self.socket.getsockname()[1]
 
         # Patch in the original socket.
         if fd is not None:
@@ -516,6 +553,7 @@
 
     """A WSGI server that does threading."""
     multithread = True
+    daemon_threads = True
 
 
 class ForkingWSGIServer(ForkingMixIn, BaseWSGIServer):
@@ -525,6 +563,8 @@
 
     def __init__(self, host, port, app, processes=40, handler=None,
                  passthrough_errors=False, ssl_context=None, fd=None):
+        if not can_fork:
+            raise ValueError('Your platform does not support forking.')
         BaseWSGIServer.__init__(self, host, port, app, handler,
                                 passthrough_errors, ssl_context, fd)
         self.max_children = processes
@@ -687,7 +727,9 @@
             else:
                 s.close()
 
-        from ._reloader import run_with_reloader
+        # Do not use relative imports, otherwise "python -m werkzeug.serving"
+        # breaks.
+        from werkzeug._reloader import run_with_reloader
         run_with_reloader(inner, extra_files, reloader_interval,
                           reloader_type)
     else:
@@ -697,7 +739,7 @@
 def run_with_reloader(*args, **kwargs):
     # People keep using undocumented APIs.  Do not use this function
     # please, we do not guarantee that it continues working.
-    from ._reloader import run_with_reloader
+    from werkzeug._reloader import run_with_reloader
     return run_with_reloader(*args, **kwargs)
 
 
--- a/MoinMoin/support/werkzeug/test.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/test.py	Tue Apr 11 22:42:23 2017 +0200
@@ -192,7 +192,8 @@
 
 
 def _iter_data(data):
-    """Iterates over a dict or multidict yielding all keys and values.
+    """Iterates over a `dict` or :class:`MultiDict` yielding all keys and
+    values.
     This is used to iterate over the data passed to the
     :class:`EnvironBuilder`.
     """
@@ -226,16 +227,21 @@
 
     `data` can be any of these values:
 
-    -   a `str`: If it's a string it is converted into a :attr:`input_stream`,
-        the :attr:`content_length` is set and you have to provide a
-        :attr:`content_type`.
-    -   a `dict`: If it's a dict the keys have to be strings and the values
-        any of the following objects:
+    -   a `str` or `bytes` object: The object is converted into an
+        :attr:`input_stream`, the :attr:`content_length` is set and you have to
+        provide a :attr:`content_type`.
+    -   a `dict` or :class:`MultiDict`: The keys have to be strings. The values
+        have to be either any of the following objects, or a list of any of the
+        following objects:
 
-        -   a :class:`file`-like object.  These are converted into
+        -   a :class:`file`-like object:  These are converted into
             :class:`FileStorage` objects automatically.
-        -   a tuple.  The :meth:`~FileMultiDict.add_file` method is called
-            with the tuple items as positional arguments.
+        -   a `tuple`:  The :meth:`~FileMultiDict.add_file` method is called
+            with the key and the unpacked `tuple` items as positional
+            arguments.
+        -   a `str`:  The string is set as form data for the associated key.
+    -   a file-like object: The object content is loaded in memory and then
+        handled like a regular `str` or a `bytes`.
 
     .. versionadded:: 0.6
        `path` and `base_url` can now be unicode strings that are encoded using
@@ -266,7 +272,8 @@
     :param multiprocess: controls `wsgi.multiprocess`.  Defaults to `False`.
     :param run_once: controls `wsgi.run_once`.  Defaults to `False`.
     :param headers: an optional list or :class:`Headers` object of headers.
-    :param data: a string or dict of form data.  See explanation above.
+    :param data: a string or dict of form data or a file-object.
+                 See explanation above.
     :param environ_base: an optional dict of environment defaults.
     :param environ_overrides: an optional dict of environment overrides.
     :param charset: the charset used to encode unicode data.
@@ -325,6 +332,8 @@
         if data:
             if input_stream is not None:
                 raise TypeError('can\'t provide input stream and data')
+            if hasattr(data, 'read'):
+                data = data.read()
             if isinstance(data, text_type):
                 data = data.encode(self.charset)
             if isinstance(data, bytes):
@@ -433,7 +442,7 @@
         def setter(self, value):
             self._input_stream = None
             setattr(self, key, value)
-        return property(getter, setter, doc)
+        return property(getter, setter, doc=doc)
 
     form = form_property('form', MultiDict, doc='''
         A :class:`MultiDict` of form values.''')
@@ -679,6 +688,10 @@
 
         cur_server_name = netloc.split(':', 1)[0].split('.')
         real_server_name = get_host(environ).rsplit(':', 1)[0].split('.')
+        if cur_server_name == ['']:
+            # this is a local redirect having autocorrect_location_header=False
+            cur_server_name = real_server_name
+            base_url = EnvironBuilder(environ).base_url
 
         if self.allow_subdomain_redirects:
             allowed = cur_server_name[-len(real_server_name):] == real_server_name
--- a/MoinMoin/support/werkzeug/useragents.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/useragents.py	Tue Apr 11 22:42:23 2017 +0200
@@ -25,8 +25,12 @@
         (r'darwin|mac|os\s*x', 'macos'),
         ('win', 'windows'),
         (r'android', 'android'),
+        ('netbsd', 'netbsd'),
+        ('openbsd', 'openbsd'),
+        ('freebsd', 'freebsd'),
+        ('dragonfly', 'dragonflybsd'),
+        ('(sun|i86)os', 'solaris'),
         (r'x11|lin(\b|ux)?', 'linux'),
-        ('(sun|i86)os', 'solaris'),
         (r'nintendo\s+wii', 'wii'),
         ('irix', 'irix'),
         ('hp-?ux', 'hpux'),
@@ -45,6 +49,7 @@
         (r'aol|america\s+online\s+browser', 'aol'),
         ('opera', 'opera'),
         ('chrome', 'chrome'),
+        ('seamonkey', 'seamonkey'),
         ('firefox|firebird|phoenix|iceweasel', 'firefox'),
         ('galeon', 'galeon'),
         ('safari|version', 'safari'),
@@ -56,10 +61,12 @@
         (r'msie|microsoft\s+internet\s+explorer|trident/.+? rv:', 'msie'),
         ('lynx', 'lynx'),
         ('links', 'links'),
-        ('seamonkey|mozilla', 'seamonkey')
+        ('Baiduspider', 'baidu'),
+        ('bingbot', 'bing'),
+        ('mozilla', 'mozilla')
     )
 
-    _browser_version_re = r'(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?(?i)'
+    _browser_version_re = r'(?:%s)[/\sa-z(]*(\d+[.\da-z]+)?'
     _language_re = re.compile(
         r'(?:;\s*|\s+)(\b\w{2}\b(?:-\b\w{2}\b)?)\s*;|'
         r'(?:\(|\[|;)\s*(\b\w{2}\b(?:-\b\w{2}\b)?)\s*(?:\]|\)|;)'
@@ -67,7 +74,7 @@
 
     def __init__(self):
         self.platforms = [(b, re.compile(a, re.I)) for a, b in self.platforms]
-        self.browsers = [(b, re.compile(self._browser_version_re % a))
+        self.browsers = [(b, re.compile(self._browser_version_re % a, re.I))
                          for a, b in self.browsers]
 
     def __call__(self, user_agent):
--- a/MoinMoin/support/werkzeug/wrappers.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/wrappers.py	Tue Apr 11 22:42:23 2017 +0200
@@ -36,7 +36,7 @@
 from werkzeug.utils import cached_property, environ_property, \
     header_property, get_content_type
 from werkzeug.wsgi import get_current_url, get_host, \
-    ClosingIterator, get_input_stream, get_content_length
+    ClosingIterator, get_input_stream, get_content_length, _RangeWrapper
 from werkzeug.datastructures import MultiDict, CombinedMultiDict, Headers, \
     EnvironHeaders, ImmutableMultiDict, ImmutableTypeConversionDict, \
     ImmutableList, MIMEAccept, CharsetAccept, LanguageAccept, \
@@ -85,6 +85,16 @@
             yield item
 
 
+def _clean_accept_ranges(accept_ranges):
+    if accept_ranges is True:
+        return "bytes"
+    elif accept_ranges is False:
+        return "none"
+    elif isinstance(accept_ranges, text_type):
+        return to_native(accept_ranges)
+    raise ValueError("Invalid accept_ranges value")
+
+
 class BaseRequest(object):
 
     """Very basic request object.  This does not implement advanced stuff like
@@ -399,11 +409,16 @@
 
     @cached_property
     def stream(self):
-        """The stream to read incoming data from.  Unlike :attr:`input_stream`
-        this stream is properly guarded that you can't accidentally read past
-        the length of the input.  Werkzeug will internally always refer to
-        this stream to read data which makes it possible to wrap this
-        object with a stream that does filtering.
+        """
+        If the incoming form data was not encoded with a known mimetype
+        the data is stored unmodified in this stream for consumption.  Most
+        of the time it is a better idea to use :attr:`data` which will give
+        you that data as a string.  The stream only returns the data once.
+
+        Unlike :attr:`input_stream` this stream is properly guarded that you
+        can't accidentally read past the length of the input.  Werkzeug will
+        internally always refer to this stream to read data which makes it
+        possible to wrap this object with a stream that does filtering.
 
         .. versionchanged:: 0.9
            This stream is now always available but might be consumed by the
@@ -422,7 +437,10 @@
 
     @cached_property
     def args(self):
-        """The parsed URL parameters.  By default an
+        """The parsed URL parameters (the part in the URL after the question
+        mark).
+
+        By default an
         :class:`~werkzeug.datastructures.ImmutableMultiDict`
         is returned from this function.  This can be changed by setting
         :attr:`parameter_storage_class` to a different type.  This might
@@ -434,6 +452,11 @@
 
     @cached_property
     def data(self):
+        """
+        Contains the incoming request data as string in case it came with
+        a mimetype Werkzeug does not handle.
+        """
+
         if self.disable_data_descriptor:
             raise AttributeError('data descriptor is disabled')
         # XXX: this should eventually be deprecated.
@@ -488,13 +511,22 @@
         is returned from this function.  This can be changed by setting
         :attr:`parameter_storage_class` to a different type.  This might
         be necessary if the order of the form data is important.
+
+        Please keep in mind that file uploads will not end up here, but instead
+        in the :attr:`files` attribute.
+
+        .. versionchanged:: 0.9
+
+            Previous to Werkzeug 0.9 this would only contain form data for POST
+            and PUT requests.
         """
         self._load_form_data()
         return self.form
 
     @cached_property
     def values(self):
-        """Combined multi dict for :attr:`args` and :attr:`form`."""
+        """A :class:`werkzeug.datastructures.CombinedMultiDict` that combines
+        :attr:`args` and :attr:`form`."""
         args = []
         for d in self.args, self.form:
             if not isinstance(d, MultiDict):
@@ -509,6 +541,11 @@
         ``<input type="file" name="">``.  Each value in :attr:`files` is a
         Werkzeug :class:`~werkzeug.datastructures.FileStorage` object.
 
+        It basically behaves like a standard file object you know from Python,
+        with the difference that it also has a
+        :meth:`~werkzeug.datastructures.FileStorage.save` function that can
+        store the file on the filesystem.
+
         Note that :attr:`files` will only contain data if the request method was
         POST, PUT or PATCH and the ``<form>`` that posted to the request had
         ``enctype="multipart/form-data"``.  It will be empty otherwise.
@@ -522,7 +559,8 @@
 
     @cached_property
     def cookies(self):
-        """Read only access to the retrieved cookie values as dictionary."""
+        """A :class:`dict` with the contents of all cookies transmitted with
+        the request."""
         return parse_cookie(self.environ, self.charset,
                             self.encoding_errors,
                             cls=self.dict_storage_class)
@@ -602,7 +640,7 @@
     method = environ_property(
         'REQUEST_METHOD', 'GET', read_only=True,
         load_func=lambda x: x.upper(),
-        doc="The transmission method. (For example ``'GET'`` or ``'POST'``).")
+        doc="The request method. (For example ``'GET'`` or ``'POST'``).")
 
     @cached_property
     def access_route(self):
@@ -690,7 +728,7 @@
     subclasses of response objects and you want to post process them with a
     known interface.
 
-    Per default the request object will assume all the text data is `utf-8`
+    Per default the response object will assume all the text data is `utf-8`
     encoded.  Please refer to `the unicode chapter <unicode.txt>`_ for more
     details about customizing the behavior.
 
@@ -713,8 +751,8 @@
     :param status: a string with a status or an integer with the status code.
     :param headers: a list of headers or a
                     :class:`~werkzeug.datastructures.Headers` object.
-    :param mimetype: the mimetype for the request.  See notice above.
-    :param content_type: the content type for the request.  See notice above.
+    :param mimetype: the mimetype for the response.  See notice above.
+    :param content_type: the content type for the response.  See notice above.
     :param direct_passthrough: if set to `True` :meth:`iter_encoded` is not
                                called before iteration which makes it
                                possible to pass special iterators through
@@ -993,7 +1031,7 @@
         return _iter_encoded(self.response, self.charset)
 
     def set_cookie(self, key, value='', max_age=None, expires=None,
-                   path='/', domain=None, secure=None, httponly=False):
+                   path='/', domain=None, secure=False, httponly=False):
         """Sets a cookie. The parameters are the same as in the cookie `Morsel`
         object in the Python standard library but it accepts unicode data, too.
 
@@ -1003,17 +1041,27 @@
                         the cookie should last only as long as the client's
                         browser session.
         :param expires: should be a `datetime` object or UNIX timestamp.
+        :param path: limits the cookie to a given path, per default it will
+                     span the whole domain.
         :param domain: if you want to set a cross-domain cookie.  For example,
                        ``domain=".example.com"`` will set a cookie that is
                        readable by the domain ``www.example.com``,
                        ``foo.example.com`` etc.  Otherwise, a cookie will only
                        be readable by the domain that set it.
-        :param path: limits the cookie to a given path, per default it will
-                     span the whole domain.
+        :param secure: If `True`, the cookie will only be available via HTTPS
+        :param httponly: disallow JavaScript to access the cookie.  This is an
+                         extension to the cookie standard and probably not
+                         supported by all browsers.
         """
-        self.headers.add('Set-Cookie', dump_cookie(key, value, max_age,
-                                                   expires, path, domain, secure, httponly,
-                                                   self.charset))
+        self.headers.add('Set-Cookie', dump_cookie(key,
+                                                   value=value,
+                                                   max_age=max_age,
+                                                   expires=expires,
+                                                   path=path,
+                                                   domain=domain,
+                                                   secure=secure,
+                                                   httponly=httponly,
+                                                   charset=self.charset))
 
     def delete_cookie(self, key, path='/', domain=None):
         """Delete a cookie.  Fails silently if key doesn't exist.
@@ -1406,7 +1454,60 @@
                                           on_update,
                                           ResponseCacheControl)
 
-    def make_conditional(self, request_or_environ):
+    def _wrap_response(self, start, length):
+        """Wrap existing Response in case of Range Request context."""
+        if self.status_code == 206:
+            self.response = _RangeWrapper(self.response, start, length)
+
+    def _is_range_request_processable(self, environ):
+        """Return ``True`` if `Range` header is present and if underlying
+        resource is considered unchanged when compared with `If-Range` header.
+        """
+        return (
+            'HTTP_IF_RANGE' not in environ
+            or not is_resource_modified(
+                environ, self.headers.get('etag'), None,
+                self.headers.get('last-modified'), ignore_if_range=False
+            )
+        ) and 'HTTP_RANGE' in environ
+
+    def _process_range_request(self, environ, complete_length=None, accept_ranges=None):
+        """Handle Range Request related headers (RFC7233).  If `Accept-Ranges`
+        header is valid, and Range Request is processable, we set the headers
+        as described by the RFC, and wrap the underlying response in a
+        RangeWrapper.
+
+        Returns ``True`` if Range Request can be fulfilled, ``False`` otherwise.
+
+        :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
+                 if `Range` header could not be parsed or satisfied.
+        """
+        from werkzeug.exceptions import RequestedRangeNotSatisfiable
+        if accept_ranges is None:
+            return False
+        self.headers['Accept-Ranges'] = accept_ranges
+        if not self._is_range_request_processable(environ) or complete_length is None:
+            return False
+        parsed_range = parse_range_header(environ.get('HTTP_RANGE'))
+        if parsed_range is None:
+            raise RequestedRangeNotSatisfiable(complete_length)
+        range_tuple = parsed_range.range_for_length(complete_length)
+        content_range_header = parsed_range.to_content_range_header(complete_length)
+        if range_tuple is None or content_range_header is None:
+            raise RequestedRangeNotSatisfiable(complete_length)
+        content_length = range_tuple[1] - range_tuple[0]
+        # Be sure not to send 206 response
+        # if requested range is the full content.
+        if content_length != complete_length:
+            self.headers['Content-Length'] = content_length
+            self.content_range = content_range_header
+            self.status_code = 206
+            self._wrap_response(range_tuple[0], content_length)
+            return True
+        return False
+
+    def make_conditional(self, request_or_environ, accept_ranges=False,
+                         complete_length=None):
         """Make the response conditional to the request.  This method works
         best if an etag was defined for the response already.  The `add_etag`
         method can be used to do that.  If called without etag just the date
@@ -1415,6 +1516,11 @@
         This does nothing if the request method in the request or environ is
         anything but GET or HEAD.
 
+        For optimal performance when handling range requests, it's recommended
+        that your response data object implements `seekable`, `seek` and `tell`
+        methods as described by :py:class:`io.IOBase`.  Objects returned by
+        :meth:`~werkzeug.wsgi.wrap_file` automatically implement those methods.
+
         It does not remove the body of the response because that's something
         the :meth:`__call__` function does for us automatically.
 
@@ -1424,6 +1530,19 @@
         :param request_or_environ: a request object or WSGI environment to be
                                    used to make the response conditional
                                    against.
+        :param accept_ranges: This parameter dictates the value of
+                              `Accept-Ranges` header. If ``False`` (default),
+                              the header is not set. If ``True``, it will be set
+                              to ``"bytes"``. If ``None``, it will be set to
+                              ``"none"``. If it's a string, it will use this
+                              value.
+        :param complete_length: Will be used only in valid Range Requests.
+                                It will set `Content-Range` complete length
+                                value and compute `Content-Length` real value.
+                                This parameter is mandatory for successful
+                                Range Requests completion.
+        :raises: :class:`~werkzeug.exceptions.RequestedRangeNotSatisfiable`
+                 if `Range` header could not be parsed or satisfied.
         """
         environ = _get_environ(request_or_environ)
         if environ['REQUEST_METHOD'] in ('GET', 'HEAD'):
@@ -1433,13 +1552,16 @@
             # wsgiref.
             if 'date' not in self.headers:
                 self.headers['Date'] = http_date()
+            accept_ranges = _clean_accept_ranges(accept_ranges)
+            is206 = self._process_range_request(environ, complete_length, accept_ranges)
+            if not is206 and not is_resource_modified(
+                environ, self.headers.get('etag'), None, self.headers.get('last-modified')
+            ):
+                self.status_code = 304
             if self.automatically_set_content_length and 'content-length' not in self.headers:
                 length = self.calculate_content_length()
                 if length is not None:
                     self.headers['Content-Length'] = length
-            if not is_resource_modified(environ, self.headers.get('etag'), None,
-                                        self.headers.get('last-modified')):
-                self.status_code = 304
         return self
 
     def add_etag(self, overwrite=False, weak=False):
--- a/MoinMoin/support/werkzeug/wsgi.py	Tue Nov 01 17:56:32 2016 +0100
+++ b/MoinMoin/support/werkzeug/wsgi.py	Tue Apr 11 22:42:23 2017 +0200
@@ -20,7 +20,7 @@
 
 from werkzeug._compat import iteritems, text_type, string_types, \
     implements_iterator, make_literal_wrapper, to_unicode, to_bytes, \
-    wsgi_get_bytes, try_coerce_native, PY2
+    wsgi_get_bytes, try_coerce_native, PY2, BytesIO
 from werkzeug._internal import _empty_stream, _encode_idna
 from werkzeug.http import is_resource_modified, http_date
 from werkzeug.urls import uri_to_iri, url_quote, url_parse, url_join
@@ -43,7 +43,7 @@
 def get_current_url(environ, root_only=False, strip_querystring=False,
                     host_only=False, trusted_hosts=None):
     """A handy helper function that recreates the full URL as IRI for the
-    current request or parts of it.  Here an example:
+    current request or parts of it.  Here's an example:
 
     >>> from werkzeug.test import create_environ
     >>> env = create_environ("/?param=foo", "http://localhost/script")
@@ -545,10 +545,11 @@
             if filesystem_bound:
                 return basename, self._opener(
                     provider.get_resource_filename(manager, path))
+            s = provider.get_resource_string(manager, path)
             return basename, lambda: (
-                provider.get_resource_stream(manager, path),
+                BytesIO(s),
                 loadtime,
-                0
+                len(s)
             )
         return loader
 
@@ -754,6 +755,22 @@
         if hasattr(self.file, 'close'):
             self.file.close()
 
+    def seekable(self):
+        if hasattr(self.file, 'seekable'):
+            return self.file.seekable()
+        if hasattr(self.file, 'seek'):
+            return True
+        return False
+
+    def seek(self, *args):
+        if hasattr(self.file, 'seek'):
+            self.file.seek(*args)
+
+    def tell(self):
+        if hasattr(self.file, 'tell'):
+            return self.file.tell()
+        return None
+
     def __iter__(self):
         return self
 
@@ -764,6 +781,87 @@
         raise StopIteration()
 
 
+@implements_iterator
+class _RangeWrapper(object):
+    # private for now, but should we make it public in the future ?
+
+    """This class can be used to convert an iterable object into
+    an iterable that will only yield a piece of the underlying content.
+    It yields blocks until the underlying stream range is fully read.
+    The yielded blocks will have a size that can't exceed the original
+    iterator defined block size, but that can be smaller.
+
+    If you're using this object together with a :class:`BaseResponse` you have
+    to use the `direct_passthrough` mode.
+
+    :param iterable: an iterable object with a :meth:`__next__` method.
+    :param start_byte: byte from which read will start.
+    :param byte_range: how many bytes to read.
+    """
+
+    def __init__(self, iterable, start_byte=0, byte_range=None):
+        self.iterable = iter(iterable)
+        self.byte_range = byte_range
+        self.start_byte = start_byte
+        self.end_byte = None
+        if byte_range is not None:
+            self.end_byte = self.start_byte + self.byte_range
+        self.read_length = 0
+        self.seekable = hasattr(iterable, 'seekable') and iterable.seekable()
+        self.end_reached = False
+
+    def __iter__(self):
+        return self
+
+    def _next_chunk(self):
+        try:
+            chunk = next(self.iterable)
+            self.read_length += len(chunk)
+            return chunk
+        except StopIteration:
+            self.end_reached = True
+            raise
+
+    def _first_iteration(self):
+        chunk = None
+        if self.seekable:
+            self.iterable.seek(self.start_byte)
+            self.read_length = self.iterable.tell()
+            contextual_read_length = self.read_length
+        else:
+            while self.read_length <= self.start_byte:
+                chunk = self._next_chunk()
+            if chunk is not None:
+                chunk = chunk[self.start_byte - self.read_length:]
+            contextual_read_length = self.start_byte
+        return chunk, contextual_read_length
+
+    def _next(self):
+        if self.end_reached:
+            raise StopIteration()
+        chunk = None
+        contextual_read_length = self.read_length
+        if self.read_length == 0:
+            chunk, contextual_read_length = self._first_iteration()
+        if chunk is None:
+            chunk = self._next_chunk()
+        if self.end_byte is not None and self.read_length >= self.end_byte:
+            self.end_reached = True
+            return chunk[:self.end_byte - contextual_read_length]
+        return chunk
+
+    def __next__(self):
+        chunk = self._next()
+        if chunk:
+            return chunk
+        self.end_reached = True
+        raise StopIteration()
+
+    def close(self):
+        if hasattr(self.iterable, 'close'):
+            self.iterable.close()
+
+
 def _make_chunk_iter(stream, limit, buffer_size):
     """Helper for the line and chunk iter functions."""
     if isinstance(stream, (bytes, bytearray, text_type)):
@@ -804,6 +902,9 @@
     .. versionadded:: 0.9
        added support for iterators as input stream.
 
+    .. versionadded:: 0.11.10
+       added support for the `cap_at_buffer` parameter.
+
     :param stream: the stream or iterate to iterate over.
     :param limit: the limit in bytes for the stream.  (Usually
                   content length.  Not necessary if the `stream`
@@ -813,8 +914,6 @@
                           than the buffer size.  Internally this is implemented
                           that the buffer size might be exhausted by a factor
                           of two however.
-    .. versionadded:: 0.11.10
-       added support for the `cap_at_buffer` parameter.
     """
     _iter = _make_chunk_iter(stream, limit, buffer_size)
 
--- a/docs/CHANGES	Tue Nov 01 17:56:32 2016 +0100
+++ b/docs/CHANGES	Tue Apr 11 22:42:23 2017 +0200
@@ -16,6 +16,15 @@
     editor_force = True
     editor_default = 'text'  # internal default, just for completeness
 
+Version 1.9.10rc1 2017-04-xx
+
+  Fixes:
+  * fix wrong digestmod of hmac.new calls (incorporate 1.9.9 patch)
+
+  Other changes:
+  * upgrade werkzeug to 0.12.1
+
+
 Version 1.9.9 aka "The undead MoinMoin Halloween Release" 2016-10-31
 
   SECURITY HINT: make sure you have allow_xslt = False (or just do not use
--- a/docs/REQUIREMENTS	Tue Nov 01 17:56:32 2016 +0100
+++ b/docs/REQUIREMENTS	Tue Apr 11 22:42:23 2017 +0200
@@ -105,7 +105,7 @@
 
 werkzeug (WSGI toolkit)
 =======================
-shipped: 0.11.11
+shipped: 0.12.1
 minimum: 0.7.0