From 5021f5de4525980e286fd4deed38f57d79205f46 Mon Sep 17 00:00:00 2001 From: Rodion Kazennov Date: Fri, 2 Oct 2026 03:49:31 +0000 Subject: [PATCH] Python: Accept data URI parameters and default the data URI media type _validate_uri treated the last ';' parameter of a data URI as its encoding and took the media type only when the prefix had a ';'. So data:text/plain;charset=utf-8,hello raised ContentError, and data:text/plain,hello and data:,hello came back with media_type None. Parse the prefix as RFC 2397 does: type/subtype, then attribute=value parameters, with base64 only as the final bare token. The media type comes from the prefix and defaults to text/plain when it is empty. --- .../packages/core/agent_framework/_types.py | 22 +++++++------- python/packages/core/tests/core/test_types.py | 29 +++++++++++++++++++ 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/python/packages/core/agent_framework/_types.py b/python/packages/core/agent_framework/_types.py index 4722699b7fa..c95a654854e 100644 --- a/python/packages/core/agent_framework/_types.py +++ b/python/packages/core/agent_framework/_types.py @@ -249,17 +249,17 @@ def _validate_uri(uri: str, media_type: str | None) -> dict[str, Any]: if "," not in uri: raise ContentError("Data URI must contain a comma separating metadata and data") prefix, _ = uri.split(",", 1) - if ";" in prefix: - parts = prefix.split(";") - if len(parts) < 2: - raise ContentError("Invalid data URI format") - # Check encoding - encoding = parts[-1] - if encoding not in ("base64", ""): - raise ContentError(f"Unsupported data URI encoding: {encoding}") - if media_type is None: - # attempt to extract: - media_type = parts[0][5:] # Remove 'data:' + # RFC 2397: data:[][;base64],, where is type/subtype followed by + # optional ;attribute=value parameters. Only a final bare "base64" token names the encoding. + declared_type, *parameters = prefix[len("data:") :].split(";") + if parameters and parameters[-1] in ("base64", ""): + parameters = parameters[:-1] + for parameter in parameters: + if "=" not in parameter: + raise ContentError(f"Unsupported data URI encoding: {parameter}") + if media_type is None: + # RFC 2397 defaults an omitted media type to text/plain. + media_type = declared_type or "text/plain" return {"type": "data", "uri": uri, "media_type": media_type} # Check for common URI schemes diff --git a/python/packages/core/tests/core/test_types.py b/python/packages/core/tests/core/test_types.py index ff4bfedf014..bde8cae2029 100644 --- a/python/packages/core/tests/core/test_types.py +++ b/python/packages/core/tests/core/test_types.py @@ -4206,6 +4206,35 @@ def test_validate_uri_data_uri(): assert "uri" in result +@mark.parametrize( + ("uri", "expected_media_type"), + [ + ("data:text/plain,hello", "text/plain"), + ("data:text/plain;charset=utf-8,hello%20world", "text/plain"), + ("data:application/json;charset=utf-8;base64,e30=", "application/json"), + ("data:,hello", "text/plain"), + ("data:;base64,aGVsbG8=", "text/plain"), + ], +) +def test_validate_uri_data_uri_parameters_and_default_media_type(uri: str, expected_media_type: str): + """RFC 2397 data URIs may carry parameters, and the media type comes from the prefix.""" + assert _validate_uri(uri, None) == {"type": "data", "uri": uri, "media_type": expected_media_type} + + +@mark.parametrize("uri", ["data:image/png;base32,AAAA", "data:text/plain;base64;charset=utf-8,AAAA"]) +def test_validate_uri_rejects_unsupported_data_uri_encoding(uri: str): + """Only a final bare base64 token names an encoding; any other bare token is rejected.""" + with raises(ContentError, match="Unsupported data URI encoding"): + _validate_uri(uri, None) + + +def test_from_uri_accepts_data_uri_with_charset_parameter(): + """Content.from_uri accepts a non-base64 data URI with a charset parameter.""" + content = Content.from_uri("data:text/plain;charset=utf-8,hello%20world") + assert content.type == "data" + assert content.media_type == "text/plain" + + # endregion