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] 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/"