[Bugfix] Accept RFC 2397 parameters in base64 data URLs (#48973)
Signed-off-by: Thomas Fahrner <thomas.fahrner@parasail.io> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Thomas Fahrner committed
Jul 24, 2026 at 08:23 UTC
d02df748bf9efd99022f1a062597dc3cb3808485
2 files changed
+47
-5
tests/multimodal/media/test_connector.py
+38
@@ -225,6 +225,44 @@ async def test_fetch_image_local_files_with_space_in_name(image_url: str):
225
assert not ImageChops.difference(image_sync, image_async).getbbox()
226
227
228
+@pytest.mark.asyncio
229
+async def test_fetch_image_data_url_with_params():
230
+ """RFC 2397 allows parameters between the mediatype and the base64
231
+ marker; they must not be rejected or leak into the media type."""
232
+ connector = MediaConnector()
233
+
234
+ image = Image.new("RGB", (4, 4), color=(255, 0, 0))
235
+ with NamedTemporaryFile(suffix=".png") as f:
236
+ image.save(f.name)
237
+ base64_image = base64.b64encode(f.read()).decode("utf-8")
238
+
239
+ data_url = f"data:image/png;charset=utf-8;base64,{base64_image}"
240
+ image_sync = connector.fetch_image(data_url)
241
+ image_async = await connector.fetch_image_async(data_url)
242
+ assert _image_equals(image_sync, image_async)
243
+
244
+
245
+def test_fetch_image_data_url_malformed():
246
+ connector = MediaConnector()
247
+
248
+ with pytest.raises(ValueError, match="missing ','"):
249
+ connector.fetch_image("data:image/png;base64")
250
+
251
+ with pytest.raises(NotImplementedError, match="base64"):
252
+ connector.fetch_image("data:text/plain,hello")
253
+
254
+ # ";base64" requires the ";"; here "base64" is a (bogus) media type.
255
+ with pytest.raises(NotImplementedError, match="base64"):
256
+ connector.fetch_image("data:base64,aGVsbG8=")
257
+
258
+ # Strict RFC 2397 grammar: lowercase "base64", no whitespace.
259
+ with pytest.raises(NotImplementedError, match="base64"):
260
+ connector.fetch_image("data:image/png;BASE64,aGVsbG8=")
261
+
262
+ with pytest.raises(NotImplementedError, match="base64"):
263
+ connector.fetch_image("data:image/png; base64,aGVsbG8=")
264
+
265
+
266
@pytest.mark.asyncio
267
async def test_fetch_image_error_conversion():
268
connector = MediaConnector()
vllm/multimodal/media/connector.py
+9
-5
@@ -302,14 +302,18 @@ class MediaConnector:
302
media_io: MediaIO[_M],
303
) -> _M: # type: ignore[type-var]
304
# Format per RFC 2397:
305
- # data:[<mediatype>][;base64],<data>
306
- data_spec, data = url[5:].split(",", 1)
307
- media_type, data_type = data_spec.split(";", 1)
308
-
309
- if data_type != "base64":
305
+ # data:[<mediatype>][;<param>=<value>]*[;base64],<data>
306
+ data_spec, sep, data = url[5:].partition(",")
307
+ if not sep:
308
+ msg = f"Invalid data URL {url[:32]!r}: missing ',' separator."
309
+ raise ValueError(msg)
310
+
311
+ media_type, sep, encoding = data_spec.rpartition(";")
312
+ if not sep or encoding != "base64":
313
msg = "Only base64 data URLs are supported for now."
314
raise NotImplementedError(msg)
315
316
+ media_type = media_type.partition(";")[0]
317
return media_io.load_base64(media_type, data)
318
319
def _load_file_url(