From eec8a14ab34ee106ccf28dc80b4c062efab1188e Mon Sep 17 00:00:00 2001 From: Akhil Date: Wed, 21 Oct 2026 22:46:22 -0700 Subject: [PATCH 1/2] fix: detect base64 from the data URL header, not the payload Data.parseDataURL decided base64 with string.contains(";base64,"), which also searched the payload, so "data:text/plain,hello;base64,world" was treated as base64 and returned nil even though isDataURL accepted it. The regex now captures the optional ;base64 marker and that capture drives the decision. --- Sources/MCP/Extensions/Data+Extensions.swift | 10 +++++---- Tests/MCPTests/Base64DataTests.swift | 23 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/Sources/MCP/Extensions/Data+Extensions.swift b/Sources/MCP/Extensions/Data+Extensions.swift index 745b4a30..be35f91d 100644 --- a/Sources/MCP/Extensions/Data+Extensions.swift +++ b/Sources/MCP/Extensions/Data+Extensions.swift @@ -4,7 +4,7 @@ import RegexBuilder extension Data { /// Regex pattern for data URLs @inline(__always) private static var dataURLRegex: - Regex<(Substring, Substring, Substring?, Substring)> + Regex<(Substring, Substring, Substring?, Substring?, Substring)> { Regex { "data:" @@ -21,7 +21,9 @@ extension Data { } } } - Optionally { ";base64" } + Optionally { + Capture { ";base64" } + } "," Capture { ZeroOrMore { .any } @@ -49,9 +51,9 @@ extension Data { } // Extract components using strongly typed captures - let (_, mediatype, charset, encodedData) = match.output + let (_, mediatype, charset, base64Marker, encodedData) = match.output - let isBase64 = string.contains(";base64,") + let isBase64 = base64Marker != nil // Process MIME type var mimeType = mediatype.isEmpty ? "text/plain" : String(mediatype) diff --git a/Tests/MCPTests/Base64DataTests.swift b/Tests/MCPTests/Base64DataTests.swift index 96025935..bced6f73 100644 --- a/Tests/MCPTests/Base64DataTests.swift +++ b/Tests/MCPTests/Base64DataTests.swift @@ -105,6 +105,29 @@ struct Base64DataTests { } } + @Test("Parse plain-text data URL whose payload contains ;base64,") + func testParseDataURLBase64MarkerInPayload() { + let dataURL = "data:text/plain,hello;base64,world" + #expect(Data.isDataURL(string: dataURL)) + + let result = Data.parseDataURL(dataURL) + #expect(result != nil) + if let (mimeType, data) = result { + #expect(mimeType == "text/plain") + #expect(String(data: data, encoding: .utf8) == "hello;base64,world") + } + } + + @Test("Parse data URL with ;base64, header still decodes base64") + func testParseDataURLBase64HeaderRegression() { + let result = Data.parseDataURL("data:application/octet-stream;base64,/wA=") + #expect(result != nil) + if let (mimeType, data) = result { + #expect(mimeType == "application/octet-stream") + #expect(data == Data([0xFF, 0x00])) + } + } + @Test("Test data URL encoding") func testDataURLEncoding() { let testData = "Hello, world!".data(using: .utf8)! From 34c8cfbcc29ea8ba368defb1cb6ea0f6ac2488e3 Mon Sep 17 00:00:00 2001 From: Akhil Date: Wed, 21 Oct 2026 23:03:24 -0700 Subject: [PATCH 2/2] test: cover ;base64 after a charset parameter --- Tests/MCPTests/Base64DataTests.swift | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Tests/MCPTests/Base64DataTests.swift b/Tests/MCPTests/Base64DataTests.swift index bced6f73..a5e04347 100644 --- a/Tests/MCPTests/Base64DataTests.swift +++ b/Tests/MCPTests/Base64DataTests.swift @@ -128,6 +128,16 @@ struct Base64DataTests { } } + @Test("Parse data URL with charset and ;base64,") + func testParseDataURLCharsetAndBase64() { + let result = Data.parseDataURL("data:text/plain;charset=utf-8;base64,SGk=") + #expect(result != nil) + if let (mimeType, data) = result { + #expect(mimeType == "text/plain;charset=utf-8") + #expect(String(data: data, encoding: .utf8) == "Hi") + } + } + @Test("Test data URL encoding") func testDataURLEncoding() { let testData = "Hello, world!".data(using: .utf8)!