From 5cef2a05e17680f79fe6dca126847b48ba2e92b9 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 27 Jul 2010 18:46:29 +0200 Subject: [PATCH 01/12] Added artwork to MANIFEST.in Signed-off-by: Armin Ronacher --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 166311d4..3fef8b5b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,4 +1,5 @@ include Makefile CHANGES LICENSE AUTHORS +recursive-include artwork * recursive-include tests * recursive-include examples * recursive-include docs * From ecd9d1b3d9cad8b86eaa0313e90dd8ac4c465c60 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 28 Jul 2010 01:18:56 +0200 Subject: [PATCH 02/12] This is now the branch for the 0.6.1 bugfix release --- CHANGES | 5 +++++ setup.py | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index 54a65836..bafa6c5f 100644 --- a/CHANGES +++ b/CHANGES @@ -3,6 +3,11 @@ Flask Changelog Here you can see the full list of changes between each Flask release. +Version 0.6.1 +------------- + +Bugfix release, release date to be announced. + Version 0.6 ----------- diff --git a/setup.py b/setup.py index b97a33f7..3b4a3381 100644 --- a/setup.py +++ b/setup.py @@ -50,7 +50,7 @@ def run_tests(): setup( name='Flask', - version='0.6', + version='0.6.1', url='http://github.com/mitsuhiko/flask/', license='BSD', author='Armin Ronacher', From 4927ce25900f5c171cfccf9af3d895884c2e7269 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Wed, 28 Jul 2010 01:25:08 +0200 Subject: [PATCH 03/12] Fixed an issue where the default `OPTIONS` response was not exposing all valid methods in the `Allow` header. This fixes #97 --- CHANGES | 3 +++ flask/app.py | 23 +++++++++++++++++++---- tests/flask_tests.py | 11 +++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/CHANGES b/CHANGES index bafa6c5f..95d2ceb1 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,9 @@ Version 0.6.1 Bugfix release, release date to be announced. +- Fixed an issue where the default `OPTIONS` response was + not exposing all valid methods in the `Allow` header. + Version 0.6 ----------- diff --git a/flask/app.py b/flask/app.py index 9b6b7836..72ed6292 100644 --- a/flask/app.py +++ b/flask/app.py @@ -19,7 +19,8 @@ from jinja2 import Environment from werkzeug import ImmutableDict from werkzeug.routing import Map, Rule -from werkzeug.exceptions import HTTPException, InternalServerError +from werkzeug.exceptions import HTTPException, InternalServerError, \ + MethodNotAllowed from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \ _tojson_filter, _endpoint_from_view_func @@ -689,14 +690,28 @@ class Flask(_PackageBoundObject): # if we provide automatic options for this URL and the # request came with the OPTIONS method, reply automatically if rule.provide_automatic_options and req.method == 'OPTIONS': - rv = self.response_class() - rv.allow.update(rule.methods) - return rv + return self._make_default_options_response() # otherwise dispatch to the handler for that endpoint return self.view_functions[rule.endpoint](**req.view_args) except HTTPException, e: return self.handle_http_exception(e) + def _make_default_options_response(self): + # This would be nicer in Werkzeug 0.7, which however currently + # is not released. Werkzeug 0.7 provides a method called + # allowed_methods() that returns all methods that are valid for + # a given path. + methods = [] + try: + _request_ctx_stack.top.url_adapter.match(method='--') + except MethodNotAllowed, e: + methods = e.valid_methods + except HTTPException, e: + pass + rv = self.response_class() + rv.allow.update(methods) + return rv + def make_response(self, rv): """Converts the return value from a view function to a real response object that is an instance of :attr:`response_class`. diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 3000e41c..93bdfef7 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -120,6 +120,17 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] assert rv.data == '' + def test_options_on_multiple_rules(self): + app = flask.Flask(__name__) + @app.route('/', methods=['GET', 'POST']) + def index(): + return 'Hello World' + @app.route('/', methods=['PUT']) + def index_put(): + return 'Aha!' + rv = app.test_client().open('/', method='OPTIONS') + assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST', 'PUT'] + def test_request_dispatching(self): app = flask.Flask(__name__) @app.route('/') From e04483bb9052fdb8c3564a26f9b521ae12206c69 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 9 Aug 2010 15:16:02 +0200 Subject: [PATCH 04/12] normpath is now used before loading templates --- CHANGES | 3 +++ flask/templating.py | 4 ++++ tests/flask_tests.py | 2 ++ tests/moduleapp/apps/admin/__init__.py | 5 +++++ 4 files changed, 14 insertions(+) diff --git a/CHANGES b/CHANGES index 95d2ceb1..6e203889 100644 --- a/CHANGES +++ b/CHANGES @@ -10,6 +10,9 @@ Bugfix release, release date to be announced. - Fixed an issue where the default `OPTIONS` response was not exposing all valid methods in the `Allow` header. +- Jinja2 template loading syntax now allows "./" in front of + a template load path. Previously this caused issues with + module setups. Version 0.6 ----------- diff --git a/flask/templating.py b/flask/templating.py index db78c3af..4db03b75 100644 --- a/flask/templating.py +++ b/flask/templating.py @@ -8,6 +8,7 @@ :copyright: (c) 2010 by Armin Ronacher. :license: BSD, see LICENSE for more details. """ +import posixpath from jinja2 import BaseLoader, TemplateNotFound from .globals import _request_ctx_stack @@ -36,6 +37,9 @@ class _DispatchingJinjaLoader(BaseLoader): self.app = app def get_source(self, environment, template): + template = posixpath.normpath(template) + if template.startswith('../'): + raise TemplateNotFound(template) loader = None try: module, name = template.split('/', 1) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 93bdfef7..03b4da36 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -767,6 +767,8 @@ class ModuleTestCase(unittest.TestCase): assert rv.data == 'Hello from the Frontend' rv = c.get('/admin/') assert rv.data == 'Hello from the Admin' + rv = c.get('/admin/index2') + assert rv.data == 'Hello from the Admin' rv = c.get('/admin/static/test.txt') assert rv.data.strip() == 'Admin File' rv = c.get('/admin/static/css/test.css') diff --git a/tests/moduleapp/apps/admin/__init__.py b/tests/moduleapp/apps/admin/__init__.py index 98af2b26..b85b8024 100644 --- a/tests/moduleapp/apps/admin/__init__.py +++ b/tests/moduleapp/apps/admin/__init__.py @@ -7,3 +7,8 @@ admin = Module(__name__, url_prefix='/admin') @admin.route('/') def index(): return render_template('admin/index.html') + + +@admin.route('/index2') +def index2(): + return render_template('./admin/index.html') From d5f67fd9e25d78ced02be9525136665e1c07fc3f Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Tue, 10 Aug 2010 22:55:30 +0200 Subject: [PATCH 05/12] Added another testcase --- tests/flask_tests.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/tests/flask_tests.py b/tests/flask_tests.py index 03b4da36..c41bf5f7 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -230,7 +230,13 @@ class BasicFunctionalityTestCase(unittest.TestCase): flask.session['test'] = 42 flask.session.permanent = permanent return '' - rv = app.test_client().get('/') + + @app.route('/test') + def test(): + return unicode(flask.session.permanent) + + client = app.test_client() + rv = client.get('/') assert 'set-cookie' in rv.headers match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) expires = parse_date(match.group()) @@ -239,6 +245,9 @@ class BasicFunctionalityTestCase(unittest.TestCase): assert expires.month == expected.month assert expires.day == expected.day + rv = client.get('/test') + assert rv.data == 'True' + permanent = False rv = app.test_client().get('/') assert 'set-cookie' in rv.headers From fdae354e3ea39468a2ef21f9da28ad9a99291b41 Mon Sep 17 00:00:00 2001 From: Heungsub Lee Date: Fri, 20 Aug 2010 15:29:13 +0800 Subject: [PATCH 06/12] Fix the 108th issue. --- flask/module.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flask/module.py b/flask/module.py index e6e1ee36..1d860493 100644 --- a/flask/module.py +++ b/flask/module.py @@ -31,7 +31,8 @@ def _register_module(module, static_path): path = state.url_prefix + path state.app.add_url_rule(path + '/', endpoint='%s.static' % module.name, - view_func=module.send_static_file) + view_func=module.send_static_file, + subdomain=module.subdomain) return _register From 07a1952f280f80e0a65f0d7750d2fafb492c76b5 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 20 Aug 2010 11:16:18 +0200 Subject: [PATCH 07/12] Added testcase. This fixes #108 --- tests/flask_tests.py | 9 +++++++++ tests/subdomaintestmodule/__init__.py | 4 ++++ tests/subdomaintestmodule/static/hello.txt | 1 + 3 files changed, 14 insertions(+) create mode 100644 tests/subdomaintestmodule/__init__.py create mode 100644 tests/subdomaintestmodule/static/hello.txt diff --git a/tests/flask_tests.py b/tests/flask_tests.py index c41bf5f7..fc911ca8 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -1049,6 +1049,15 @@ class SubdomainTestCase(unittest.TestCase): rv = c.get('/', 'http://test.localhost/') assert rv.data == 'test index' + def test_module_static_path_subdomain(self): + app = flask.Flask(__name__) + app.config['SERVER_NAME'] = 'example.com' + from subdomaintestmodule import mod + app.register_module(mod) + c = app.test_client() + rv = c.get('/static/hello.txt', 'http://foo.example.com/') + assert rv.data.strip() == 'Hello Subdomain' + def test_subdomain_matching(self): app = flask.Flask(__name__) app.config['SERVER_NAME'] = 'localhost' diff --git a/tests/subdomaintestmodule/__init__.py b/tests/subdomaintestmodule/__init__.py new file mode 100644 index 00000000..3c5e3583 --- /dev/null +++ b/tests/subdomaintestmodule/__init__.py @@ -0,0 +1,4 @@ +from flask import Module + + +mod = Module(__name__, 'foo', subdomain='foo') diff --git a/tests/subdomaintestmodule/static/hello.txt b/tests/subdomaintestmodule/static/hello.txt new file mode 100644 index 00000000..12e23c16 --- /dev/null +++ b/tests/subdomaintestmodule/static/hello.txt @@ -0,0 +1 @@ +Hello Subdomain From 63268b36161f05366a898369e9cc69dc375a4513 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 20 Aug 2010 11:20:09 +0200 Subject: [PATCH 08/12] Added a changelog entry for #108 --- CHANGES | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES b/CHANGES index 6e203889..4fc8dd23 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,8 @@ Bugfix release, release date to be announced. - Jinja2 template loading syntax now allows "./" in front of a template load path. Previously this caused issues with module setups. +- Fixed an issue where the subdomain setting for modules was + ignored for the static folder. Version 0.6 ----------- From aeed530e3221c1b445c13269dcd2fb67548bcefc Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 Dec 2010 14:15:18 +0100 Subject: [PATCH 09/12] Make sure that windows servers do not allow downloading arbitrary files Signed-off-by: Armin Ronacher --- flask/helpers.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/flask/helpers.py b/flask/helpers.py index a2d951b4..3cc3a7f0 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. @@ -386,7 +393,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 179da5895f1fd3ebddb08cba7c93a9d78a8a2783 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 Dec 2010 14:18:14 +0100 Subject: [PATCH 10/12] Documented security fix in changelog --- CHANGES | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES b/CHANGES index 4fc8dd23..e3f5475e 100644 --- a/CHANGES +++ b/CHANGES @@ -15,6 +15,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 16bf25ffaa3728f88a4b4a283e74ce8015ced673 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Thu, 23 Dec 2010 14:23:33 +0100 Subject: [PATCH 11/12] 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 fc911ca8..74aba698 100644 --- a/tests/flask_tests.py +++ b/tests/flask_tests.py @@ -817,6 +817,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 774b7f768214f5b0c125a1b80daa97247a0ac1a6 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Fri, 31 Dec 2010 15:21:46 +0100 Subject: [PATCH 12/12] Released 0.6.1 --- CHANGES | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES b/CHANGES index e3f5475e..a48fb9e6 100644 --- a/CHANGES +++ b/CHANGES @@ -6,7 +6,7 @@ Here you can see the full list of changes between each Flask release. 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.