mirror of https://github.com/pallets/flask.git
Deprecated send_file etag support and mimetype guessing for file-like objects. This fixes #104
This commit is contained in:
parent
faa1c71e45
commit
fda14678c0
4
CHANGES
4
CHANGES
|
@ -13,6 +13,10 @@ Release date to be announced, codename to be selected
|
||||||
behaviour for `OPTIONS` responses.
|
behaviour for `OPTIONS` responses.
|
||||||
- Unbound locals now raise a proper :exc:`RuntimeError` instead
|
- Unbound locals now raise a proper :exc:`RuntimeError` instead
|
||||||
of an :exc:`AttributeError`.
|
of an :exc:`AttributeError`.
|
||||||
|
- Mimetype guessing and etag support based on file objects is now
|
||||||
|
deprecated for :func:`flask.send_file` because it was unreliable.
|
||||||
|
Pass filenames instead or attach your own etags and provide a
|
||||||
|
proper mimetype by hand.
|
||||||
|
|
||||||
Version 0.6.1
|
Version 0.6.1
|
||||||
-------------
|
-------------
|
||||||
|
|
|
@ -27,6 +27,23 @@ raise a :exc:`RuntimeError` instead of an :exc:`AttributeError` when they
|
||||||
are unbound. If you cought these exceptions with :exc:`AttributeError`
|
are unbound. If you cought these exceptions with :exc:`AttributeError`
|
||||||
before, you should catch them with :exc:`RuntimeError` now.
|
before, you should catch them with :exc:`RuntimeError` now.
|
||||||
|
|
||||||
|
Additionally the :func:`~flask.send_file` function is now issuing
|
||||||
|
deprecation warnings if you depend on functionality that will be removed
|
||||||
|
in Flask 1.0. Previously it was possible to use etags and mimetypes
|
||||||
|
when file objects were passed. This was unreliable and caused issues
|
||||||
|
for a few setups. If you get a deprecation warning, make sure to
|
||||||
|
update your application to work with either filenames there or disable
|
||||||
|
etag attaching and attach them yourself.
|
||||||
|
|
||||||
|
Old code::
|
||||||
|
|
||||||
|
return send_file(my_file_object)
|
||||||
|
return send_file(my_file_object)
|
||||||
|
|
||||||
|
New code::
|
||||||
|
|
||||||
|
return send_file(my_file_object, add_etags=False)
|
||||||
|
|
||||||
Version 0.6
|
Version 0.6
|
||||||
-----------
|
-----------
|
||||||
|
|
||||||
|
|
|
@ -259,7 +259,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
|
|
||||||
By default it will try to guess the mimetype for you, but you can
|
By default it will try to guess the mimetype for you, but you can
|
||||||
also explicitly provide one. For extra security you probably want
|
also explicitly provide one. For extra security you probably want
|
||||||
to sent certain files as attachment (HTML for instance).
|
to sent certain files as attachment (HTML for instance). The mimetype
|
||||||
|
guessing requires a `filename` or an `attachment_filename` to be
|
||||||
|
provided.
|
||||||
|
|
||||||
Please never pass filenames to this function from user sources without
|
Please never pass filenames to this function from user sources without
|
||||||
checking them first. Something like this is usually sufficient to
|
checking them first. Something like this is usually sufficient to
|
||||||
|
@ -274,6 +276,12 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
The `add_etags`, `cache_timeout` and `conditional` parameters were
|
The `add_etags`, `cache_timeout` and `conditional` parameters were
|
||||||
added. The default behaviour is now to attach etags.
|
added. The default behaviour is now to attach etags.
|
||||||
|
|
||||||
|
.. versionchanged:: 0.7
|
||||||
|
mimetype guessing and etag support for file objects was
|
||||||
|
deprecated because it was unreliable. Pass a filename if you are
|
||||||
|
able to, otherwise attach an etag yourself. This functionality
|
||||||
|
will be removed in Flask 1.0
|
||||||
|
|
||||||
:param filename_or_fp: the filename of the file to send. This is
|
:param filename_or_fp: the filename of the file to send. This is
|
||||||
relative to the :attr:`~Flask.root_path` if a
|
relative to the :attr:`~Flask.root_path` if a
|
||||||
relative path is specified.
|
relative path is specified.
|
||||||
|
@ -295,8 +303,25 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||||
filename = filename_or_fp
|
filename = filename_or_fp
|
||||||
file = None
|
file = None
|
||||||
else:
|
else:
|
||||||
|
from warnings import warn
|
||||||
file = filename_or_fp
|
file = filename_or_fp
|
||||||
filename = getattr(file, 'name', None)
|
filename = getattr(file, 'name', None)
|
||||||
|
|
||||||
|
# XXX: this behaviour is now deprecated because it was unreliable.
|
||||||
|
# removed in Flask 1.0
|
||||||
|
if not attachment_filename and not mimetype \
|
||||||
|
and isinstance(filename, basestring):
|
||||||
|
warn(DeprecationWarning('The filename support for file objects '
|
||||||
|
'passed to send_file is not deprecated. Pass an '
|
||||||
|
'attach_filename if you want mimetypes to be guessed.'),
|
||||||
|
stacklevel=2)
|
||||||
|
if add_etags:
|
||||||
|
warn(DeprecationWarning('In future flask releases etags will no '
|
||||||
|
'longer be generated for file objects passed to the send_file '
|
||||||
|
'function because this behaviour was unreliable. Pass '
|
||||||
|
'filenames instead if possible, otherwise attach an etag '
|
||||||
|
'yourself based on another value'), stacklevel=2)
|
||||||
|
|
||||||
if filename is not None:
|
if filename is not None:
|
||||||
if not os.path.isabs(filename):
|
if not os.path.isabs(filename):
|
||||||
filename = os.path.join(current_app.root_path, filename)
|
filename = os.path.join(current_app.root_path, filename)
|
||||||
|
|
|
@ -33,8 +33,27 @@ TEST_KEY = 'foo'
|
||||||
SECRET_KEY = 'devkey'
|
SECRET_KEY = 'devkey'
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def catch_warnings():
|
||||||
|
"""Catch warnings in a with block in a list"""
|
||||||
|
import warnings
|
||||||
|
filters = warnings.filters
|
||||||
|
warnings.filters = filters[:]
|
||||||
|
old_showwarning = warnings.showwarning
|
||||||
|
log = []
|
||||||
|
def showwarning(message, category, filename, lineno, file=None, line=None):
|
||||||
|
log.append(locals())
|
||||||
|
try:
|
||||||
|
warnings.showwarning = showwarning
|
||||||
|
yield log
|
||||||
|
finally:
|
||||||
|
warnings.filters = filters
|
||||||
|
warnings.showwarning = old_showwarning
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
@contextmanager
|
||||||
def catch_stderr():
|
def catch_stderr():
|
||||||
|
"""Catch stderr in a StringIO"""
|
||||||
old_stderr = sys.stderr
|
old_stderr = sys.stderr
|
||||||
sys.stderr = rv = StringIO()
|
sys.stderr = rv = StringIO()
|
||||||
try:
|
try:
|
||||||
|
@ -834,46 +853,64 @@ class SendfileTestCase(unittest.TestCase):
|
||||||
|
|
||||||
def test_send_file_object(self):
|
def test_send_file_object(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f)
|
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||||
with app.open_resource('static/index.html') as f:
|
rv = flask.send_file(f)
|
||||||
assert rv.data == f.read()
|
with app.open_resource('static/index.html') as f:
|
||||||
assert rv.mimetype == 'text/html'
|
assert rv.data == f.read()
|
||||||
|
assert rv.mimetype == 'text/html'
|
||||||
|
# mimetypes + etag
|
||||||
|
assert len(captured) == 2
|
||||||
|
|
||||||
app.use_x_sendfile = True
|
app.use_x_sendfile = True
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f)
|
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||||
assert rv.mimetype == 'text/html'
|
rv = flask.send_file(f)
|
||||||
assert 'x-sendfile' in rv.headers
|
assert rv.mimetype == 'text/html'
|
||||||
assert rv.headers['x-sendfile'] == \
|
assert 'x-sendfile' in rv.headers
|
||||||
os.path.join(app.root_path, 'static/index.html')
|
assert rv.headers['x-sendfile'] == \
|
||||||
|
os.path.join(app.root_path, 'static/index.html')
|
||||||
|
# mimetypes + etag
|
||||||
|
assert len(captured) == 2
|
||||||
|
|
||||||
app.use_x_sendfile = False
|
app.use_x_sendfile = False
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
f = StringIO('Test')
|
with catch_warnings() as captured:
|
||||||
rv = flask.send_file(f)
|
f = StringIO('Test')
|
||||||
assert rv.data == 'Test'
|
rv = flask.send_file(f)
|
||||||
assert rv.mimetype == 'application/octet-stream'
|
assert rv.data == 'Test'
|
||||||
f = StringIO('Test')
|
assert rv.mimetype == 'application/octet-stream'
|
||||||
rv = flask.send_file(f, mimetype='text/plain')
|
# etags
|
||||||
assert rv.data == 'Test'
|
assert len(captured) == 1
|
||||||
assert rv.mimetype == 'text/plain'
|
with catch_warnings() as captured:
|
||||||
|
f = StringIO('Test')
|
||||||
|
rv = flask.send_file(f, mimetype='text/plain')
|
||||||
|
assert rv.data == 'Test'
|
||||||
|
assert rv.mimetype == 'text/plain'
|
||||||
|
# etags
|
||||||
|
assert len(captured) == 1
|
||||||
|
|
||||||
app.use_x_sendfile = True
|
app.use_x_sendfile = True
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = StringIO('Test')
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f)
|
f = StringIO('Test')
|
||||||
assert 'x-sendfile' not in rv.headers
|
rv = flask.send_file(f)
|
||||||
|
assert 'x-sendfile' not in rv.headers
|
||||||
|
# etags
|
||||||
|
assert len(captured) == 1
|
||||||
|
|
||||||
def test_attachment(self):
|
def test_attachment(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
with app.test_request_context():
|
with catch_warnings() as captured:
|
||||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
with app.test_request_context():
|
||||||
rv = flask.send_file(f, as_attachment=True)
|
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
rv = flask.send_file(f, as_attachment=True)
|
||||||
assert value == 'attachment'
|
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||||
|
assert value == 'attachment'
|
||||||
|
# mimetypes + etag
|
||||||
|
assert len(captured) == 2
|
||||||
|
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
assert options['filename'] == 'index.html'
|
assert options['filename'] == 'index.html'
|
||||||
|
@ -884,7 +921,8 @@ class SendfileTestCase(unittest.TestCase):
|
||||||
|
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
rv = flask.send_file(StringIO('Test'), as_attachment=True,
|
rv = flask.send_file(StringIO('Test'), as_attachment=True,
|
||||||
attachment_filename='index.txt')
|
attachment_filename='index.txt',
|
||||||
|
add_etags=False)
|
||||||
assert rv.mimetype == 'text/plain'
|
assert rv.mimetype == 'text/plain'
|
||||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||||
assert value == 'attachment'
|
assert value == 'attachment'
|
||||||
|
|
Loading…
Reference in New Issue