question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[QUESTION] Strategies for limiting upload file size

See original GitHub issue

Description

I’m trying to create an upload endpoint. I want to limit the maximum size that can be uploaded.

My endpoint looks like this:

@app.post('/upload', response_model=UploadedFileDTO)
async def upload_file(file: UploadFile = File(...), db: Session = Depends(get_db_session)):
    save_path = local.generate_path(file.filename)
    with file.file as f:
        local.save(stream=f, save_path=save_path)

    u = Upload(filename=file.filename,
               path=str(save_path))
    db.add(u)
    db.commit()

    return u

I checked out the source for fastapi.params.File, but it doesn’t seem to add anything over fastapi.params.Form.

The only solution that came to my mind is to start saving the uploaded file in chunks, and when the read size exceeds the limit, raise an exception. But I’m wondering if there are any idiomatic ways of handling such scenarios?

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Reactions:1
  • Comments:13 (4 by maintainers)

github_iconTop GitHub Comments

52reactions
tiangolocommented, Feb 10, 2020

Thanks everyone for the discussion here!

So, here’s the thing, a file is not completely sent to the server and received by your FastAPI app before the code in the path operation starts to execute.

So, you don’t really have an actual way of knowing the actual size of the file before reading it.


You could require the Content-Length header and check it and make sure that it’s a valid value. E.g.

from fastapi import FastAPI, File, Header, Depends, UploadFile


async def valid_content_length(content_length: int = Header(..., lt=50_000_000)):
    return content_length


app = FastAPI()

@app.post('/upload', dependencies=[Depends(valid_content_length)])
async def upload_file(file: UploadFile = File(...)):
    # do something with file
    return {"ok": True}

And then you could re-use that valid_content_length dependency in other places if you need to.

⚠️ but it probably won’t prevent an attacker from sending a valid Content-Length header and a body bigger than what your app can take ⚠️


Another option would be to, on top of the header, read the data in chunks. And once it’s bigger than a certain size, throw an error.

E.g.

from typing import IO

from tempfile import NamedTemporaryFile
import shutil

from fastapi import FastAPI, File, Header, Depends, UploadFile, HTTPException
from starlette import status


async def valid_content_length(content_length: int = Header(..., lt=80_000)):
    return content_length


app = FastAPI()


@app.post("/upload")
def upload_file(
    file: UploadFile = File(...), file_size: int = Depends(valid_content_length)
):
    real_file_size = 0
    temp: IO = NamedTemporaryFile(delete=False)
    for chunk in file.file:
        real_file_size += len(chunk)
        if real_file_size > file_size:
            raise HTTPException(
                status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="Too large"
            )
        temp.write(chunk)
    temp.close()
    shutil.move(temp.name, "/tmp/some_final_destiny_file")
    return {"ok": True}
22reactions
abduscocommented, Jul 6, 2019

Ok, I’ve found an acceptable solution. But it relies on Content-Length header being present.

Edit: I’ve added a check to reject requests without Content-Length

from starlette import status
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
from starlette.requests import Request
from starlette.responses import Response
from starlette.types import ASGIApp


class LimitUploadSize(BaseHTTPMiddleware):
    def __init__(self, app: ASGIApp, max_upload_size: int) -> None:
        super().__init__(app)
        self.max_upload_size = max_upload_size

    async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
        if request.method == 'POST':
            if 'content-length' not in request.headers:
                return Response(status_code=status.HTTP_411_LENGTH_REQUIRED)
            content_length = int(request.headers['content-length'])
            if content_length > self.max_upload_size:
                return Response(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE)
        return await call_next(request)

using it is quite straightforward:

app = FastAPI()
app.add_middleware(LimitUploadSize, max_upload_size=50_000_000)  # ~50MB

The server sends HTTP 413 response when the upload size is too large, but I’m not sure how to handle if there’s no Content-Length header. Edit: Solution: Send 411 response

Read more comments on GitHub >

github_iconTop Results From Across the Web

Changing the File Upload Size Limit - Jotform
We've made a quick reference list of the upload size limits as stated on Dropbox's and Google Drive's official pages. Note that these...
Read more >
How can we go about deciding an appropriate filesize upload ...
I'm merely saying, determine why you need to set a limit first. If the only reason is truly because it's a required setting,...
Read more >
Limit the size of a file upload (html input element)
Save this question. Show activity on this post. I would like to simply limit the size of a file that a user can...
Read more >
File upload size - MoodleDocs
Upload file size restrictions. Probably the most frequently asked question on moodle.org is "How do I increase the upload file size limit?
Read more >
How to Increase the Max Upload Size in WordPress - Kinsta
If you're facing issues with uploading files and file size limits, reaching out to your hosting provider for assistance is one of the...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found