Merge branch 'master' of git://github.com/mitsuhiko/flask

This commit is contained in:
Dag Odenhall 2011-01-10 02:12:29 +01:00
commit 048b3008ba
11 changed files with 141 additions and 30 deletions

13
CHANGES
View file

@ -23,11 +23,15 @@ Release date to be announced, codename to be selected
1.0 the old behaviour will continue to work but issue dependency 1.0 the old behaviour will continue to work but issue dependency
warnings. warnings.
- fixed a problem for Flask to run on jython. - fixed a problem for Flask to run on jython.
- added a `PROPAGATE_EXCEPTIONS` configuration variable that can be
used to flip the setting of exception propagation which previously
was linked to `DEBUG` alone and is now linked to either `DEBUG` or
`TESTING`.
Version 0.6.1 Version 0.6.1
------------- -------------
Bugfix release, release date to be announced. Bugfix release, released on December 31st 2010
- Fixed an issue where the default `OPTIONS` response was - Fixed an issue where the default `OPTIONS` response was
not exposing all valid methods in the `Allow` header. not exposing all valid methods in the `Allow` header.
@ -36,6 +40,9 @@ Bugfix release, release date to be announced.
module setups. module setups.
- Fixed an issue where the subdomain setting for modules was - Fixed an issue where the subdomain setting for modules was
ignored for the static folder. ignored for the static folder.
- Fixed a security problem that allowed clients to download arbitrary files
if the host server was a windows based operating system and the client
uses backslashes to escape the directory the files where exposed from.
Version 0.6 Version 0.6
----------- -----------
@ -45,7 +52,7 @@ Released on July 27th 2010, codename Whisky
- after request functions are now called in reverse order of - after request functions are now called in reverse order of
registration. registration.
- OPTIONS is now automatically implemented by Flask unless the - OPTIONS is now automatically implemented by Flask unless the
application explictly adds 'OPTIONS' as method to the URL rule. application explicitly adds 'OPTIONS' as method to the URL rule.
In this case no automatic OPTIONS handling kicks in. In this case no automatic OPTIONS handling kicks in.
- static rules are now even in place if there is no static folder - static rules are now even in place if there is no static folder
for the module. This was implemented to aid GAE which will for the module. This was implemented to aid GAE which will
@ -65,7 +72,7 @@ Released on July 27th 2010, codename Whisky
- added signalling support based on blinker. This feature is currently - added signalling support based on blinker. This feature is currently
optional and supposed to be used by extensions and applications. If optional and supposed to be used by extensions and applications. If
you want to use it, make sure to have `blinker`_ installed. you want to use it, make sure to have `blinker`_ installed.
- refactored the way url adapters are created. This process is now - refactored the way URL adapters are created. This process is now
fully customizable with the :meth:`~flask.Flask.create_url_adapter` fully customizable with the :meth:`~flask.Flask.create_url_adapter`
method. method.
- modules can now register for a subdomain instead of just an URL - modules can now register for a subdomain instead of just an URL

View file

@ -54,6 +54,11 @@ The following configuration values are used internally by Flask:
=============================== ========================================= =============================== =========================================
``DEBUG`` enable/disable debug mode ``DEBUG`` enable/disable debug mode
``TESTING`` enable/disable testing mode ``TESTING`` enable/disable testing mode
``PROPAGATE_EXCEPTIONS`` explicitly enable or disable the
propagation of exceptions. If not set or
explicitly set to `None` this is
implicitly true if either `TESTING` or
`DEBUG` is true.
``SECRET_KEY`` the secret key ``SECRET_KEY`` the secret key
``SESSION_COOKIE_NAME`` the name of the session cookie ``SESSION_COOKIE_NAME`` the name of the session cookie
``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as ``PERMANENT_SESSION_LIFETIME`` the lifetime of a permanent session as
@ -96,6 +101,9 @@ The following configuration values are used internally by Flask:
.. versionadded:: 0.6 .. versionadded:: 0.6
``MAX_CONTENT_LENGTH`` ``MAX_CONTENT_LENGTH``
.. versionadded:: 0.7
``PROPAGATE_EXCEPTIONS``
Configuring from Files Configuring from Files
---------------------- ----------------------

View file

@ -106,4 +106,4 @@ Werkzeug and Flask will be ported to Python 3 as soon as a solution for
WSGI is found, and we will provide helpful tips how to upgrade existing WSGI is found, and we will provide helpful tips how to upgrade existing
applications to Python 3. Until then, we strongly recommend using Python applications to Python 3. Until then, we strongly recommend using Python
2.6 and 2.7 with activated Python 3 warnings during development, as well 2.6 and 2.7 with activated Python 3 warnings during development, as well
as the unicode literals `__future__` feature. as the Unicode literals `__future__` feature.

View file

@ -81,16 +81,23 @@ context are appended to it.
Additionally there is a convenient helper method Additionally there is a convenient helper method
(:meth:`~blinker.base.Signal.connected_to`). that allows you to (:meth:`~blinker.base.Signal.connected_to`). that allows you to
temporarily subscribe a function to a signal with is a context manager on temporarily subscribe a function to a signal with is a context manager on
its own which simplifies the example above:: its own. Because the return value of the context manager cannot be
specified that way one has to pass the list in as argument::
from flask import template_rendered from flask import template_rendered
def captured_templates(app): def captured_templates(app, recorded):
recorded = []
def record(sender, template, context): def record(sender, template, context):
recorded.append((template, context)) recorded.append((template, context))
return template_rendered.connected_to(record, app) return template_rendered.connected_to(record, app)
The example above would then look like this::
templates = []
with captured_templates(app, templates):
...
template, context = templates[0]
.. admonition:: Blinker API Changes .. admonition:: Blinker API Changes
The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker

View file

@ -1,23 +1,23 @@
Unicode in Flask Unicode in Flask
================ ================
Flask like Jinja2 and Werkzeug is totally unicode based when it comes to Flask like Jinja2 and Werkzeug is totally Unicode based when it comes to
text. Not only these libraries, also the majority of web related Python text. Not only these libraries, also the majority of web related Python
libraries that deal with text. If you don't know unicode so far, you libraries that deal with text. If you don't know Unicode so far, you
should probably read `The Absolute Minimum Every Software Developer should probably read `The Absolute Minimum Every Software Developer
Absolutely, Positively Must Know About Unicode and Character Sets Absolutely, Positively Must Know About Unicode and Character Sets
<http://www.joelonsoftware.com/articles/Unicode.html>`_. This part of the <http://www.joelonsoftware.com/articles/Unicode.html>`_. This part of the
documentation just tries to cover the very basics so that you have a documentation just tries to cover the very basics so that you have a
pleasant experience with unicode related things. pleasant experience with Unicode related things.
Automatic Conversion Automatic Conversion
-------------------- --------------------
Flask has a few assumptions about your application (which you can change Flask has a few assumptions about your application (which you can change
of course) that give you basic and painless unicode support: of course) that give you basic and painless Unicode support:
- the encoding for text on your website is UTF-8 - the encoding for text on your website is UTF-8
- internally you will always use unicode exclusively for text except - internally you will always use Unicode exclusively for text except
for literal strings with only ASCII character points. for literal strings with only ASCII character points.
- encoding and decoding happens whenever you are talking over a protocol - encoding and decoding happens whenever you are talking over a protocol
that requires bytes to be transmitted. that requires bytes to be transmitted.
@ -29,27 +29,27 @@ address documents on servers (so called URIs or URLs). However HTML which
is usually transmitted on top of HTTP supports a large variety of is usually transmitted on top of HTTP supports a large variety of
character sets and which ones are used, are transmitted in an HTTP header. character sets and which ones are used, are transmitted in an HTTP header.
To not make this too complex Flask just assumes that if you are sending To not make this too complex Flask just assumes that if you are sending
unicode out you want it to be UTF-8 encoded. Flask will do the encoding Unicode out you want it to be UTF-8 encoded. Flask will do the encoding
and setting of the appropriate headers for you. and setting of the appropriate headers for you.
The same is true if you are talking to databases with the help of The same is true if you are talking to databases with the help of
SQLAlchemy or a similar ORM system. Some databases have a protocol that SQLAlchemy or a similar ORM system. Some databases have a protocol that
already transmits unicode and if they do not, SQLAlchemy or your other ORM already transmits Unicode and if they do not, SQLAlchemy or your other ORM
should take care of that. should take care of that.
The Golden Rule The Golden Rule
--------------- ---------------
So the rule of thumb: if you are not dealing with binary data, work with So the rule of thumb: if you are not dealing with binary data, work with
unicode. What does working with unicode in Python 2.x mean? Unicode. What does working with Unicode in Python 2.x mean?
- as long as you are using ASCII charpoints only (basically numbers, - as long as you are using ASCII charpoints only (basically numbers,
some special characters of latin letters without umlauts or anything some special characters of latin letters without umlauts or anything
fancy) you can use regular string literals (``'Hello World'``). fancy) you can use regular string literals (``'Hello World'``).
- if you need anything else than ASCII in a string you have to mark - if you need anything else than ASCII in a string you have to mark
this string as unicode string by prefixing it with a lowercase `u`. this string as Unicode string by prefixing it with a lowercase `u`.
(like ``u'Hänsel und Gretel'``) (like ``u'Hänsel und Gretel'``)
- if you are using non-unicode characters in your Python files you have - if you are using non-Unicode characters in your Python files you have
to tell Python which encoding your file uses. Again, I recommend to tell Python which encoding your file uses. Again, I recommend
UTF-8 for this purpose. To tell the interpreter your encoding you can UTF-8 for this purpose. To tell the interpreter your encoding you can
put the ``# -*- coding: utf-8 -*-`` into the first or second line of put the ``# -*- coding: utf-8 -*-`` into the first or second line of
@ -61,21 +61,21 @@ Encoding and Decoding Yourself
------------------------------ ------------------------------
If you are talking with a filesystem or something that is not really based If you are talking with a filesystem or something that is not really based
on unicode you will have to ensure that you decode properly when working on Unicode you will have to ensure that you decode properly when working
with unicode interface. So for example if you want to load a file on the with Unicode interface. So for example if you want to load a file on the
filesystem and embed it into a Jinja2 template you will have to decode it filesystem and embed it into a Jinja2 template you will have to decode it
from the encoding of that file. Here the old problem that text files do from the encoding of that file. Here the old problem that text files do
not specify their encoding comes into play. So do yourself a favour and not specify their encoding comes into play. So do yourself a favour and
limit yourself to UTF-8 for text files as well. limit yourself to UTF-8 for text files as well.
Anyways. To load such a file with unicode you can use the built-in Anyways. To load such a file with Unicode you can use the built-in
:meth:`str.decode` method:: :meth:`str.decode` method::
def read_file(filename, charset='utf-8'): def read_file(filename, charset='utf-8'):
with open(filename, 'r') as f: with open(filename, 'r') as f:
return f.read().decode(charset) return f.read().decode(charset)
To go from unicode into a specific charset such as UTF-8 you can use the To go from Unicode into a specific charset such as UTF-8 you can use the
:meth:`unicode.encode` method:: :meth:`unicode.encode` method::
def write_file(filename, contents, charset='utf-8'): def write_file(filename, contents, charset='utf-8'):

View file

@ -10,7 +10,7 @@
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
from __future__ import with_statement from __future__ import with_statement
import sqlite3 from sqlite3 import dbapi2 as sqlite3
from contextlib import closing from contextlib import closing
from flask import Flask, request, session, g, redirect, url_for, abort, \ from flask import Flask, request, session, g, redirect, url_for, abort, \
render_template, flash render_template, flash

View file

@ -10,7 +10,7 @@
""" """
from __future__ import with_statement from __future__ import with_statement
import time import time
import sqlite3 from sqlite3 import dbapi2 as sqlite3
from hashlib import md5 from hashlib import md5
from datetime import datetime from datetime import datetime
from contextlib import closing from contextlib import closing

View file

@ -189,6 +189,7 @@ class Flask(_PackageBoundObject):
default_config = ImmutableDict({ default_config = ImmutableDict({
'DEBUG': False, 'DEBUG': False,
'TESTING': False, 'TESTING': False,
'PROPAGATE_EXCEPTIONS': None,
'SECRET_KEY': None, 'SECRET_KEY': None,
'SESSION_COOKIE_NAME': 'session', 'SESSION_COOKIE_NAME': 'session',
'PERMANENT_SESSION_LIFETIME': timedelta(days=31), 'PERMANENT_SESSION_LIFETIME': timedelta(days=31),
@ -198,6 +199,11 @@ class Flask(_PackageBoundObject):
'MAX_CONTENT_LENGTH': None 'MAX_CONTENT_LENGTH': None
}) })
#: the test client that is used with when `test_client` is used.
#:
#: .. versionadded:: 0.7
test_client_class = None
def __init__(self, import_name, static_path=None): def __init__(self, import_name, static_path=None):
_PackageBoundObject.__init__(self, import_name) _PackageBoundObject.__init__(self, import_name)
if static_path is not None: if static_path is not None:
@ -303,6 +309,18 @@ class Flask(_PackageBoundObject):
self.jinja_env = self.create_jinja_environment() self.jinja_env = self.create_jinja_environment()
self.init_jinja_globals() self.init_jinja_globals()
@property
def propagate_exceptions(self):
"""Returns the value of the `PROPAGATE_EXCEPTIONS` configuration
value in case it's set, otherwise a sensible default is returned.
.. versionadded:: 0.7
"""
rv = self.config['PROPAGATE_EXCEPTIONS']
if rv is not None:
return rv
return self.testing or self.debug
@property @property
def logger(self): def logger(self):
"""A :class:`logging.Logger` object for this application. The """A :class:`logging.Logger` object for this application. The
@ -416,7 +434,7 @@ class Flask(_PackageBoundObject):
options.setdefault('use_debugger', self.debug) options.setdefault('use_debugger', self.debug)
return run_simple(host, port, self, **options) return run_simple(host, port, self, **options)
def test_client(self): def test_client(self, use_cookies=True):
"""Creates a test client for this application. For information """Creates a test client for this application. For information
about unit testing head over to :ref:`testing`. about unit testing head over to :ref:`testing`.
@ -430,9 +448,16 @@ class Flask(_PackageBoundObject):
.. versionchanged:: 0.4 .. versionchanged:: 0.4
added support for `with` block usage for the client. added support for `with` block usage for the client.
.. versionadded:: 0.7
The `use_cookies` parameter was added as well as the ability
to override the client to be used by setting the
:attr:`test_client_class` attribute.
""" """
from flask.testing import FlaskClient cls = self.test_client_class
return FlaskClient(self, self.response_class, use_cookies=True) if cls is None:
from flask.testing import FlaskClient as cls
return cls(self, self.response_class, use_cookies=use_cookies)
def open_session(self, request): def open_session(self, request):
"""Creates or opens a new session. Default implementation stores all """Creates or opens a new session. Default implementation stores all
@ -682,7 +707,7 @@ class Flask(_PackageBoundObject):
""" """
got_request_exception.send(self, exception=e) got_request_exception.send(self, exception=e)
handler = self.error_handlers.get(500) handler = self.error_handlers.get(500)
if self.debug: if self.propagate_exceptions:
raise raise
self.logger.exception('Exception on %s [%s]' % ( self.logger.exception('Exception on %s [%s]' % (
request.path, request.path,

View file

@ -58,6 +58,13 @@ else:
_tojson_filter = json.dumps _tojson_filter = json.dumps
# what separators does this operating system provide that are not a slash?
# this is used by the send_from_directory function to ensure that nobody is
# able to access files from outside the filesystem.
_os_alt_seps = list(sep for sep in [os.path.sep, os.path.altsep]
if sep not in (None, '/'))
def _endpoint_from_view_func(view_func): def _endpoint_from_view_func(view_func):
"""Internal helper that returns the default endpoint for a given """Internal helper that returns the default endpoint for a given
function. This always is the function name. function. This always is the function name.
@ -413,7 +420,10 @@ def send_from_directory(directory, filename, **options):
forwarded to :func:`send_file`. forwarded to :func:`send_file`.
""" """
filename = posixpath.normpath(filename) filename = posixpath.normpath(filename)
if filename.startswith(('/', '../')): for sep in _os_alt_seps:
if sep in filename:
raise NotFound()
if os.path.isabs(filename) or filename.startswith('../'):
raise NotFound() raise NotFound()
filename = os.path.join(directory, filename) filename = os.path.join(directory, filename)
if not os.path.isfile(filename): if not os.path.isfile(filename):

View file

@ -32,7 +32,7 @@ def _register_module(module, static_path):
state.app.add_url_rule(path + '/<path:filename>', state.app.add_url_rule(path + '/<path:filename>',
endpoint='%s.static' % module.name, endpoint='%s.static' % module.name,
view_func=module.send_static_file, view_func=module.send_static_file,
subdomain=module.subdomain) subdomain=state.subdomain)
return _register return _register

View file

@ -15,6 +15,7 @@ import re
import sys import sys
import flask import flask
import unittest import unittest
from threading import Thread
from logging import StreamHandler from logging import StreamHandler
from contextlib import contextmanager from contextlib import contextmanager
from datetime import datetime from datetime import datetime
@ -28,6 +29,15 @@ sys.path.append(os.path.join(example_path, 'flaskr'))
sys.path.append(os.path.join(example_path, 'minitwit')) sys.path.append(os.path.join(example_path, 'minitwit'))
def has_encoding(name):
try:
import codecs
codecs.lookup(name)
return True
except LookupError:
return False
# config keys used for the ConfigTestCase # config keys used for the ConfigTestCase
TEST_KEY = 'foo' TEST_KEY = 'foo'
SECRET_KEY = 'devkey' SECRET_KEY = 'devkey'
@ -547,7 +557,6 @@ class BasicFunctionalityTestCase(unittest.TestCase):
"No ValueError exception should have been raised \"%s\"" % e "No ValueError exception should have been raised \"%s\"" % e
) )
def test_test_app_proper_environ(self): def test_test_app_proper_environ(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.config.update( app.config.update(
@ -619,6 +628,33 @@ class BasicFunctionalityTestCase(unittest.TestCase):
"No ValueError exception should have been raised \"%s\"" % e "No ValueError exception should have been raised \"%s\"" % e
) )
def test_exception_propagation(self):
def apprunner(configkey):
app = flask.Flask(__name__)
@app.route('/')
def index():
1/0
c = app.test_client()
if config_key is not None:
app.config[config_key] = True
try:
resp = c.get('/')
except Exception:
pass
else:
self.fail('expected exception')
else:
assert c.get('/').status_code == 500
# we have to run this test in an isolated thread because if the
# debug flag is set to true and an exception happens the context is
# not torn down. This causes other tests that run after this fail
# when they expect no exception on the stack.
for config_key in 'TESTING', 'PROPAGATE_EXCEPTIONS', 'DEBUG', None:
t = Thread(target=apprunner, args=(config_key,))
t.start()
t.join()
class JSONTestCase(unittest.TestCase): class JSONTestCase(unittest.TestCase):
@ -671,6 +707,9 @@ class JSONTestCase(unittest.TestCase):
assert rv.status_code == 200 assert rv.status_code == 200
assert rv.data == u'정상처리'.encode('utf-8') assert rv.data == u'정상처리'.encode('utf-8')
if not has_encoding('euc-kr'):
test_modified_url_encoding = None
class TemplatingTestCase(unittest.TestCase): class TemplatingTestCase(unittest.TestCase):
@ -957,6 +996,21 @@ class ModuleTestCase(unittest.TestCase):
else: else:
assert 0, 'expected exception' assert 0, 'expected exception'
# testcase for a security issue that may exist on windows systems
import os
import ntpath
old_path = os.path
os.path = ntpath
try:
try:
f('..\\__init__.py')
except NotFound:
pass
else:
assert 0, 'expected exception'
finally:
os.path = old_path
class SendfileTestCase(unittest.TestCase): class SendfileTestCase(unittest.TestCase):