Python: add size limit to HTTP downloader

Add an option to the HTTP downloader that sets a size limit (in bytes).
Any download that's larger than this will be rejected with a
`ContentLengthTooBigError` exception.

This is useful to prevent Blender from choking on the parsing of huge
JSON files, which can be served relatively cheaply via compression
techniques.

```py
from _bpy_internal.http import downloader as http_dl

downloader = http_dl.ConditionalDownloader(
                     metadata_provider=metadata_provider)
downloader.max_size_bytes = 100

downloader.download_to_file("https://example.com/huge.json",
                            Path("/tmp/huge.json"))
```

The `DownloaderOptions` dataclass has also been extended with a
`max_size_bytes` parameter, so that this can be passed to the background
downloader process as well.

Note: the HTTP downloader is not used in Blender 5.1 (it's intended
for the Remote Asset Libraries in 5.2). However, since this can be
considered a fix for a security issue (potential DOS of Blender), I
feel it's still warranted to include this in 5.1. See !154464 for this
change in context.

Pull Request: https://projects.blender.org/blender/blender/pulls/155259
This commit is contained in:
Sybren A. Stüvel 2026-03-10 13:37:55 +01:00
parent 61f26af3fa
commit 705ff33bc0
2 changed files with 54 additions and 1 deletions

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-2.0-or-later
"""
blender -b --factory-startup -P tests/python/bl_http_downloader.py -- output-dir /tmp/should-not-exist --verbose
blender -b --factory-startup -P tests/python/bl_http_downloader.py -- --verbose
"""
@ -62,6 +62,32 @@ class BasicImportTest(unittest.TestCase):
self.assertFalse(downloader.is_subprocess_alive)
class MaxDownloadSizeTest(unittest.TestCase):
def test_max_size(self) -> None:
from _bpy_internal.http import downloader as http_dl
from unittest.mock import MagicMock, patch
metadata_provider = MagicMock(spec=http_dl.MetadataProvider)
metadata_provider.load.return_value = None
downloader = http_dl.ConditionalDownloader(metadata_provider=metadata_provider)
downloader.max_size_bytes = 100
mock_response = MagicMock()
mock_response.headers = {"Content-Length": "200", "Content-Type": "text/plain"}
mock_response.status_code = 200
# When used as context manager, return itself.
mock_response.__enter__.return_value = mock_response
# NOTE: when this test fails, it will say "TypeError: a bytes-like object is required, not 'MagicMock'". This
# indicates that the HTTP downloader tried to actually read bytes from the mock response, which it doesn't
# support.
with patch.object(http_dl.requests.Session, 'send', return_value=mock_response):
with self.assertRaises(http_dl.ContentLengthTooBigError):
downloader.download_to_file("https://example.com/huge.json", Path("/tmp/huge.json"))
class BackgroundDownloaderProcessTest(unittest.TestCase):
"""Start & stop the background process for the BackgroundDownloader.