We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Upload to S3 Failing
- CTFd Version/Commit: 3.6.1
- Operating System: Linux (Docker container)
- Web Browser and Version: Chrome

**What happened?**

Upgrading CTFd resulting in S3 file uploads beginning to return 400 (bad request) status codes. I see one of the fixes for 3.6.1 was for S3, so perhaps a new bug was introduced.

Here are some additional facts which may be helpful:

  - The files are successfully making there way into S3, despite the error
  - The timezone I have configured for my server is CST

I can also confirm that my deployment had working file upload before upgrade to version 3.6.1 (file upload was working for 3.6.0).

**What did you expect to happen?**

File upload to continue working.

**How to reproduce your issue**

Deploy CTFd free version using version 3.6.1 with S3 file upload configured.

**Any associated stack traces or error logs**

The browser request returns error (400 status code):

```
{
    "success": false,
    "errors": {
        "location": [
            "I/O operation on closed file."
        ]
    }
}
```

The backend error is:

```
[ERROR] Error handling request
Traceback (most recent call last):
File "/opt/venv/lib/python3.9/site-packages/gunicorn/workers/base_async.py", line 113, in handle_request
resp.write_file(respiter)
File "/opt/venv/lib/python3.9/site-packages/gunicorn/http/wsgi.py", line 385, in write_file
if not self.sendfile(respiter):
File "/opt/venv/lib/python3.9/site-packages/gunicorn/http/wsgi.py", line 375, in sendfile
self.sock.sendfile(respiter.filelike, count=nbytes)
File "/opt/venv/lib/python3.9/site-packages/gevent/_socket3.py", line 486, in sendfile
return self._sendfile_use_send(file, offset, count)
File "/opt/venv/lib/python3.9/site-packages/gevent/_socket3.py", line 416, in _sendfile_use_send
self._check_sendfile_params(file, offset, count)
File "/opt/venv/lib/python3.9/site-packages/gevent/_socket3.py", line 461, in _check_sendfile_params
raise ValueError(
ValueError: count must be a positive integer (got 0)
```

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `CTFd/utils/uploads/__init__.py`
Content:
```
1 import hashlib
2 import shutil
3 from pathlib import Path
4 
5 from CTFd.models import ChallengeFiles, Files, PageFiles, db
6 from CTFd.utils import get_app_config
7 from CTFd.utils.uploads.uploaders import FilesystemUploader, S3Uploader
8 
9 UPLOADERS = {"filesystem": FilesystemUploader, "s3": S3Uploader}
10 
11 
12 def get_uploader():
13     return UPLOADERS.get(get_app_config("UPLOAD_PROVIDER") or "filesystem")()
14 
15 
16 def upload_file(*args, **kwargs):
17     file_obj = kwargs.get("file")
18     challenge_id = kwargs.get("challenge_id") or kwargs.get("challenge")
19     page_id = kwargs.get("page_id") or kwargs.get("page")
20     file_type = kwargs.get("type", "standard")
21     location = kwargs.get("location")
22 
23     # Validate location and default filename to uploaded file's name
24     parent = None
25     filename = file_obj.filename
26     if location:
27         path = Path(location)
28         if len(path.parts) != 2:
29             raise ValueError(
30                 "Location must contain two parts, a directory and a filename"
31             )
32         # Allow location to override the directory and filename
33         parent = path.parts[0]
34         filename = path.parts[1]
35         location = parent + "/" + filename
36 
37     model_args = {"type": file_type, "location": location}
38 
39     model = Files
40     if file_type == "challenge":
41         model = ChallengeFiles
42         model_args["challenge_id"] = challenge_id
43     if file_type == "page":
44         model = PageFiles
45         model_args["page_id"] = page_id
46 
47     uploader = get_uploader()
48     location = uploader.upload(file_obj=file_obj, filename=filename, path=parent)
49 
50     sha1sum = hash_file(fp=file_obj)
51 
52     model_args["location"] = location
53     model_args["sha1sum"] = sha1sum
54 
55     existing_file = Files.query.filter_by(location=location).first()
56     if existing_file:
57         for k, v in model_args.items():
58             setattr(existing_file, k, v)
59         db.session.commit()
60         file_row = existing_file
61     else:
62         file_row = model(**model_args)
63         db.session.add(file_row)
64         db.session.commit()
65     return file_row
66 
67 
68 def hash_file(fp, algo="sha1"):
69     fp.seek(0)
70     if algo == "sha1":
71         h = hashlib.sha1()  # nosec
72         # https://stackoverflow.com/a/64730457
73         while chunk := fp.read(1024):
74             h.update(chunk)
75         fp.seek(0)
76         return h.hexdigest()
77     else:
78         raise NotImplementedError
79 
80 
81 def delete_file(file_id):
82     f = Files.query.filter_by(id=file_id).first_or_404()
83 
84     uploader = get_uploader()
85     uploader.delete(filename=f.location)
86 
87     db.session.delete(f)
88     db.session.commit()
89     return True
90 
91 
92 def rmdir(directory):
93     shutil.rmtree(directory, ignore_errors=True)
94
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/CTFd/utils/uploads/__init__.py b/CTFd/utils/uploads/__init__.py
--- a/CTFd/utils/uploads/__init__.py
+++ b/CTFd/utils/uploads/__init__.py
@@ -44,11 +44,12 @@
         model = PageFiles
         model_args["page_id"] = page_id
 
+    # Hash is calculated before upload since S3 file upload closes file object
+    sha1sum = hash_file(fp=file_obj)
+
     uploader = get_uploader()
     location = uploader.upload(file_obj=file_obj, filename=filename, path=parent)
 
-    sha1sum = hash_file(fp=file_obj)
-
     model_args["location"] = location
     model_args["sha1sum"] = sha1sum

{"golden_diff": "diff --git a/CTFd/utils/uploads/__init__.py b/CTFd/utils/uploads/__init__.py\n--- a/CTFd/utils/uploads/__init__.py\n+++ b/CTFd/utils/uploads/__init__.py\n@@ -44,11 +44,12 @@\n         model = PageFiles\n         model_args[\"page_id\"] = page_id\n \n+    # Hash is calculated before upload since S3 file upload closes file object\n+    sha1sum = hash_file(fp=file_obj)\n+\n     uploader = get_uploader()\n     location = uploader.upload(file_obj=file_obj, filename=filename, path=parent)\n \n-    sha1sum = hash_file(fp=file_obj)\n-\n     model_args[\"location\"] = location\n     model_args[\"sha1sum\"] = sha1sum\n", "issue": "Upload to S3 Failing\n- CTFd Version/Commit: 3.6.1\r\n- Operating System: Linux (Docker container)\r\n- Web Browser and Version: Chrome\r\n\r\n**What happened?**\r\n\r\nUpgrading CTFd resulting in S3 file uploads beginning to return 400 (bad request) status codes. I see one of the fixes for 3.6.1 was for S3, so perhaps a new bug was introduced.\r\n\r\nHere are some additional facts which may be helpful:\r\n\r\n  - The files are successfully making there way into S3, despite the error\r\n  - The timezone I have configured for my server is CST\r\n\r\nI can also confirm that my deployment had working file upload before upgrade to version 3.6.1 (file upload was working for 3.6.0).\r\n\r\n**What did you expect to happen?**\r\n\r\nFile upload to continue working.\r\n\r\n**How to reproduce your issue**\r\n\r\nDeploy CTFd free version using version 3.6.1 with S3 file upload configured.\r\n\r\n**Any associated stack traces or error logs**\r\n\r\nThe browser request returns error (400 status code):\r\n\r\n```\r\n{\r\n    \"success\": false,\r\n    \"errors\": {\r\n        \"location\": [\r\n            \"I/O operation on closed file.\"\r\n        ]\r\n    }\r\n}\r\n```\r\n\r\nThe backend error is:\r\n\r\n```\r\n[ERROR] Error handling request\r\nTraceback (most recent call last):\r\nFile \"/opt/venv/lib/python3.9/site-packages/gunicorn/workers/base_async.py\", line 113, in handle_request\r\nresp.write_file(respiter)\r\nFile \"/opt/venv/lib/python3.9/site-packages/gunicorn/http/wsgi.py\", line 385, in write_file\r\nif not self.sendfile(respiter):\r\nFile \"/opt/venv/lib/python3.9/site-packages/gunicorn/http/wsgi.py\", line 375, in sendfile\r\nself.sock.sendfile(respiter.filelike, count=nbytes)\r\nFile \"/opt/venv/lib/python3.9/site-packages/gevent/_socket3.py\", line 486, in sendfile\r\nreturn self._sendfile_use_send(file, offset, count)\r\nFile \"/opt/venv/lib/python3.9/site-packages/gevent/_socket3.py\", line 416, in _sendfile_use_send\r\nself._check_sendfile_params(file, offset, count)\r\nFile \"/opt/venv/lib/python3.9/site-packages/gevent/_socket3.py\", line 461, in _check_sendfile_params\r\nraise ValueError(\r\nValueError: count must be a positive integer (got 0)\r\n```\n", "before_files": [{"content": "import hashlib\nimport shutil\nfrom pathlib import Path\n\nfrom CTFd.models import ChallengeFiles, Files, PageFiles, db\nfrom CTFd.utils import get_app_config\nfrom CTFd.utils.uploads.uploaders import FilesystemUploader, S3Uploader\n\nUPLOADERS = {\"filesystem\": FilesystemUploader, \"s3\": S3Uploader}\n\n\ndef get_uploader():\n    return UPLOADERS.get(get_app_config(\"UPLOAD_PROVIDER\") or \"filesystem\")()\n\n\ndef upload_file(*args, **kwargs):\n    file_obj = kwargs.get(\"file\")\n    challenge_id = kwargs.get(\"challenge_id\") or kwargs.get(\"challenge\")\n    page_id = kwargs.get(\"page_id\") or kwargs.get(\"page\")\n    file_type = kwargs.get(\"type\", \"standard\")\n    location = kwargs.get(\"location\")\n\n    # Validate location and default filename to uploaded file's name\n    parent = None\n    filename = file_obj.filename\n    if location:\n        path = Path(location)\n        if len(path.parts) != 2:\n            raise ValueError(\n                \"Location must contain two parts, a directory and a filename\"\n            )\n        # Allow location to override the directory and filename\n        parent = path.parts[0]\n        filename = path.parts[1]\n        location = parent + \"/\" + filename\n\n    model_args = {\"type\": file_type, \"location\": location}\n\n    model = Files\n    if file_type == \"challenge\":\n        model = ChallengeFiles\n        model_args[\"challenge_id\"] = challenge_id\n    if file_type == \"page\":\n        model = PageFiles\n        model_args[\"page_id\"] = page_id\n\n    uploader = get_uploader()\n    location = uploader.upload(file_obj=file_obj, filename=filename, path=parent)\n\n    sha1sum = hash_file(fp=file_obj)\n\n    model_args[\"location\"] = location\n    model_args[\"sha1sum\"] = sha1sum\n\n    existing_file = Files.query.filter_by(location=location).first()\n    if existing_file:\n        for k, v in model_args.items():\n            setattr(existing_file, k, v)\n        db.session.commit()\n        file_row = existing_file\n    else:\n        file_row = model(**model_args)\n        db.session.add(file_row)\n        db.session.commit()\n    return file_row\n\n\ndef hash_file(fp, algo=\"sha1\"):\n    fp.seek(0)\n    if algo == \"sha1\":\n        h = hashlib.sha1()  # nosec\n        # https://stackoverflow.com/a/64730457\n        while chunk := fp.read(1024):\n            h.update(chunk)\n        fp.seek(0)\n        return h.hexdigest()\n    else:\n        raise NotImplementedError\n\n\ndef delete_file(file_id):\n    f = Files.query.filter_by(id=file_id).first_or_404()\n\n    uploader = get_uploader()\n    uploader.delete(filename=f.location)\n\n    db.session.delete(f)\n    db.session.commit()\n    return True\n\n\ndef rmdir(directory):\n    shutil.rmtree(directory, ignore_errors=True)\n", "path": "CTFd/utils/uploads/__init__.py"}], "after_files": [{"content": "import hashlib\nimport shutil\nfrom pathlib import Path\n\nfrom CTFd.models import ChallengeFiles, Files, PageFiles, db\nfrom CTFd.utils import get_app_config\nfrom CTFd.utils.uploads.uploaders import FilesystemUploader, S3Uploader\n\nUPLOADERS = {\"filesystem\": FilesystemUploader, \"s3\": S3Uploader}\n\n\ndef get_uploader():\n    return UPLOADERS.get(get_app_config(\"UPLOAD_PROVIDER\") or \"filesystem\")()\n\n\ndef upload_file(*args, **kwargs):\n    file_obj = kwargs.get(\"file\")\n    challenge_id = kwargs.get(\"challenge_id\") or kwargs.get(\"challenge\")\n    page_id = kwargs.get(\"page_id\") or kwargs.get(\"page\")\n    file_type = kwargs.get(\"type\", \"standard\")\n    location = kwargs.get(\"location\")\n\n    # Validate location and default filename to uploaded file's name\n    parent = None\n    filename = file_obj.filename\n    if location:\n        path = Path(location)\n        if len(path.parts) != 2:\n            raise ValueError(\n                \"Location must contain two parts, a directory and a filename\"\n            )\n        # Allow location to override the directory and filename\n        parent = path.parts[0]\n        filename = path.parts[1]\n        location = parent + \"/\" + filename\n\n    model_args = {\"type\": file_type, \"location\": location}\n\n    model = Files\n    if file_type == \"challenge\":\n        model = ChallengeFiles\n        model_args[\"challenge_id\"] = challenge_id\n    if file_type == \"page\":\n        model = PageFiles\n        model_args[\"page_id\"] = page_id\n\n    # Hash is calculated before upload since S3 file upload closes file object\n    sha1sum = hash_file(fp=file_obj)\n\n    uploader = get_uploader()\n    location = uploader.upload(file_obj=file_obj, filename=filename, path=parent)\n\n    model_args[\"location\"] = location\n    model_args[\"sha1sum\"] = sha1sum\n\n    existing_file = Files.query.filter_by(location=location).first()\n    if existing_file:\n        for k, v in model_args.items():\n            setattr(existing_file, k, v)\n        db.session.commit()\n        file_row = existing_file\n    else:\n        file_row = model(**model_args)\n        db.session.add(file_row)\n        db.session.commit()\n    return file_row\n\n\ndef hash_file(fp, algo=\"sha1\"):\n    fp.seek(0)\n    if algo == \"sha1\":\n        h = hashlib.sha1()  # nosec\n        # https://stackoverflow.com/a/64730457\n        while chunk := fp.read(1024):\n            h.update(chunk)\n        fp.seek(0)\n        return h.hexdigest()\n    else:\n        raise NotImplementedError\n\n\ndef delete_file(file_id):\n    f = Files.query.filter_by(id=file_id).first_or_404()\n\n    uploader = get_uploader()\n    uploader.delete(filename=f.location)\n\n    db.session.delete(f)\n    db.session.commit()\n    return True\n\n\ndef rmdir(directory):\n    shutil.rmtree(directory, ignore_errors=True)\n", "path": "CTFd/utils/uploads/__init__.py"}]}