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.
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
-----------

View file

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

View file

@ -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`.

View file

@ -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):

View file

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

View file

@ -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)

View file

@ -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',

View file

@ -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('/')
@ -219,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())
@ -228,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
@ -756,6 +776,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')
@ -795,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):
@ -1027,6 +1064,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'

View file

@ -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')

View file

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

View file

@ -0,0 +1 @@
Hello Subdomain