Flask update
@@ -11,12 +11,17 @@
|
||||
library.
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from types import ModuleType
|
||||
import sys
|
||||
|
||||
|
||||
# the version. Usually set automatically by a script.
|
||||
__version__ = '0.8-dev'
|
||||
|
||||
|
||||
# This import magic raises concerns quite often which is why the implementation
|
||||
# and motivation is explained here in detail now.
|
||||
#
|
||||
@@ -106,11 +111,6 @@ for module, items in all_by_module.iteritems():
|
||||
object_origins[item] = module
|
||||
|
||||
|
||||
#: the cached version of the library. We get the distribution from
|
||||
#: pkg_resources the first time this attribute is accessed. Because
|
||||
#: this operation is quite slow it speeds up importing a lot.
|
||||
version = None
|
||||
|
||||
class module(ModuleType):
|
||||
"""Automatically import objects from the modules."""
|
||||
|
||||
@@ -132,17 +132,6 @@ class module(ModuleType):
|
||||
'__package__', '__version__'))
|
||||
return result
|
||||
|
||||
@property
|
||||
def __version__(self):
|
||||
global version
|
||||
if version is None:
|
||||
try:
|
||||
version = __import__('pkg_resources') \
|
||||
.get_distribution('Werkzeug').version
|
||||
except Exception:
|
||||
version = 'unknown'
|
||||
return version
|
||||
|
||||
# keep a reference to this module so that it's not garbage collected
|
||||
old_module = sys.modules['werkzeug']
|
||||
|
||||
@@ -154,6 +143,7 @@ new_module.__dict__.update({
|
||||
'__package__': 'werkzeug',
|
||||
'__path__': __path__,
|
||||
'__doc__': __doc__,
|
||||
'__version__': __version__,
|
||||
'__all__': tuple(object_origins) + tuple(attribute_modules),
|
||||
'__docformat__': 'restructuredtext en'
|
||||
})
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
This module provides internally used helpers and constants.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import inspect
|
||||
@@ -204,7 +204,7 @@ def _decode_unicode(value, charset, errors):
|
||||
return value.decode(charset, errors)
|
||||
except UnicodeError, e:
|
||||
if fallback is not None:
|
||||
return value.decode(fallback, 'ignore')
|
||||
return value.decode(fallback, 'replace')
|
||||
from werkzeug.exceptions import HTTPUnicodeError
|
||||
raise HTTPUnicodeError(str(e))
|
||||
|
||||
@@ -383,8 +383,11 @@ mj2Z/FM1vQWgDynsRwNvrWnJHlespkrp8+vO1jNaibm+PhqXPPv30YwDZ6jApe3wUjFQobghvW9p
|
||||
if environ.get('QUERY_STRING') != 'macgybarchakku':
|
||||
return app(environ, injecting_start_response)
|
||||
injecting_start_response('200 OK', [('Content-Type', 'text/html')])
|
||||
return ['''<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN">
|
||||
<title>About Werkzeug</>
|
||||
return ['''
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>About Werkzeug</title>
|
||||
<style type="text/css">
|
||||
body { font: 15px Georgia, serif; text-align: center; }
|
||||
a { color: #333; text-decoration: none; }
|
||||
@@ -392,7 +395,11 @@ mj2Z/FM1vQWgDynsRwNvrWnJHlespkrp8+vO1jNaibm+PhqXPPv30YwDZ6jApe3wUjFQobghvW9p
|
||||
p { margin: 0 0 30px 0; }
|
||||
pre { font: 11px 'Consolas', 'Monaco', monospace; line-height: 0.95; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1><a href="http://werkzeug.pocoo.org/">Werkzeug</a></h1>
|
||||
<p>the Swiss Army knife of Python web development.
|
||||
<pre>%s\n\n\n</>''' % gyver]
|
||||
<p>the Swiss Army knife of Python web development.</p>
|
||||
<pre>%s\n\n\n</pre>
|
||||
</body>
|
||||
</html>''' % gyver]
|
||||
return easteregged
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
This file itself is mostly for informational purposes and to tell the
|
||||
Python interpreter that `contrib` is a package.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
updated=post.last_update, published=post.pub_date)
|
||||
return feed.get_response()
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from datetime import datetime
|
||||
@@ -152,7 +152,7 @@ class AtomFeed(object):
|
||||
# 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):
|
||||
self.author = ({'name': u'unbekannter Autor'},)
|
||||
self.author = ({'name': 'Unknown author'},)
|
||||
|
||||
if not self.updated:
|
||||
dates = sorted([entry.updated for entry in self.entries])
|
||||
|
||||
@@ -53,7 +53,7 @@
|
||||
you have access to it (either as a module global you can import or you just
|
||||
put it into your WSGI application).
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
@@ -68,6 +68,19 @@ from time import time
|
||||
from cPickle import loads, dumps, load, dump, HIGHEST_PROTOCOL
|
||||
from werkzeug.posixemulation import rename
|
||||
|
||||
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
|
||||
|
||||
"""
|
||||
return mappingorseq.iteritems() if hasattr(mappingorseq, 'iteritems') \
|
||||
else mappingorseq
|
||||
|
||||
class BaseCache(object):
|
||||
"""Baseclass for the cache systems. All the cache systems implement this
|
||||
@@ -145,13 +158,13 @@ class BaseCache(object):
|
||||
pass
|
||||
|
||||
def set_many(self, mapping, timeout=None):
|
||||
"""Sets multiple keys and values from a dict.
|
||||
"""Sets multiple keys and values from a mapping.
|
||||
|
||||
:param mapping: a dict with the keys/values to set.
|
||||
: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).
|
||||
"""
|
||||
for key, value in mapping.iteritems():
|
||||
for key, value in _items(mapping):
|
||||
self.set(key, value, timeout)
|
||||
|
||||
def delete_many(self, *keys):
|
||||
@@ -279,35 +292,40 @@ class MemcachedCache(BaseCache):
|
||||
def __init__(self, servers, default_timeout=300, key_prefix=None):
|
||||
BaseCache.__init__(self, default_timeout)
|
||||
if isinstance(servers, (list, tuple)):
|
||||
is_cmemcached = is_cmemcache = is_pylibmc = False
|
||||
try:
|
||||
import cmemcache as memcache
|
||||
is_cmemcache = True
|
||||
import cmemcached as memcache
|
||||
is_cmemcached = True
|
||||
except ImportError:
|
||||
try:
|
||||
import memcache
|
||||
is_cmemcache = False
|
||||
is_pylibmc = False
|
||||
import cmemcache as memcache
|
||||
is_cmemcache = True
|
||||
except ImportError:
|
||||
try:
|
||||
import pylibmc as memcache
|
||||
import memcache
|
||||
is_cmemcache = False
|
||||
is_pylibmc = True
|
||||
is_pylibmc = False
|
||||
except ImportError:
|
||||
raise RuntimeError('no memcache module found')
|
||||
try:
|
||||
import pylibmc as memcache
|
||||
is_cmemcache = False
|
||||
is_pylibmc = True
|
||||
except ImportError:
|
||||
raise RuntimeError('no memcache module found')
|
||||
|
||||
# cmemcache has a bug that debuglog is not defined for the
|
||||
# client. Whenever pickle fails you get a weird AttributeError.
|
||||
if is_cmemcache:
|
||||
# cmemcache has a bug that debuglog is not defined for the
|
||||
# client. Whenever pickle fails you get a weird
|
||||
# AttributeError.
|
||||
client = memcache.Client(map(str, servers))
|
||||
try:
|
||||
client.debuglog = lambda *a: None
|
||||
except Exception:
|
||||
pass
|
||||
elif is_pylibmc or is_cmemcached:
|
||||
client = memcache.Client(servers, False)
|
||||
else:
|
||||
if is_pylibmc:
|
||||
client = memcache.Client(servers, False)
|
||||
else:
|
||||
client = memcache.Client(servers, False, HIGHEST_PROTOCOL)
|
||||
client = memcache.Client(servers, False, HIGHEST_PROTOCOL)
|
||||
else:
|
||||
client = servers
|
||||
|
||||
@@ -378,7 +396,7 @@ class MemcachedCache(BaseCache):
|
||||
if timeout is None:
|
||||
timeout = self.default_timeout
|
||||
new_mapping = {}
|
||||
for key, value in mapping.iteritems():
|
||||
for key, value in _items(mapping):
|
||||
if isinstance(key, unicode):
|
||||
key = key.encode('utf-8')
|
||||
if self.key_prefix:
|
||||
@@ -441,6 +459,77 @@ class GAEMemcachedCache(MemcachedCache):
|
||||
default_timeout, key_prefix)
|
||||
|
||||
|
||||
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
|
||||
|
||||
: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 default_timeout: the default timeout that is used if no timeout is
|
||||
specified on :meth:`~BaseCache.set`.
|
||||
|
||||
"""
|
||||
def __init__(self, host='localhost', port=6379, default_timeout=300):
|
||||
BaseCache.__init__(self, default_timeout)
|
||||
if isinstance(host, basestring):
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
raise RuntimeError('no redis module found')
|
||||
self._client = redis.Redis(host=host, port=port)
|
||||
else:
|
||||
self._client = host
|
||||
|
||||
def get(self, key):
|
||||
return self._client.get(key)
|
||||
|
||||
def get_many(self, *keys):
|
||||
return self._client.mget(keys)
|
||||
|
||||
def set(self, key, value, timeout=None):
|
||||
if timeout is None:
|
||||
timeout = self.default_timeout
|
||||
self._client.setex(key, value, timeout)
|
||||
|
||||
def add(self, key, value, timeout=None):
|
||||
if timeout is None:
|
||||
timeout = self.default_timeout
|
||||
added = self._client.setnx(key, value)
|
||||
if added:
|
||||
self._client.expire(key, timeout)
|
||||
|
||||
def set_many(self, mapping, timeout=None):
|
||||
if timeout is None:
|
||||
timeout = self.default_timeout
|
||||
pipe = self._client.pipeline()
|
||||
for key, value in _items(mapping):
|
||||
pipe.setex(key, value, timeout)
|
||||
pipe.execute()
|
||||
|
||||
def delete(self, key):
|
||||
self._client.delete(key)
|
||||
|
||||
def delete_many(self, *keys):
|
||||
self._client.delete(*keys)
|
||||
|
||||
def clear(self):
|
||||
self._client.flushdb()
|
||||
|
||||
def inc(self, key, delta=1):
|
||||
return self._client.incr(key, delta)
|
||||
|
||||
def dec(self, key, delta=1):
|
||||
return self._client.decr(key, 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
|
||||
|
||||
@@ -17,9 +17,8 @@
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from urllib import unquote
|
||||
from werkzeug.wrappers import BaseRequest
|
||||
from werkzeug.http import parse_options_header, parse_cache_control_header, \
|
||||
parse_set_header, dump_header
|
||||
parse_set_header
|
||||
from werkzeug.useragents import UserAgent
|
||||
from werkzeug.datastructures import Headers, ResponseCacheControl
|
||||
|
||||
@@ -35,8 +34,13 @@ class LighttpdCGIRootFix(object):
|
||||
self.app = app
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
#only set PATH_INFO for older versions of Lighty:
|
||||
if environ['SERVER_SOFTWARE'] < 'lighttpd/1.4.28':
|
||||
# only set PATH_INFO for older versions of Lighty or if no
|
||||
# server software is provided. That's because the test was
|
||||
# added in newer Werkzeug versions and we don't want to break
|
||||
# people's code if they are using this fixer in a test that
|
||||
# does not set the SERVER_SOFTWARE key.
|
||||
if 'SERVER_SOFTWARE' not in environ or \
|
||||
environ['SERVER_SOFTWARE'] < 'lighttpd/1.4.28':
|
||||
environ['PATH_INFO'] = environ.get('SCRIPT_NAME', '') + \
|
||||
environ.get('PATH_INFO', '')
|
||||
environ['SCRIPT_NAME'] = ''
|
||||
@@ -79,9 +83,6 @@ class ProxyFix(object):
|
||||
application that was not designed with HTTP proxies in mind. It
|
||||
sets `REMOTE_ADDR`, `HTTP_HOST` from `X-Forwarded` headers.
|
||||
|
||||
Werkzeug wrappers have builtin support for this by setting the
|
||||
:attr:`~BaseRequest.is_behind_proxy` attribute to `True`.
|
||||
|
||||
Do not use this middleware in non-proxy setups for security reasons.
|
||||
|
||||
The original values of `REMOTE_ADDR` and `HTTP_HOST` are stored in
|
||||
|
||||
@@ -36,11 +36,11 @@ r"""
|
||||
|
||||
.. _greenlet: http://codespeak.net/py/dist/greenlet.html
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
from py.magic import greenlet
|
||||
import greenlet
|
||||
except ImportError:
|
||||
greenlet = None
|
||||
|
||||
|
||||
@@ -6,29 +6,31 @@
|
||||
Addon module that allows to create a JavaScript function from a map
|
||||
that generates rules.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
from simplejson import dumps
|
||||
except ImportError:
|
||||
def dumps(*args):
|
||||
raise RuntimeError('simplejson required for jsrouting')
|
||||
try:
|
||||
from json import dumps
|
||||
except ImportError:
|
||||
def dumps(*args):
|
||||
raise RuntimeError('simplejson required for jsrouting')
|
||||
|
||||
from inspect import getmro
|
||||
from werkzeug.templates import Template
|
||||
from werkzeug.routing import NumberConverter
|
||||
|
||||
|
||||
_javascript_routing_template = Template(u'''\
|
||||
<% if name_parts %>\
|
||||
<% for idx in xrange(0, len(name_parts) - 1) %>\
|
||||
if (typeof ${'.'.join(name_parts[:idx + 1])} === 'undefined') \
|
||||
${'.'.join(name_parts[:idx + 1])} = {};
|
||||
<% endfor %>\
|
||||
${'.'.join(name_parts)} = <% endif %>\
|
||||
(function (server_name, script_name, subdomain, url_scheme) {
|
||||
var converters = ${', '.join(converters)};
|
||||
def render_template(name_parts, rules, converters):
|
||||
result = u''
|
||||
if name_parts:
|
||||
for idx in xrange(0, len(name_parts) - 1):
|
||||
name = u'.'.join(name_parts[:idx + 1])
|
||||
result += u"if (typeof %s === 'undefined') %s = {}\n" % (name, name)
|
||||
result += '%s = ' % '.'.join(name_parts)
|
||||
result += """(function (server_name, script_name, subdomain, url_scheme) {
|
||||
var converters = %(converters)s;
|
||||
var rules = $rules;
|
||||
function in_array(array, value) {
|
||||
if (array.indexOf != undefined) {
|
||||
@@ -160,7 +162,8 @@ ${'.'.join(name_parts)} = <% endif %>\
|
||||
+ '/' + lstrip(rv.path, '/');
|
||||
}
|
||||
};
|
||||
})''')
|
||||
})""" % {'converters': u', '.join(converters)}
|
||||
return result
|
||||
|
||||
|
||||
def generate_map(map, name='url_map'):
|
||||
@@ -203,11 +206,9 @@ def generate_map(map, name='url_map'):
|
||||
u'defaults': rule.defaults
|
||||
})
|
||||
|
||||
return _javascript_routing_template.render({
|
||||
'name_parts': name and name.split('.') or [],
|
||||
'rules': dumps(rules),
|
||||
'converters': converters
|
||||
})
|
||||
return render_template(name_parts=name and name.split('.') or [],
|
||||
rules=dumps(rules),
|
||||
converters=converters)
|
||||
|
||||
|
||||
def generate_adapter(adapter, name='url_for', map_name='url_map'):
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
.. _Genshi: http://genshi.edgewall.org/
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from os import path
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
.. _Trac: http://trac.edgewall.org/
|
||||
.. _Django: http://www.djangoproject.com/
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from warnings import warn
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
|
||||
It's strongly recommended to use it during development.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from urlparse import urlparse
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
from werkzeug.contrib.profiler import ProfilerMiddleware
|
||||
app = ProfilerMiddleware(app)
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
|
||||
@@ -85,17 +85,17 @@ r"""
|
||||
request.client_session.save_cookie(response)
|
||||
return response(environ, start_response)
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
import cPickle as pickle
|
||||
from hmac import new as hmac
|
||||
from itertools import izip
|
||||
from time import time
|
||||
from werkzeug.urls import url_quote_plus, url_unquote_plus
|
||||
from werkzeug._internal import _date_to_unix
|
||||
from werkzeug.contrib.sessions import ModificationTrackingDict
|
||||
from werkzeug.security import safe_str_cmp
|
||||
|
||||
|
||||
# rather ugly way to import the correct hash method. Because
|
||||
@@ -115,18 +115,6 @@ if _default_hash is None:
|
||||
import sha as _default_hash
|
||||
|
||||
|
||||
def safe_str_cmp(a, b):
|
||||
"""This function compares strings in somewhat constant time. In case
|
||||
someone actually finds a way to measure that over the network which
|
||||
I strongly doubt."""
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
rv = 0
|
||||
for x, y in izip(a, b):
|
||||
rv |= ord(x) ^ ord(y)
|
||||
return rv == 0
|
||||
|
||||
|
||||
class UnquoteError(Exception):
|
||||
"""Internal exception used to signal failures on quoting."""
|
||||
|
||||
@@ -261,7 +249,7 @@ class SecureCookie(ModificationTrackingDict):
|
||||
:return: a new :class:`SecureCookie`.
|
||||
"""
|
||||
if isinstance(string, unicode):
|
||||
string = string.encode('utf-8', 'ignore')
|
||||
string = string.encode('utf-8', 'replace')
|
||||
try:
|
||||
base64_hash, data = string.split('?', 1)
|
||||
except (ValueError, IndexError):
|
||||
|
||||
@@ -48,7 +48,7 @@ r"""
|
||||
response.set_cookie('cookie_name', request.session.sid)
|
||||
return response(environ, start_response)
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
A response wrapper which adds various cached attributes for
|
||||
simplified assertions on various content types.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
from werkzeug.utils import cached_property, import_string
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
Afterwards this request object provides the extra functionality of the
|
||||
:class:`JSONRequestMixin`.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import codecs
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
This module provides mixins and classes with an immutable interface.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
@@ -291,11 +291,6 @@ class MultiDict(TypeConversionDict):
|
||||
or `None`.
|
||||
"""
|
||||
|
||||
# the key error this class raises. Because of circular dependencies
|
||||
# with the http exception module this class is created at the end of
|
||||
# this module.
|
||||
KeyError = None
|
||||
|
||||
def __init__(self, mapping=None):
|
||||
if isinstance(mapping, MultiDict):
|
||||
dict.__init__(self, ((k, l[:]) for k, l in mapping.iterlists()))
|
||||
@@ -333,7 +328,7 @@ class MultiDict(TypeConversionDict):
|
||||
"""
|
||||
if key in self:
|
||||
return dict.__getitem__(self, key)[0]
|
||||
raise self.KeyError(key)
|
||||
raise BadRequestKeyError(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
"""Like :meth:`add` but removes an existing key first.
|
||||
@@ -539,7 +534,7 @@ class MultiDict(TypeConversionDict):
|
||||
except KeyError, e:
|
||||
if default is not _missing:
|
||||
return default
|
||||
raise self.KeyError(str(e))
|
||||
raise BadRequestKeyError(str(e))
|
||||
|
||||
def popitem(self):
|
||||
"""Pop an item from the dict."""
|
||||
@@ -547,7 +542,7 @@ class MultiDict(TypeConversionDict):
|
||||
item = dict.popitem(self)
|
||||
return (item[0], item[1][0])
|
||||
except KeyError, e:
|
||||
raise self.KeyError(str(e))
|
||||
raise BadRequestKeyError(str(e))
|
||||
|
||||
def poplist(self, key):
|
||||
"""Pop the list for a key from the dict. If the key is not in the dict
|
||||
@@ -564,7 +559,7 @@ class MultiDict(TypeConversionDict):
|
||||
try:
|
||||
return dict.popitem(self)
|
||||
except KeyError, e:
|
||||
raise self.KeyError(str(e))
|
||||
raise BadRequestKeyError(str(e))
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, self.items(multi=True))
|
||||
@@ -617,11 +612,6 @@ class OrderedMultiDict(MultiDict):
|
||||
the internal bucket objects are exposed.
|
||||
"""
|
||||
|
||||
# the key error this class raises. Because of circular dependencies
|
||||
# with the http exception module this class is created at the end of
|
||||
# this module.
|
||||
KeyError = None
|
||||
|
||||
def __init__(self, mapping=None):
|
||||
dict.__init__(self)
|
||||
self._first_bucket = self._last_bucket = None
|
||||
@@ -670,7 +660,7 @@ class OrderedMultiDict(MultiDict):
|
||||
def __getitem__(self, key):
|
||||
if key in self:
|
||||
return dict.__getitem__(self, key)[0].value
|
||||
raise self.KeyError(key)
|
||||
raise BadRequestKeyError(key)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.poplist(key)
|
||||
@@ -755,7 +745,7 @@ class OrderedMultiDict(MultiDict):
|
||||
except KeyError, e:
|
||||
if default is not _missing:
|
||||
return default
|
||||
raise self.KeyError(str(e))
|
||||
raise BadRequestKeyError(str(e))
|
||||
for bucket in buckets:
|
||||
bucket.unlink(self)
|
||||
return buckets[0].value
|
||||
@@ -764,7 +754,7 @@ class OrderedMultiDict(MultiDict):
|
||||
try:
|
||||
key, buckets = dict.popitem(self)
|
||||
except KeyError, e:
|
||||
raise self.KeyError(str(e))
|
||||
raise BadRequestKeyError(str(e))
|
||||
for bucket in buckets:
|
||||
bucket.unlink(self)
|
||||
return key, buckets[0].value
|
||||
@@ -773,7 +763,7 @@ class OrderedMultiDict(MultiDict):
|
||||
try:
|
||||
key, buckets = dict.popitem(self)
|
||||
except KeyError, e:
|
||||
raise self.KeyError(str(e))
|
||||
raise BadRequestKeyError(str(e))
|
||||
for bucket in buckets:
|
||||
bucket.unlink(self)
|
||||
return key, [x.value for x in buckets]
|
||||
@@ -810,11 +800,6 @@ class Headers(object):
|
||||
:param defaults: The list of default values for the :class:`Headers`.
|
||||
"""
|
||||
|
||||
# the key error this class raises. Because of circular dependencies
|
||||
# with the http exception module this class is created at the end of
|
||||
# this module.
|
||||
KeyError = None
|
||||
|
||||
def __init__(self, defaults=None, _list=None):
|
||||
if _list is None:
|
||||
_list = []
|
||||
@@ -856,7 +841,7 @@ class Headers(object):
|
||||
# key error instead of our special one.
|
||||
if _get_mode:
|
||||
raise KeyError()
|
||||
raise self.KeyError(key)
|
||||
raise BadRequestKeyError(key)
|
||||
|
||||
def __eq__(self, other):
|
||||
return other.__class__ is self.__class__ and \
|
||||
@@ -1267,7 +1252,7 @@ class CombinedMultiDict(ImmutableMultiDictMixin, MultiDict):
|
||||
for d in self.dicts:
|
||||
if key in d:
|
||||
return d[key]
|
||||
raise self.KeyError(key)
|
||||
raise BadRequestKeyError(key)
|
||||
|
||||
def get(self, key, default=None, type=None):
|
||||
for d in self.dicts:
|
||||
@@ -1385,16 +1370,18 @@ class FileMultiDict(MultiDict):
|
||||
:param content_type: an optional content type
|
||||
"""
|
||||
if isinstance(file, FileStorage):
|
||||
self[name] = file
|
||||
return
|
||||
if isinstance(file, basestring):
|
||||
if filename is None:
|
||||
filename = file
|
||||
file = open(file, 'rb')
|
||||
if filename and content_type is None:
|
||||
content_type = mimetypes.guess_type(filename)[0] or \
|
||||
'application/octet-stream'
|
||||
self[name] = FileStorage(file, filename, name, content_type)
|
||||
value = file
|
||||
else:
|
||||
if isinstance(file, basestring):
|
||||
if filename is None:
|
||||
filename = file
|
||||
file = open(file, 'rb')
|
||||
if filename and content_type is None:
|
||||
content_type = mimetypes.guess_type(filename)[0] or \
|
||||
'application/octet-stream'
|
||||
value = FileStorage(file, filename, name, content_type)
|
||||
|
||||
self.add(name, value)
|
||||
|
||||
|
||||
class ImmutableDict(ImmutableDictMixin, dict):
|
||||
@@ -1651,6 +1638,11 @@ class MIMEAccept(Accept):
|
||||
'application/xml' in self
|
||||
)
|
||||
|
||||
@property
|
||||
def accept_json(self):
|
||||
"""True if this object accepts JSON."""
|
||||
return 'application/json' in self
|
||||
|
||||
|
||||
class LanguageAccept(Accept):
|
||||
"""Like :class:`Accept` but with normalization for languages."""
|
||||
@@ -2068,6 +2060,165 @@ class ETags(object):
|
||||
return '<%s %r>' % (self.__class__.__name__, str(self))
|
||||
|
||||
|
||||
class IfRange(object):
|
||||
"""Very simple object that represents the `If-Range` header in parsed
|
||||
form. It will either have neither a etag or date or one of either but
|
||||
never both.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
def __init__(self, etag=None, date=None):
|
||||
#: The etag parsed and unquoted. Ranges always operate on strong
|
||||
#: etags so the weakness information is not necessary.
|
||||
self.etag = etag
|
||||
#: The date in parsed format or `None`.
|
||||
self.date = date
|
||||
|
||||
def to_header(self):
|
||||
"""Converts the object back into an HTTP header."""
|
||||
if self.date is not None:
|
||||
return http_date(self.date)
|
||||
if self.etag is not None:
|
||||
return quote_etag(self.etag)
|
||||
return ''
|
||||
|
||||
def __str__(self):
|
||||
return self.to_header()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (self.__class__.__name__, str(self))
|
||||
|
||||
|
||||
class Range(object):
|
||||
"""Represents a range header. All the methods are only supporting bytes
|
||||
as unit. It does store multiple ranges but :meth:`range_for_length` will
|
||||
only work if only one range is provided.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
def __init__(self, units, ranges):
|
||||
#: The units of this range. Usually "bytes".
|
||||
self.units = units
|
||||
#: A list of ``(begin, end)`` tuples for the range header provided.
|
||||
#: The ranges are non-inclusive.
|
||||
self.ranges = ranges
|
||||
|
||||
def range_for_length(self, length):
|
||||
"""If the range is for bytes, the length is not None and there is
|
||||
exactly one range and it is satisfiable it returns a ``(start, stop)``
|
||||
tuple, otherwise `None`.
|
||||
"""
|
||||
if self.units != 'bytes' or length is None or len(self.ranges) != 1:
|
||||
return None
|
||||
start, end = self.ranges[0]
|
||||
if end is None:
|
||||
end = length
|
||||
if start < 0:
|
||||
start += length
|
||||
if is_byte_range_valid(start, end, length):
|
||||
return start, min(end, length)
|
||||
|
||||
def make_content_range(self, length):
|
||||
"""Creates a :class:`~werkzeug.datastructures.ContentRange` object
|
||||
from the current range and given content length.
|
||||
"""
|
||||
rng = self.range_for_length(length)
|
||||
if rng is not None:
|
||||
return ContentRange(self.units, rng[0], rng[1], length)
|
||||
|
||||
def to_header(self):
|
||||
"""Converts the object back into an HTTP header."""
|
||||
ranges = []
|
||||
for begin, end in self.ranges:
|
||||
if end is None:
|
||||
ranges.append(begin >= 0 and '%s-' % begin or str(begin))
|
||||
else:
|
||||
ranges.append('%s-%s' % (begin, end - 1))
|
||||
return '%s=%s' % (self.units, ','.join(ranges))
|
||||
|
||||
def __str__(self):
|
||||
return self.to_header()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (self.__class__.__name__, str(self))
|
||||
|
||||
|
||||
class ContentRange(object):
|
||||
"""Represents the content range header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
|
||||
def __init__(self, units, start, stop, length=None, on_update=None):
|
||||
assert is_byte_range_valid(start, stop, length), \
|
||||
'Bad range provided'
|
||||
self.on_update = on_update
|
||||
self.set(start, stop, length, units)
|
||||
|
||||
def _callback_property(name):
|
||||
def fget(self):
|
||||
return getattr(self, name)
|
||||
def fset(self, value):
|
||||
setattr(self, name, value)
|
||||
if self.on_update is not None:
|
||||
self.on_update(self)
|
||||
return property(fget, fset)
|
||||
|
||||
#: The units to use, usually "bytes"
|
||||
units = _callback_property('_units')
|
||||
#: The start point of the range or `None`.
|
||||
start = _callback_property('_start')
|
||||
#: The stop point of the range (non-inclusive) or `None`. Can only be
|
||||
#: `None` if also start is `None`.
|
||||
stop = _callback_property('_stop')
|
||||
#: The length of the range or `None`.
|
||||
length = _callback_property('_length')
|
||||
|
||||
def set(self, start, stop, length=None, units='bytes'):
|
||||
"""Simple method to update the ranges."""
|
||||
assert is_byte_range_valid(start, stop, length), \
|
||||
'Bad range provided'
|
||||
self._units = units
|
||||
self._start = start
|
||||
self._stop = stop
|
||||
self._length = length
|
||||
if self.on_update is not None:
|
||||
self.on_update(self)
|
||||
|
||||
def unset(self):
|
||||
"""Sets the units to `None` which indicates that the header should
|
||||
no longer be used.
|
||||
"""
|
||||
self.set(None, None, units=None)
|
||||
|
||||
def to_header(self):
|
||||
if self.units is None:
|
||||
return ''
|
||||
if self.length is None:
|
||||
length = '*'
|
||||
else:
|
||||
length = self.length
|
||||
if self.start is None:
|
||||
return '%s */%s' % (self.units, length)
|
||||
return '%s %s-%s/%s' % (
|
||||
self.units,
|
||||
self.start,
|
||||
self.stop - 1,
|
||||
length
|
||||
)
|
||||
|
||||
def __nonzero__(self):
|
||||
return self.units is not None
|
||||
|
||||
def __str__(self):
|
||||
return self.to_header()
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s %r>' % (self.__class__.__name__, str(self))
|
||||
|
||||
|
||||
class Authorization(ImmutableDictMixin, dict):
|
||||
"""Represents an `Authorization` header sent by the client. You should
|
||||
not create this kind of object yourself but use it when it's returned by
|
||||
@@ -2275,7 +2426,17 @@ class FileStorage(object):
|
||||
headers=None):
|
||||
self.name = name
|
||||
self.stream = stream or _empty_stream
|
||||
self.filename = filename or getattr(stream, 'name', None)
|
||||
|
||||
# if no filename is provided we can attempt to get the filename
|
||||
# from the stream object passed. There we have to be careful to
|
||||
# skip things like <fdopen>, <stderr> etc. Python marks these
|
||||
# special filenames with angular brackets.
|
||||
if filename is None:
|
||||
filename = getattr(stream, 'name', None)
|
||||
if filename and filename[0] == '<' and filename[-1] == '>':
|
||||
filename = None
|
||||
|
||||
self.filename = filename
|
||||
self.content_type = content_type
|
||||
self.content_length = content_length
|
||||
if headers is None:
|
||||
@@ -2361,13 +2522,6 @@ class FileStorage(object):
|
||||
|
||||
# circular dependencies
|
||||
from werkzeug.http import dump_options_header, dump_header, generate_etag, \
|
||||
quote_header_value, parse_set_header, unquote_etag, \
|
||||
parse_options_header
|
||||
|
||||
|
||||
# create all the special key errors now that the classes are defined.
|
||||
from werkzeug.exceptions import BadRequest
|
||||
for _cls in MultiDict, OrderedMultiDict, CombinedMultiDict, Headers, \
|
||||
EnvironHeaders:
|
||||
_cls.KeyError = BadRequest.wrap(KeyError, _cls.__name__ + '.KeyError')
|
||||
del _cls
|
||||
quote_header_value, parse_set_header, unquote_etag, quote_etag, \
|
||||
parse_options_header, http_date, is_byte_range_valid
|
||||
from werkzeug.exceptions import BadRequestKeyError
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
WSGI application traceback debugger.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import mimetypes
|
||||
@@ -13,6 +13,7 @@ from os.path import join, dirname, basename, isfile
|
||||
from werkzeug.wrappers import BaseRequest as Request, BaseResponse as Response
|
||||
from werkzeug.debug.tbtools import get_current_traceback, render_console_html
|
||||
from werkzeug.debug.console import Console
|
||||
from werkzeug.security import gen_salt
|
||||
|
||||
|
||||
#: import this here because it once was documented as being available
|
||||
@@ -77,7 +78,8 @@ class DebuggedApplication(object):
|
||||
self.console_path = console_path
|
||||
self.console_init_func = console_init_func
|
||||
self.show_hidden_frames = show_hidden_frames
|
||||
self.lodgeit_url=lodgeit_url
|
||||
self.lodgeit_url = lodgeit_url
|
||||
self.secret = gen_salt(20)
|
||||
|
||||
def debug_application(self, environ, start_response):
|
||||
"""Run the application and conserve the traceback frames."""
|
||||
@@ -113,7 +115,8 @@ class DebuggedApplication(object):
|
||||
'sent.\n')
|
||||
else:
|
||||
yield traceback.render_full(evalex=self.evalex,
|
||||
lodgeit_url=self.lodgeit_url) \
|
||||
lodgeit_url=self.lodgeit_url,
|
||||
secret=self.secret) \
|
||||
.encode('utf-8', 'replace')
|
||||
|
||||
traceback.log(environ['wsgi.errors'])
|
||||
@@ -126,12 +129,13 @@ class DebuggedApplication(object):
|
||||
"""Display a standalone shell."""
|
||||
if 0 not in self.frames:
|
||||
self.frames[0] = _ConsoleFrame(self.console_init_func())
|
||||
return Response(render_console_html(), mimetype='text/html')
|
||||
return Response(render_console_html(secret=self.secret),
|
||||
mimetype='text/html')
|
||||
|
||||
def paste_traceback(self, request, traceback):
|
||||
"""Paste the traceback and return a JSON response."""
|
||||
paste_id = traceback.paste(self.lodgeit_url)
|
||||
return Response('{"url": "%sshow/%s/", "id": %s}'
|
||||
return Response('{"url": "%sshow/%s/", "id": "%s"}'
|
||||
% (self.lodgeit_url, paste_id, paste_id),
|
||||
mimetype='application/json')
|
||||
|
||||
@@ -162,15 +166,18 @@ class DebuggedApplication(object):
|
||||
if request.args.get('__debugger__') == 'yes':
|
||||
cmd = request.args.get('cmd')
|
||||
arg = request.args.get('f')
|
||||
secret = request.args.get('s')
|
||||
traceback = self.tracebacks.get(request.args.get('tb', type=int))
|
||||
frame = self.frames.get(request.args.get('frm', type=int))
|
||||
if cmd == 'resource' and arg:
|
||||
response = self.get_resource(request, arg)
|
||||
elif cmd == 'paste' and traceback is not None:
|
||||
elif cmd == 'paste' and traceback is not None and \
|
||||
secret == self.secret:
|
||||
response = self.paste_traceback(request, traceback)
|
||||
elif cmd == 'source' and frame:
|
||||
elif cmd == 'source' and frame and self.secret == secret:
|
||||
response = self.get_source(request, frame)
|
||||
elif self.evalex and cmd is not None and frame is not None:
|
||||
elif self.evalex and cmd is not None and frame is not None and \
|
||||
self.secret == secret:
|
||||
response = self.execute_command(request, cmd, frame)
|
||||
elif self.evalex and self.console_path is not None and \
|
||||
request.path == self.console_path:
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
Interactive console support.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD.
|
||||
"""
|
||||
import sys
|
||||
@@ -35,10 +35,14 @@ class HTMLStringO(object):
|
||||
pass
|
||||
|
||||
def seek(self, n, mode=0):
|
||||
raise IOError('Bad file descriptor')
|
||||
pass
|
||||
|
||||
def readline(self):
|
||||
raise IOError('Bad file descriptor')
|
||||
if len(self._buffer) == 0:
|
||||
return ''
|
||||
ret = self._buffer[0]
|
||||
del self._buffer[0]
|
||||
return ret
|
||||
|
||||
def reset(self):
|
||||
val = ''.join(self._buffer)
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.render
|
||||
~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Render the traceback debugging page.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import pprint
|
||||
from os.path import dirname, join
|
||||
|
||||
from werkzeug.templates import Template
|
||||
|
||||
|
||||
def get_template(name):
|
||||
return Template.from_file(join(dirname(__file__), 'shared', name),
|
||||
unicode_mode=False, errors='ignore')
|
||||
|
||||
|
||||
def load_resource(res):
|
||||
try:
|
||||
f = file(join(dirname(__file__), 'shared', res))
|
||||
except IOError:
|
||||
return ''
|
||||
try:
|
||||
return f.read()
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
|
||||
t_body = get_template('body.tmpl')
|
||||
t_codetable = get_template('codetable.tmpl')
|
||||
t_vartable = get_template('vartable.tmpl')
|
||||
|
||||
|
||||
def code_table(frame):
|
||||
from werkzeug.debug.util import Namespace
|
||||
lines = []
|
||||
lineno = frame['context_lineno']
|
||||
if lineno is not None:
|
||||
lineno += 1
|
||||
for l in frame['pre_context']:
|
||||
lines.append(Namespace(mode='pre', lineno=lineno, code=l))
|
||||
lineno += 1
|
||||
lines.append(Namespace(mode='cur', lineno=lineno,
|
||||
code=frame['context_line']))
|
||||
lineno += 1
|
||||
for l in frame['post_context']:
|
||||
lines.append(Namespace(mode='post', lineno=lineno, code=l))
|
||||
lineno += 1
|
||||
else:
|
||||
lines.append(Namespace(mode='cur', lineno=1,
|
||||
code='Sourcecode not available'))
|
||||
|
||||
return t_codetable.render(lines=lines)
|
||||
|
||||
|
||||
def var_table(var):
|
||||
def safe_pformat(x):
|
||||
try:
|
||||
lines = pprint.pformat(x).splitlines()
|
||||
except Exception:
|
||||
return '?'
|
||||
tmp = []
|
||||
for line in lines:
|
||||
if len(line) > 79:
|
||||
line = line[:79] + '...'
|
||||
tmp.append(line)
|
||||
return '\n'.join(tmp)
|
||||
|
||||
# dicts
|
||||
if isinstance(var, dict) or hasattr(var, 'items'):
|
||||
value = var.items()
|
||||
if not value:
|
||||
typ = 'empty'
|
||||
else:
|
||||
typ = 'dict'
|
||||
value.sort()
|
||||
value = [(repr(key), safe_pformat(val)) for key, val in value]
|
||||
|
||||
# lists
|
||||
elif isinstance(var, list):
|
||||
if not var:
|
||||
typ = 'empty'
|
||||
else:
|
||||
typ = 'list'
|
||||
value = [safe_pformat(item) for item in var]
|
||||
|
||||
# others
|
||||
else:
|
||||
typ = 'simple'
|
||||
value = repr(var)
|
||||
|
||||
return t_vartable.render(type=typ, value=value)
|
||||
|
||||
|
||||
def debug_page(context):
|
||||
tc = context.to_dict()
|
||||
tc['var_table'] = var_table
|
||||
tc['code_table'] = code_table
|
||||
return t_body.render(tc)
|
||||
@@ -10,7 +10,7 @@
|
||||
Together with the CSS and JavaScript files of the debugger this gives
|
||||
a colorful and more compact output.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD.
|
||||
"""
|
||||
import sys
|
||||
|
||||
|
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 507 B |
@@ -45,7 +45,7 @@ $(function() {
|
||||
sourceView.slideUp('fast');
|
||||
});
|
||||
$.get(document.location.pathname, {__debugger__: 'yes', cmd:
|
||||
'source', frm: frameID}, function(data) {
|
||||
'source', frm: frameID, s: SECRET}, function(data) {
|
||||
$('table', sourceView)
|
||||
.replaceWith(data);
|
||||
if (!sourceView.is(':visible'))
|
||||
@@ -96,7 +96,8 @@ $(function() {
|
||||
$.ajax({
|
||||
dataType: 'json',
|
||||
url: document.location.pathname,
|
||||
data: {__debugger__: 'yes', tb: TRACEBACK, cmd: 'paste'},
|
||||
data: {__debugger__: 'yes', tb: TRACEBACK, cmd: 'paste',
|
||||
s: SECRET},
|
||||
success: function(data) {
|
||||
$('div.plain span.pastemessage')
|
||||
.removeClass('pastemessage')
|
||||
@@ -134,7 +135,7 @@ function openShell(consoleNode, target, frameID) {
|
||||
.submit(function() {
|
||||
var cmd = command.val();
|
||||
$.get(document.location.pathname, {
|
||||
__debugger__: 'yes', cmd: cmd, frm: frameID}, function(data) {
|
||||
__debugger__: 'yes', cmd: cmd, frm: frameID, s: SECRET}, function(data) {
|
||||
var tmp = $('<div>').html(data);
|
||||
$('span.extended', tmp).each(function() {
|
||||
var hidden = $(this).wrap('<span>').hide();
|
||||
|
||||
|
Before Width: | Height: | Size: 191 B After Width: | Height: | Size: 191 B |
|
Before Width: | Height: | Size: 200 B After Width: | Height: | Size: 200 B |
|
Before Width: | Height: | Size: 818 B After Width: | Height: | Size: 818 B |
@@ -23,7 +23,7 @@ textarea { font-family: 'Consolas', 'Monaco', 'Bitstream Vera Sans Mono',
|
||||
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; }
|
||||
div.detail p { margin: 0 0 8px 13px; font-size: 14px; white-space: pre-wrap; }
|
||||
div.explanation { margin: 20px 13px; font-size: 15px; color: #555; }
|
||||
div.footer { font-size: 13px; text-align: right; margin: 30px 0;
|
||||
color: #86989B; }
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
This module provides various traceback related utility functions.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD.
|
||||
"""
|
||||
import re
|
||||
@@ -42,7 +42,8 @@ HEADER = u'''\
|
||||
<script type="text/javascript">
|
||||
var TRACEBACK = %(traceback_id)d,
|
||||
CONSOLE_MODE = %(console)s,
|
||||
EVALEX = %(evalex)s
|
||||
EVALEX = %(evalex)s,
|
||||
SECRET = "%(secret)s";
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
@@ -127,11 +128,12 @@ SOURCE_LINE_HTML = u'''\
|
||||
'''
|
||||
|
||||
|
||||
def render_console_html():
|
||||
def render_console_html(secret):
|
||||
return CONSOLE_HTML % {
|
||||
'evalex': 'true',
|
||||
'console': 'true',
|
||||
'title': 'Console',
|
||||
'secret': secret,
|
||||
'traceback_id': -1
|
||||
}
|
||||
|
||||
@@ -261,7 +263,7 @@ class Traceback(object):
|
||||
"""Create a paste and return the paste id."""
|
||||
from xmlrpclib import ServerProxy
|
||||
srv = ServerProxy('%sxmlrpc/' % lodgeit_url)
|
||||
return srv.pastes.newPaste('pytb', self.plaintext)
|
||||
return srv.pastes.newPaste('pytb', self.plaintext, '', '', '', True)
|
||||
|
||||
def render_summary(self, include_title=True):
|
||||
"""Render the traceback for the interactive console."""
|
||||
@@ -296,7 +298,8 @@ class Traceback(object):
|
||||
'description': description_wrapper % escape(self.exception)
|
||||
}
|
||||
|
||||
def render_full(self, evalex=False, lodgeit_url=None):
|
||||
def render_full(self, evalex=False, lodgeit_url=None,
|
||||
secret=None):
|
||||
"""Render the Full HTML page with the traceback info."""
|
||||
exc = escape(self.exception)
|
||||
return PAGE_HTML % {
|
||||
@@ -309,7 +312,8 @@ class Traceback(object):
|
||||
'summary': self.render_summary(include_title=False),
|
||||
'plaintext': self.plaintext,
|
||||
'plaintext_cs': re.sub('-{2,}', '-', self.plaintext),
|
||||
'traceback_id': self.id
|
||||
'traceback_id': self.id,
|
||||
'secret': secret
|
||||
}
|
||||
|
||||
def generate_plaintext_traceback(self):
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
werkzeug.debug.utils
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Various other utilities.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD.
|
||||
"""
|
||||
from os.path import join, dirname
|
||||
from werkzeug.templates import Template
|
||||
|
||||
|
||||
def get_template(filename):
|
||||
return Template.from_file(join(dirname(__file__), 'templates', filename))
|
||||
|
||||
|
||||
def render_template(template_filename, **context):
|
||||
return get_template(template_filename).render(**context)
|
||||
@@ -54,7 +54,7 @@
|
||||
return e
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
@@ -279,6 +279,21 @@ class RequestTimeout(HTTPException):
|
||||
)
|
||||
|
||||
|
||||
class Conflict(HTTPException):
|
||||
"""*409* `Conflict`
|
||||
|
||||
Raise to signal that a request cannot be completed because it conflicts
|
||||
with the current state on the server.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 409
|
||||
description = (
|
||||
'<p>A conflict happened while processing the request. The resource '
|
||||
'might have been modified while the request was being processed.'
|
||||
)
|
||||
|
||||
|
||||
class Gone(HTTPException):
|
||||
"""*410* `Gone`
|
||||
|
||||
@@ -355,6 +370,47 @@ class UnsupportedMediaType(HTTPException):
|
||||
)
|
||||
|
||||
|
||||
class RequestedRangeNotSatisfiable(HTTPException):
|
||||
"""*416* `Requested Range Not Satisfiable`
|
||||
|
||||
The client asked for a part of the file that lies beyond the end
|
||||
of the file.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 416
|
||||
description = (
|
||||
'<p>The server cannot provide the requested range.'
|
||||
)
|
||||
|
||||
|
||||
class ExpectationFailed(HTTPException):
|
||||
"""*417* `Expectation Failed`
|
||||
|
||||
The server cannot meet the requirements of the Expect request-header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 417
|
||||
description = (
|
||||
'<p>The server could not meet the requirements of the Expect header'
|
||||
)
|
||||
|
||||
|
||||
class ImATeapot(HTTPException):
|
||||
"""*418* `I'm a teapot`
|
||||
|
||||
The server should return this if it is a teapot and someone attempted
|
||||
to brew coffee with it.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
code = 418
|
||||
description = (
|
||||
'<p>This server is a teapot, not a coffee machine'
|
||||
)
|
||||
|
||||
|
||||
class InternalServerError(HTTPException):
|
||||
"""*500* `Internal Server Error`
|
||||
|
||||
@@ -456,5 +512,10 @@ class Aborter(object):
|
||||
abort = Aborter()
|
||||
|
||||
|
||||
#: an exception that is used internally to signal both a key error and a
|
||||
#: bad request. Used by a lot of the datastructures.
|
||||
BadRequestKeyError = BadRequest.wrap(KeyError)
|
||||
|
||||
|
||||
# imported here because of circular dependencies of werkzeug.utils
|
||||
from werkzeug.utils import escape
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
This module implements the form parsing. It supports url-encoded forms
|
||||
as well as non-nested multipart uploads.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
@@ -42,12 +42,12 @@ def default_stream_factory(total_content_length, filename, content_type,
|
||||
|
||||
|
||||
def parse_form_data(environ, stream_factory=None, charset='utf-8',
|
||||
errors='ignore', max_form_memory_size=None,
|
||||
errors='replace', max_form_memory_size=None,
|
||||
max_content_length=None, cls=None,
|
||||
silent=True):
|
||||
"""Parse the form data in the environ and return it as tuple in the form
|
||||
``(stream, form, files)``. You should only call this method if the
|
||||
transport method is `POST` or `PUT`.
|
||||
transport method is `POST`, `PUT`, or `PATCH`.
|
||||
|
||||
If the mimetype of the data transmitted is `multipart/form-data` the
|
||||
files multidict will be filled with `FileStorage` objects. If the
|
||||
@@ -169,7 +169,7 @@ def is_valid_multipart_boundary(boundary):
|
||||
|
||||
|
||||
def parse_multipart(file, boundary, content_length, stream_factory=None,
|
||||
charset='utf-8', errors='ignore', buffer_size=10 * 1024,
|
||||
charset='utf-8', errors='replace', buffer_size=10 * 1024,
|
||||
max_form_memory_size=None):
|
||||
"""Parse a multipart/form-data stream. This is invoked by
|
||||
:func:`utils.parse_form_data` if the content type matches. Currently it
|
||||
|
||||
@@ -13,11 +13,11 @@
|
||||
module.
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import inspect
|
||||
from time import time
|
||||
try:
|
||||
from email.utils import parsedate_tz
|
||||
except ImportError: # pragma: no cover
|
||||
@@ -32,7 +32,8 @@ except ImportError: # pragma: no cover
|
||||
|
||||
#: HTTP_STATUS_CODES is "exported" from this module.
|
||||
#: XXX: move to werkzeug.consts or something
|
||||
from werkzeug._internal import HTTP_STATUS_CODES, _dump_date
|
||||
from werkzeug._internal import HTTP_STATUS_CODES, _dump_date, \
|
||||
_ExtendedCookie, _ExtendedMorsel, _decode_unicode
|
||||
|
||||
|
||||
_accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
|
||||
@@ -284,13 +285,14 @@ def parse_cache_control_header(value, on_update=None, cls=None):
|
||||
|
||||
.. versionadded:: 0.5
|
||||
The `cls` was added. If not specified an immutable
|
||||
:class:`RequestCacheControl` is returned.
|
||||
:class:`~werkzeug.datastructures.RequestCacheControl` is returned.
|
||||
|
||||
:param value: a cache control header to be parsed.
|
||||
:param on_update: an optional callable that is called every time a
|
||||
value on the :class:`CacheControl` object is changed.
|
||||
:param on_update: an optional callable that is called every time a value
|
||||
on the :class:`~werkzeug.datastructures.CacheControl`
|
||||
object is changed.
|
||||
:param cls: the class for the returned object. By default
|
||||
:class:`RequestCacheControl` is used.
|
||||
:class:`~werkzeug.datastructures.RequestCacheControl` is used.
|
||||
:return: a `cls` object.
|
||||
"""
|
||||
if cls is None:
|
||||
@@ -301,7 +303,8 @@ def parse_cache_control_header(value, on_update=None, cls=None):
|
||||
|
||||
|
||||
def parse_set_header(value, on_update=None):
|
||||
"""Parse a set-like header and return a :class:`HeaderSet` object:
|
||||
"""Parse a set-like header and return a
|
||||
:class:`~werkzeug.datastructures.HeaderSet` object:
|
||||
|
||||
>>> hs = parse_set_header('token, "quoted value"')
|
||||
|
||||
@@ -320,8 +323,9 @@ def parse_set_header(value, on_update=None):
|
||||
|
||||
:param value: a set header to be parsed.
|
||||
:param on_update: an optional callable that is called every time a
|
||||
value on the :class:`HeaderSet` object is changed.
|
||||
:return: a :class:`HeaderSet`
|
||||
value on the :class:`~werkzeug.datastructures.HeaderSet`
|
||||
object is changed.
|
||||
:return: a :class:`~werkzeug.datastructures.HeaderSet`
|
||||
"""
|
||||
if not value:
|
||||
return HeaderSet(None, on_update)
|
||||
@@ -331,10 +335,11 @@ def parse_set_header(value, on_update=None):
|
||||
def parse_authorization_header(value):
|
||||
"""Parse an HTTP basic/digest authorization header transmitted by the web
|
||||
browser. The return value is either `None` if the header was invalid or
|
||||
not given, otherwise an :class:`Authorization` object.
|
||||
not given, otherwise an :class:`~werkzeug.datastructures.Authorization`
|
||||
object.
|
||||
|
||||
:param value: the authorization header to parse.
|
||||
:return: a :class:`Authorization` object or `None`.
|
||||
:return: a :class:`~werkzeug.datastructures.Authorization` object or `None`.
|
||||
"""
|
||||
if not value:
|
||||
return
|
||||
@@ -360,13 +365,14 @@ def parse_authorization_header(value):
|
||||
|
||||
|
||||
def parse_www_authenticate_header(value, on_update=None):
|
||||
"""Parse an HTTP WWW-Authenticate header into a :class:`WWWAuthenticate`
|
||||
object.
|
||||
"""Parse an HTTP WWW-Authenticate header into a
|
||||
:class:`~werkzeug.datastructures.WWWAuthenticate` object.
|
||||
|
||||
:param value: a WWW-Authenticate header to parse.
|
||||
:param on_update: an optional callable that is called every time a
|
||||
value on the :class:`WWWAuthenticate` object is changed.
|
||||
:return: a :class:`WWWAuthenticate` object.
|
||||
:param on_update: an optional callable that is called every time a value
|
||||
on the :class:`~werkzeug.datastructures.WWWAuthenticate`
|
||||
object is changed.
|
||||
:return: a :class:`~werkzeug.datastructures.WWWAuthenticate` object.
|
||||
"""
|
||||
if not value:
|
||||
return WWWAuthenticate(on_update=on_update)
|
||||
@@ -379,6 +385,109 @@ def parse_www_authenticate_header(value, on_update=None):
|
||||
on_update)
|
||||
|
||||
|
||||
def parse_if_range_header(value):
|
||||
"""Parses an if-range header which can be an etag or a date. Returns
|
||||
a :class:`~werkzeug.datastructures.IfRange` object.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
if not value:
|
||||
return IfRange()
|
||||
date = parse_date(value)
|
||||
if date is not None:
|
||||
return IfRange(date=date)
|
||||
# drop weakness information
|
||||
return IfRange(unquote_etag(value)[0])
|
||||
|
||||
|
||||
def parse_range_header(value, make_inclusive=True):
|
||||
"""Parses a range header into a :class:`~werkzeug.datastructures.Range`
|
||||
object. If the header is missing or malformed `None` is returned.
|
||||
`ranges` is a list of ``(start, stop)`` tuples where the ranges are
|
||||
non-inclusive.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
if not value or '=' not in value:
|
||||
return None
|
||||
|
||||
ranges = []
|
||||
last_end = 0
|
||||
units, rng = value.split('=', 1)
|
||||
units = units.strip().lower()
|
||||
|
||||
for item in rng.split(','):
|
||||
item = item.strip()
|
||||
if '-' not in item:
|
||||
return None
|
||||
if item.startswith('-'):
|
||||
if last_end < 0:
|
||||
return None
|
||||
begin = int(item)
|
||||
end = None
|
||||
last_end = -1
|
||||
elif '-' in item:
|
||||
begin, end = item.split('-', 1)
|
||||
begin = int(begin)
|
||||
if begin < last_end or last_end < 0:
|
||||
return None
|
||||
if end:
|
||||
end = int(end) + 1
|
||||
if begin >= end:
|
||||
return None
|
||||
else:
|
||||
end = None
|
||||
last_end = end
|
||||
ranges.append((begin, end))
|
||||
|
||||
return Range(units, ranges)
|
||||
|
||||
|
||||
def parse_content_range_header(value, on_update=None):
|
||||
"""Parses a range header into a
|
||||
:class:`~werkzeug.datastructures.ContentRange` object or `None` if
|
||||
parsing is not possible.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
:param value: a content range header to be parsed.
|
||||
:param on_update: an optional callable that is called every time a value
|
||||
on the :class:`~werkzeug.datastructures.ContentRange`
|
||||
object is changed.
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
try:
|
||||
units, rangedef = (value or '').strip().split(None, 1)
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
if '/' not in rangedef:
|
||||
return None
|
||||
rng, length = rangedef.split('/', 1)
|
||||
if length == '*':
|
||||
length = None
|
||||
elif length.isdigit():
|
||||
length = int(length)
|
||||
else:
|
||||
return None
|
||||
|
||||
if rng == '*':
|
||||
return ContentRange(units, None, None, length, on_update=on_update)
|
||||
elif '-' not in rng:
|
||||
return None
|
||||
|
||||
start, stop = rng.split('-', 1)
|
||||
try:
|
||||
start = int(start)
|
||||
stop = int(stop) + 1
|
||||
except ValueError:
|
||||
return None
|
||||
|
||||
if is_byte_range_valid(start, stop, length):
|
||||
return ContentRange(units, start, stop, length, on_update=on_update)
|
||||
|
||||
|
||||
def quote_etag(etag, weak=False):
|
||||
"""Quote an etag.
|
||||
|
||||
@@ -420,7 +529,7 @@ def parse_etags(value):
|
||||
"""Parse an etag header.
|
||||
|
||||
:param value: the tag header to parse
|
||||
:return: an :class:`ETags` object.
|
||||
:return: an :class:`~werkzeug.datastructures.ETags` object.
|
||||
"""
|
||||
if not value:
|
||||
return ETags()
|
||||
@@ -532,6 +641,12 @@ def is_resource_modified(environ, etag=None, data=None, last_modified=None):
|
||||
unmodified = False
|
||||
if isinstance(last_modified, basestring):
|
||||
last_modified = parse_date(last_modified)
|
||||
|
||||
# ensure that microsecond is zero because the HTTP spec does not transmit
|
||||
# that either and we might have some false positives. See issue #39
|
||||
if last_modified is not None:
|
||||
last_modified = last_modified.replace(microsecond=0)
|
||||
|
||||
modified_since = parse_date(environ.get('HTTP_IF_MODIFIED_SINCE'))
|
||||
|
||||
if modified_since and last_modified and last_modified <= modified_since:
|
||||
@@ -596,12 +711,129 @@ def is_hop_by_hop_header(header):
|
||||
return header.lower() in _hop_by_pop_headers
|
||||
|
||||
|
||||
def parse_cookie(header, charset='utf-8', errors='replace',
|
||||
cls=None):
|
||||
"""Parse a cookie. Either from a string or WSGI environ.
|
||||
|
||||
Per default encoding errors are ignored. If you want a different behavior
|
||||
you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a
|
||||
:exc:`HTTPUnicodeError` is raised.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
This function now returns a :class:`TypeConversionDict` instead of a
|
||||
regular dict. The `cls` parameter was added.
|
||||
|
||||
:param header: the header to be used to parse the cookie. Alternatively
|
||||
this can be a WSGI environment.
|
||||
:param charset: the charset for the cookie values.
|
||||
:param errors: the error behavior for the charset decoding.
|
||||
:param cls: an optional dict class to use. If this is not specified
|
||||
or `None` the default :class:`TypeConversionDict` is
|
||||
used.
|
||||
"""
|
||||
if isinstance(header, dict):
|
||||
header = header.get('HTTP_COOKIE', '')
|
||||
if cls is None:
|
||||
cls = TypeConversionDict
|
||||
cookie = _ExtendedCookie()
|
||||
cookie.load(header)
|
||||
result = {}
|
||||
|
||||
# decode to unicode and skip broken items. Our extended morsel
|
||||
# and extended cookie will catch CookieErrors and convert them to
|
||||
# `None` items which we have to skip here.
|
||||
for key, value in cookie.iteritems():
|
||||
if value.value is not None:
|
||||
result[key] = _decode_unicode(unquote_header_value(value.value),
|
||||
charset, errors)
|
||||
|
||||
return cls(result)
|
||||
|
||||
|
||||
def dump_cookie(key, value='', max_age=None, expires=None, path='/',
|
||||
domain=None, secure=None, httponly=False, charset='utf-8',
|
||||
sync_expires=True):
|
||||
"""Creates a new Set-Cookie header without the ``Set-Cookie`` prefix
|
||||
The parameters are the same as in the cookie Morsel object in the
|
||||
Python standard library but it accepts unicode data, too.
|
||||
|
||||
:param max_age: should be a number of seconds, or `None` (default) if
|
||||
the cookie should last only as long as the client's
|
||||
browser session. Additionally `timedelta` objects
|
||||
are accepted, too.
|
||||
: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: Use this 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 secure: 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.
|
||||
:param charset: the encoding for unicode values.
|
||||
:param sync_expires: automatically set expires if max_age is defined
|
||||
but expires not.
|
||||
"""
|
||||
try:
|
||||
key = str(key)
|
||||
except UnicodeError:
|
||||
raise TypeError('invalid key %r' % key)
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode(charset)
|
||||
value = quote_header_value(value)
|
||||
morsel = _ExtendedMorsel(key, value)
|
||||
if isinstance(max_age, timedelta):
|
||||
max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds
|
||||
if expires is not None:
|
||||
if not isinstance(expires, basestring):
|
||||
expires = cookie_date(expires)
|
||||
morsel['expires'] = expires
|
||||
elif max_age is not None and sync_expires:
|
||||
morsel['expires'] = cookie_date(time() + max_age)
|
||||
if domain and ':' in domain:
|
||||
# The port part of the domain should NOT be used. Strip it
|
||||
domain = domain.split(':', 1)[0]
|
||||
if domain:
|
||||
assert '.' in domain, (
|
||||
"Setting \"domain\" for a cookie on a server running localy (ex: "
|
||||
"localhost) is not supportted by complying browsers. You should "
|
||||
"have something like: \"127.0.0.1 localhost dev.localhost\" on "
|
||||
"your hosts file and then point your server to run on "
|
||||
"\"dev.localhost\" and also set \"domain\" for \"dev.localhost\""
|
||||
)
|
||||
for k, v in (('path', path), ('domain', domain), ('secure', secure),
|
||||
('max-age', max_age), ('httponly', httponly)):
|
||||
if v is not None and v is not False:
|
||||
morsel[k] = str(v)
|
||||
return morsel.output(header='').lstrip()
|
||||
|
||||
|
||||
def is_byte_range_valid(start, stop, length):
|
||||
"""Checks if a given byte content range is valid for the given length.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
if (start is None) != (stop is None):
|
||||
return False
|
||||
elif start is None:
|
||||
return length is None or length >= 0
|
||||
elif length is None:
|
||||
return 0 <= start < stop
|
||||
elif start >= stop:
|
||||
return False
|
||||
return 0 <= start < length
|
||||
|
||||
|
||||
# circular dependency fun
|
||||
from werkzeug.datastructures import Headers, Accept, RequestCacheControl, \
|
||||
ResponseCacheControl, HeaderSet, ETags, Authorization, \
|
||||
WWWAuthenticate
|
||||
from werkzeug.datastructures import Accept, HeaderSet, ETags, Authorization, \
|
||||
WWWAuthenticate, TypeConversionDict, IfRange, Range, ContentRange, \
|
||||
RequestCacheControl
|
||||
|
||||
|
||||
# DEPRECATED
|
||||
# backwards compatible imports
|
||||
from werkzeug.datastructures import MIMEAccept, CharsetAccept, LanguageAccept
|
||||
from werkzeug.datastructures import MIMEAccept, CharsetAccept, \
|
||||
LanguageAccept, Headers
|
||||
|
||||
@@ -5,19 +5,13 @@
|
||||
|
||||
This module implements context-local objects.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
try:
|
||||
from greenlet import getcurrent as get_current_greenlet
|
||||
except ImportError: # pragma: no cover
|
||||
try:
|
||||
from py.magic import greenlet
|
||||
get_current_greenlet = greenlet.getcurrent
|
||||
del greenlet
|
||||
except Exception:
|
||||
# catch all, py.* fails with so many different errors.
|
||||
get_current_greenlet = int
|
||||
get_current_greenlet = int
|
||||
try:
|
||||
from thread import get_ident as get_current_thread, allocate_lock
|
||||
except ImportError: # pragma: no cover
|
||||
|
||||
@@ -14,7 +14,7 @@ r"""
|
||||
This module was introduced in 0.6.1 and is not a public interface.
|
||||
It might become one in later versions of Werkzeug.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
|
||||
@@ -92,15 +92,13 @@
|
||||
method is raised.
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
Thomas Johansson.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import posixpath
|
||||
from pprint import pformat
|
||||
from urlparse import urljoin
|
||||
from itertools import izip
|
||||
|
||||
from werkzeug.urls import url_encode, url_decode, url_quote
|
||||
from werkzeug.utils import redirect, format_string
|
||||
@@ -121,6 +119,56 @@ _rule_re = re.compile(r'''
|
||||
>
|
||||
''', re.VERBOSE)
|
||||
_simple_rule_re = re.compile(r'<([^>]+)>')
|
||||
_converter_args_re = re.compile(r'''
|
||||
((?P<name>\w+)\s*=\s*)?
|
||||
(?P<value>
|
||||
True|False|
|
||||
\d+.\d+|
|
||||
\d+.|
|
||||
\d+|
|
||||
\w+|
|
||||
[urUR]?(?P<stringval>"[^"]*?"|'[^']*')
|
||||
)\s*,
|
||||
''', re.VERBOSE|re.UNICODE)
|
||||
|
||||
|
||||
_PYTHON_CONSTANTS = {
|
||||
'None': None,
|
||||
'True': True,
|
||||
'False': False
|
||||
}
|
||||
|
||||
|
||||
def _pythonize(value):
|
||||
if value in _PYTHON_CONSTANTS:
|
||||
return _PYTHON_CONSTANTS[value]
|
||||
for convert in int, float:
|
||||
try:
|
||||
return convert(value)
|
||||
except ValueError:
|
||||
pass
|
||||
if value[:1] == value[-1:] and value[0] in '"\'':
|
||||
value = value[1:-1]
|
||||
return unicode(value)
|
||||
|
||||
|
||||
def parse_converter_args(argstr):
|
||||
argstr += ','
|
||||
args = []
|
||||
kwargs = {}
|
||||
|
||||
for item in _converter_args_re.finditer(argstr):
|
||||
value = item.group('stringval')
|
||||
if value is None:
|
||||
value = item.group('value')
|
||||
value = _pythonize(value)
|
||||
if not item.group('name'):
|
||||
args.append(value)
|
||||
else:
|
||||
name = item.group('name')
|
||||
kwargs[name] = value
|
||||
|
||||
return tuple(args), kwargs
|
||||
|
||||
|
||||
def parse_rule(rule):
|
||||
@@ -164,8 +212,7 @@ def get_converter(map, name, args):
|
||||
if not name in map.converters:
|
||||
raise LookupError('the converter %r does not exist' % name)
|
||||
if args:
|
||||
storage = type('_Storage', (), {'__getitem__': lambda s, x: x})()
|
||||
args, kwargs = eval(u'(lambda *a, **kw: (a, kw))(%s)' % args, {}, storage)
|
||||
args, kwargs = parse_converter_args(args)
|
||||
else:
|
||||
args = ()
|
||||
kwargs = {}
|
||||
@@ -200,6 +247,13 @@ class RequestSlash(RoutingException):
|
||||
"""Internal exception."""
|
||||
|
||||
|
||||
class RequestAliasRedirect(RoutingException):
|
||||
"""This rule is an alias and wants to redirect to the canonical URL."""
|
||||
|
||||
def __init__(self, matched_values):
|
||||
self.matched_values = matched_values
|
||||
|
||||
|
||||
class BuildError(RoutingException, LookupError):
|
||||
"""Raised if the build system cannot find a URL for an endpoint with the
|
||||
values provided.
|
||||
@@ -353,7 +407,7 @@ class RuleTemplateFactory(RuleFactory):
|
||||
for rulefactory in self.rules:
|
||||
for rule in rulefactory.get_rules(map):
|
||||
new_defaults = subdomain = None
|
||||
if rule.defaults is not None:
|
||||
if rule.defaults:
|
||||
new_defaults = {}
|
||||
for key, value in rule.defaults.iteritems():
|
||||
if isinstance(value, basestring):
|
||||
@@ -475,11 +529,23 @@ class Rule(RuleFactory):
|
||||
Keep in mind that the URL will be joined against the URL root of the
|
||||
script so don't use a leading slash on the target URL unless you
|
||||
really mean root of that domain.
|
||||
|
||||
`alias`
|
||||
If enabled this rule serves as an alias for another rule with the same
|
||||
endpoint and arguments.
|
||||
|
||||
`host`
|
||||
If provided and the URL map has host matching enabled this can be
|
||||
used to provide a match rule for the whole host. This also means
|
||||
that the subdomain feature is disabled.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
The `alias` and `host` parameters were added.
|
||||
"""
|
||||
|
||||
def __init__(self, string, defaults=None, subdomain=None, methods=None,
|
||||
build_only=False, endpoint=None, strict_slashes=None,
|
||||
redirect_to=None):
|
||||
redirect_to=None, alias=False, host=None):
|
||||
if not string.startswith('/'):
|
||||
raise ValueError('urls must start with a leading slash')
|
||||
self.rule = string
|
||||
@@ -488,8 +554,10 @@ class Rule(RuleFactory):
|
||||
self.map = None
|
||||
self.strict_slashes = strict_slashes
|
||||
self.subdomain = subdomain
|
||||
self.host = host
|
||||
self.defaults = defaults
|
||||
self.build_only = build_only
|
||||
self.alias = alias
|
||||
if methods is None:
|
||||
self.methods = None
|
||||
else:
|
||||
@@ -497,10 +565,9 @@ class Rule(RuleFactory):
|
||||
if 'HEAD' not in self.methods and 'GET' in self.methods:
|
||||
self.methods.add('HEAD')
|
||||
self.endpoint = endpoint
|
||||
self.greediness = 0
|
||||
self.redirect_to = redirect_to
|
||||
|
||||
if defaults is not None:
|
||||
if defaults:
|
||||
self.arguments = set(map(str, defaults))
|
||||
else:
|
||||
self.arguments = set()
|
||||
@@ -510,11 +577,11 @@ class Rule(RuleFactory):
|
||||
"""Return an unbound copy of this rule. This can be useful if you
|
||||
want to reuse an already bound URL for another map."""
|
||||
defaults = None
|
||||
if self.defaults is not None:
|
||||
if self.defaults:
|
||||
defaults = dict(self.defaults)
|
||||
return Rule(self.rule, defaults, self.subdomain, self.methods,
|
||||
self.build_only, self.endpoint, self.strict_slashes,
|
||||
self.redirect_to)
|
||||
self.redirect_to, self.alias, self.host)
|
||||
|
||||
def get_rules(self, map):
|
||||
yield self
|
||||
@@ -546,42 +613,54 @@ class Rule(RuleFactory):
|
||||
def compile(self):
|
||||
"""Compiles the regular expression and stores it."""
|
||||
assert self.map is not None, 'rule not bound'
|
||||
rule = self.subdomain + '|' + (self.is_leaf and self.rule
|
||||
or self.rule.rstrip('/'))
|
||||
|
||||
if self.map.host_matching:
|
||||
domain_rule = self.host or ''
|
||||
else:
|
||||
domain_rule = self.subdomain or ''
|
||||
|
||||
self._trace = []
|
||||
self._converters = {}
|
||||
self._weights = []
|
||||
|
||||
regex_parts = []
|
||||
for converter, arguments, variable in parse_rule(rule):
|
||||
if converter is None:
|
||||
regex_parts.append(re.escape(variable))
|
||||
self._trace.append((False, variable))
|
||||
self._weights.append(len(variable))
|
||||
else:
|
||||
convobj = get_converter(self.map, converter, arguments)
|
||||
regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
|
||||
self._converters[variable] = convobj
|
||||
self._trace.append((True, variable))
|
||||
self._weights.append(convobj.weight)
|
||||
self.arguments.add(str(variable))
|
||||
if convobj.is_greedy:
|
||||
self.greediness += 1
|
||||
|
||||
def _build_regex(rule):
|
||||
for converter, arguments, variable in parse_rule(rule):
|
||||
if converter is None:
|
||||
regex_parts.append(re.escape(variable))
|
||||
self._trace.append((False, variable))
|
||||
for part in variable.split('/'):
|
||||
if part:
|
||||
self._weights.append((0, -len(part)))
|
||||
else:
|
||||
convobj = get_converter(self.map, converter, arguments)
|
||||
regex_parts.append('(?P<%s>%s)' % (variable, convobj.regex))
|
||||
self._converters[variable] = convobj
|
||||
self._trace.append((True, variable))
|
||||
self._weights.append((1, convobj.weight))
|
||||
self.arguments.add(str(variable))
|
||||
|
||||
_build_regex(domain_rule)
|
||||
regex_parts.append('\\|')
|
||||
self._trace.append((False, '|'))
|
||||
_build_regex(self.is_leaf and self.rule or self.rule.rstrip('/'))
|
||||
if not self.is_leaf:
|
||||
self._trace.append((False, '/'))
|
||||
|
||||
if not self.build_only:
|
||||
regex = r'^%s%s$' % (
|
||||
u''.join(regex_parts),
|
||||
(not self.is_leaf or not self.strict_slashes) and \
|
||||
'(?<!/)(?P<__suffix__>/?)' or ''
|
||||
)
|
||||
self._regex = re.compile(regex, re.UNICODE)
|
||||
if self.build_only:
|
||||
return
|
||||
regex = r'^%s%s$' % (
|
||||
u''.join(regex_parts),
|
||||
(not self.is_leaf or not self.strict_slashes) and \
|
||||
'(?<!/)(?P<__suffix__>/?)' or ''
|
||||
)
|
||||
self._regex = re.compile(regex, re.UNICODE)
|
||||
|
||||
def match(self, path):
|
||||
"""Check if the rule matches a given path. Path is a string in the
|
||||
form ``"subdomain|/path(method)"`` and is assembled by the map.
|
||||
form ``"subdomain|/path(method)"`` and is assembled by the map. If
|
||||
the map is doing host matching the subdomain part will be the host
|
||||
instead.
|
||||
|
||||
If the rule matches a dict with the converted values is returned,
|
||||
otherwise the return value is `None`.
|
||||
@@ -611,8 +690,12 @@ class Rule(RuleFactory):
|
||||
except ValidationError:
|
||||
return
|
||||
result[str(name)] = value
|
||||
if self.defaults is not None:
|
||||
if self.defaults:
|
||||
result.update(self.defaults)
|
||||
|
||||
if self.alias and self.map.redirect_defaults:
|
||||
raise RequestAliasRedirect(result)
|
||||
|
||||
return result
|
||||
|
||||
def build(self, values, append_unknown=True):
|
||||
@@ -633,7 +716,7 @@ class Rule(RuleFactory):
|
||||
processed.add(data)
|
||||
else:
|
||||
add(data)
|
||||
subdomain, url = (u''.join(tmp)).split('|', 1)
|
||||
domain_part, url = (u''.join(tmp)).split('|', 1)
|
||||
|
||||
if append_unknown:
|
||||
query_vars = MultiDict(values)
|
||||
@@ -646,14 +729,14 @@ class Rule(RuleFactory):
|
||||
sort=self.map.sort_parameters,
|
||||
key=self.map.sort_key)
|
||||
|
||||
return subdomain, url
|
||||
return domain_part, url
|
||||
|
||||
def provides_defaults_for(self, rule):
|
||||
"""Check if this rule has defaults for a given rule.
|
||||
|
||||
:internal:
|
||||
"""
|
||||
return not self.build_only and self.defaults is not None and \
|
||||
return not self.build_only and self.defaults and \
|
||||
self.endpoint == rule.endpoint and self != rule and \
|
||||
self.arguments == rule.arguments
|
||||
|
||||
@@ -662,83 +745,52 @@ class Rule(RuleFactory):
|
||||
|
||||
:internal:
|
||||
"""
|
||||
if method is not None:
|
||||
if self.methods is not None and method not in self.methods:
|
||||
# if a method was given explicitly and that method is not supported
|
||||
# by this rule, this rule is not suitable.
|
||||
if method is not None and self.methods is not None \
|
||||
and method not in self.methods:
|
||||
return False
|
||||
|
||||
defaults = self.defaults or ()
|
||||
|
||||
# all arguments required must be either in the defaults dict or
|
||||
# the value dictionary otherwise it's not suitable
|
||||
for key in self.arguments:
|
||||
if key not in defaults and key not in values:
|
||||
return False
|
||||
|
||||
valueset = set(values)
|
||||
|
||||
for key in self.arguments - set(self.defaults or ()):
|
||||
if key not in values:
|
||||
return False
|
||||
|
||||
if self.arguments.issubset(valueset):
|
||||
if self.defaults is None:
|
||||
return True
|
||||
for key, value in self.defaults.iteritems():
|
||||
if value != values[key]:
|
||||
# in case defaults are given we ensure taht either the value was
|
||||
# skipped or the value is the same as the default value.
|
||||
if defaults:
|
||||
for key, value in defaults.iteritems():
|
||||
if key in values and value != values[key]:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def match_compare(self, other):
|
||||
"""Compare this object with another one for matching.
|
||||
def match_compare_key(self):
|
||||
"""The match compare key for sorting.
|
||||
|
||||
Current implementation:
|
||||
|
||||
1. rules without any arguments come first for performance
|
||||
reasons only as we expect them to match faster and some
|
||||
common ones usually don't have any arguments (index pages etc.)
|
||||
2. The more complex rules come first so the second argument is the
|
||||
negative length of the number of weights.
|
||||
3. lastly we order by the actual weights.
|
||||
|
||||
:internal:
|
||||
"""
|
||||
for sw, ow in izip(self._weights, other._weights):
|
||||
if sw > ow:
|
||||
return -1
|
||||
elif sw < ow:
|
||||
return 1
|
||||
if len(self._weights) > len(other._weights):
|
||||
return -1
|
||||
if len(self._weights) < len(other._weights):
|
||||
return 1
|
||||
if not other.arguments and self.arguments:
|
||||
return 1
|
||||
elif other.arguments and not self.arguments:
|
||||
return -1
|
||||
elif other.defaults is None and self.defaults is not None:
|
||||
return 1
|
||||
elif other.defaults is not None and self.defaults is None:
|
||||
return -1
|
||||
elif self.greediness > other.greediness:
|
||||
return -1
|
||||
elif self.greediness < other.greediness:
|
||||
return 1
|
||||
elif len(self.arguments) > len(other.arguments):
|
||||
return 1
|
||||
elif len(self.arguments) < len(other.arguments):
|
||||
return -1
|
||||
return 1
|
||||
return bool(self.arguments), -len(self._weights), self._weights
|
||||
|
||||
def build_compare(self, other):
|
||||
"""Compare this object with another one for building.
|
||||
def build_compare_key(self):
|
||||
"""The build compare key for sorting.
|
||||
|
||||
:internal:
|
||||
"""
|
||||
if not other.arguments and self.arguments:
|
||||
return -1
|
||||
elif other.arguments and not self.arguments:
|
||||
return 1
|
||||
elif other.defaults is None and self.defaults is not None:
|
||||
return -1
|
||||
elif other.defaults is not None and self.defaults is None:
|
||||
return 1
|
||||
elif self.provides_defaults_for(other):
|
||||
return -1
|
||||
elif other.provides_defaults_for(self):
|
||||
return 1
|
||||
elif self.greediness > other.greediness:
|
||||
return -1
|
||||
elif self.greediness < other.greediness:
|
||||
return 1
|
||||
elif len(self.arguments) > len(other.arguments):
|
||||
return -1
|
||||
elif len(self.arguments) < len(other.arguments):
|
||||
return 1
|
||||
return -1
|
||||
return self.alias and 1 or 0, -len(self.arguments), \
|
||||
-len(self.defaults or ())
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.__class__ is other.__class__ and \
|
||||
@@ -776,7 +828,6 @@ class Rule(RuleFactory):
|
||||
class BaseConverter(object):
|
||||
"""Base class for all converters."""
|
||||
regex = '[^/]+'
|
||||
is_greedy = False
|
||||
weight = 100
|
||||
|
||||
def __init__(self, map):
|
||||
@@ -825,9 +876,9 @@ class UnicodeConverter(BaseConverter):
|
||||
|
||||
class AnyConverter(BaseConverter):
|
||||
"""Matches one of the items provided. Items can either be Python
|
||||
identifiers or unicode strings::
|
||||
identifiers or strings::
|
||||
|
||||
Rule('/<any(about, help, imprint, u"class"):page_name>')
|
||||
Rule('/<any(about, help, imprint, class, "foo,bar"):page_name>')
|
||||
|
||||
:param map: the :class:`Map`.
|
||||
:param items: this function accepts the possible items as positional
|
||||
@@ -849,8 +900,7 @@ class PathConverter(BaseConverter):
|
||||
:param map: the :class:`Map`.
|
||||
"""
|
||||
regex = '[^/].*?'
|
||||
is_greedy = True
|
||||
weight = 50
|
||||
weight = 200
|
||||
|
||||
|
||||
class NumberConverter(BaseConverter):
|
||||
@@ -858,6 +908,7 @@ class NumberConverter(BaseConverter):
|
||||
|
||||
:internal:
|
||||
"""
|
||||
weight = 50
|
||||
|
||||
def __init__(self, map, fixed_digits=0, min=None, max=None):
|
||||
BaseConverter.__init__(self, map)
|
||||
@@ -951,12 +1002,16 @@ class Map(object):
|
||||
See `url_encode` for more details.
|
||||
:param sort_key: The sort key function for `url_encode`.
|
||||
:param encoding_errors: the error method to use for decoding
|
||||
:param host_matching: if set to `True` it enables the host matching
|
||||
feature and disables the subdomain one. If
|
||||
enabled the `host` parameter to rules is used
|
||||
instead of the `subdomain` one.
|
||||
|
||||
.. versionadded:: 0.5
|
||||
`sort_parameters` and `sort_key` was added.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
`encoding_errors` was added.
|
||||
`encoding_errors` and `host_matching` was added.
|
||||
"""
|
||||
|
||||
#: .. versionadded:: 0.6
|
||||
@@ -966,7 +1021,7 @@ class Map(object):
|
||||
def __init__(self, rules=None, default_subdomain='', charset='utf-8',
|
||||
strict_slashes=True, redirect_defaults=True,
|
||||
converters=None, sort_parameters=False, sort_key=None,
|
||||
encoding_errors='ignore'):
|
||||
encoding_errors='replace', host_matching=False):
|
||||
self._rules = []
|
||||
self._rules_by_endpoint = {}
|
||||
self._remap = True
|
||||
@@ -976,6 +1031,7 @@ class Map(object):
|
||||
self.encoding_errors = encoding_errors
|
||||
self.strict_slashes = strict_slashes
|
||||
self.redirect_defaults = redirect_defaults
|
||||
self.host_matching = host_matching
|
||||
|
||||
self.converters = self.default_converters.copy()
|
||||
if converters:
|
||||
@@ -1014,6 +1070,7 @@ class Map(object):
|
||||
are returned.
|
||||
:return: an iterator
|
||||
"""
|
||||
self.update()
|
||||
if endpoint is not None:
|
||||
return iter(self._rules_by_endpoint[endpoint])
|
||||
return iter(self._rules)
|
||||
@@ -1051,8 +1108,15 @@ class Map(object):
|
||||
|
||||
.. versionadded:: 0.7
|
||||
`query_args` added
|
||||
|
||||
.. versionadded:: 0.8
|
||||
`query_args` can now also be a string.
|
||||
"""
|
||||
if subdomain is None:
|
||||
if self.host_matching:
|
||||
if subdomain is not None:
|
||||
raise RuntimeError('host matching enabled and a '
|
||||
'subdomain was provided')
|
||||
elif subdomain is None:
|
||||
subdomain = self.default_subdomain
|
||||
if script_name is None:
|
||||
script_name = '/'
|
||||
@@ -1098,7 +1162,7 @@ class Map(object):
|
||||
if (environ['wsgi.url_scheme'], environ['SERVER_PORT']) not \
|
||||
in (('https', '443'), ('http', '80')):
|
||||
server_name += ':' + environ['SERVER_PORT']
|
||||
elif subdomain is None:
|
||||
elif subdomain is None and not self.host_matching:
|
||||
if 'HTTP_HOST' in environ:
|
||||
wsgi_server_name = environ.get('HTTP_HOST')
|
||||
else:
|
||||
@@ -1117,21 +1181,22 @@ class Map(object):
|
||||
subdomain = '.'.join(filter(None, cur_server_name[:offset]))
|
||||
return Map.bind(self, server_name, environ.get('SCRIPT_NAME'),
|
||||
subdomain, environ['wsgi.url_scheme'],
|
||||
environ['REQUEST_METHOD'], environ.get('PATH_INFO'))
|
||||
environ['REQUEST_METHOD'], environ.get('PATH_INFO'),
|
||||
query_args=environ.get('QUERY_STRING', ''))
|
||||
|
||||
def update(self):
|
||||
"""Called before matching and building to keep the compiled rules
|
||||
in the correct order after things changed.
|
||||
"""
|
||||
if self._remap:
|
||||
self._rules.sort(lambda a, b: a.match_compare(b))
|
||||
self._rules.sort(key=lambda x: x.match_compare_key())
|
||||
for rules in self._rules_by_endpoint.itervalues():
|
||||
rules.sort(lambda a, b: a.build_compare(b))
|
||||
rules.sort(key=lambda x: x.build_compare_key())
|
||||
self._remap = False
|
||||
|
||||
def __repr__(self):
|
||||
rules = self.iter_rules()
|
||||
return '%s([%s])' % (self.__class__.__name__, pformat(list(rules)))
|
||||
return '%s(%s)' % (self.__class__.__name__, pformat(list(rules)))
|
||||
|
||||
|
||||
class MapAdapter(object):
|
||||
@@ -1272,14 +1337,18 @@ class MapAdapter(object):
|
||||
:param return_rule: return the rule that matched instead of just the
|
||||
endpoint (defaults to `False`).
|
||||
:param query_args: optional query arguments that are used for
|
||||
automatic redirects. It's currently not possible
|
||||
to use the query arguments for URL matching.
|
||||
automatic redirects as string or dictionary. It's
|
||||
currently not possible to use the query arguments
|
||||
for URL matching.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
`return_rule` was added.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
`query_args` was added.
|
||||
|
||||
.. versionchanged:: 0.8
|
||||
`query_args` can now also be a string.
|
||||
"""
|
||||
self.map.update()
|
||||
if path_info is None:
|
||||
@@ -1287,13 +1356,13 @@ class MapAdapter(object):
|
||||
if not isinstance(path_info, unicode):
|
||||
path_info = path_info.decode(self.map.charset,
|
||||
self.map.encoding_errors)
|
||||
if '?' in path_info:
|
||||
path_info, query_args = path_info.split('?')
|
||||
query_args = url_decode(query_args, self.map.charset)
|
||||
if query_args is None:
|
||||
query_args = self.query_args
|
||||
method = (method or self.default_method).upper()
|
||||
path = u'%s|/%s' % (self.subdomain, path_info.lstrip('/'))
|
||||
|
||||
path = u'%s|/%s' % (self.map.host_matching and self.server_name or
|
||||
self.subdomain, path_info.lstrip('/'))
|
||||
|
||||
have_match_for = set()
|
||||
for rule in self.map._rules:
|
||||
try:
|
||||
@@ -1301,19 +1370,21 @@ class MapAdapter(object):
|
||||
except RequestSlash:
|
||||
raise RequestRedirect(self.make_redirect_url(
|
||||
path_info + '/', query_args))
|
||||
except RequestAliasRedirect, e:
|
||||
raise RequestRedirect(self.make_alias_redirect_url(
|
||||
path, rule.endpoint, e.matched_values, method, query_args))
|
||||
if rv is None:
|
||||
continue
|
||||
if rule.methods is not None and method not in rule.methods:
|
||||
have_match_for.update(rule.methods)
|
||||
continue
|
||||
|
||||
if self.map.redirect_defaults:
|
||||
for r in self.map._rules_by_endpoint[rule.endpoint]:
|
||||
if r.provides_defaults_for(rule) and \
|
||||
r.suitable_for(rv, method):
|
||||
rv.update(r.defaults)
|
||||
subdomain, path = r.build(rv)
|
||||
raise RequestRedirect(self.make_redirect_url(
|
||||
path, query_args, subdomain=subdomain))
|
||||
redirect_url = self.get_default_redirect(rule, method, rv,
|
||||
query_args)
|
||||
if redirect_url is not None:
|
||||
raise RequestRedirect(redirect_url)
|
||||
|
||||
if rule.redirect_to is not None:
|
||||
if isinstance(rule.redirect_to, basestring):
|
||||
def _handle_match(match):
|
||||
@@ -1329,10 +1400,12 @@ class MapAdapter(object):
|
||||
self.server_name,
|
||||
self.script_name
|
||||
), redirect_url)))
|
||||
|
||||
if return_rule:
|
||||
return rule, rv
|
||||
else:
|
||||
return rule.endpoint, rv
|
||||
|
||||
if have_match_for:
|
||||
raise MethodNotAllowed(valid_methods=list(have_match_for))
|
||||
raise NotFound()
|
||||
@@ -1350,7 +1423,7 @@ class MapAdapter(object):
|
||||
self.match(path_info, method)
|
||||
except RequestRedirect:
|
||||
pass
|
||||
except (NotFound, MethodNotAllowed):
|
||||
except HTTPException:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -1367,22 +1440,71 @@ class MapAdapter(object):
|
||||
pass
|
||||
return []
|
||||
|
||||
def make_redirect_url(self, path_info, query_args=None, subdomain=None):
|
||||
"""Creates a redirect URL."""
|
||||
suffix = ''
|
||||
if query_args:
|
||||
suffix = '?' + url_encode(query_args, self.map.charset)
|
||||
def get_host(self, domain_part):
|
||||
"""Figures out the full host name for the given domain part. The
|
||||
domain part is a subdomain in case host matching is disabled or
|
||||
a full host name.
|
||||
"""
|
||||
if self.map.host_matching:
|
||||
if domain_part is None:
|
||||
return self.server_name
|
||||
return domain_part
|
||||
subdomain = domain_part
|
||||
if subdomain is None:
|
||||
subdomain = self.subdomain
|
||||
return str('%s://%s%s/%s%s' % (
|
||||
return (subdomain and subdomain + '.' or '') + self.server_name
|
||||
|
||||
def get_default_redirect(self, rule, method, values, query_args):
|
||||
"""A helper that returns the URL to redirect to if it finds one.
|
||||
This is used for default redirecting only.
|
||||
|
||||
:internal:
|
||||
"""
|
||||
assert self.map.redirect_defaults
|
||||
for r in self.map._rules_by_endpoint[rule.endpoint]:
|
||||
# every rule that comes after this one, including ourself
|
||||
# has a lower priority for the defaults. We order the ones
|
||||
# with the highest priority up for building.
|
||||
if r is rule:
|
||||
break
|
||||
if r.provides_defaults_for(rule) and \
|
||||
r.suitable_for(values, method):
|
||||
values.update(r.defaults)
|
||||
domain_part, path = r.build(values)
|
||||
return self.make_redirect_url(
|
||||
path, query_args, domain_part=domain_part)
|
||||
|
||||
def encode_query_args(self, query_args):
|
||||
if not isinstance(query_args, basestring):
|
||||
query_args = url_encode(query_args, self.map.charset)
|
||||
return query_args
|
||||
|
||||
def make_redirect_url(self, path_info, query_args=None, domain_part=None):
|
||||
"""Creates a redirect URL.
|
||||
|
||||
:internal:
|
||||
"""
|
||||
suffix = ''
|
||||
if query_args:
|
||||
suffix = '?' + self.encode_query_args(query_args)
|
||||
return str('%s://%s/%s%s' % (
|
||||
self.url_scheme,
|
||||
subdomain and subdomain + '.' or '',
|
||||
self.server_name,
|
||||
self.get_host(domain_part),
|
||||
posixpath.join(self.script_name[:-1].lstrip('/'),
|
||||
url_quote(path_info.lstrip('/'), self.map.charset)),
|
||||
suffix
|
||||
))
|
||||
|
||||
def make_alias_redirect_url(self, path, endpoint, values, method, query_args):
|
||||
"""Internally called to make an alias redirect URL."""
|
||||
url = self.build(endpoint, values, method, append_unknown=False,
|
||||
force_external=True)
|
||||
if query_args:
|
||||
url += '?' + self.encode_query_args(query_args)
|
||||
assert url != path, 'detected invalid alias setting. No canonical ' \
|
||||
'URL found'
|
||||
return url
|
||||
|
||||
def _partial_build(self, endpoint, values, method, append_unknown):
|
||||
"""Helper for :meth:`build`. Returns subdomain and path for the
|
||||
rule that accepts this endpoint, values and method.
|
||||
@@ -1471,14 +1593,18 @@ class MapAdapter(object):
|
||||
rv = self._partial_build(endpoint, values, method, append_unknown)
|
||||
if rv is None:
|
||||
raise BuildError(endpoint, values, method)
|
||||
subdomain, path = rv
|
||||
domain_part, path = rv
|
||||
|
||||
if not force_external and subdomain == self.subdomain:
|
||||
host = self.get_host(domain_part)
|
||||
|
||||
# shortcut this.
|
||||
if not force_external and (
|
||||
(self.map.host_matching and host == self.server_name) or
|
||||
(not self.map.host_matching and domain_part == self.subdomain)):
|
||||
return str(urljoin(self.script_name, './' + path.lstrip('/')))
|
||||
return str('%s://%s%s%s/%s' % (
|
||||
return str('%s://%s%s/%s' % (
|
||||
self.url_scheme,
|
||||
subdomain and subdomain + '.' or '',
|
||||
self.server_name,
|
||||
host,
|
||||
self.script_name[:-1],
|
||||
path.lstrip('/')
|
||||
))
|
||||
|
||||
@@ -67,7 +67,7 @@ r'''
|
||||
or as named parameters, pretty much like Python function calls.
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
'''
|
||||
import sys
|
||||
@@ -262,11 +262,15 @@ def make_shell(init_func=None, banner=None, use_ipython=True):
|
||||
namespace = init_func()
|
||||
if ipython:
|
||||
try:
|
||||
import IPython
|
||||
try:
|
||||
from IPython.frontend.terminal.embed import InteractiveShellEmbed
|
||||
sh = InteractiveShellEmbed(banner1=banner)
|
||||
except ImportError:
|
||||
from IPython.Shell import IPShellEmbed
|
||||
sh = IPShellEmbed(banner=banner)
|
||||
except ImportError:
|
||||
pass
|
||||
else:
|
||||
sh = IPython.Shell.IPShellEmbed(banner=banner)
|
||||
sh(global_ns={}, local_ns=namespace)
|
||||
return
|
||||
from code import interact
|
||||
|
||||
@@ -5,11 +5,14 @@
|
||||
|
||||
Security related helpers such as secure password hashing tools.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
import hmac
|
||||
import string
|
||||
import posixpath
|
||||
from itertools import izip
|
||||
from random import SystemRandom
|
||||
|
||||
# because the API of hmac changed with the introduction of the
|
||||
@@ -31,6 +34,24 @@ SALT_CHARS = string.letters + string.digits
|
||||
|
||||
|
||||
_sys_rng = SystemRandom()
|
||||
_os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
|
||||
if sep not in (None, '/'))
|
||||
|
||||
|
||||
def safe_str_cmp(a, b):
|
||||
"""This function compares strings in somewhat constant time. This
|
||||
requires that the length of at least one string is known in advance.
|
||||
|
||||
Returns `True` if the two strings are equal or `False` if they are not.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
"""
|
||||
if len(a) != len(b):
|
||||
return False
|
||||
rv = 0
|
||||
for x, y in izip(a, b):
|
||||
rv |= ord(x) ^ ord(y)
|
||||
return rv == 0
|
||||
|
||||
|
||||
def gen_salt(length):
|
||||
@@ -101,4 +122,20 @@ def check_password_hash(pwhash, password):
|
||||
if pwhash.count('$') < 2:
|
||||
return False
|
||||
method, salt, hashval = pwhash.split('$', 2)
|
||||
return _hash_internal(method, salt, password) == hashval
|
||||
return safe_str_cmp(_hash_internal(method, salt, password), hashval)
|
||||
|
||||
|
||||
def safe_join(directory, filename):
|
||||
"""Safely join `directory` and `filename`. If this cannot be done,
|
||||
this function returns ``None``.
|
||||
|
||||
:param directory: the base directory.
|
||||
:param filename: the untrusted filename relative to that directory.
|
||||
"""
|
||||
filename = posixpath.normpath(filename)
|
||||
for sep in _os_alt_seps:
|
||||
if sep in filename:
|
||||
return None
|
||||
if os.path.isabs(filename) or filename.startswith('../'):
|
||||
return None
|
||||
return os.path.join(directory, filename)
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
instead of a simple start file.
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
@@ -40,6 +40,7 @@ import socket
|
||||
import sys
|
||||
import time
|
||||
import thread
|
||||
import signal
|
||||
import subprocess
|
||||
from urllib import unquote
|
||||
from SocketServer import ThreadingMixIn, ForkingMixIn
|
||||
@@ -63,6 +64,10 @@ class WSGIRequestHandler(BaseHTTPRequestHandler, object):
|
||||
else:
|
||||
path_info = self.path
|
||||
query = ''
|
||||
|
||||
def shutdown_server():
|
||||
self.server.shutdown_signal = True
|
||||
|
||||
url_scheme = self.server.ssl_context is None and 'http' or 'https'
|
||||
environ = {
|
||||
'wsgi.version': (1, 0),
|
||||
@@ -72,6 +77,8 @@ class WSGIRequestHandler(BaseHTTPRequestHandler, object):
|
||||
'wsgi.multithread': self.server.multithread,
|
||||
'wsgi.multiprocess': self.server.multiprocess,
|
||||
'wsgi.run_once': False,
|
||||
'werkzeug.server.shutdown':
|
||||
shutdown_server,
|
||||
'SERVER_SOFTWARE': self.server_version,
|
||||
'REQUEST_METHOD': self.command,
|
||||
'SCRIPT_NAME': '',
|
||||
@@ -171,12 +178,27 @@ class WSGIRequestHandler(BaseHTTPRequestHandler, object):
|
||||
def handle(self):
|
||||
"""Handles a request ignoring dropped connections."""
|
||||
try:
|
||||
return BaseHTTPRequestHandler.handle(self)
|
||||
rv = BaseHTTPRequestHandler.handle(self)
|
||||
except (socket.error, socket.timeout), e:
|
||||
self.connection_dropped(e)
|
||||
except Exception:
|
||||
if self.server.ssl_context is None or not is_ssl_error():
|
||||
raise
|
||||
if self.server.shutdown_signal:
|
||||
self.initiate_shutdown()
|
||||
return rv
|
||||
|
||||
def initiate_shutdown(self):
|
||||
"""A horrible, horrible way to kill the server for Python 2.6 and
|
||||
later. It's the best we can do.
|
||||
"""
|
||||
# reloader active
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||
os.kill(os.getpid(), signal.SIGKILL)
|
||||
# python 2.7
|
||||
self.server._BaseServer__shutdown_request = True
|
||||
# python 2.6
|
||||
self.server._BaseServer__serving = False
|
||||
|
||||
def connection_dropped(self, error, environ=None):
|
||||
"""Called if the connection was closed by the client. By default
|
||||
@@ -299,6 +321,7 @@ class BaseWSGIServer(HTTPServer, object):
|
||||
"""Simple single-threaded, single-process WSGI server."""
|
||||
multithread = False
|
||||
multiprocess = False
|
||||
request_queue_size = 128
|
||||
|
||||
def __init__(self, host, port, app, handler=None,
|
||||
passthrough_errors=False, ssl_context=None):
|
||||
@@ -308,6 +331,7 @@ class BaseWSGIServer(HTTPServer, object):
|
||||
HTTPServer.__init__(self, (host, int(port)), handler)
|
||||
self.app = app
|
||||
self.passthrough_errors = passthrough_errors
|
||||
self.shutdown_signal = False
|
||||
|
||||
if ssl_context is not None:
|
||||
try:
|
||||
@@ -326,6 +350,7 @@ class BaseWSGIServer(HTTPServer, object):
|
||||
_log(type, message, *args)
|
||||
|
||||
def serve_forever(self):
|
||||
self.shutdown_signal = False
|
||||
try:
|
||||
HTTPServer.serve_forever(self)
|
||||
except KeyboardInterrupt:
|
||||
@@ -410,6 +435,7 @@ def reloader_loop(extra_files=None, interval=1):
|
||||
|
||||
reloader(fnames, interval=interval)
|
||||
|
||||
|
||||
def _reloader_stat_loop(fnames, interval=1):
|
||||
mtimes = {}
|
||||
while 1:
|
||||
@@ -428,15 +454,23 @@ def _reloader_stat_loop(fnames, interval=1):
|
||||
sys.exit(3)
|
||||
time.sleep(interval)
|
||||
|
||||
|
||||
def _reloader_inotify(fnames, interval=None):
|
||||
#: Mutated by inotify loop when changes occur.
|
||||
# Mutated by inotify loop when changes occur.
|
||||
changed = [False]
|
||||
|
||||
# Setup inotify watches
|
||||
from pyinotify import WatchManager, EventsCodes, Notifier
|
||||
from pyinotify import WatchManager, Notifier
|
||||
|
||||
# this API changed at one point, support both
|
||||
try:
|
||||
from pyinotify import EventsCodes as ec
|
||||
ec.IN_ATTRIB
|
||||
except (ImportError, AttributeError):
|
||||
import pyinotify as ec
|
||||
|
||||
wm = WatchManager()
|
||||
mask = "IN_DELETE_SELF IN_MOVE_SELF IN_MODIFY IN_ATTRIB".split()
|
||||
mask = reduce(lambda m, a: m | getattr(EventsCodes, a), mask, 0)
|
||||
mask = ec.IN_DELETE_SELF | ec.IN_MOVE_SELF | ec.IN_MODIFY | ec.IN_ATTRIB
|
||||
|
||||
def signal_changed(event):
|
||||
if changed[0]:
|
||||
@@ -459,15 +493,11 @@ def _reloader_inotify(fnames, interval=None):
|
||||
notif.stop()
|
||||
sys.exit(3)
|
||||
|
||||
# Decide which reloader to use
|
||||
try:
|
||||
__import__("pyinotify") # Pyflakes-avoidant
|
||||
except ImportError:
|
||||
reloader = _reloader_stat_loop
|
||||
reloader_name = "stat() polling"
|
||||
else:
|
||||
reloader = _reloader_inotify
|
||||
reloader_name = "inotify events"
|
||||
|
||||
# currently we always use the stat loop reloader for the simple reason
|
||||
# that the inotify one does not respond to added files properly. Also
|
||||
# it's quite buggy and the API is a mess.
|
||||
reloader = _reloader_stat_loop
|
||||
|
||||
|
||||
def restart_with_reloader():
|
||||
@@ -475,7 +505,7 @@ def restart_with_reloader():
|
||||
but running the reloader thread.
|
||||
"""
|
||||
while 1:
|
||||
_log('info', ' * Restarting with reloader: %s', reloader_name)
|
||||
_log('info', ' * Restarting with reloader')
|
||||
args = [sys.executable] + sys.argv
|
||||
new_environ = os.environ.copy()
|
||||
new_environ['WERKZEUG_RUN_MAIN'] = 'true'
|
||||
@@ -495,6 +525,8 @@ def restart_with_reloader():
|
||||
|
||||
def run_with_reloader(main_func, extra_files=None, interval=1):
|
||||
"""Run the given function in an independent python interpreter."""
|
||||
import signal
|
||||
signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
|
||||
if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
|
||||
thread.start_new_thread(main_func, ())
|
||||
try:
|
||||
|
||||
@@ -5,7 +5,7 @@ r"""
|
||||
|
||||
A minimal template engine.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD License.
|
||||
"""
|
||||
import sys
|
||||
@@ -19,6 +19,11 @@ from werkzeug._internal import _decode_unicode
|
||||
from werkzeug.datastructures import MultiDict
|
||||
|
||||
|
||||
from warnings import warn
|
||||
warn(DeprecationWarning('werkzeug.templates is deprecated and '
|
||||
'will be removed in Werkzeug 1.0'))
|
||||
|
||||
|
||||
# Copyright notice: The `parse_data` method uses the string interpolation
|
||||
# algorithm by Ka-Ping Yee which originally was part of `Itpl20.py`_.
|
||||
#
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
This module implements a client to WSGI applications for testing.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import sys
|
||||
@@ -23,6 +23,7 @@ from werkzeug._internal import _empty_stream, _get_environ
|
||||
from werkzeug.wrappers import BaseRequest
|
||||
from werkzeug.urls import url_encode, url_fix, iri_to_uri, _unquote
|
||||
from werkzeug.wsgi import get_host, get_current_url, ClosingIterator
|
||||
from werkzeug.utils import dump_cookie
|
||||
from werkzeug.datastructures import FileMultiDict, MultiDict, \
|
||||
CombinedMultiDict, Headers, FileStorage
|
||||
|
||||
@@ -85,7 +86,7 @@ def stream_encode_multipart(values, use_tempfile=True, threshold=1024 * 500,
|
||||
else:
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode(charset)
|
||||
write('\r\n\r\n' + value)
|
||||
write('\r\n\r\n' + str(value))
|
||||
write('\r\n')
|
||||
write('--%s--\r\n' % boundary)
|
||||
|
||||
@@ -357,7 +358,7 @@ class EnvironBuilder(object):
|
||||
def _get_content_type(self):
|
||||
ct = self.headers.get('Content-Type')
|
||||
if ct is None and not self._input_stream:
|
||||
if self.method in ('POST', 'PUT'):
|
||||
if self.method in ('POST', 'PUT', 'PATCH'):
|
||||
if self._files:
|
||||
return 'multipart/form-data'
|
||||
return 'application/x-www-form-urlencoded'
|
||||
@@ -609,6 +610,25 @@ class Client(object):
|
||||
self.redirect_client = None
|
||||
self.allow_subdomain_redirects = allow_subdomain_redirects
|
||||
|
||||
def set_cookie(self, server_name, key, value='', max_age=None,
|
||||
expires=None, path='/', domain=None, secure=None,
|
||||
httponly=False, charset='utf-8'):
|
||||
"""Sets a cookie in the client's cookie jar. The server name
|
||||
is required and has to match the one that is also passed to
|
||||
the open call.
|
||||
"""
|
||||
assert self.cookie_jar is not None, 'cookies disabled'
|
||||
header = dump_cookie(key, value, max_age, expires, path, domain,
|
||||
secure, httponly, charset)
|
||||
environ = create_environ(path, base_url='http://' + server_name)
|
||||
headers = [('Set-Cookie', header)]
|
||||
self.cookie_jar.extract_wsgi(environ, headers)
|
||||
|
||||
def delete_cookie(self, server_name, key, path='/', domain=None):
|
||||
"""Deletes a cookie in the test client."""
|
||||
self.set_cookie(server_name, key, expires=0, max_age=0,
|
||||
path=path, domain=domain)
|
||||
|
||||
def open(self, *args, **kwargs):
|
||||
"""Takes the same arguments as the :class:`EnvironBuilder` class with
|
||||
some additions: You can provide a :class:`EnvironBuilder` or a WSGI
|
||||
@@ -714,6 +734,11 @@ class Client(object):
|
||||
kw['method'] = 'GET'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def patch(self, *args, **kw):
|
||||
"""Like open but method is enforced to PATCH."""
|
||||
kw['method'] = 'PATCH'
|
||||
return self.open(*args, **kw)
|
||||
|
||||
def post(self, *args, **kw):
|
||||
"""Like open but method is enforced to POST."""
|
||||
kw['method'] = 'POST'
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
Provide a small test application that can be used to test a WSGI server
|
||||
and check it for WSGI compliance.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
@@ -160,9 +160,13 @@ def render_testapp(req):
|
||||
b.project_name.lower()))
|
||||
python_eggs = []
|
||||
for egg in eggs:
|
||||
try:
|
||||
version = egg.version
|
||||
except (ValueError, AttributeError):
|
||||
version = 'unknown'
|
||||
python_eggs.append('<li>%s <small>[%s]</small>' % (
|
||||
escape(egg.project_name),
|
||||
escape(egg.version)
|
||||
escape(version)
|
||||
))
|
||||
|
||||
wsgi_env = []
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
This module implements various URL related functions.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import urlparse
|
||||
@@ -57,13 +57,13 @@ def _safe_urlsplit(s):
|
||||
to what we think it is.
|
||||
"""
|
||||
rv = urlparse.urlsplit(s)
|
||||
if type(rv[1]) is not type(s):
|
||||
try:
|
||||
return tuple(map(type(s), rv))
|
||||
except UnicodeError:
|
||||
# oh well, we most likely will break later again, but
|
||||
# let's just say it worked out well to that point.
|
||||
pass
|
||||
# we have to check rv[2] here and not rv[1] as rv[1] will be
|
||||
# an empty bytestring in case no domain was given.
|
||||
if type(rv[2]) is not type(s):
|
||||
assert hasattr(urlparse, 'clear_cache')
|
||||
urlparse.clear_cache()
|
||||
rv = urlparse.urlsplit(s)
|
||||
assert type(rv[2]) is type(s)
|
||||
return rv
|
||||
|
||||
|
||||
@@ -143,10 +143,12 @@ def iri_to_uri(iri, charset='utf-8'):
|
||||
path = _quote(path.encode(charset), safe="/:~+")
|
||||
query = _quote(query.encode(charset), safe="=%&[]:;$()+,!?*/")
|
||||
|
||||
return urlparse.urlunsplit([scheme, hostname, path, query, fragment])
|
||||
# this absolutely always must return a string. Otherwise some parts of
|
||||
# the system might perform double quoting (#61)
|
||||
return str(urlparse.urlunsplit([scheme, hostname, path, query, fragment]))
|
||||
|
||||
|
||||
def uri_to_iri(uri, charset='utf-8', errors='ignore'):
|
||||
def uri_to_iri(uri, charset='utf-8', errors='replace'):
|
||||
r"""Converts a URI in a given charset to a IRI.
|
||||
|
||||
Examples for URI versus IRI
|
||||
@@ -203,7 +205,7 @@ def uri_to_iri(uri, charset='utf-8', errors='ignore'):
|
||||
|
||||
|
||||
def url_decode(s, charset='utf-8', decode_keys=False, include_empty=True,
|
||||
errors='ignore', separator='&', cls=None):
|
||||
errors='replace', separator='&', cls=None):
|
||||
"""Parse a querystring and return it as :class:`MultiDict`. Per default
|
||||
only values are decoded into unicode strings. If `decode_keys` is set to
|
||||
`True` the same will happen for keys.
|
||||
@@ -321,7 +323,7 @@ def url_quote_plus(s, charset='utf-8', safe=''):
|
||||
return _quote_plus(s, safe=safe)
|
||||
|
||||
|
||||
def url_unquote(s, charset='utf-8', errors='ignore'):
|
||||
def url_unquote(s, charset='utf-8', errors='replace'):
|
||||
"""URL decode a single string with a given decoding.
|
||||
|
||||
Per default encoding errors are ignored. If you want a different behavior
|
||||
@@ -337,7 +339,7 @@ def url_unquote(s, charset='utf-8', errors='ignore'):
|
||||
return _decode_unicode(_unquote(s), charset, errors)
|
||||
|
||||
|
||||
def url_unquote_plus(s, charset='utf-8', errors='ignore'):
|
||||
def url_unquote_plus(s, charset='utf-8', errors='replace'):
|
||||
"""URL decode a single string with the given decoding and decode
|
||||
a "+" to whitespace.
|
||||
|
||||
@@ -368,7 +370,7 @@ def url_fix(s, charset='utf-8'):
|
||||
unicode string.
|
||||
"""
|
||||
if isinstance(s, unicode):
|
||||
s = s.encode(charset, 'ignore')
|
||||
s = s.encode(charset, 'replace')
|
||||
scheme, netloc, path, qs, anchor = _safe_urlsplit(s)
|
||||
path = _quote(path, '/%')
|
||||
qs = _quote_plus(qs, ':&%=')
|
||||
@@ -458,7 +460,7 @@ class Href(object):
|
||||
if path:
|
||||
if not rv.endswith('/'):
|
||||
rv += '/'
|
||||
rv = urlparse.urljoin(rv, path)
|
||||
rv = urlparse.urljoin(rv, './' + path)
|
||||
if query:
|
||||
rv += '?' + url_encode(query, self.charset, sort=self.sort,
|
||||
key=self.key)
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
browsers.
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
@@ -18,7 +18,7 @@ class UserAgentParser(object):
|
||||
"""A simple user agent parser. Used by the `UserAgent`."""
|
||||
|
||||
platforms = (
|
||||
('iphone', 'iphone'),
|
||||
('iphone|ios', 'iphone'),
|
||||
(r'darwin|mac|os\s*x', 'macos'),
|
||||
('win', 'windows'),
|
||||
(r'android', 'android'),
|
||||
|
||||
@@ -7,17 +7,15 @@
|
||||
them are used by the request and response wrappers but especially for
|
||||
middleware development it makes sense to use them without the wrappers.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
from time import time
|
||||
from datetime import datetime, timedelta
|
||||
import sys
|
||||
|
||||
from werkzeug._internal import _decode_unicode, \
|
||||
_iter_modules, _ExtendedCookie, _ExtendedMorsel, \
|
||||
_DictAccessorProperty, _parse_signature, _missing
|
||||
from werkzeug._internal import _iter_modules, _DictAccessorProperty, \
|
||||
_parse_signature, _missing
|
||||
|
||||
|
||||
_format_re = re.compile(r'\$(?:(%s)|\{(%s)\})' % (('[a-zA-Z_][a-zA-Z0-9_]*',) * 2))
|
||||
@@ -340,106 +338,6 @@ def unescape(s):
|
||||
return _entity_re.sub(handle_match, s)
|
||||
|
||||
|
||||
def parse_cookie(header, charset='utf-8', errors='ignore',
|
||||
cls=None):
|
||||
"""Parse a cookie. Either from a string or WSGI environ.
|
||||
|
||||
Per default encoding errors are ignored. If you want a different behavior
|
||||
you can set `errors` to ``'replace'`` or ``'strict'``. In strict mode a
|
||||
:exc:`HTTPUnicodeError` is raised.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
This function now returns a :class:`TypeConversionDict` instead of a
|
||||
regular dict. The `cls` parameter was added.
|
||||
|
||||
:param header: the header to be used to parse the cookie. Alternatively
|
||||
this can be a WSGI environment.
|
||||
:param charset: the charset for the cookie values.
|
||||
:param errors: the error behavior for the charset decoding.
|
||||
:param cls: an optional dict class to use. If this is not specified
|
||||
or `None` the default :class:`TypeConversionDict` is
|
||||
used.
|
||||
"""
|
||||
if isinstance(header, dict):
|
||||
header = header.get('HTTP_COOKIE', '')
|
||||
if cls is None:
|
||||
cls = TypeConversionDict
|
||||
cookie = _ExtendedCookie()
|
||||
cookie.load(header)
|
||||
result = {}
|
||||
|
||||
# decode to unicode and skip broken items. Our extended morsel
|
||||
# and extended cookie will catch CookieErrors and convert them to
|
||||
# `None` items which we have to skip here.
|
||||
for key, value in cookie.iteritems():
|
||||
if value.value is not None:
|
||||
result[key] = _decode_unicode(unquote_header_value(value.value),
|
||||
charset, errors)
|
||||
|
||||
return cls(result)
|
||||
|
||||
|
||||
def dump_cookie(key, value='', max_age=None, expires=None, path='/',
|
||||
domain=None, secure=None, httponly=False, charset='utf-8',
|
||||
sync_expires=True):
|
||||
"""Creates a new Set-Cookie header without the ``Set-Cookie`` prefix
|
||||
The parameters are the same as in the cookie Morsel object in the
|
||||
Python standard library but it accepts unicode data, too.
|
||||
|
||||
:param max_age: should be a number of seconds, or `None` (default) if
|
||||
the cookie should last only as long as the client's
|
||||
browser session. Additionally `timedelta` objects
|
||||
are accepted, too.
|
||||
: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: Use this 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 secure: 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.
|
||||
:param charset: the encoding for unicode values.
|
||||
:param sync_expires: automatically set expires if max_age is defined
|
||||
but expires not.
|
||||
"""
|
||||
try:
|
||||
key = str(key)
|
||||
except UnicodeError:
|
||||
raise TypeError('invalid key %r' % key)
|
||||
if isinstance(value, unicode):
|
||||
value = value.encode(charset)
|
||||
value = quote_header_value(value)
|
||||
morsel = _ExtendedMorsel(key, value)
|
||||
if isinstance(max_age, timedelta):
|
||||
max_age = (max_age.days * 60 * 60 * 24) + max_age.seconds
|
||||
if expires is not None:
|
||||
if not isinstance(expires, basestring):
|
||||
expires = cookie_date(expires)
|
||||
morsel['expires'] = expires
|
||||
elif max_age is not None and sync_expires:
|
||||
morsel['expires'] = cookie_date(time() + max_age)
|
||||
if domain and ':' in domain:
|
||||
# The port part of the domain should NOT be used. Strip it
|
||||
domain = domain.split(':', 1)[0]
|
||||
if domain:
|
||||
assert '.' in domain, (
|
||||
"Setting \"domain\" for a cookie on a server running localy (ex: "
|
||||
"localhost) is not supportted by complying browsers. You should "
|
||||
"have something like: \"127.0.0.1 localhost dev.localhost\" on "
|
||||
"your hosts file and then point your server to run on "
|
||||
"\"dev.localhost\" and also set \"domain\" for \"dev.localhost\""
|
||||
)
|
||||
for k, v in (('path', path), ('domain', domain), ('secure', secure),
|
||||
('max-age', max_age), ('httponly', httponly)):
|
||||
if v is not None and v is not False:
|
||||
morsel[k] = str(v)
|
||||
return morsel.output(header='').lstrip()
|
||||
|
||||
|
||||
def redirect(location, code=302):
|
||||
"""Return a response object (a WSGI application) that, if called,
|
||||
redirects the client to the target location. Supported codes are 301,
|
||||
@@ -452,7 +350,7 @@ def redirect(location, code=302):
|
||||
the :func:`iri_to_uri` function.
|
||||
|
||||
:param location: the location the response should redirect to.
|
||||
:param code: the redirect status code.
|
||||
:param code: the redirect status code. defaults to 302.
|
||||
"""
|
||||
assert code in (201, 301, 302, 303, 305, 307), 'invalid code'
|
||||
from werkzeug.wrappers import BaseResponse
|
||||
@@ -521,9 +419,9 @@ def import_string(import_name, silent=False):
|
||||
modname = module + '.' + obj
|
||||
__import__(modname)
|
||||
return sys.modules[modname]
|
||||
except ImportError:
|
||||
except ImportError, e:
|
||||
if not silent:
|
||||
raise
|
||||
raise ImportStringError(import_name, e), None, sys.exc_info()[2]
|
||||
|
||||
|
||||
def find_modules(import_path, include_packages=False, recursive=False):
|
||||
@@ -658,13 +556,56 @@ class ArgumentValidationError(ValueError):
|
||||
))
|
||||
|
||||
|
||||
class ImportStringError(ImportError):
|
||||
"""Provides information about a failed :func:`import_string` attempt."""
|
||||
|
||||
#: String in dotted notation that failed to be imported.
|
||||
import_name = None
|
||||
#: Wrapped exception.
|
||||
exception = None
|
||||
|
||||
def __init__(self, import_name, exception):
|
||||
self.import_name = import_name
|
||||
self.exception = exception
|
||||
|
||||
msg = (
|
||||
'import_string() failed for %r. Possible reasons are:\n\n'
|
||||
'- missing __init__.py in a package;\n'
|
||||
'- package or module path not included in sys.path;\n'
|
||||
'- duplicated package or module name taking precedence in '
|
||||
'sys.path;\n'
|
||||
'- missing module, class, function or variable;\n\n'
|
||||
'Debugged import:\n\n%s\n\n'
|
||||
'Original exception:\n\n%s: %s')
|
||||
|
||||
name = ''
|
||||
tracked = []
|
||||
for part in import_name.replace(':', '.').split('.'):
|
||||
name += (name and '.') + part
|
||||
imported = import_string(name, silent=True)
|
||||
if imported:
|
||||
tracked.append((name, imported.__file__))
|
||||
else:
|
||||
track = ['- %r found in %r.' % (n, i) for n, i in tracked]
|
||||
track.append('- %r not found.' % name)
|
||||
msg = msg % (import_name, '\n'.join(track),
|
||||
exception.__class__.__name__, str(exception))
|
||||
break
|
||||
|
||||
ImportError.__init__(self, msg)
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s(%r, %r)>' % (self.__class__.__name__, self.import_name,
|
||||
self.exception)
|
||||
|
||||
|
||||
# circular dependencies
|
||||
from werkzeug.http import quote_header_value, unquote_header_value, \
|
||||
cookie_date
|
||||
from werkzeug.datastructures import TypeConversionDict
|
||||
|
||||
# DEPRECATED
|
||||
# these objects were previously in this module as well. we import
|
||||
# them here for backwards compatibility with old pickles.
|
||||
from werkzeug.datastructures import MultiDict, CombinedMultiDict, \
|
||||
Headers, EnvironHeaders
|
||||
from werkzeug.http import parse_cookie, dump_cookie
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
decoded into an unicode object if possible and if it makes sense.
|
||||
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import urlparse
|
||||
@@ -28,17 +28,20 @@ from werkzeug.http import HTTP_STATUS_CODES, \
|
||||
parse_date, generate_etag, is_resource_modified, unquote_etag, \
|
||||
quote_etag, parse_set_header, parse_authorization_header, \
|
||||
parse_www_authenticate_header, remove_entity_headers, \
|
||||
parse_options_header, dump_options_header, http_date
|
||||
parse_options_header, dump_options_header, http_date, \
|
||||
parse_if_range_header, parse_cookie, dump_cookie, \
|
||||
parse_range_header, parse_content_range_header, dump_header
|
||||
from werkzeug.urls import url_decode, iri_to_uri
|
||||
from werkzeug.formparser import parse_form_data, default_stream_factory
|
||||
from werkzeug.utils import cached_property, environ_property, \
|
||||
parse_cookie, dump_cookie, header_property, get_content_type
|
||||
header_property, get_content_type
|
||||
from werkzeug.wsgi import get_current_url, get_host, LimitedStream, \
|
||||
ClosingIterator
|
||||
from werkzeug.datastructures import MultiDict, CombinedMultiDict, Headers, \
|
||||
EnvironHeaders, ImmutableMultiDict, ImmutableTypeConversionDict, \
|
||||
ImmutableList, MIMEAccept, CharsetAccept, LanguageAccept, \
|
||||
ResponseCacheControl, RequestCacheControl, CallbackDict
|
||||
ResponseCacheControl, RequestCacheControl, CallbackDict, \
|
||||
ContentRange
|
||||
from werkzeug._internal import _empty_stream, _decode_unicode, \
|
||||
_patch_wrapper, _get_environ
|
||||
|
||||
@@ -113,17 +116,14 @@ class BaseRequest(object):
|
||||
#: the charset for the request, defaults to utf-8
|
||||
charset = 'utf-8'
|
||||
|
||||
#: the error handling procedure for errors, defaults to 'ignore'
|
||||
encoding_errors = 'ignore'
|
||||
|
||||
#: set to True if the application runs behind an HTTP proxy
|
||||
is_behind_proxy = False
|
||||
#: the error handling procedure for errors, defaults to 'replace'
|
||||
encoding_errors = 'replace'
|
||||
|
||||
#: the maximum content length. This is forwarded to the form data
|
||||
#: parsing function (:func:`parse_form_data`). When set and the
|
||||
#: :attr:`form` or :attr:`files` attribute is accessed and the
|
||||
#: parsing fails because more than the specified value is transmitted
|
||||
#: a :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#: a :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#:
|
||||
#: Have a look at :ref:`dealing-with-request-data` for more details.
|
||||
#:
|
||||
@@ -134,7 +134,7 @@ class BaseRequest(object):
|
||||
#: parsing function (:func:`parse_form_data`). When set and the
|
||||
#: :attr:`form` or :attr:`files` attribute is accessed and the
|
||||
#: data in memory for post data is longer than the specified value a
|
||||
#: :exc:`~exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#: :exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception is raised.
|
||||
#:
|
||||
#: Have a look at :ref:`dealing-with-request-data` for more details.
|
||||
#:
|
||||
@@ -142,25 +142,27 @@ class BaseRequest(object):
|
||||
max_form_memory_size = None
|
||||
|
||||
#: the class to use for `args` and `form`. The default is an
|
||||
#: :class:`ImmutableMultiDict` which supports multiple values per key.
|
||||
#: alternatively it makes sense to use an :class:`ImmutableOrderedMultiDict`
|
||||
#: which preserves order or a :class:`ImmutableDict` which is
|
||||
#: the fastest but only remembers the last key. It is also possible
|
||||
#: to use mutable structures, but this is not recommended.
|
||||
#: :class:`~werkzeug.datastructures.ImmutableMultiDict` which supports
|
||||
#: multiple values per key. alternatively it makes sense to use an
|
||||
#: :class:`~werkzeug.datastructures.ImmutableOrderedMultiDict` which
|
||||
#: preserves order or a :class:`~werkzeug.datastructures.ImmutableDict`
|
||||
#: which is the fastest but only remembers the last key. It is also
|
||||
#: possible to use mutable structures, but this is not recommended.
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
parameter_storage_class = ImmutableMultiDict
|
||||
|
||||
#: the type to be used for list values from the incoming WSGI
|
||||
#: environment. By default an :class:`ImmutableList` is used
|
||||
#: the type to be used for list values from the incoming WSGI environment.
|
||||
#: By default an :class:`~werkzeug.datastructures.ImmutableList` is used
|
||||
#: (for example for :attr:`access_list`).
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
list_storage_class = ImmutableList
|
||||
|
||||
#: the type to be used for dict values from the incoming WSGI
|
||||
#: environment. By default an :class:`ImmutableTypeConversionDict`
|
||||
#: is used (for example for :attr:`cookies`).
|
||||
#: the type to be used for dict values from the incoming WSGI environment.
|
||||
#: By default an
|
||||
#: :class:`~werkzeug.datastructures.ImmutableTypeConversionDict` is used
|
||||
#: (for example for :attr:`cookies`).
|
||||
#:
|
||||
#: .. versionadded:: 0.6
|
||||
dict_storage_class = ImmutableTypeConversionDict
|
||||
@@ -205,12 +207,13 @@ class BaseRequest(object):
|
||||
object (:class:`Client`) that allows to create multipart requests,
|
||||
support for cookies etc.
|
||||
|
||||
This accepts the same options as the :class:`EnvironBuilder`.
|
||||
This accepts the same options as the
|
||||
:class:`~werkzeug.test.EnvironBuilder`.
|
||||
|
||||
.. versionchanged:: 0.5
|
||||
This method now accepts the same arguments as
|
||||
:class:`EnvironBuilder`. Because of this the `environ` parameter
|
||||
is now called `environ_overrides`.
|
||||
:class:`~werkzeug.test.EnvironBuilder`. Because of this the
|
||||
`environ` parameter is now called `environ_overrides`.
|
||||
|
||||
:return: request object
|
||||
"""
|
||||
@@ -277,7 +280,7 @@ class BaseRequest(object):
|
||||
filled with the incoming form data. As a matter of fact the input
|
||||
stream will be empty afterwards.
|
||||
|
||||
:internal:
|
||||
.. versionadded:: 0.8
|
||||
"""
|
||||
# abort early if we have already consumed the stream
|
||||
if 'stream' in self.__dict__:
|
||||
@@ -288,7 +291,7 @@ class BaseRequest(object):
|
||||
'that, set `shallow` to False.')
|
||||
data = None
|
||||
stream = _empty_stream
|
||||
if self.environ['REQUEST_METHOD'] in ('POST', 'PUT'):
|
||||
if self.environ['REQUEST_METHOD'] in ('POST', 'PUT', 'PATCH'):
|
||||
try:
|
||||
data = parse_form_data(self.environ, self._get_file_stream,
|
||||
self.charset, self.encoding_errors,
|
||||
@@ -344,7 +347,8 @@ class BaseRequest(object):
|
||||
|
||||
@cached_property
|
||||
def args(self):
|
||||
"""The parsed URL parameters. By default a :class:`ImmutableMultiDict`
|
||||
"""The parsed URL parameters. 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
|
||||
be necessary if the order of the form data is important.
|
||||
@@ -366,7 +370,8 @@ class BaseRequest(object):
|
||||
|
||||
@cached_property
|
||||
def form(self):
|
||||
"""The form parameters. By default a :class:`ImmutableMultiDict`
|
||||
"""The form parameters. 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
|
||||
be necessary if the order of the form data is important.
|
||||
@@ -386,16 +391,18 @@ class BaseRequest(object):
|
||||
|
||||
@cached_property
|
||||
def files(self):
|
||||
""":class:`MultiDict` object containing all uploaded files. Each key in
|
||||
:attr:`files` is the name from the ``<input type="file" name="">``. Each
|
||||
value in :attr:`files` is a Werkzeug :class:`FileStorage` object.
|
||||
""":class:`~werkzeug.datastructures.MultiDict` object containing
|
||||
all uploaded files. Each key in :attr:`files` is the name from the
|
||||
``<input type="file" name="">``. Each value in :attr:`files` is a
|
||||
Werkzeug :class:`~werkzeug.datastructures.FileStorage` object.
|
||||
|
||||
Note that :attr:`files` will only contain data if the request method was
|
||||
POST or PUT and the ``<form>`` that posted to the request had
|
||||
POST, PUT or PATCH and the ``<form>`` that posted to the request had
|
||||
``enctype="multipart/form-data"``. It will be empty otherwise.
|
||||
|
||||
See the :class:`MultiDict` / :class:`FileStorage` documentation for more
|
||||
details about the used data structure.
|
||||
See the :class:`~werkzeug.datastructures.MultiDict` /
|
||||
:class:`~werkzeug.datastructures.FileStorage` documentation for
|
||||
more details about the used data structure.
|
||||
"""
|
||||
self._load_form_data()
|
||||
return self.files
|
||||
@@ -409,7 +416,7 @@ class BaseRequest(object):
|
||||
@cached_property
|
||||
def headers(self):
|
||||
"""The headers from the WSGI environ as immutable
|
||||
:class:`EnvironHeaders`.
|
||||
:class:`~werkzeug.datastructures.EnvironHeaders`.
|
||||
"""
|
||||
return EnvironHeaders(self.environ)
|
||||
|
||||
@@ -473,8 +480,6 @@ class BaseRequest(object):
|
||||
@property
|
||||
def remote_addr(self):
|
||||
"""The remote address of the client."""
|
||||
if self.is_behind_proxy and self.access_route:
|
||||
return self.access_route[0]
|
||||
return self.environ.get('REMOTE_ADDR')
|
||||
|
||||
remote_user = environ_property('REMOTE_USER', doc='''
|
||||
@@ -482,6 +487,11 @@ class BaseRequest(object):
|
||||
protected, this attribute contains the username the user has
|
||||
authenticated as.''')
|
||||
|
||||
scheme = environ_property('wsgi.url_scheme', doc='''
|
||||
URL scheme (http or https).
|
||||
|
||||
.. versionadded:: 0.7''')
|
||||
|
||||
is_xhr = property(lambda x: x.environ.get('HTTP_X_REQUESTED_WITH', '')
|
||||
.lower() == 'xmlhttprequest', doc='''
|
||||
True if the request was triggered via a JavaScript XMLHttpRequest.
|
||||
@@ -544,9 +554,10 @@ class BaseResponse(object):
|
||||
encoded. Please refer to `the unicode chapter <unicode.txt>`_ for more
|
||||
details about customizing the behavior.
|
||||
|
||||
Response can be any kind of iterable or string. If it's a string
|
||||
it's considered being an iterable with one item which is the string
|
||||
passed. Headers can be a list of tuples or a :class:`Headers` object.
|
||||
Response can be any kind of iterable or string. If it's a string it's
|
||||
considered being an iterable with one item which is the string passed.
|
||||
Headers can be a list of tuples or a
|
||||
:class:`~werkzeug.datastructures.Headers` object.
|
||||
|
||||
Special note for `mimetype` and `content_type`: For most mime types
|
||||
`mimetype` and `content_type` work the same, the difference affects
|
||||
@@ -560,7 +571,8 @@ class BaseResponse(object):
|
||||
|
||||
:param response: a string or response iterable.
|
||||
:param status: a string with a status or an integer with the status code.
|
||||
:param headers: a list of headers or an :class:`Headers` object.
|
||||
: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 direct_passthrough: if set to `True` :meth:`iter_encoded` is not
|
||||
@@ -627,11 +639,14 @@ class BaseResponse(object):
|
||||
|
||||
def call_on_close(self, func):
|
||||
"""Adds a function to the internal list of functions that should
|
||||
be called as part of closing down the response.
|
||||
be called as part of closing down the response. Since 0.7 this
|
||||
function also returns the function that was passed so that this
|
||||
can be used as a decorator.
|
||||
|
||||
.. versionadded:: 0.6
|
||||
"""
|
||||
self._on_close.append(func)
|
||||
return func
|
||||
|
||||
def __repr__(self):
|
||||
if self.is_sequence:
|
||||
@@ -918,7 +933,8 @@ class BaseResponse(object):
|
||||
encoded and the iterable is buffered.
|
||||
|
||||
:param environ: the WSGI environment of the request.
|
||||
:return: returns a new :class:`Headers` object.
|
||||
:return: returns a new :class:`~werkzeug.datastructures.Headers`
|
||||
object.
|
||||
"""
|
||||
headers = Headers(self.headers)
|
||||
location = None
|
||||
@@ -963,8 +979,10 @@ class BaseResponse(object):
|
||||
# if we can determine the content length automatically, we
|
||||
# should try to do that. But only if this does not involve
|
||||
# flattening the iterator or encoding of unicode strings in
|
||||
# the response.
|
||||
if self.is_sequence and content_length is None:
|
||||
# the response. We however should not do that if we have a 304
|
||||
# response.
|
||||
if self.is_sequence and content_length is None and \
|
||||
self.status_code != 304:
|
||||
try:
|
||||
content_length = sum(len(str(x)) for x in self.response)
|
||||
except UnicodeError:
|
||||
@@ -1027,7 +1045,7 @@ class BaseResponse(object):
|
||||
else:
|
||||
headers = self.get_wsgi_headers(environ)
|
||||
app_iter = self.get_app_iter(environ)
|
||||
return app_iter, self.status, headers.to_list(self.charset)
|
||||
return app_iter, self.status, headers.to_list()
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
"""Process this response as WSGI application.
|
||||
@@ -1043,22 +1061,23 @@ class BaseResponse(object):
|
||||
|
||||
|
||||
class AcceptMixin(object):
|
||||
"""A mixin for classes with an :attr:`~BaseResponse.environ` attribute to
|
||||
get all the HTTP accept headers as :class:`Accept` objects (or subclasses
|
||||
"""A mixin for classes with an :attr:`~BaseResponse.environ` attribute
|
||||
to get all the HTTP accept headers as
|
||||
:class:`~werkzeug.datastructures.Accept` objects (or subclasses
|
||||
thereof).
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
def accept_mimetypes(self):
|
||||
"""List of mimetypes this client supports as :class:`MIMEAccept`
|
||||
object.
|
||||
"""List of mimetypes this client supports as
|
||||
:class:`~werkzeug.datastructures.MIMEAccept` object.
|
||||
"""
|
||||
return parse_accept_header(self.environ.get('HTTP_ACCEPT'), MIMEAccept)
|
||||
|
||||
@cached_property
|
||||
def accept_charsets(self):
|
||||
"""List of charsets this client supports as :class:`CharsetAccept`
|
||||
object.
|
||||
"""List of charsets this client supports as
|
||||
:class:`~werkzeug.datastructures.CharsetAccept` object.
|
||||
"""
|
||||
return parse_accept_header(self.environ.get('HTTP_ACCEPT_CHARSET'),
|
||||
CharsetAccept)
|
||||
@@ -1073,11 +1092,12 @@ class AcceptMixin(object):
|
||||
|
||||
@cached_property
|
||||
def accept_languages(self):
|
||||
"""List of languages this client accepts as :class:`LanguageAccept`
|
||||
object.
|
||||
"""List of languages this client accepts as
|
||||
:class:`~werkzeug.datastructures.LanguageAccept` object.
|
||||
|
||||
.. versionchanged 0.5
|
||||
In previous versions this was a regular :class:`Accept` object.
|
||||
In previous versions this was a regular
|
||||
:class:`~werkzeug.datastructures.Accept` object.
|
||||
"""
|
||||
return parse_accept_header(self.environ.get('HTTP_ACCEPT_LANGUAGE'),
|
||||
LanguageAccept)
|
||||
@@ -1091,8 +1111,8 @@ class ETagRequestMixin(object):
|
||||
|
||||
@cached_property
|
||||
def cache_control(self):
|
||||
"""A :class:`RequestCacheControl` object for the incoming cache control
|
||||
headers.
|
||||
"""A :class:`~werkzeug.datastructures.RequestCacheControl` object
|
||||
for the incoming cache control headers.
|
||||
"""
|
||||
cache_control = self.environ.get('HTTP_CACHE_CONTROL')
|
||||
return parse_cache_control_header(cache_control, None,
|
||||
@@ -1102,8 +1122,7 @@ class ETagRequestMixin(object):
|
||||
def if_match(self):
|
||||
"""An object containing all the etags in the `If-Match` header.
|
||||
|
||||
:rtype: :class:`~ETags`
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.ETags`
|
||||
"""
|
||||
return parse_etags(self.environ.get('HTTP_IF_MATCH'))
|
||||
|
||||
@@ -1111,8 +1130,7 @@ class ETagRequestMixin(object):
|
||||
def if_none_match(self):
|
||||
"""An object containing all the etags in the `If-None-Match` header.
|
||||
|
||||
:rtype: :class:`~ETags`
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.ETags`
|
||||
"""
|
||||
return parse_etags(self.environ.get('HTTP_IF_NONE_MATCH'))
|
||||
|
||||
@@ -1126,11 +1144,31 @@ class ETagRequestMixin(object):
|
||||
"""The parsed `If-Unmodified-Since` header as datetime object."""
|
||||
return parse_date(self.environ.get('HTTP_IF_UNMODIFIED_SINCE'))
|
||||
|
||||
@cached_property
|
||||
def if_range(self):
|
||||
"""The parsed `If-Range` header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.IfRange`
|
||||
"""
|
||||
return parse_if_range_header(self.environ.get('HTTP_IF_RANGE'))
|
||||
|
||||
@cached_property
|
||||
def range(self):
|
||||
"""The parsed `Range` header.
|
||||
|
||||
.. versionadded:: 0.7
|
||||
|
||||
:rtype: :class:`~werkzeug.datastructures.Range`
|
||||
"""
|
||||
return parse_range_header(self.environ.get('HTTP_RANGE'))
|
||||
|
||||
|
||||
class UserAgentMixin(object):
|
||||
"""Adds a `user_agent` attribute to the request object which contains the
|
||||
parsed user agent of the browser that triggered the request as a
|
||||
:class:`~UserAgent` object.
|
||||
:class:`~werkzeug.useragents.UserAgent` object.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
@@ -1141,8 +1179,9 @@ class UserAgentMixin(object):
|
||||
|
||||
|
||||
class AuthorizationMixin(object):
|
||||
"""Adds an :attr:`authorization` property that represents the parsed value
|
||||
of the `Authorization` header as :class:`Authorization` object.
|
||||
"""Adds an :attr:`authorization` property that represents the parsed
|
||||
value of the `Authorization` header as
|
||||
:class:`~werkzeug.datastructures.Authorization` object.
|
||||
"""
|
||||
|
||||
@cached_property
|
||||
@@ -1155,7 +1194,8 @@ class AuthorizationMixin(object):
|
||||
class ETagResponseMixin(object):
|
||||
"""Adds extra functionality to a response object for etag and cache
|
||||
handling. This mixin requires an object with at least a `headers`
|
||||
object that implements a dict like interface similar to :class:`Headers`.
|
||||
object that implements a dict like interface similar to
|
||||
:class:`~werkzeug.datastructures.Headers`.
|
||||
|
||||
If you want the :meth:`freeze` method to automatically add an etag, you
|
||||
have to mixin this method before the response base class. The default
|
||||
@@ -1235,6 +1275,44 @@ class ETagResponseMixin(object):
|
||||
self.add_etag()
|
||||
super(ETagResponseMixin, self).freeze()
|
||||
|
||||
accept_ranges = header_property('Accept-Ranges', doc='''
|
||||
The `Accept-Ranges` header. Even though the name would indicate
|
||||
that multiple values are supported, it must be one string token only.
|
||||
|
||||
The values ``'bytes'`` and ``'none'`` are common.
|
||||
|
||||
.. versionadded:: 0.7''')
|
||||
|
||||
def _get_content_range(self):
|
||||
def on_update(rng):
|
||||
if not rng:
|
||||
del self.headers['content-range']
|
||||
else:
|
||||
self.headers['Content-Range'] = rng.to_header()
|
||||
rv = parse_content_range_header(self.headers.get('content-range'),
|
||||
on_update)
|
||||
# always provide a content range object to make the descriptor
|
||||
# more user friendly. It provides an unset() method that can be
|
||||
# used to remove the header quickly.
|
||||
if rv is None:
|
||||
rv = ContentRange(None, None, None, on_update=on_update)
|
||||
return rv
|
||||
def _set_content_range(self, value):
|
||||
if not value:
|
||||
del self.headers['content-range']
|
||||
elif isinstance(value, basestring):
|
||||
self.headers['Content-Range'] = value
|
||||
else:
|
||||
self.headers['Content-Range'] = value.to_header()
|
||||
content_range = property(_get_content_range, _set_content_range, doc='''
|
||||
The `Content-Range` header as
|
||||
:class:`~werkzeug.datastructures.ContentRange` object. Even if the
|
||||
header is not set it wil provide such an object for easier
|
||||
manipulation.
|
||||
|
||||
.. versionadded:: 0.7''')
|
||||
del _get_content_range, _set_content_range
|
||||
|
||||
|
||||
class ResponseStream(object):
|
||||
"""A file descriptor like object used by the :class:`ResponseStreamMixin` to
|
||||
@@ -1469,7 +1547,14 @@ class CommonResponseDescriptorsMixin(object):
|
||||
elif header_set:
|
||||
self.headers[name] = header_set.to_header()
|
||||
return parse_set_header(self.headers.get(name), on_update)
|
||||
return property(fget, doc=doc)
|
||||
def fset(self, value):
|
||||
if not value:
|
||||
del self.headers[name]
|
||||
elif isinstance(value, basestring):
|
||||
self.headers[name] = value
|
||||
else:
|
||||
self.headers[name] = dump_header(value)
|
||||
return property(fget, fset, doc=doc)
|
||||
|
||||
vary = _set_property('Vary', doc='''
|
||||
The Vary field value indicates the set of request-header fields that
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
This module implements WSGI related helpers.
|
||||
|
||||
:copyright: (c) 2010 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import os
|
||||
@@ -153,7 +153,7 @@ def peek_path_info(environ):
|
||||
|
||||
|
||||
def extract_path_info(environ_or_baseurl, path_or_url, charset='utf-8',
|
||||
errors='ignore', collapse_http_schemes=True):
|
||||
errors='replace', collapse_http_schemes=True):
|
||||
"""Extracts the path info from the given URL (or WSGI environment) and
|
||||
path. The path info returned is a unicode string, not a bytestring
|
||||
suitable for a WSGI environment. The URLs might also be IRIs.
|
||||
@@ -298,7 +298,7 @@ class SharedDataMiddleware(object):
|
||||
:param app: the application to wrap. If you don't want to wrap an
|
||||
application you can pass it :exc:`NotFound`.
|
||||
:param exports: a dict of exported files and folders.
|
||||
:param diallow: a list of :func:`~fnmatch.fnmatch` rules.
|
||||
:param disallow: a list of :func:`~fnmatch.fnmatch` rules.
|
||||
:param fallback_mimetype: the fallback mimetype for unknown files.
|
||||
:param cache: enable or disable caching headers.
|
||||
:Param cache_timeout: the cache timeout in seconds for the headers.
|
||||
|
||||