diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/CHANGELOG.md b/sdk/spring/spring-cloud-azure-appconfiguration-config/CHANGELOG.md index 16944b197b61..c7b463921ac1 100644 --- a/sdk/spring/spring-cloud-azure-appconfiguration-config/CHANGELOG.md +++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs Fixed +- Fixes a bug where ';' was ignored in JSON content type checking. + ### Other Changes ## 7.1.0 (2026-03-11) diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParser.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParser.java index 824f111ae238..a7d1a00a0748 100644 --- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParser.java +++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/main/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParser.java @@ -2,10 +2,8 @@ // Licensed under the MIT License. package com.azure.spring.cloud.appconfiguration.config.implementation; -import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; -import java.util.List; import java.util.Map; import org.springframework.boot.context.properties.source.InvalidConfigurationPropertyValueException; @@ -23,25 +21,29 @@ final class JsonConfigurationParser { private static final ObjectMapper MAPPER = JsonMapper.builder().enable(JsonReadFeature.ALLOW_JAVA_COMMENTS).build(); static boolean isJsonContentType(String contentType) { - String acceptedMainType = "application"; - String acceptedSubType = "json"; - if (!StringUtils.hasText(contentType)) { return false; } - if (contentType.contains("/")) { - String mainType = contentType.split("/")[0]; - String subType = contentType.split("/")[1]; + contentType = contentType.strip().toLowerCase(); + String mimeType = contentType.split(";")[0].strip(); - if (mainType.equalsIgnoreCase(acceptedMainType)) { - if (subType.contains("+")) { - List subtypes = Arrays.asList(subType.split("\\+")); - return subtypes.contains(acceptedSubType); - } else { - return subType.equalsIgnoreCase(acceptedSubType); - } - } + String[] typeParts = mimeType.split("/"); + if (typeParts.length != 2) { + return false; + } + + String mainType = typeParts[0]; + String subType = typeParts[1]; + + if (!"application".equals(mainType)) { + return false; + } + + String[] subTypes = subType.split("\\+"); + // Check if the last part (suffix) is "json" + if (subTypes.length > 0 && subTypes[subTypes.length - 1].equals("json")) { + return true; } return false; diff --git a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParserTest.java b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParserTest.java index 51cc010bb88a..52641e43abc7 100644 --- a/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParserTest.java +++ b/sdk/spring/spring-cloud-azure-appconfiguration-config/src/test/java/com/azure/spring/cloud/appconfiguration/config/implementation/JsonConfigurationParserTest.java @@ -25,17 +25,77 @@ public class JsonConfigurationParserTest { @Test public void isJsonContentType() { + // Basic valid JSON content types assertTrue(JsonConfigurationParser.isJsonContentType("application/json")); assertTrue(JsonConfigurationParser.isJsonContentType("application/api+json")); - assertTrue(JsonConfigurationParser.isJsonContentType("application/json+activity")); assertTrue(JsonConfigurationParser.isJsonContentType("application/vnd.xxxx+json")); assertTrue(JsonConfigurationParser.isJsonContentType("application/vnd.microsoft.appconfig.document+json")); + + // Invalid content types assertFalse(JsonConfigurationParser.isJsonContentType("application")); assertFalse(JsonConfigurationParser.isJsonContentType("app/json")); assertFalse(JsonConfigurationParser.isJsonContentType("app/config")); assertFalse(JsonConfigurationParser.isJsonContentType("application/config")); assertFalse(JsonConfigurationParser.isJsonContentType("")); assertFalse(JsonConfigurationParser.isJsonContentType(null)); + assertFalse(JsonConfigurationParser.isJsonContentType("application/json+activity")); // activity is the suffix, not json + } + + @Test + public void isJsonContentTypeWithParameters() { + // Content types with charset and other parameters + assertTrue(JsonConfigurationParser.isJsonContentType("application/json; charset=utf-8")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/json;charset=utf-8")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/json ; charset=utf-8")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/vnd.api+json; charset=utf-8")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/json; charset=ISO-8859-1")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/json;boundary=something")); + } + + @Test + public void isJsonContentTypeWithWhitespace() { + // Content types with various whitespace at the boundaries + assertTrue(JsonConfigurationParser.isJsonContentType(" application/json")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/json ")); + assertTrue(JsonConfigurationParser.isJsonContentType(" application/json ")); + + // Internal whitespace around '/' or '+' is not allowed by RFC 7231/6838 token rules + assertFalse(JsonConfigurationParser.isJsonContentType("application / json")); + assertFalse(JsonConfigurationParser.isJsonContentType("application/vnd.api + json")); + } + + @Test + public void isJsonContentTypeCaseInsensitive() { + // Case variations + assertTrue(JsonConfigurationParser.isJsonContentType("Application/Json")); + assertTrue(JsonConfigurationParser.isJsonContentType("APPLICATION/JSON")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/JSON")); + assertTrue(JsonConfigurationParser.isJsonContentType("Application/vnd.api+JSON")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/API+JSON")); + } + + @Test + public void isJsonContentTypeEdgeCases() { + // Edge cases and boundary conditions + assertFalse(JsonConfigurationParser.isJsonContentType("application/")); // Empty subtype + assertFalse(JsonConfigurationParser.isJsonContentType("/json")); // Empty main type + assertFalse(JsonConfigurationParser.isJsonContentType("/")); // Both empty + assertFalse(JsonConfigurationParser.isJsonContentType("application/xml")); + assertFalse(JsonConfigurationParser.isJsonContentType("text/json")); // Wrong main type + assertFalse(JsonConfigurationParser.isJsonContentType("application/json+xml")); // json not as suffix + assertFalse(JsonConfigurationParser.isJsonContentType("application/xml+html")); // No json at all + assertFalse(JsonConfigurationParser.isJsonContentType(" ")); // Only whitespace + } + + @Test + public void isJsonContentTypeComplexStructuredSyntax() { + // Complex structured syntax suffixes (RFC 6839) + assertTrue(JsonConfigurationParser.isJsonContentType("application/problem+json")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/merge-patch+json")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/json-patch+json")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/ld+json")); // JSON-LD + assertTrue(JsonConfigurationParser.isJsonContentType("application/hal+json")); + assertTrue(JsonConfigurationParser.isJsonContentType("application/vnd.geo+json")); } @Test