Update Flask

This commit is contained in:
Ruud
2012-10-29 10:54:38 +01:00
parent c0900cfe94
commit b0f6f9b2ea
111 changed files with 817 additions and 477 deletions
Executable → Regular
+1 -1
View File
@@ -19,7 +19,7 @@ import sys
# the version. Usually set automatically by a script.
__version__ = '0.9-dev'
__version__ = '0.8.3'
# This import magic raises concerns quite often which is why the implementation
Executable → Regular
View File
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
View File
View File
Executable → Regular
View File
Executable → Regular
View File
View File
+16 -1
View File
@@ -88,6 +88,7 @@ r"""
: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 time import time
@@ -97,7 +98,21 @@ from werkzeug.contrib.sessions import ModificationTrackingDict
from werkzeug.security import safe_str_cmp
from hashlib import sha1 as _default_hash
# rather ugly way to import the correct hash method. Because
# hmac either accepts modules with a new method (sha, md5 etc.)
# or a hashlib factory function we have to figure out what to
# pass to it. If we have 2.5 or higher (so not 2.4 with a
# custom hashlib) we import from hashlib and fail if it does
# not exist (have seen that in old OS X versions).
# in all other cases the now deprecated sha module is used.
_default_hash = None
if sys.version_info >= (2, 5):
try:
from hashlib import sha1 as _default_hash
except ImportError:
pass
if _default_hash is None:
import sha as _default_hash
class UnquoteError(Exception):
+4 -1
View File
@@ -58,7 +58,10 @@ import tempfile
from os import path
from time import time
from random import random
from hashlib import sha1
try:
from hashlib import sha1
except ImportError:
from sha import new as sha1
from cPickle import dump, load, HIGHEST_PROTOCOL
from werkzeug.datastructures import CallbackDict
View File
View File
Executable → Regular
+1 -1
View File
@@ -2079,7 +2079,7 @@ class ETags(object):
return etag in self._strong
def __nonzero__(self):
return bool(self.star_tag or self._strong or self._weak)
return bool(self.star_tag or self._strong)
def __str__(self):
return self.to_header()
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
View File
View File
Vendored Executable → Regular
View File
View File
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
+3 -4
View File
@@ -100,7 +100,7 @@ import posixpath
from pprint import pformat
from urlparse import urljoin
from werkzeug.urls import url_encode, url_quote
from werkzeug.urls import url_encode, url_decode, url_quote
from werkzeug.utils import redirect, format_string
from werkzeug.exceptions import HTTPException, NotFound, MethodNotAllowed
from werkzeug._internal import _get_environ
@@ -715,7 +715,7 @@ class Rule(RuleFactory):
return
processed.add(data)
else:
add(url_quote(data, self.map.charset, safe='/:|+'))
add(url_quote(data, self.map.charset, safe='/:|'))
domain_part, url = (u''.join(tmp)).split('|', 1)
if append_unknown:
@@ -1503,8 +1503,7 @@ class MapAdapter(object):
self.url_scheme,
self.get_host(domain_part),
posixpath.join(self.script_name[:-1].lstrip('/'),
url_quote(path_info.lstrip('/'), self.map.charset,
safe='/:|+')),
url_quote(path_info.lstrip('/'), self.map.charset)),
suffix
))
Executable → Regular
View File
Executable → Regular
+17 -17
View File
@@ -14,6 +14,20 @@ import posixpath
from itertools import izip
from random import SystemRandom
# because the API of hmac changed with the introduction of the
# new hashlib module, we have to support both. This sets up a
# mapping to the digest factory functions and the digest modules
# (or factory functions with changed API)
try:
from hashlib import sha1, md5
_hash_funcs = _hash_mods = {'sha1': sha1, 'md5': md5}
_sha1_mod = sha1
_md5_mod = md5
except ImportError:
import sha as _sha1_mod, md5 as _md5_mod
_hash_mods = {'sha1': _sha1_mod, 'md5': _md5_mod}
_hash_funcs = {'sha1': _sha1_mod.new, 'md5': _md5_mod.new}
SALT_CHARS = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
@@ -23,20 +37,6 @@ _os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
if sep not in (None, '/'))
def _find_hashlib_algorithms():
import hashlib
algos = getattr(hashlib, 'algorithms', None)
if algos is None:
algos = ('md5', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512')
rv = {}
for algo in algos:
func = getattr(hashlib, algo, None)
if func is not None:
rv[algo] = func
return rv
_hash_funcs = _find_hashlib_algorithms()
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.
@@ -68,11 +68,11 @@ def _hash_internal(method, salt, password):
if method == 'plain':
return password
if salt:
if method not in _hash_funcs:
if method not in _hash_mods:
return None
if isinstance(salt, unicode):
salt = salt.encode('utf-8')
h = hmac.new(salt, None, _hash_funcs[method])
h = hmac.new(salt, None, _hash_mods[method])
else:
if method not in _hash_funcs:
return None
@@ -97,7 +97,7 @@ def generate_password_hash(password, method='sha1', salt_length=8):
is used, hmac is used internally to salt the password.
:param password: the password to hash
:param method: the hash method to use (one that hashlib supports)
:param method: the hash method to use (``'md5'`` or ``'sha1'``)
:param salt_length: the lengt of the salt in letters
"""
salt = method != 'plain' and gen_salt(salt_length) or ''
Executable → Regular
+5 -69
View File
@@ -35,8 +35,6 @@
:copyright: (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
:license: BSD, see LICENSE for more details.
"""
from __future__ import with_statement
import os
import socket
import sys
@@ -252,13 +250,10 @@ class WSGIRequestHandler(BaseHTTPRequestHandler, object):
BaseRequestHandler = WSGIRequestHandler
def generate_adhoc_ssl_pair(cn=None):
def generate_adhoc_ssl_context():
"""Generates an adhoc SSL context for the development server."""
from random import random
from OpenSSL import crypto
# pretty damn sure that this is not actually accepted by anyone
if cn is None:
cn = '*'
from OpenSSL import crypto, SSL
cert = crypto.X509()
cert.set_serial_number(int(random() * sys.maxint))
@@ -266,7 +261,7 @@ def generate_adhoc_ssl_pair(cn=None):
cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
subject = cert.get_subject()
subject.CN = cn
subject.CN = '*'
subject.O = 'Dummy Certificate'
issuer = cert.get_issuer()
@@ -278,59 +273,10 @@ def generate_adhoc_ssl_pair(cn=None):
cert.set_pubkey(pkey)
cert.sign(pkey, 'md5')
return cert, pkey
def make_ssl_devcert(base_path, host=None, cn=None):
"""Creates an SSL key for development. This should be used instead of
the ``'adhoc'`` key which generates a new cert on each server start.
It accepts a path for where it should store the key and cert and
either a host or CN. If a host is given it will use the CN
``*.host/CN=host``.
For more information see :func:`run_simple`.
.. versionadded:: 0.9
:param base_path: the path to the certificate and key. The extension
``.crt`` is added for the certificate, ``.key`` is
added for the key.
:param host: the name of the host. This can be used as an alternative
for the `cn`.
:param cn: the `CN` to use.
"""
from OpenSSL import crypto
if host is not None:
cn = '*.%s/CN=%s' % (host, host)
cert, pkey = generate_adhoc_ssl_pair(cn=cn)
cert_file = base_path + '.crt'
pkey_file = base_path + '.key'
with open(cert_file, 'w') as f:
f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
with open(pkey_file, 'w') as f:
f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
return cert_file, pkey_file
def generate_adhoc_ssl_context():
"""Generates an adhoc SSL context for the development server."""
from OpenSSL import SSL
pkey, cert = generate_adhoc_ssl_pair()
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.use_privatekey(pkey)
ctx.use_certificate(cert)
return ctx
def load_ssl_context(cert_file, pkey_file):
"""Loads an SSL context from a certificate and private key file."""
from OpenSSL import SSL
ctx = SSL.Context(SSL.SSLv23_METHOD)
ctx.use_certificate_file(cert_file)
ctx.use_privatekey_file(pkey_file)
return ctx
@@ -354,9 +300,6 @@ class _SSLConnectionFix(object):
def __getattr__(self, attrib):
return getattr(self._con, attrib)
def shutdown(self, arg=None):
self._con.shutdown()
def select_ip_version(host, port):
"""Returns AF_INET4 or AF_INET6 depending on where to connect to."""
@@ -399,8 +342,6 @@ class BaseWSGIServer(HTTPServer, object):
except ImportError:
raise TypeError('SSL is not available if the OpenSSL '
'library is not installed.')
if isinstance(ssl_context, tuple):
ssl_context = load_ssl_context(*ssl_context)
if ssl_context == 'adhoc':
ssl_context = generate_adhoc_ssl_context()
self.socket = tsafe.Connection(ssl_context, self.socket)
@@ -614,10 +555,6 @@ def run_simple(hostname, port, application, use_reloader=False,
.. versionadded:: 0.6
support for SSL was added.
.. versionadded:: 0.8
Added support for automatically loading a SSL context from certificate
file and private key.
:param hostname: The host for the application. eg: ``'localhost'``
:param port: The port for the server. eg: ``8080``
:param application: the WSGI application to execute
@@ -645,8 +582,7 @@ def run_simple(hostname, port, application, use_reloader=False,
This means that the server will die on errors but
it can be useful to hook debuggers in (pdb etc.)
:param ssl_context: an SSL context for the connection. Either an OpenSSL
context, a tuple in the form ``(cert_file, pkey_file)``,
the string ``'adhoc'`` if the server should
context, the string ``'adhoc'`` if the server should
automatically create one, or `None` to disable SSL
(which is the default).
"""
Executable → Regular
View File
Executable → Regular
+55 -59
View File
@@ -474,10 +474,7 @@ class EnvironBuilder(object):
return 80
def __del__(self):
try:
self.close()
except Exception:
pass
self.close()
def close(self):
"""Closes all files. If you put real :class:`file` objects into the
@@ -603,11 +600,14 @@ class Client(object):
def __init__(self, application, response_wrapper=None, use_cookies=True,
allow_subdomain_redirects=False):
self.application = application
if response_wrapper is None:
response_wrapper = lambda a, s, h: (a, s, h)
self.response_wrapper = response_wrapper
if use_cookies:
self.cookie_jar = _TestCookieJar()
else:
self.cookie_jar = None
self.redirect_client = None
self.allow_subdomain_redirects = allow_subdomain_redirects
def set_cookie(self, server_name, key, value='', max_age=None,
@@ -629,46 +629,6 @@ class Client(object):
self.set_cookie(server_name, key, expires=0, max_age=0,
path=path, domain=domain)
def run_wsgi_app(self, environ, buffered=False):
"""Runs the wrapped WSGI app with the given environment."""
if self.cookie_jar is not None:
self.cookie_jar.inject_wsgi(environ)
rv = run_wsgi_app(self.application, environ, buffered=buffered)
if self.cookie_jar is not None:
self.cookie_jar.extract_wsgi(environ, rv[2])
return rv
def resolve_redirect(self, response, new_location, environ, buffered=False):
"""Resolves a single redirect and triggers the request again
directly on this redirect client.
"""
scheme, netloc, script_root, qs, anchor = urlparse.urlsplit(new_location)
base_url = urlparse.urlunsplit((scheme, netloc, '', '', '')).rstrip('/') + '/'
cur_server_name = netloc.split(':', 1)[0].split('.')
real_server_name = get_host(environ).rsplit(':', 1)[0].split('.')
if self.allow_subdomain_redirects:
allowed = cur_server_name[-len(real_server_name):] == real_server_name
else:
allowed = cur_server_name == real_server_name
if not allowed:
raise RuntimeError('%r does not support redirect to '
'external targets' % self.__class__)
# For redirect handling we temporarily disable the response
# wrapper. This is not threadsafe but not a real concern
# since the test client must not be shared anyways.
old_response_wrapper = self.response_wrapper
self.response_wrapper = None
try:
return self.open(path=script_root, base_url=base_url,
query_string=qs, as_tuple=True,
buffered=buffered)
finally:
self.response_wrapper = old_response_wrapper
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
@@ -710,25 +670,61 @@ class Client(object):
finally:
builder.close()
response = self.run_wsgi_app(environ, buffered=buffered)
if self.cookie_jar is not None:
self.cookie_jar.inject_wsgi(environ)
rv = run_wsgi_app(self.application, environ, buffered=buffered)
if self.cookie_jar is not None:
self.cookie_jar.extract_wsgi(environ, rv[2])
# handle redirects
redirect_chain = []
while 1:
status_code = int(response[1].split(None, 1)[0])
if status_code not in (301, 302, 303, 305, 307) \
or not follow_redirects:
break
new_location = Headers.linked(response[2])['location']
new_redirect_entry = (new_location, status_code)
if new_redirect_entry in redirect_chain:
raise ClientRedirectError('loop detected')
redirect_chain.append(new_redirect_entry)
environ, response = self.resolve_redirect(response, new_location,
environ, buffered=buffered)
status_code = int(rv[1].split(None, 1)[0])
while status_code in (301, 302, 303, 305, 307) and follow_redirects:
if not self.redirect_client:
# assume that we're not using the user defined response wrapper
# so that we don't need any ugly hacks to get the status
# code from the response.
self.redirect_client = Client(self.application)
self.redirect_client.cookie_jar = self.cookie_jar
if self.response_wrapper is not None:
response = self.response_wrapper(*response)
redirect = dict(rv[2])['Location']
scheme, netloc, script_root, qs, anchor = urlparse.urlsplit(redirect)
base_url = urlparse.urlunsplit((scheme, netloc, '', '', '')).rstrip('/') + '/'
cur_server_name = netloc.split(':', 1)[0].split('.')
real_server_name = get_host(environ).split(':', 1)[0].split('.')
if self.allow_subdomain_redirects:
allowed = cur_server_name[-len(real_server_name):] == real_server_name
else:
allowed = cur_server_name == real_server_name
if not allowed:
raise RuntimeError('%r does not support redirect to '
'external targets' % self.__class__)
redirect_chain.append((redirect, status_code))
# the redirect request should be a new request, and not be based on
# the old request
redirect_kwargs = {
'path': script_root,
'base_url': base_url,
'query_string': qs,
'as_tuple': True,
'buffered': buffered,
'follow_redirects': False,
}
environ, rv = self.redirect_client.open(**redirect_kwargs)
status_code = int(rv[1].split(None, 1)[0])
# Prevent loops
if redirect_chain[-1] in redirect_chain[:-1]:
raise ClientRedirectError("loop detected")
response = self.response_wrapper(*rv)
if as_tuple:
return environ, response
return response
Executable → Regular
View File
Executable → Regular
+1 -1
View File
@@ -141,7 +141,7 @@ def iri_to_uri(iri, charset='utf-8'):
if port:
hostname += ':' + port
path = _quote(path.encode(charset), safe="/:~+%")
path = _quote(path.encode(charset), safe="/:~+")
query = _quote(query.encode(charset), safe="=%&[]:;$()+,!?*/")
# this absolutely always must return a string. Otherwise some parts of
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
View File
Executable → Regular
+37 -39
View File
@@ -14,11 +14,10 @@ import urllib
import urlparse
import posixpath
import mimetypes
from itertools import chain, repeat
from itertools import chain
from zlib import adler32
from time import time, mktime
from datetime import datetime
from functools import partial
from werkzeug._internal import _patch_wrapper
from werkzeug.http import is_resource_modified, http_date
@@ -582,13 +581,6 @@ def make_limited_stream(stream, limit):
return stream
def make_chunk_iter_func(stream, limit, buffer_size):
"""Helper for the line and chunk iter functions."""
if hasattr(stream, 'read'):
return partial(make_limited_stream(stream, limit).read, buffer_size)
return iter(chain(stream, repeat(''))).next
def make_line_iter(stream, limit=None, buffer_size=10 * 1024):
"""Safely iterates line-based over an input stream. If the input stream
is not a :class:`LimitedStream` the `limit` parameter is mandatory.
@@ -605,31 +597,46 @@ def make_line_iter(stream, limit=None, buffer_size=10 * 1024):
.. versionchanged:: 0.8
This function now ensures that the limit was reached.
.. versionadded:: 0.9
added support for iterators as input stream.
:param stream: the stream or iterate to iterate over.
:param stream: the stream to iterate over.
:param limit: the limit in bytes for the stream. (Usually
content length. Not necessary if the `stream`
is a :class:`LimitedStream`.
:param buffer_size: The optional buffer size.
"""
stream = make_limited_stream(stream, limit)
def _iter_basic_lines():
_read = make_chunk_iter_func(stream, limit, buffer_size)
_read = stream.read
buffer = []
while 1:
new_data = _read()
if not new_data:
if len(buffer) > 1:
yield buffer.pop()
continue
# we reverse the chunks because popping from the last
# position of the list is O(1) and the number of chunks
# read will be quite large for binary files.
chunks = _read(buffer_size).splitlines(True)
chunks.reverse()
first_chunk = buffer and buffer[0] or ''
if chunks:
if first_chunk and first_chunk[-1] in '\r\n':
yield first_chunk
first_chunk = ''
first_chunk += chunks.pop()
else:
yield first_chunk
break
new_buf = []
for item in chain(buffer, new_data.splitlines(True)):
new_buf.append(item)
if item and item[-1:] in '\r\n':
yield ''.join(new_buf)
new_buf = []
buffer = new_buf
if buffer:
yield ''.join(buffer)
buffer = chunks
# in case the line is longer than the buffer size we
# can't yield yet. This will only happen if the buffer
# is empty.
if not buffer and first_chunk[-1] not in '\r\n':
buffer = [first_chunk]
else:
yield first_chunk
# This hackery is necessary to merge 'foo\r' and '\n' into one item
# of 'foo\r\n' if we were unlucky and we hit a chunk boundary.
@@ -648,26 +655,24 @@ def make_line_iter(stream, limit=None, buffer_size=10 * 1024):
def make_chunk_iter(stream, separator, limit=None, buffer_size=10 * 1024):
"""Works like :func:`make_line_iter` but accepts a separator
which divides chunks. If you want newline based processing
you should use :func:`make_limited_stream` instead as it
you shuold use :func:`make_limited_stream` instead as it
supports arbitrary newline markers.
.. versionadded:: 0.8
.. versionadded:: 0.9
added support for iterators as input stream.
:param stream: the stream or iterate to iterate over.
:param stream: the stream to iterate over.
:param separator: the separator that divides chunks.
:param limit: the limit in bytes for the stream. (Usually
content length. Not necessary if the `stream`
is a :class:`LimitedStream`.
:param buffer_size: The optional buffer size.
"""
_read = make_chunk_iter_func(stream, limit, buffer_size)
stream = make_limited_stream(stream, limit)
_read = stream.read
_split = re.compile(r'(%s)' % re.escape(separator)).split
buffer = []
while 1:
new_data = _read()
new_data = _read(buffer_size)
if not new_data:
break
chunks = _split(new_data)
@@ -840,13 +845,6 @@ class LimitedStream(object):
last_pos = self._pos
return result
def tell(self):
"""Returns the position of the stream.
.. versionadded:: 0.9
"""
return self._pos
def next(self):
line = self.readline()
if line is None: