Compare commits

...

12 commits
main ... 0.6.1

Author SHA1 Message Date
Armin Ronacher
774b7f7682 Released 0.6.1 2010-12-31 15:21:46 +01:00
Armin Ronacher
16bf25ffaa Added testcase for an issue that may exist on windows 2010-12-23 14:23:44 +01:00
Armin Ronacher
179da5895f Documented security fix in changelog 2010-12-23 14:18:14 +01:00
Armin Ronacher
aeed530e32 Make sure that windows servers do not allow downloading arbitrary files
Signed-off-by: Armin Ronacher <armin.ronacher@active-4.com>
2010-12-23 14:16:30 +01:00
Armin Ronacher
63268b3616 Added a changelog entry for #108 2010-08-20 11:20:09 +02:00
Armin Ronacher
07a1952f28 Added testcase. This fixes #108 2010-08-20 11:19:41 +02:00
Heungsub Lee
fdae354e3e Fix the 108th issue. 2010-08-20 11:19:35 +02:00
Armin Ronacher
d5f67fd9e2 Added another testcase 2010-08-10 22:55:30 +02:00
Armin Ronacher
e04483bb90 normpath is now used before loading templates 2010-08-09 15:16:20 +02:00
Armin Ronacher
4927ce2590 Fixed an issue where the default OPTIONS response was
not exposing all valid methods in the `Allow` header.

This fixes #97
2010-07-28 01:25:08 +02:00
Armin Ronacher
ecd9d1b3d9 This is now the branch for the 0.6.1 bugfix release 2010-07-28 01:18:56 +02:00
Armin Ronacher
5cef2a05e1 Added artwork to MANIFEST.in
Signed-off-by: Armin Ronacher <armin.ronacher@active-4.com>
2010-07-27 18:51:34 +02:00
11 changed files with 111 additions and 8 deletions

16
CHANGES
View file

@ -3,6 +3,22 @@ Flask Changelog
Here you can see the full list of changes between each Flask release. Here you can see the full list of changes between each Flask release.
Version 0.6.1
-------------
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.
- 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.
- 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
----------- -----------

View file

@ -1,4 +1,5 @@
include Makefile CHANGES LICENSE AUTHORS include Makefile CHANGES LICENSE AUTHORS
recursive-include artwork *
recursive-include tests * recursive-include tests *
recursive-include examples * recursive-include examples *
recursive-include docs * recursive-include docs *

View file

@ -19,7 +19,8 @@ from jinja2 import Environment
from werkzeug import ImmutableDict from werkzeug import ImmutableDict
from werkzeug.routing import Map, Rule 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, \ from .helpers import _PackageBoundObject, url_for, get_flashed_messages, \
_tojson_filter, _endpoint_from_view_func _tojson_filter, _endpoint_from_view_func
@ -689,14 +690,28 @@ class Flask(_PackageBoundObject):
# if we provide automatic options for this URL and the # if we provide automatic options for this URL and the
# request came with the OPTIONS method, reply automatically # request came with the OPTIONS method, reply automatically
if rule.provide_automatic_options and req.method == 'OPTIONS': if rule.provide_automatic_options and req.method == 'OPTIONS':
rv = self.response_class() return self._make_default_options_response()
rv.allow.update(rule.methods)
return rv
# otherwise dispatch to the handler for that endpoint # otherwise dispatch to the handler for that endpoint
return self.view_functions[rule.endpoint](**req.view_args) return self.view_functions[rule.endpoint](**req.view_args)
except HTTPException, e: except HTTPException, e:
return self.handle_http_exception(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): def make_response(self, rv):
"""Converts the return value from a view function to a real """Converts the return value from a view function to a real
response object that is an instance of :attr:`response_class`. response object that is an instance of :attr:`response_class`.

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.
@ -386,7 +393,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

@ -31,7 +31,8 @@ def _register_module(module, static_path):
path = state.url_prefix + path path = state.url_prefix + 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)
return _register return _register

View file

@ -8,6 +8,7 @@
:copyright: (c) 2010 by Armin Ronacher. :copyright: (c) 2010 by Armin Ronacher.
:license: BSD, see LICENSE for more details. :license: BSD, see LICENSE for more details.
""" """
import posixpath
from jinja2 import BaseLoader, TemplateNotFound from jinja2 import BaseLoader, TemplateNotFound
from .globals import _request_ctx_stack from .globals import _request_ctx_stack
@ -36,6 +37,9 @@ class _DispatchingJinjaLoader(BaseLoader):
self.app = app self.app = app
def get_source(self, environment, template): def get_source(self, environment, template):
template = posixpath.normpath(template)
if template.startswith('../'):
raise TemplateNotFound(template)
loader = None loader = None
try: try:
module, name = template.split('/', 1) module, name = template.split('/', 1)

View file

@ -50,7 +50,7 @@ def run_tests():
setup( setup(
name='Flask', name='Flask',
version='0.6', version='0.6.1',
url='http://github.com/mitsuhiko/flask/', url='http://github.com/mitsuhiko/flask/',
license='BSD', license='BSD',
author='Armin Ronacher', author='Armin Ronacher',

View file

@ -120,6 +120,17 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST'] assert sorted(rv.allow) == ['GET', 'HEAD', 'OPTIONS', 'POST']
assert rv.data == '' 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): def test_request_dispatching(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
@app.route('/') @app.route('/')
@ -219,7 +230,13 @@ class BasicFunctionalityTestCase(unittest.TestCase):
flask.session['test'] = 42 flask.session['test'] = 42
flask.session.permanent = permanent flask.session.permanent = permanent
return '' 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 assert 'set-cookie' in rv.headers
match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie']) match = re.search(r'\bexpires=([^;]+)', rv.headers['set-cookie'])
expires = parse_date(match.group()) expires = parse_date(match.group())
@ -228,6 +245,9 @@ class BasicFunctionalityTestCase(unittest.TestCase):
assert expires.month == expected.month assert expires.month == expected.month
assert expires.day == expected.day assert expires.day == expected.day
rv = client.get('/test')
assert rv.data == 'True'
permanent = False permanent = False
rv = app.test_client().get('/') rv = app.test_client().get('/')
assert 'set-cookie' in rv.headers assert 'set-cookie' in rv.headers
@ -756,6 +776,8 @@ class ModuleTestCase(unittest.TestCase):
assert rv.data == 'Hello from the Frontend' assert rv.data == 'Hello from the Frontend'
rv = c.get('/admin/') rv = c.get('/admin/')
assert rv.data == 'Hello from the 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') rv = c.get('/admin/static/test.txt')
assert rv.data.strip() == 'Admin File' assert rv.data.strip() == 'Admin File'
rv = c.get('/admin/static/css/test.css') rv = c.get('/admin/static/css/test.css')
@ -795,6 +817,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):
@ -1027,6 +1064,15 @@ class SubdomainTestCase(unittest.TestCase):
rv = c.get('/', 'http://test.localhost/') rv = c.get('/', 'http://test.localhost/')
assert rv.data == 'test index' 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): def test_subdomain_matching(self):
app = flask.Flask(__name__) app = flask.Flask(__name__)
app.config['SERVER_NAME'] = 'localhost' app.config['SERVER_NAME'] = 'localhost'

View file

@ -7,3 +7,8 @@ admin = Module(__name__, url_prefix='/admin')
@admin.route('/') @admin.route('/')
def index(): def index():
return render_template('admin/index.html') return render_template('admin/index.html')
@admin.route('/index2')
def index2():
return render_template('./admin/index.html')

View file

@ -0,0 +1,4 @@
from flask import Module
mod = Module(__name__, 'foo', subdomain='foo')

View file

@ -0,0 +1 @@
Hello Subdomain