Flask File Upload Flask File Upload Form
A common characteristic in spider web applications is to let users upload files to the server. The HTTP protocol documents the mechanism for a client to upload a file in RFC 1867, and our favorite web framework Flask fully supports information technology, but there are many implementation details that fall outside of the formal specification that are unclear for many developers. Things such as where to store uploaded files, how to use them afterwards, or how to protect the server against malicious file uploads generate a lot of defoliation and dubiousness.
In this article I'chiliad going to show you how to implement a robust file upload feature for your Flask server that is uniform with the standard file upload support in your web browser as well as the cool JavaScript-based upload widgets:
A Bones File Upload Form
From a loftier-level perspective, a client uploading a file is treated the same as whatever other form data submission. In other words, you lot have to ascertain an HTML form with a file field in it.
Here is a simple HTML page with a form that accepts a file:
<!doctype html> <html> <head> <title>File Upload</championship> </head> <body> <h1>File Upload</h1> <class method="POST" action="" enctype="multipart/form-data"> <p><input blazon="file" name="file"></p> <p><input type="submit" value="Submit"></p> </form> </torso> </html>
As you probably know, the method
aspect of the <course>
element tin can be GET
or POST
. With Go
, the data is submitted in the query cord of the request URL, while with Post
information technology goes in the request trunk. When files are beingness included in the course, yous must use Post
, as it would be impossible to submit file data in the query string.
The enctype
attribute in the <form>
element is normally not included with forms that don't have files. This attribute defines how the browser should format the information earlier it is submitted to the server. The HTML specification defines 3 possible values for it:
-
application/x-world wide web-class-urlencoded
: This is the default, and the best format for any forms except those that incorporate file fields. -
multipart/form-data
: This format is required when at to the lowest degree ane of the fields in the form is a file field. -
text/plain
: This format has no applied use, so you lot should ignore it.
The actual file field is the standard <input>
element that we use for almost other class fields, with the type gear up to file
. In the example higher up I oasis't included any additional attributes, simply the file field supports ii that are sometimes useful:
-
multiple
can be used to permit multiple files to be uploaded in a single file field. Example:
<input type="file" name="file" multiple>
-
accept
can be used to filter the allowed file types that tin can be selected, either by file extension or by media blazon. Examples:
<input type="file" name="doc_file" accept=".medico,.docx"> <input type="file" name="image_file" accept="image/*">
Accepting File Submissions with Flask
For regular forms, Flask provides access to submitted grade fields in the request.form
dictionary. File fields, nevertheless, are included in the asking.files
dictionary. The request.form
and asking.files
dictionaries are really "multi-dicts", a specialized dictionary implementation that supports duplicate keys. This is necessary considering forms can include multiple fields with the aforementioned name, as is often the case with groups of check boxes. This as well happens with file fields that allow multiple files.
Ignoring important aspects such as validation and security for the moment, the short Flask application shown below accepts a file uploaded with the course shown in the previous section, and writes the submitted file to the current directory:
from flask import Flask, render_template, request, redirect, url_for app = Flask(__name__) @app.route('/') def index(): render render_template('index.html') @app.route('/', methods=['POST']) def upload_file(): uploaded_file = asking.files['file'] if uploaded_file.filename != '': uploaded_file.save(uploaded_file.filename) render redirect(url_for('alphabetize'))
The upload_file()
function is decorated with @app.road
so that information technology is invoked when the browser sends a POST
request. Annotation how the same root URL is separate between two view functions, with index()
ready to accept the Go
requests and upload_file()
the POST
ones.
The uploaded_file
variable holds the submitted file object. This is an example of class FileStorage, which Flask imports from Werkzeug.
The filename
attribute in the FileStorage
provides the filename submitted past the customer. If the user submits the class without selecting a file in the file field, then the filename is going to exist an empty cord, so it is important to always check the filename to determine if a file is available or non.
When Flask receives a file submission information technology does not automatically write it to disk. This is actually a proficient thing, because it gives the application the opportunity to review and validate the file submission, every bit you will see afterwards. The actual file data tin be accessed from the stream
attribute. If the application but wants to save the file to disk, then information technology can call the salve()
method, passing the desired path as an statement. If the file'due south save()
method is non chosen, then the file is discarded.
Want to test file uploads with this application? Make a directory for your awarding and write the code above as app.py. Then create a templates subdirectory, and write the HTML folio from the previous section as templates/alphabetize.html. Create a virtual surroundings and install Flask on it, then run the application with flask run
. Every fourth dimension you submit a file, the server volition write a copy of it in the electric current directory.
Before I motion on to the topic of security, I'm going to discuss a few variations on the lawmaking shown above that you may find useful. Equally I mentioned earlier, the file upload field tin be configured to accept multiple files. If you use request.files['file']
as above you will get merely ane of the submitted files, just with the getlist()
method you lot can access all of them in a for-loop:
for uploaded_file in asking.files.getlist('file'): if uploaded_file.filename != '': uploaded_file.save(uploaded_file.filename)
Many people code their course handling routes in Flask using a unmarried view office for both the Get
and Mail
requests. A version of the instance awarding using a single view office could be coded as follows:
@app.route('/', methods=['GET', 'POST']) def index(): if asking.method == 'POST': uploaded_file = asking.files['file'] if uploaded_file.filename != '': uploaded_file.save(uploaded_file.filename) return redirect(url_for('index')) return render_template('index.html')
Finally, if you use the Flask-WTF extension to handle your forms, you lot can use the FileField
object for your file uploads. The form used in the examples you've seen then far can be written using Flask-WTF as follows:
from flask_wtf import FlaskForm from flask_wtf.file import FileField from wtforms import SubmitField class MyForm(FlaskForm): file = FileField('File') submit = SubmitField('Submit')
Note that the FileField
object comes from the flask_wtf
package, unlike nearly other field classes, which are imported directly from the wtforms
package. Flask-WTF provides two validators for file fields, FileRequired
, which performs a check similar to the empty cord check, and FileAllowed
, which ensures the file extension is included in an immune extensions listing.
When yous utilise a Flask-WTF form, the data
attribute of the file field object points to the FileStorage
example, so saving a file to disk works in the same way equally in the examples above.
Securing file uploads
The file upload example presented in the previous department is an extremely simplistic implementation that is not very robust. One of the most important rules in web development is that information submitted by clients should never be trusted, and for that reason when working with regular forms, an extension such every bit Flask-WTF performs strict validation of all fields before the class is accustomed and the data incorporated into the application. For forms that include file fields there needs to exist validation as well, because without file validation the server leaves the door open to attacks. For example:
- An attacker tin upload a file that is and so large that the deejay space in the server is completely filled, causing the server to malfunction.
- An attacker can craft an upload asking that uses a filename such every bit ../../../.bashrc or like, with the endeavor to fox the server into rewriting organization configuration files.
- An assaulter can upload files with viruses or other types of malware in a place where the application, for example, expects images.
Limiting the size of uploaded files
To forestall clients from uploading very big files, you lot can utilise a configuration pick provided by Flask. The MAX_CONTENT_LENGTH
option controls the maximum size a request body can have. While this isn't an option that is specific to file uploads, setting a maximum asking body size effectively makes Flask discard any incoming requests that are larger than the allowed amount with a 413 status code.
Let'south modify the app.py instance from the previous section to only accept requests that are upwardly to 1MB in size:
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024
If you lot attempt to upload a file that is larger than 1MB, the awarding will now decline information technology.
Validating filenames
Nosotros can't really trust that the filenames provided past the customer are valid and safe to utilise, so filenames coming with uploaded files take to exist validated.
A very uncomplicated validation to perform is to make sure that the file extension is ane that the application is willing to accept, which is similar to what the FileAllowed
validator does when using Flask-WTF. Let's say the application accepts images, and so information technology tin configure the list of canonical file extensions:
app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']
For every uploaded file, the application tin make certain that the file extension is one of the allowed ones:
filename = uploaded_file.filename if filename != '': file_ext = os.path.splitext(filename)[i] if file_ext not in current_app.config['UPLOAD_EXTENSIONS']: abort(400)
With this logic, whatever filenames that exercise not have 1 of the approved file extensions is going to exist responded with a 400 error.
In improver to the file extension, it is also of import to validate the filename, and any path given with it. If your application does not intendance nigh the filename provided past the client, the most secure manner to handle the upload is to ignore the client provided filename and generate your own filename instead, that you pass to the save()
method. An instance use example where this technique works well is with avatar paradigm uploads. Each user's avatar tin be saved with the user id every bit filename, and then the filename provided past the customer can be discarded. If your application uses Flask-Login, you could implement the following save()
telephone call:
uploaded_file.salve(os.path.join('static/avatars', current_user.get_id()))
In other cases information technology may exist improve to preserve the filenames provided by the customer, so the filename must be sanitized first. For those cases Werkzeug provides the secure_filename() role. Allow's run into how this function works past running a few tests in a Python session:
>>> from werkzeug.utils import secure_filename >>> secure_filename('foo.jpg') 'foo.jpg' >>> secure_filename('/some/path/foo.jpg') 'some_path_foo.jpg' >>> secure_filename('../../../.bashrc') 'bashrc'
As yous come across in the examples, no affair how complicated or malicious the filename is, the secure_filename()
office reduces it to a flat filename.
Let's incorporate secure_filename()
into the case upload server, and also add a configuration variable that defines a dedicated location for file uploads. Here is the complete app.py source file with secure filenames:
import os from flask import Flask, render_template, request, redirect, url_for, abort from werkzeug.utils import secure_filename app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads' @app.route('/') def index(): render render_template('alphabetize.html') @app.road('/', methods=['Mail service']) def upload_files(): uploaded_file = request.files['file'] filename = secure_filename(uploaded_file.filename) if filename != '': file_ext = os.path.splitext(filename)[1] if file_ext not in app.config['UPLOAD_EXTENSIONS']: arrest(400) uploaded_file.salvage(os.path.join(app.config['UPLOAD_PATH'], filename)) return redirect(url_for('index'))
Validating file contents
The tertiary layer of validation that I'one thousand going to discuss is the most circuitous. If your awarding accepts uploads of a certain file type, it should ideally perform some grade of content validation and reject any files that are of a different type.
How y'all attain content validation largely depends on the file types your application accepts. For the example application in this article I'grand using images, so I tin can use the imghdr packet from the Python standard library to validate that the header of the file is, in fact, an image.
Let'south write a validate_image()
function that performs content validation on images:
import imghdr def validate_image(stream): header = stream.read(512) stream.seek(0) format = imghdr.what(None, header) if not format: return None render '.' + (format if format != 'jpeg' else 'jpg')
This function takes a byte stream as an argument. It starts by reading 512 bytes from the stream, and so resetting the stream pointer back, because subsequently when the salve()
function is called we desire it to see the entire stream. The first 512 bytes of the prototype data are going to exist sufficient to identify the format of the image.
The imghdr.what()
office tin can look at a file stored on disk if the first argument is the filename, or else information technology can look at data stored in memory if the start argument is None
and the data is passed in the second argument. The FileStorage
object gives us a stream, so the most convenient option is to read a safe corporeality of data from it and laissez passer it as a byte sequence in the 2d argument.
The return value of imghdr.what()
is the detected image format. The function supports a variety of formats, among them the pop jpeg
, png
and gif
. If not known image format is detected, then the render value is None
. If a format is detected, the proper noun of the format is returned. The most convenient is to render the format as a file extension, because the application can then ensure that the detected extension matches the file extension, so the validate_image()
function converts the detected format into a file extension. This is as uncomplicated equally adding a dot as prefix for all image formats except jpeg
, which usually uses the .jpg
extension, so this case is treated as an exception.
Here is the complete app.py, with all the features from the previous sections plus content validation:
import imghdr import os from flask import Flask, render_template, request, redirect, url_for, abort from werkzeug.utils import secure_filename app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads' def validate_image(stream): header = stream.read(512) stream.seek(0) format = imghdr.what(None, header) if not format: return None render '.' + (format if format != 'jpeg' else 'jpg') @app.route('/') def index(): return render_template('index.html') @app.route('/', methods=['POST']) def upload_files(): uploaded_file = request.files['file'] filename = secure_filename(uploaded_file.filename) if filename != '': file_ext = os.path.splitext(filename)[i] if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \ file_ext != validate_image(uploaded_file.stream): arrest(400) uploaded_file.save(bone.path.join(app.config['UPLOAD_PATH'], filename)) return redirect(url_for('index'))
The just change in the view part to incorporate this last validation logic is here:
if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \ file_ext != validate_image(uploaded_file.stream): abort(400)
This expanded check showtime makes certain that the file extension is in the allowed listing, and then ensures that the detected file extension from looking at the information stream is the aforementioned as the file extension.
Earlier yous examination this version of the application create a directory named uploads (or the path that you divers in the UPLOAD_PATH
configuration variable, if different) so that files can be saved there.
Using Uploaded Files
Y'all now know how to handle file uploads. For some applications this is all that is needed, as the files are used for some internal process. Simply for a big number of applications, in particular those with social features such as avatars, the files that are uploaded by users have to be integrated with the awarding. Using the instance of avatars, in one case a user uploads their avatar image, whatsoever mention of the username requires the uploaded image to appear to the side.
I divide file uploads into ii large groups, depending on whether the files uploaded by users are intended for public utilize, or they are private to each user. The avatar images discussed several times in this article are conspicuously in the first group, every bit these avatars are intended to exist publicly shared with other users. On the other side, an application that performs editing operations on uploaded images would probably exist in the second group, considering you'd want each user to but have access to their ain images.
Consuming public uploads
When images are of a public nature, the easiest style to make the images bachelor for use by the application is to put the upload directory within the application'southward static folder. For example, an avatars subdirectory can be created inside static, and then avatar images can be saved there using the user id as name.
Referencing these uploads stored in a subdirectory of the static binder is washed in the aforementioned way every bit regular static files of the application, using the url_for()
function. I previously suggested using the user id every bit a filename, when saving an uploaded avatar image. This was the fashion the images were saved:
uploaded_file.save(os.path.join('static/avatars', current_user.get_id()))
With this implementation, given a user_id
, the URL for the user'due south avatar can be generated every bit follows:
url_for('static', filename='avatars/' + str(user_id))
Alternatively, the uploads can be saved to a directory exterior of the static folder, and and then a new route can be added to serve them. In the example app.py application file uploads are saved to the location set in the UPLOAD_PATH
configuration variable. To serve these files from that location, we tin implement the following route:
from flask import send_from_directory @app.route('/uploads/<filename>') def upload(filename): return send_from_directory(app.config['UPLOAD_PATH'], filename)
Ane advantage that this solution has over storing uploads inside the static binder is that hither you can implement additional restrictions earlier these files are returned, either direct with Python logic inside the body of the function, or with decorators. For example, if yous want to just provide access to the uploads to logged in users, you tin add Flask-Login's @login_required
decorator to this route, or any other hallmark or function checking mechanism that you utilise for your normal routes.
Let'south utilize this implementation idea to show uploaded files in our example application. Here is a new complete version of app.py:
import imghdr import os from flask import Flask, render_template, request, redirect, url_for, abort, \ send_from_directory from werkzeug.utils import secure_filename app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads' def validate_image(stream): header = stream.read(512) # 512 bytes should be enough for a header cheque stream.seek(0) # reset stream pointer format = imghdr.what(None, header) if non format: return None return '.' + (format if format != 'jpeg' else 'jpg') @app.route('/') def alphabetize(): files = os.listdir(app.config['UPLOAD_PATH']) return render_template('index.html', files=files) @app.route('/', methods=['Mail']) def upload_files(): uploaded_file = request.files['file'] filename = secure_filename(uploaded_file.filename) if filename != '': file_ext = os.path.splitext(filename)[i] if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \ file_ext != validate_image(uploaded_file.stream): arrest(400) uploaded_file.save(os.path.bring together(app.config['UPLOAD_PATH'], filename)) return redirect(url_for('index')) @app.road('/uploads/<filename>') def upload(filename): return send_from_directory(app.config['UPLOAD_PATH'], filename)
In add-on to the new upload()
function, the index()
view function gets the list of files in the upload location using os.listdir()
and sends it downwards to the template for rendering. The index.html template updated to show uploads is shown below:
<!doctype html> <html> <head> <championship>File Upload</championship> </head> <body> <h1>File Upload</h1> <class method="POST" action="" enctype="multipart/form-information"> <p><input type="file" name="file"></p> <p><input type="submit" value="Submit"></p> </form> <hour> {% for file in files %} <img src="{{ url_for('upload', filename=file) }}" style="width: 64px"> {% endfor %} </body> </html>
With these changes, every time y'all upload an image, a thumbnail is added at the bottom of the page:
Consuming individual uploads
When users upload private files to the application, boosted checks need to be in identify to prevent sharing files from one user with unauthorized parties. The solution for these cases require variations of the upload()
view function shown above, with boosted access checks.
A common requirement is to only share uploaded files with their possessor. A convenient way to shop uploads when this requirement is present is to utilise a separate directory for each user. For instance, uploads for a given user tin can be saved to the uploads/<user_id>
directory, and then the uploads()
function can be modified to simply serve uploads from the user'south own upload directory, making it incommunicable for one user to encounter files from another. Beneath you tin see a possible implementation of this technique, over again assuming Flask-Login is used:
@app.route('/uploads/<filename>') @login_required def upload(filename): return send_from_directory(os.path.join( app.config['UPLOAD_PATH'], current_user.get_id()), filename)
Showing upload progress
Up until now we have relied on the native file upload widget provided past the web browser to initiate our file uploads. I'g sure we can all agree that this widget is not very highly-seasoned. Not only that, only the lack of an upload progress brandish makes information technology unusable for uploads of big files, as the user receives no feedback during the entire upload process. While the scope of this article is to cover the server side, I thought it would be useful to give y'all a few ideas on how to implement a modern JavaScript-based file upload widget that displays upload progress.
The good news is that on the server there aren't whatsoever large changes needed, the upload machinery works in the aforementioned way regardless of what method you lot apply in the browser to initiate the upload. To prove yous an example implementation I'm going to replace the HTML form in alphabetize.html with ane that is compatible with dropzone.js, a pop file upload client.
Here is a new version of templates/index.html that loads the dropzone CSS and JavaScript files from a CDN, and implements an upload form according to the dropzone documentation:
<html> <head> <title>File Upload</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.css"> </head> <trunk> <h1>File Upload</h1> <class action="{{ url_for('upload_files') }}" form="dropzone"> </class> <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/five.seven.ane/min/dropzone.min.js"></script> </body> </html>
The one interesting thing that I've institute when implementing dropzone is that information technology requires the action
attribute in the <form>
element to be gear up, even though normal forms accept an empty action to indicate that the submission goes to the aforementioned URL.
Start the server with this new version of the template, and this is what you'll get:
That'south basically it! You tin can now drib files and they'll exist uploaded to the server with a progress bar and a last indication of success or failure.
If the file upload fails, either due to the file beingness too large or invalid, dropzone wants to display an error message. Considering our server is currently returning the standard Flask error pages for the 413 and 400 errors, you lot will see some HTML gibberish in the error popup. To correct this we can update the server to render its error responses equally text.
The 413 error for the file likewise large condition is generated by Flask when the request payload is bigger than the size prepare in the configuration. To override the default error page we have to utilize the app.errorhandler
decorator:
@app.errorhandler(413) def too_large(due east): return "File is too large", 413
The 2nd fault status is generated by the awarding when any of the validation checks fails. In this case the error was generated with a abort(400)
telephone call. Instead of that the response tin be generated directly:
if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \ file_ext != validate_image(uploaded_file.stream): return "Invalid image", 400
The final change that I'one thousand going to make isn't really necessary, but it saves a chip of bandwidth. For a successful upload the server returned a redirect()
back to the main route. This caused the upload form to be displayed again, and also to refresh the list of upload thumbnails at the bottom of the page. None of that is necessary now because the uploads are washed as groundwork requests by dropzone, so we tin eliminate that redirect and switch to an empty response with a code 204.
Hither is the complete and updated version of app.py designed to work with dropzone.js:
import imghdr import os from flask import Flask, render_template, asking, redirect, url_for, abort, \ send_from_directory from werkzeug.utils import secure_filename app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads' def validate_image(stream): header = stream.read(512) stream.seek(0) format = imghdr.what(None, header) if non format: return None render '.' + (format if format != 'jpeg' else 'jpg') @app.errorhandler(413) def too_large(e): return "File is too large", 413 @app.route('/') def index(): files = os.listdir(app.config['UPLOAD_PATH']) return render_template('index.html', files=files) @app.route('/', methods=['Mail']) def upload_files(): uploaded_file = request.files['file'] filename = secure_filename(uploaded_file.filename) if filename != '': file_ext = os.path.splitext(filename)[1] if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \ file_ext != validate_image(uploaded_file.stream): render "Invalid image", 400 uploaded_file.salvage(bone.path.bring together(app.config['UPLOAD_PATH'], filename)) return '', 204 @app.route('/uploads/<filename>') def upload(filename): return send_from_directory(app.config['UPLOAD_PATH'], filename)
Restart the application with this update and now errors will take a proper bulletin:
The dropzone.js library is very flexible and has many options for customization, so I encourage you to visit their documentation to learn how to conform it to your needs. You can besides wait for other JavaScript file upload libraries, as they all follow the HTTP standard, which means that your Flask server is going to piece of work well with all of them.
Conclusion
This was a long overdue topic for me, I can't believe I have never written anything on file uploads! I'd dear you hear what you call back about this topic, and if you remember at that place are aspects of this characteristic that I haven't covered in this article. Feel costless to permit me know below in the comments!
Source: https://blog.miguelgrinberg.com/post/handling-file-uploads-with-flask
0 Response to "Flask File Upload Flask File Upload Form"
Enregistrer un commentaire