Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flask.send_file incorrectly sends blank files #3358

Closed
ColdHeat opened this issue Sep 10, 2019 · 4 comments · Fixed by #3435
Closed

Flask.send_file incorrectly sends blank files #3358

ColdHeat opened this issue Sep 10, 2019 · 4 comments · Fixed by #3435
Milestone

Comments

@ColdHeat
Copy link

ColdHeat commented Sep 10, 2019

Flask.send_file sends blank files when using io.StringIO in Python 3. There is a warning that this is wrong but the response code is still 200 but with a blank file.

Testing this behavior with a test_client will however still result in output. Which doesn't really make sense.

Related Info:
https://stackoverflow.com/questions/35710361/python-flask-send-file-stringio-blank-files

Expected Behavior

  • Flask.send_file should accept an io.StringIO file object and send it to the browser properly
  • Flask.send_file should 500 on the the request instead of a blank file
  • app.test_client shouldn't have data in this setup

Test Case:
Run in Python 3

import sys
if sys.version_info >= (3, 0):
    from io import StringIO
else:
    from StringIO import StringIO

from flask import Flask, send_file
import csv
app = Flask(__name__)

@app.route('/')
def hello_world():
    temp = StringIO()
    writer = csv.writer(temp)
    header = ['test', 'test']
    writer.writerow(header)

    temp.seek(0)

    return send_file(
        temp,
        as_attachment=True,
        cache_timeout=-1,
        attachment_filename="test.csv"
    )

app.test_client()

with app.test_client() as c:
    rv = c.get('/')
    print(rv.data)

app.run(debug=True, threaded=True, host="127.0.0.1", port=4000)
❯ python3 test_case.py
b'test,test\r\n'
 * Serving Flask app "test_case" (lazy loading)
 * Environment: production
   WARNING: Do not use the development server in a production environment.
   Use a production WSGI server instead.
 * Debug mode: on
 * Running on http://127.0.0.1:4000/ (Press CTRL+C to quit)
 * Restarting with stat
b'test,test\r\n'
 * Debugger is active!
 * Debugger PIN: 881-854-930
127.0.0.1 - - [10/Sep/2019 02:15:54] "GET / HTTP/1.1" 200 -
Error on request:
Traceback (most recent call last):
  File "/Users/kchung/.virtualenvs/ctfd3/lib/python3.7/site-packages/werkzeug/serving.py", line 303, in run_wsgi
    execute(self.server.app)
  File "/Users/kchung/.virtualenvs/ctfd3/lib/python3.7/site-packages/werkzeug/serving.py", line 294, in execute
    write(data)
  File "/Users/kchung/.virtualenvs/ctfd3/lib/python3.7/site-packages/werkzeug/serving.py", line 274, in write
    assert isinstance(data, bytes), "applications must write bytes"
AssertionError: applications must write bytes
❯ curl -vvv http://localhost:4000
* Rebuilt URL to: http://localhost:4000/
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 4000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 4000 (#0)
> GET / HTTP/1.1
> Host: localhost:4000
> User-Agent: curl/7.54.0
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 200 OK
< Content-Disposition: attachment; filename=test.csv
< Content-Type: text/csv; charset=utf-8
< Cache-Control: public, max-age=-1
< Expires: Tue, 10 Sep 2019 06:29:28 GMT
< Connection: close
< Server: Werkzeug/0.15.6 Python/3.7.4
< Date: Tue, 10 Sep 2019 06:29:29 GMT
<
* Closing connection 0

Actual Behavior

  • With Python 3 Hitting the endpoint in a browser results in an empty file but in the test_client there is a data response.

Environment

  • Python version: 3.7.4
  • Flask version: 1.1.1
  • Werkzeug version: 0.15.3
@wagnerluis1982
Copy link

Sorry the spam, I didn't see the PR was from another project...

@wagnerluis1982
Copy link

Ok, now entering in the subject, I understand the suggested behavior is not retrocompatible, right?

@davidism
Copy link
Member

The fact that we support BytesIO is not an indication that we should support StringIO. send_file only expects to be given binary file-like objects. The same issue happens when passing an open file in text mode. The function doesn't deal with encoding, so it wouldn't know how to encode the string to bytes, and I think that should remain up to the user code calling it.

@davidism
Copy link
Member

Determining if a given file-like object reads bytes appears to be non-trivial, especially with compatibility with Python 2's file. It's probably not worth the overhead in general for send_file. As a compromise, I can add a mention of the mode to the docs, and raise an error if it's an instance of io.TextIOBase, which should cover most cases including the one given here.

@davidism davidism added this to the 2.0.0 milestone Nov 19, 2019
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Nov 14, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants