patx/gitman
add limits to work with bottles multipart parser
Commit e3e4904 · patx · 2026-05-06T22:00:09-04:00
Comments
No comments yet.
Diff
diff --git a/app.py b/app.py
index 0063f87..4657e80 100644
--- a/app.py
+++ b/app.py
@@ -65,6 +65,7 @@ PASSWORD_ITERATIONS = 260_000
SQLITE_BUSY_TIMEOUT_MS = 30_000
MAX_FORM_BYTES = env_int("GITMAN_MAX_FORM_BYTES", 64 * 1024)
MAX_IMPORT_BYTES = env_int("GITMAN_MAX_IMPORT_BYTES", 5 * 1024 * 1024 * 1024)
+IMPORT_UPLOAD_CHUNK_BYTES = 1024 * 1024
GIT_IMPORT_TIMEOUT_SECONDS = env_int("GITMAN_IMPORT_TIMEOUT_SECONDS", 3600, minimum=1)
MAX_RENDER_BYTES = env_int("GITMAN_MAX_RENDER_BYTES", 256 * 1024)
MAX_GIT_RESPONSE_BYTES = env_int("GITMAN_MAX_GIT_RESPONSE_BYTES", 256 * 1024 * 1024)
@@ -1490,15 +1491,20 @@ def save_bundle_upload(upload, destination):
except (AttributeError, OSError):
pass
- with destination.open("wb") as target:
- while True:
- chunk = source.read(1024 * 1024)
- if not chunk:
- break
- size += len(chunk)
- if MAX_IMPORT_BYTES and size > MAX_IMPORT_BYTES:
- raise UploadTooLarge("Request body too large.")
- target.write(chunk)
+ try:
+ with destination.open("wb") as target:
+ while True:
+ chunk = source.read(IMPORT_UPLOAD_CHUNK_BYTES)
+ if not chunk:
+ break
+ size += len(chunk)
+ if MAX_IMPORT_BYTES and size > MAX_IMPORT_BYTES:
+ raise UploadTooLarge("Request body too large.")
+ target.write(chunk)
+ except UploadTooLarge:
+ if destination.exists():
+ destination.unlink()
+ raise
if size == 0:
raise ValueError("Uploaded bundle is empty.")
diff --git a/tests/test_app.py b/tests/test_app.py
index 4cf6d0d..2417733 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -841,6 +841,34 @@ def test_import_git_bundle_rejects_invalid_upload_without_replacing_repo(isolate
assert path.joinpath("HEAD").read_text(encoding="utf-8").strip() == "ref: refs/heads/main"
+def test_save_bundle_upload_uses_import_chunk_size_and_removes_oversized_partial(tmp_path, monkeypatch):
+ class RecordingSource(BytesIO):
+ def __init__(self, content):
+ super().__init__(content)
+ self.read_sizes = []
+
+ def read(self, size=-1):
+ self.read_sizes.append(size)
+ return super().read(size)
+
+ class Upload:
+ filename = "repo.bundle"
+
+ def __init__(self, content):
+ self.file = RecordingSource(content)
+
+ monkeypatch.setattr(gitman, "MAX_IMPORT_BYTES", 10)
+ monkeypatch.setattr(gitman, "IMPORT_UPLOAD_CHUNK_BYTES", 4)
+ upload = Upload(b"x" * 12)
+ destination = tmp_path / "repo.bundle"
+
+ with pytest.raises(gitman.UploadTooLarge):
+ gitman.save_bundle_upload(upload, destination)
+
+ assert upload.file.read_sizes == [4, 4, 4]
+ assert not destination.exists()
+
+
def test_import_git_bundle_size_limit_returns_413(isolated_app, monkeypatch):
monkeypatch.setattr(gitman, "MAX_IMPORT_BYTES", 100)
owner = create_user("alice")