From 6b696f64ee94df71d91f33d46a35f7b94000af91 Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Mon, 26 Oct 2026 14:51:11 +0800 Subject: [PATCH 1/2] fix(contents): accept RFC 6838 media types and parameters in data URIs The data URI parser only allowed letters and hyphens in the media type and treated everything after the first semicolon as the data format, so image/svg+xml was rejected and charset=utf-8;base64 left the base64 payload undecoded. Parse the media type and parameters with the RFC 6838 token character set and keep the optional data format as a token. --- .../contents/utils/data_uri.py | 11 +++++++- python/tests/unit/contents/test_data_uri.py | 27 +++++++++++++++++++ .../tests/unit/contents/test_image_content.py | 6 +++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/python/semantic_kernel/contents/utils/data_uri.py b/python/semantic_kernel/contents/utils/data_uri.py index 22f514fac9b0..bdbbdbc7d351 100644 --- a/python/semantic_kernel/contents/utils/data_uri.py +++ b/python/semantic_kernel/contents/utils/data_uri.py @@ -135,7 +135,16 @@ def from_data_uri(cls: type[_T], data_uri: str | Url, default_mime_type: str = " if not data or "," not in data: raise ContentInitializationError("Invalid data uri format. The data is missing.") - pattern = "(((?P[a-zA-Z]+/[a-zA-Z-]+)(?P(;[a-zA-Z0-9]+=+[a-zA-Z0-9]+)*))?(;+(?P.*)))?(,(?P.*))" # noqa: E501 + # Media types and parameters use the RFC 6838/RFC 2045 token character set, which + # includes characters such as "+" (image/svg+xml) and "-" (charset=utf-8). Keeping + # the data format as a token prevents unparsed parameters from being silently + # treated as part of the format (for example "charset=utf-8;base64"). + pattern = ( + r"(((?P[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+)" + r"(?P(;[a-zA-Z0-9!#$&^_.+-]+=+[a-zA-Z0-9!#$&^_.+-]+)*))?" + r"(;+(?P[a-zA-Z0-9!#$&^_.+-]*))?)?" + r"(,(?P.*))" + ) match = re.match(pattern, data) if not match: raise ContentInitializationError("Invalid data uri format.") diff --git a/python/tests/unit/contents/test_data_uri.py b/python/tests/unit/contents/test_data_uri.py index fc98679a9765..28decca895a9 100644 --- a/python/tests/unit/contents/test_data_uri.py +++ b/python/tests/unit/contents/test_data_uri.py @@ -66,6 +66,33 @@ "base64", id="with_params", ), + pytest.param( + "data:image/svg+xml;base64,PHN2Zy8+", + b"", + "PHN2Zy8+", + "image/svg+xml", + {}, + "base64", + id="mime_type_with_plus", + ), + pytest.param( + "data:text/plain;charset=utf-8;base64,SGVsbG8=", + b"Hello", + "SGVsbG8=", + "text/plain", + {"charset": "utf-8"}, + "base64", + id="parameter_before_data_format", + ), + pytest.param( + "data:application/vnd.ms-excel;base64,AAAA", + b"\x00\x00\x00", + "AAAA", + "application/vnd.ms-excel", + {}, + "base64", + id="vendor_mime_type", + ), ], ) def test_data_uri_from_data_uri_str( diff --git a/python/tests/unit/contents/test_image_content.py b/python/tests/unit/contents/test_image_content.py index 7d84a1746271..aeec05f6f1dd 100644 --- a/python/tests/unit/contents/test_image_content.py +++ b/python/tests/unit/contents/test_image_content.py @@ -38,6 +38,12 @@ def test_create_data(): assert image.data == b"test_data" +def test_create_svg_data_uri(): + image = ImageContent(data_uri="data:image/svg+xml;base64,PHN2Zy8+") + assert image.mime_type == "image/svg+xml" + assert image.data == b"" + + def test_to_str_uri(): image = ImageContent(uri="http://test_uri") assert str(image) == "http://test_uri/" From b0e34c53a3bbd033335e180c9b20ca0765456e95 Mon Sep 17 00:00:00 2001 From: fei <204683769+feiiiiii5@users.noreply.github.com> Date: Fri, 30 Oct 2026 09:12:54 +0800 Subject: [PATCH 2/2] fix: accept data URI parameters without a media type --- .../semantic_kernel/contents/utils/data_uri.py | 14 +++++++------- python/tests/unit/contents/test_data_uri.py | 17 +++++++++++++++++ 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/python/semantic_kernel/contents/utils/data_uri.py b/python/semantic_kernel/contents/utils/data_uri.py index bdbbdbc7d351..194c7fb47ef1 100644 --- a/python/semantic_kernel/contents/utils/data_uri.py +++ b/python/semantic_kernel/contents/utils/data_uri.py @@ -135,14 +135,14 @@ def from_data_uri(cls: type[_T], data_uri: str | Url, default_mime_type: str = " if not data or "," not in data: raise ContentInitializationError("Invalid data uri format. The data is missing.") - # Media types and parameters use the RFC 6838/RFC 2045 token character set, which - # includes characters such as "+" (image/svg+xml) and "-" (charset=utf-8). Keeping - # the data format as a token prevents unparsed parameters from being silently - # treated as part of the format (for example "charset=utf-8;base64"). + # Parameters use the RFC 2045 token alphabet, which is broader than a + # media type's restricted-name alphabet. RFC 2397 allows parameters even + # when the default text/plain media type is omitted. + parameter_token = r"[a-zA-Z0-9!#$%&'*+.^_`|~-]+" pattern = ( - r"(((?P[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+)" - r"(?P(;[a-zA-Z0-9!#$&^_.+-]+=+[a-zA-Z0-9!#$&^_.+-]+)*))?" - r"(;+(?P[a-zA-Z0-9!#$&^_.+-]*))?)?" + r"(?P[a-zA-Z0-9!#$&^_.+-]+/[a-zA-Z0-9!#$&^_.+-]+)?" + rf"(?P(;{parameter_token}={parameter_token})*)" + r"(;+(?P[a-zA-Z0-9!#$&^_.+-]*))?" r"(,(?P.*))" ) match = re.match(pattern, data) diff --git a/python/tests/unit/contents/test_data_uri.py b/python/tests/unit/contents/test_data_uri.py index 28decca895a9..b2812d236c84 100644 --- a/python/tests/unit/contents/test_data_uri.py +++ b/python/tests/unit/contents/test_data_uri.py @@ -337,3 +337,20 @@ def test_array(): def test__data_str(data_bytes, data_str, data_array, data_format, expected_output): data_uri = DataUri(data_bytes=data_bytes, data_str=data_str, data_array=data_array, data_format=data_format) assert data_uri._data_str() == expected_output + + +@pytest.mark.parametrize("media_type", ["text/plain", ""]) +@pytest.mark.parametrize("encoding,payload", [("", "Hello"), (";base64", "SGVsbG8=")]) +def test_data_uri_parameter_without_required_media_type(media_type, encoding, payload): + result = DataUri.from_data_uri(f"data:{media_type};charset=utf-8{encoding},{payload}") + assert result.data_bytes == b"Hello" + assert result.mime_type == "text/plain" + assert result.parameters == {"charset": "utf-8"} + assert result.data_format == ("base64" if encoding else None) + + +@pytest.mark.parametrize("value", ["foo*bar", "foo'bar", "foo~bar", "foo%20bar", "foo%3Bbar"]) +def test_data_uri_parameter_uses_parameter_token_alphabet(value): + result = DataUri.from_data_uri(f"data:text/plain;name={value},Hello") + assert result.data_bytes == b"Hello" + assert result.parameters == {"name": value}