diff --git a/docs/patterns/fileuploads.rst b/docs/patterns/fileuploads.rst new file mode 100644 index 00000000..fd94605d --- /dev/null +++ b/docs/patterns/fileuploads.rst @@ -0,0 +1,158 @@ +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 ``
`` tag is marked with ``enctype=multipart/form-data`` + and an ```` 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.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 import secure_filename + + UPLOAD_FOLDER = '/path/to/the/uploads' + ALLOWED_EXTENSIONS = set(['txt', 'pdf', 'png', 'jpg', 'jpeg', 'gif']) + + app = Flask(__name__) + app.add_url_rule('/uploads/', 'uploaded_file', + build_only=True) + +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. 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('/') + def upload_file(): + if request.method == 'POST': + file = request.files['file'] + if file and allowed_file(file.filename): + filename = secure_filename(file.filename) + file.save(os.path.join(UPLOAD_FOLDER, filename)) + return redirect(url_for('uploaded_file', + filename=filename)) + return ''' + + Upload new File +

Upload new File

+ +

+ +

+ ''' + +So what does that :func:`~werkzeug.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.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 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 if we run that application, you will notice that uploading works, but +you won't actually see that uploaded file. Well, you would have to +configure the server to serve that file for you. This is not handy for +development situations or when you are just too lazy to properly set up +the server. Would be nice to have the files still be available in that +situation, and that is really easy to do, just hook in a middleware:: + + from werkzeug import SharedDataMiddleware + app.wsgi_app = SharedDataMiddleware(app.wsgi_app, { + '/uploads': UPLOAD_FOLDER + }) + +If you now run the application everything should work as expected. + + +Improving Uploads +----------------- + +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 subclassing the request and overriding +the Werkzeug provided :attr:`~werkzeug.BaseRequest.max_form_memory_size` +attribute:: + + from flask import Flask, Request + + class LimitedRequest(Request): + max_form_memory_size = 16 * 1024 * 1024 + + app = Flask(__name__) + app.request_class = LimitedRequest + +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. + + +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 he has transmitted +already. Do you realize the irony? The client is asking for something he +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 `_ - HTML5, Java, Flash +- `SWFUpload `_ - Flash +- `JumpLoader `_ - Java