From 2ac1b7d4388209d8e1309ed5b43ebe8d023eaa53 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 19 Nov 2010 13:25:45 +0100 Subject: [PATCH 01/11] Fixed a documentation error. This fixes #143 --- docs/signals.rst | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/signals.rst b/docs/signals.rst index 356bfcb2..ed5ecd51 100644 --- a/docs/signals.rst +++ b/docs/signals.rst @@ -81,16 +81,23 @@ context are appended to it. Additionally there is a convenient helper method (:meth:`~blinker.base.Signal.connected_to`). that allows you to 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 - def captured_templates(app): - recorded = [] + def captured_templates(app, recorded): def record(sender, template, context): recorded.append((template, context)) 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 The :meth:`~blinker.base.Signal.connected_to` method arrived in Blinker From ed517c7215fc17a46f85c2c0324d519572b67f6b Mon Sep 17 00:00:00 2001 From: Jason Davies Date: Sat, 13 Nov 2010 11:28:42 +0000 Subject: [PATCH 02/11] Minor spelling fixes Signed-off-by: Armin Ronacher --- CHANGES | 4 ++-- docs/foreword.rst | 2 +- docs/unicode.rst | 28 ++++++++++++++-------------- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 358be659..1a452f40 100644 --- a/CHANGES +++ b/CHANGES @@ -45,7 +45,7 @@ Released on July 27th 2010, codename Whisky - after request functions are now called in reverse order of registration. - 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. - static rules are now even in place if there is no static folder for the module. This was implemented to aid GAE which will @@ -65,7 +65,7 @@ Released on July 27th 2010, codename Whisky - added signalling support based on blinker. This feature is currently optional and supposed to be used by extensions and applications. If 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` method. - modules can now register for a subdomain instead of just an URL diff --git a/docs/foreword.rst b/docs/foreword.rst index bdb6a10d..2c073218 100644 --- a/docs/foreword.rst +++ b/docs/foreword.rst @@ -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 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 -as the unicode literals `__future__` feature. +as the Unicode literals `__future__` feature. diff --git a/docs/unicode.rst b/docs/unicode.rst index 5b4202ad..413ea84d 100644 --- a/docs/unicode.rst +++ b/docs/unicode.rst @@ -1,23 +1,23 @@ 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 -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 Absolutely, Positively Must Know About Unicode and Character Sets `_. This part of the 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 -------------------- 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 -- 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. - encoding and decoding happens whenever you are talking over a protocol 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 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 -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. 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 -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. The Golden Rule --------------- 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, some special characters of latin letters without umlauts or anything fancy) you can use regular string literals (``'Hello World'``). - 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'``) -- 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 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 @@ -61,21 +61,21 @@ Encoding and Decoding Yourself ------------------------------ 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 -with unicode interface. So for example if you want to load a file on the +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 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 not specify their encoding comes into play. So do yourself a favour and 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:: def read_file(filename, charset='utf-8'): with open(filename, 'r') as f: 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:: def write_file(filename, contents, charset='utf-8'): From 8569dfee6136bfda4a5fa2297aed3d82700bb1a5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 29 Nov 2010 08:57:38 +0100 Subject: [PATCH 03/11] Added a PROPAGATE_EXCEPTIONS flag --- CHANGES | 4 ++++ docs/config.rst | 8 ++++++++ flask/app.py | 15 ++++++++++++++- tests/flask_tests.py | 29 ++++++++++++++++++++++++++++- 4 files changed, 54 insertions(+), 2 deletions(-) diff --git a/CHANGES b/CHANGES index 1a452f40..7d9f9d25 100644 --- a/CHANGES +++ b/CHANGES @@ -23,6 +23,10 @@ Release date to be announced, codename to be selected 1.0 the old behaviour will continue to work but issue dependency warnings. - 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 ------------- diff --git a/docs/config.rst b/docs/config.rst index 1c2648a5..de74aa2b 100644 --- a/docs/config.rst +++ b/docs/config.rst @@ -54,6 +54,11 @@ The following configuration values are used internally by Flask: =============================== ========================================= ``DEBUG`` enable/disable debug 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 ``SESSION_COOKIE_NAME`` the name of the session cookie ``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 ``MAX_CONTENT_LENGTH`` +.. versionadded:: 0.7 + ``PROPAGATE_EXCEPTIONS`` + Configuring from Files ---------------------- diff --git a/flask/app.py b/flask/app.py index f2ec45e9..312291e1 100644 --- a/flask/app.py +++ b/flask/app.py @@ -189,6 +189,7 @@ class Flask(_PackageBoundObject): default_config = ImmutableDict({ 'DEBUG': False, 'TESTING': False, + 'PROPAGATE_EXCEPTIONS': None, 'SECRET_KEY': None, 'SESSION_COOKIE_NAME': 'session', 'PERMANENT_SESSION_LIFETIME': timedelta(days=31), @@ -303,6 +304,18 @@ class Flask(_PackageBoundObject): self.jinja_env = self.create_jinja_environment() 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 def logger(self): """A :class:`logging.Logger` object for this application. The @@ -682,7 +695,7 @@ class Flask(_PackageBoundObject): """ got_request_exception.send(self, exception=e) handler = self.error_handlers.get(500) - if self.debug: + if self.propagate_exceptions: raise self.logger.exception('Exception on %s [%s]' % ( request.path, diff --git a/tests/flask_tests.py b/tests/flask_tests.py index dddabf21..c1cb95c2 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -15,6 +15,7 @@ import re import sys import flask import unittest +from threading import Thread from logging import StreamHandler from contextlib import contextmanager from datetime import datetime @@ -547,7 +548,6 @@ class BasicFunctionalityTestCase(unittest.TestCase): "No ValueError exception should have been raised \"%s\"" % e ) - def test_test_app_proper_environ(self): app = flask.Flask(__name__) app.config.update( @@ -619,6 +619,33 @@ class BasicFunctionalityTestCase(unittest.TestCase): "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): From 164067920ba7832e587a24725384789ab92bd55f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 1 Dec 2010 17:22:55 +0100 Subject: [PATCH 04/11] Updated examples to work with pypy which has a incomplete sqlite3 in 1.4. Also disable a euc-kr test that does not work on pypy --- examples/flaskr/flaskr.py | 2 +- examples/minitwit/minitwit.py | 2 +- tests/flask_tests.py | 12 ++++++++++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/examples/flaskr/flaskr.py b/examples/flaskr/flaskr.py index 1df24293..69953555 100644 --- a/examples/flaskr/flaskr.py +++ b/examples/flaskr/flaskr.py @@ -10,7 +10,7 @@ :license: BSD, see LICENSE for more details. """ from __future__ import with_statement -import sqlite3 +from sqlite3 import dbapi2 as sqlite3 from contextlib import closing from flask import Flask, request, session, g, redirect, url_for, abort, \ render_template, flash diff --git a/examples/minitwit/minitwit.py b/examples/minitwit/minitwit.py index b740bc25..7726e9f4 100644 --- a/examples/minitwit/minitwit.py +++ b/examples/minitwit/minitwit.py @@ -10,7 +10,7 @@ """ from __future__ import with_statement import time -import sqlite3 +from sqlite3 import dbapi2 as sqlite3 from hashlib import md5 from datetime import datetime from contextlib import closing diff --git a/tests/flask_tests.py b/tests/flask_tests.py index c1cb95c2..9e68a7a3 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -29,6 +29,15 @@ sys.path.append(os.path.join(example_path, 'flaskr')) 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 TEST_KEY = 'foo' SECRET_KEY = 'devkey' @@ -698,6 +707,9 @@ class JSONTestCase(unittest.TestCase): assert rv.status_code == 200 assert rv.data == u'정상처리'.encode('utf-8') + if not has_encoding('euc-kr'): + test_modified_url_encoding = None + class TemplatingTestCase(unittest.TestCase): From 4b65c7ed5a31d56e194817231eb88e1083ba285d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 6 Dec 2010 03:16:13 +0100 Subject: [PATCH 05/11] Added the ability to override the test client's class. This fixes #148 --- flask/app.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/flask/app.py b/flask/app.py index 312291e1..3d344abb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -199,6 +199,11 @@ class Flask(_PackageBoundObject): '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): _PackageBoundObject.__init__(self, import_name) if static_path is not None: @@ -429,7 +434,7 @@ class Flask(_PackageBoundObject): options.setdefault('use_debugger', self.debug) 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 about unit testing head over to :ref:`testing`. @@ -443,9 +448,16 @@ class Flask(_PackageBoundObject): .. versionchanged:: 0.4 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 - return FlaskClient(self, self.response_class, use_cookies=True) + cls = self.test_client_class + 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): """Creates or opens a new session. Default implementation stores all From fb88d9d0253bffdd67665d2e1b761bd3d9986804 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 7 Dec 2010 22:08:46 +0100 Subject: [PATCH 06/11] Whitespace nazi was here --- flask/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/app.py b/flask/app.py index 3d344abb..7172fccb 100644 --- a/flask/app.py +++ b/flask/app.py @@ -200,7 +200,7 @@ class Flask(_PackageBoundObject): }) #: the test client that is used with when `test_client` is used. - #: + #: #: .. versionadded:: 0.7 test_client_class = None From ed70b42798a31bce951917ff22b996c810e2c3a9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 Dec 2010 14:15:18 +0100 Subject: [PATCH 07/11] Make sure that windows servers do not allow downloading arbitrary files --- flask/helpers.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index 33aa7ee2..a783dc12 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -58,6 +58,13 @@ else: _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): """Internal helper that returns the default endpoint for a given function. This always is the function name. @@ -413,7 +420,10 @@ def send_from_directory(directory, filename, **options): forwarded to :func:`send_file`. """ 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() filename = os.path.join(directory, filename) if not os.path.isfile(filename): From b92120b190e92288468d8617bc0e270dbf32ea71 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 Dec 2010 14:18:14 +0100 Subject: [PATCH 08/11] Documented security fix in changelog --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 7d9f9d25..e83b2b95 100644 --- a/CHANGES +++ b/CHANGES @@ -40,6 +40,9 @@ Bugfix release, release date to be announced. module setups. - Fixed an issue where the subdomain setting for modules was 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 ----------- From 11c66be80e3e249c5f14dd9a23f49a073cd03903 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 Dec 2010 14:23:33 +0100 Subject: [PATCH 09/11] Added testcase for an issue that may exist on windows --- tests/flask_tests.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 9e68a7a3..c5700ff8 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -996,6 +996,21 @@ class ModuleTestCase(unittest.TestCase): else: 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): From 4c76607553e92f6e1b03930e053cc7078fc32f8d Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 31 Dec 2010 15:21:46 +0100 Subject: [PATCH 10/11] Released 0.6.1 Signed-off-by: Armin Ronacher --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e83b2b95..7d204466 100644 --- a/CHANGES +++ b/CHANGES @@ -31,7 +31,7 @@ Release date to be announced, codename to be selected 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 not exposing all valid methods in the `Allow` header. From 9ae4eba9861bfa959bac6adfaf5842ef7d937eb6 Mon Sep 17 00:00:00 2001 From: Daniel Gerber Date: Thu, 6 Jan 2011 16:43:49 +0100 Subject: [PATCH 11/11] fix subdomain for static rule on registering module Signed-off-by: Armin Ronacher --- flask/module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flask/module.py b/flask/module.py index 5f3c8c1f..4e719a4e 100644 --- a/flask/module.py +++ b/flask/module.py @@ -32,7 +32,7 @@ def _register_module(module, static_path): state.app.add_url_rule(path + '/', endpoint='%s.static' % module.name, view_func=module.send_static_file, - subdomain=module.subdomain) + subdomain=state.subdomain) return _register