From f6147f8f47dfac8a02e4b7444fb2d9e4c4e47b75 Mon Sep 17 00:00:00 2001 From: Dan Sully Date: Thu, 2 Jun 2016 10:04:48 -0700 Subject: [PATCH] Remove deprecation warnings for add_etags & mimetype guessing for send_file() --- AUTHORS | 1 + flask/helpers.py | 31 +++------- tests/test_helpers.py | 132 ++++++++++++++++++------------------------ 3 files changed, 66 insertions(+), 98 deletions(-) diff --git a/AUTHORS b/AUTHORS index d081d9f832..cc157dc41b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -15,6 +15,7 @@ Patches and Suggestions - Chris Grindstaff - Christopher Grebs - Daniel Neuhäuser +- Dan Sully - David Lord @davidism - Edmond Burnett - Florent Xicluna diff --git a/flask/helpers.py b/flask/helpers.py index c744bb8ca1..9e1948e1df 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -437,11 +437,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, to ``True`` to directly emit an ``X-Sendfile`` header. This however requires support of the underlying webserver for ``X-Sendfile``. - By default it will try to guess the mimetype for you, but you can - also explicitly provide one. For extra security you probably want - to send certain files as attachment (HTML for instance). The mimetype - guessing requires a `filename` or an `attachment_filename` to be - provided. + You must explicitly provide the mimetype for the filename or file object. Please never pass filenames to this function from user sources; you should use :func:`send_from_directory` instead. @@ -461,6 +457,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. versionchanged:: 0.9 cache_timeout pulls its default from application config, when None. + .. versionchanged:: 1.0 + mimetype guessing and etag support removed for file objects. + If no mimetype or attachment_filename is provided, application/octet-stream + will be used. + :param filename_or_fp: the filename of the file to send in `latin-1`. This is relative to the :attr:`~Flask.root_path` if a relative path is specified. @@ -488,25 +489,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, filename = filename_or_fp file = None else: - from warnings import warn file = filename_or_fp filename = getattr(file, 'name', None) - # XXX: this behavior is now deprecated because it was unreliable. - # removed in Flask 1.0 - if not attachment_filename and not mimetype \ - and isinstance(filename, string_types): - warn(DeprecationWarning('The filename support for file objects ' - 'passed to send_file is now 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 behavior was unreliable. Pass ' - 'filenames instead if possible, otherwise attach an etag ' - 'yourself based on another value'), stacklevel=2) - if filename is not None: if not os.path.isabs(filename): filename = os.path.join(current_app.root_path, filename) @@ -553,7 +538,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, rv.cache_control.max_age = cache_timeout rv.expires = int(time() + cache_timeout) - if add_etags and filename is not None: + if add_etags and filename is not None and file is None: + from warnings import warn + try: rv.set_etag('%s-%s-%s' % ( os.path.getmtime(filename), diff --git a/tests/test_helpers.py b/tests/test_helpers.py index 6dc41fad83..f7c4e36ad4 100644 --- a/tests/test_helpers.py +++ b/tests/test_helpers.py @@ -329,7 +329,7 @@ class TestSendfile(object): def test_send_file_regular(self): app = flask.Flask(__name__) with app.test_request_context(): - rv = flask.send_file('static/index.html') + rv = flask.send_file('static/index.html', mimetype='text/html') assert rv.direct_passthrough assert rv.mimetype == 'text/html' with app.open_resource('static/index.html') as f: @@ -341,7 +341,7 @@ def test_send_file_xsendfile(self): app = flask.Flask(__name__) app.use_x_sendfile = True with app.test_request_context(): - rv = flask.send_file('static/index.html') + rv = flask.send_file('static/index.html', mimetype='text/html') assert rv.direct_passthrough assert 'x-sendfile' in rv.headers assert rv.headers['x-sendfile'] == \ @@ -351,88 +351,69 @@ def test_send_file_xsendfile(self): def test_send_file_object(self, catch_deprecation_warnings): app = flask.Flask(__name__) - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html'), mode='rb') - rv = flask.send_file(f) - rv.direct_passthrough = False - with app.open_resource('static/index.html') as f: - assert rv.data == f.read() - assert rv.mimetype == 'text/html' - rv.close() - # mimetypes + etag - assert len(captured) == 2 + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html'), mode='rb') + rv = flask.send_file(f, mimetype='text/html') + rv.direct_passthrough = False + with app.open_resource('static/index.html') as f: + assert rv.data == f.read() + assert rv.mimetype == 'text/html' + rv.close() app.use_x_sendfile = True - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f) - assert rv.mimetype == 'text/html' - assert 'x-sendfile' in rv.headers - assert rv.headers['x-sendfile'] == \ - os.path.join(app.root_path, 'static/index.html') - rv.close() - # mimetypes + etag - assert len(captured) == 2 + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f, mimetype='text/html') + assert rv.mimetype == 'text/html' + assert 'x-sendfile' in rv.headers + assert rv.headers['x-sendfile'] == \ + os.path.join(app.root_path, 'static/index.html') + rv.close() app.use_x_sendfile = False with app.test_request_context(): - with catch_deprecation_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f) - rv.direct_passthrough = False - assert rv.data == b'Test' - assert rv.mimetype == 'application/octet-stream' - rv.close() - # etags - assert len(captured) == 1 - with catch_deprecation_warnings() as captured: - class PyStringIO(object): - def __init__(self, *args, **kwargs): - self._io = StringIO(*args, **kwargs) - def __getattr__(self, name): - return getattr(self._io, name) - f = PyStringIO('Test') - f.name = 'test.txt' - rv = flask.send_file(f) - rv.direct_passthrough = False - assert rv.data == b'Test' - assert rv.mimetype == 'text/plain' - rv.close() - # attachment_filename and etags - assert len(captured) == 3 - with catch_deprecation_warnings() as captured: - f = StringIO('Test') - rv = flask.send_file(f, mimetype='text/plain') - rv.direct_passthrough = False - assert rv.data == b'Test' - assert rv.mimetype == 'text/plain' - rv.close() - # etags - assert len(captured) == 1 + f = StringIO('Test') + rv = flask.send_file(f) + rv.direct_passthrough = False + assert rv.data == b'Test' + assert rv.mimetype == 'application/octet-stream' + rv.close() + + class PyStringIO(object): + def __init__(self, *args, **kwargs): + self._io = StringIO(*args, **kwargs) + def __getattr__(self, name): + return getattr(self._io, name) + f = PyStringIO('Test') + f.name = 'test.txt' + rv = flask.send_file(f, attachment_filename=f.name) + rv.direct_passthrough = False + assert rv.data == b'Test' + assert rv.mimetype == 'text/plain' + rv.close() + + f = StringIO('Test') + rv = flask.send_file(f, mimetype='text/plain') + rv.direct_passthrough = False + assert rv.data == b'Test' + assert rv.mimetype == 'text/plain' + rv.close() app.use_x_sendfile = True - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = StringIO('Test') - rv = flask.send_file(f) - assert 'x-sendfile' not in rv.headers - rv.close() - # etags - assert len(captured) == 1 + with app.test_request_context(): + f = StringIO('Test') + rv = flask.send_file(f) + assert 'x-sendfile' not in rv.headers + rv.close() def test_attachment(self, catch_deprecation_warnings): app = flask.Flask(__name__) - with catch_deprecation_warnings() as captured: - with app.test_request_context(): - f = open(os.path.join(app.root_path, 'static/index.html')) - rv = flask.send_file(f, as_attachment=True) - value, options = parse_options_header(rv.headers['Content-Disposition']) - assert value == 'attachment' - rv.close() - # mimetypes + etag - assert len(captured) == 2 + with app.test_request_context(): + f = open(os.path.join(app.root_path, 'static/index.html')) + rv = flask.send_file(f, as_attachment=True, mimetype='text/html') + value, options = parse_options_header(rv.headers['Content-Disposition']) + assert value == 'attachment' + rv.close() with app.test_request_context(): assert options['filename'] == 'index.html' @@ -444,8 +425,7 @@ def test_attachment(self, catch_deprecation_warnings): with app.test_request_context(): rv = flask.send_file(StringIO('Test'), as_attachment=True, - attachment_filename='index.txt', - add_etags=False) + attachment_filename='index.txt') assert rv.mimetype == 'text/plain' value, options = parse_options_header(rv.headers['Content-Disposition']) assert value == 'attachment'