Uploading Files — Flask Documentation (1.1.x)
Ah yes, the good old problem of file uploads. The basic idea of file
uploads is actually quite simple. It basically works like this:
use the save() method of the file to save the file permanently somewhere on the filesystem.
The application accesses the file from the files dictionary on the request object.
A <form> tag is marked with enctype=multipart/form-data and an <input type=file> is placed in that form.
Mục lục bài viết
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
,
flash
,
request
,
redirect
,
url_for
from
werkzeug.utils
import
secure_filename
UPLOAD_FOLDER
=
'/path/to/the/uploads'
ALLOWED_EXTENSIONS
=
{
'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, thewerkzeug.secure_filename()
is explained a little bit later. TheUPLOAD_FOLDER
is where we will store the uploaded files and theALLOWED_EXTENSIONS
is the set of allowed file extensions.
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
Cross-Site Scripting (XSS)). Also make sure to disallow .php
files if the server
executes them, but who has PHP installed on their 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
]
.
lower
()
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 an 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 method=post enctype=multipart/form-data>
<input type=file name=file>
<input type=submit value=Upload>
</form>
'''
So what does that 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.
Information for the Pros
So you’re interested in what that 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. In theupload_file()
we redirect the user tourl_for('uploaded_file', filename=filename)
, that is, /uploads/filename
.
So we write the uploaded_file()
function to return the file of that name. 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 SharedDataMiddleware
. This also works with
older versions of Flask:
from
werkzeug.middleware.shared_data
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.