mirror of https://github.com/pallets/flask.git
URL generation rule explained for file uploads
This commit is contained in:
parent
d2a380451b
commit
50fef79b20
|
|
@ -1,190 +0,0 @@
|
||||||
.. _uploading-files:
|
|
||||||
|
|
||||||
Uploading Files
|
|
||||||
===============
|
|
||||||
|
|
||||||
Ah yes, the good old problem of file uploads. The basic idea of file
|
|
||||||
uploads is actually quite simple. It basically works like this:
|
|
||||||
|
|
||||||
1. A ``<form>`` tag is marked with ``enctype=multipart/form-data``
|
|
||||||
and an ``<input type=file>`` is placed in that form.
|
|
||||||
2. The application accesses the file from the :attr:`~flask.request.files`
|
|
||||||
dictionary on the request object.
|
|
||||||
3. use the :meth:`~werkzeug.datastructures.FileStorage.save` method of the file to save
|
|
||||||
the file permanently somewhere on the filesystem.
|
|
||||||
|
|
||||||
A Gentle Introduction
|
|
||||||
---------------------
|
|
||||||
|
|
||||||
Let's start with a very basic application that uploads a file to a
|
|
||||||
specific upload folder and displays a file to the user. Let's look at the
|
|
||||||
bootstrapping code for our application::
|
|
||||||
|
|
||||||
import os
|
|
||||||
from flask import Flask, request, redirect, url_for
|
|
||||||
from werkzeug.utils import secure_filename
|
|
||||||
|
|
||||||
UPLOAD_FOLDER = '/path/to/the/uploads'
|
|
||||||
ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif'])
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
|
|
||||||
|
|
||||||
So first we need a couple of imports. Most should be straightforward, the
|
|
||||||
:func:`werkzeug.secure_filename` is explained a little bit later. The
|
|
||||||
``UPLOAD_FOLDER`` is where we will store the uploaded files and the
|
|
||||||
``ALLOWED_EXTENSIONS`` is the set of allowed file extensions. Then we add a
|
|
||||||
URL rule by hand to the application. Now usually we're not doing that, so
|
|
||||||
why here? The reasons is that we want the webserver (or our development
|
|
||||||
server) to serve these files for us and so we only need a rule to generate
|
|
||||||
the URL to these files.
|
|
||||||
|
|
||||||
Why do we limit the extensions that are allowed? You probably don't want
|
|
||||||
your users to be able to upload everything there if the server is directly
|
|
||||||
sending out the data to the client. That way you can make sure that users
|
|
||||||
are not able to upload HTML files that would cause XSS problems (see
|
|
||||||
:ref:`xss`). Also make sure to disallow ``.php`` files if the server
|
|
||||||
executes them, but who has PHP installed on his server, right? :)
|
|
||||||
|
|
||||||
Next the functions that check if an extension is valid and that uploads
|
|
||||||
the file and redirects the user to the URL for the uploaded file::
|
|
||||||
|
|
||||||
def allowed_file(filename):
|
|
||||||
return '.' in filename and \
|
|
||||||
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
|
|
||||||
|
|
||||||
@app.route('/', methods=['GET', 'POST'])
|
|
||||||
def upload_file():
|
|
||||||
if request.method == 'POST':
|
|
||||||
# check if the post request has the file part
|
|
||||||
if 'file' not in request.files:
|
|
||||||
flash('No file part')
|
|
||||||
return redirect(request.url)
|
|
||||||
file = request.files['file']
|
|
||||||
# if user does not select file, browser also
|
|
||||||
# submit a empty part without filename
|
|
||||||
if file.filename == '':
|
|
||||||
flash('No selected file')
|
|
||||||
return redirect(request.url)
|
|
||||||
if file and allowed_file(file.filename):
|
|
||||||
filename = secure_filename(file.filename)
|
|
||||||
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
|
|
||||||
return redirect(url_for('uploaded_file',
|
|
||||||
filename=filename))
|
|
||||||
return '''
|
|
||||||
<!doctype html>
|
|
||||||
<title>Upload new File</title>
|
|
||||||
<h1>Upload new File</h1>
|
|
||||||
<form action="" method=post enctype=multipart/form-data>
|
|
||||||
<p><input type=file name=file>
|
|
||||||
<input type=submit value=Upload>
|
|
||||||
</form>
|
|
||||||
'''
|
|
||||||
|
|
||||||
So what does that :func:`~werkzeug.utils.secure_filename` function actually do?
|
|
||||||
Now the problem is that there is that principle called "never trust user
|
|
||||||
input". This is also true for the filename of an uploaded file. All
|
|
||||||
submitted form data can be forged, and filenames can be dangerous. For
|
|
||||||
the moment just remember: always use that function to secure a filename
|
|
||||||
before storing it directly on the filesystem.
|
|
||||||
|
|
||||||
.. admonition:: Information for the Pros
|
|
||||||
|
|
||||||
So you're interested in what that :func:`~werkzeug.utils.secure_filename`
|
|
||||||
function does and what the problem is if you're not using it? So just
|
|
||||||
imagine someone would send the following information as `filename` to
|
|
||||||
your application::
|
|
||||||
|
|
||||||
filename = "../../../../home/username/.bashrc"
|
|
||||||
|
|
||||||
Assuming the number of ``../`` is correct and you would join this with
|
|
||||||
the ``UPLOAD_FOLDER`` the user might have the ability to modify a file on
|
|
||||||
the server's filesystem he or she should not modify. This does require some
|
|
||||||
knowledge about how the application looks like, but trust me, hackers
|
|
||||||
are patient :)
|
|
||||||
|
|
||||||
Now let's look how that function works:
|
|
||||||
|
|
||||||
>>> secure_filename('../../../../home/username/.bashrc')
|
|
||||||
'home_username_.bashrc'
|
|
||||||
|
|
||||||
Now one last thing is missing: the serving of the uploaded files. As of
|
|
||||||
Flask 0.5 we can use a function that does that for us::
|
|
||||||
|
|
||||||
from flask import send_from_directory
|
|
||||||
|
|
||||||
@app.route('/uploads/<filename>')
|
|
||||||
def uploaded_file(filename):
|
|
||||||
return send_from_directory(app.config['UPLOAD_FOLDER'],
|
|
||||||
filename)
|
|
||||||
|
|
||||||
Alternatively you can register `uploaded_file` as `build_only` rule and
|
|
||||||
use the :class:`~werkzeug.wsgi.SharedDataMiddleware`. This also works with
|
|
||||||
older versions of Flask::
|
|
||||||
|
|
||||||
from werkzeug import SharedDataMiddleware
|
|
||||||
app.add_url_rule('/uploads/<filename>', 'uploaded_file',
|
|
||||||
build_only=True)
|
|
||||||
app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
|
|
||||||
'/uploads': app.config['UPLOAD_FOLDER']
|
|
||||||
})
|
|
||||||
|
|
||||||
If you now run the application everything should work as expected.
|
|
||||||
|
|
||||||
|
|
||||||
Improving Uploads
|
|
||||||
-----------------
|
|
||||||
|
|
||||||
.. versionadded:: 0.6
|
|
||||||
|
|
||||||
So how exactly does Flask handle uploads? Well it will store them in the
|
|
||||||
webserver's memory if the files are reasonable small otherwise in a
|
|
||||||
temporary location (as returned by :func:`tempfile.gettempdir`). But how
|
|
||||||
do you specify the maximum file size after which an upload is aborted? By
|
|
||||||
default Flask will happily accept file uploads to an unlimited amount of
|
|
||||||
memory, but you can limit that by setting the ``MAX_CONTENT_LENGTH``
|
|
||||||
config key::
|
|
||||||
|
|
||||||
from flask import Flask, Request
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
|
||||||
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
|
|
||||||
|
|
||||||
The code above will limited the maximum allowed payload to 16 megabytes.
|
|
||||||
If a larger file is transmitted, Flask will raise an
|
|
||||||
:exc:`~werkzeug.exceptions.RequestEntityTooLarge` exception.
|
|
||||||
|
|
||||||
This feature was added in Flask 0.6 but can be achieved in older versions
|
|
||||||
as well by subclassing the request object. For more information on that
|
|
||||||
consult the Werkzeug documentation on file handling.
|
|
||||||
|
|
||||||
|
|
||||||
Upload Progress Bars
|
|
||||||
--------------------
|
|
||||||
|
|
||||||
A while ago many developers had the idea to read the incoming file in
|
|
||||||
small chunks and store the upload progress in the database to be able to
|
|
||||||
poll the progress with JavaScript from the client. Long story short: the
|
|
||||||
client asks the server every 5 seconds how much it has transmitted
|
|
||||||
already. Do you realize the irony? The client is asking for something it
|
|
||||||
should already know.
|
|
||||||
|
|
||||||
Now there are better solutions to that work faster and more reliable. The
|
|
||||||
web changed a lot lately and you can use HTML5, Java, Silverlight or Flash
|
|
||||||
to get a nicer uploading experience on the client side. Look at the
|
|
||||||
following libraries for some nice examples how to do that:
|
|
||||||
|
|
||||||
- `Plupload <http://www.plupload.com/>`_ - HTML5, Java, Flash
|
|
||||||
- `SWFUpload <http://www.swfupload.org/>`_ - Flash
|
|
||||||
- `JumpLoader <http://jumploader.com/>`_ - Java
|
|
||||||
|
|
||||||
|
|
||||||
An Easier Solution
|
|
||||||
------------------
|
|
||||||
|
|
||||||
Because the common pattern for file uploads exists almost unchanged in all
|
|
||||||
applications dealing with uploads, there is a Flask extension called
|
|
||||||
`Flask-Uploads`_ that implements a full fledged upload mechanism with
|
|
||||||
white and blacklisting of extensions and more.
|
|
||||||
|
|
||||||
.. _Flask-Uploads: http://pythonhosted.org/Flask-Uploads/
|
|
||||||
Loading…
Reference in New Issue