diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java index 2512ecd69d..a2ff8055ca 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeType.java +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -165,8 +165,8 @@ public class MimeType implements Comparable, Serializable { * @throws IllegalArgumentException if any of the parameters contains illegal characters */ public MimeType(String type, String subtype, Map parameters) { - Assert.hasLength(type, "type must not be empty"); - Assert.hasLength(subtype, "subtype must not be empty"); + Assert.hasLength(type, "'type' must not be empty"); + Assert.hasLength(subtype, "'subtype' must not be empty"); checkToken(type); checkToken(subtype); this.type = type.toLowerCase(Locale.ENGLISH); @@ -202,8 +202,8 @@ public class MimeType implements Comparable, Serializable { } protected void checkParameters(String attribute, String value) { - Assert.hasLength(attribute, "parameter attribute must not be empty"); - Assert.hasLength(value, "parameter value must not be empty"); + Assert.hasLength(attribute, "'attribute' must not be empty"); + Assert.hasLength(value, "'value' must not be empty"); checkToken(attribute); if (PARAM_CHARSET.equals(attribute)) { value = unquote(value); @@ -277,8 +277,8 @@ public class MimeType implements Comparable, Serializable { * @since 4.3 */ public Charset getCharset() { - String charSet = getParameter(PARAM_CHARSET); - return (charSet != null ? Charset.forName(unquote(charSet)) : null); + String charset = getParameter(PARAM_CHARSET); + return (charset != null ? Charset.forName(unquote(charset)) : null); } /** diff --git a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java index 968942cd08..581334e7bc 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java +++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java @@ -47,6 +47,11 @@ public abstract class MimeTypeUtils { private static final Random RND = new Random(); + /** + * Comparator used by {@link #sortBySpecificity(List)}. + */ + public static final Comparator SPECIFICITY_COMPARATOR = new SpecificityComparator<>(); + /** * Public constant mime type that includes all media ranges (i.e. "*/*"). */ @@ -216,12 +221,13 @@ public abstract class MimeTypeUtils { if (!StringUtils.hasLength(mimeType)) { throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty"); } - String[] parts = StringUtils.tokenizeToStringArray(mimeType, ";"); - if (parts.length == 0) { + + int index = mimeType.indexOf(';'); + String fullType = (index >= 0 ? mimeType.substring(0, index) : mimeType).trim(); + if (fullType.length() == 0) { throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty"); } - String fullType = parts[0].trim(); // java.net.HttpURLConnection returns a *; q=.2 Accept header if (MimeType.WILDCARD_TYPE.equals(fullType)) { fullType = "*/*"; @@ -240,18 +246,36 @@ public abstract class MimeTypeUtils { } Map parameters = null; - if (parts.length > 1) { - parameters = new LinkedHashMap<>(parts.length - 1); - for (int i = 1; i < parts.length; i++) { - String parameter = parts[i]; + do { + int nextIndex = index + 1; + boolean quoted = false; + while (nextIndex < mimeType.length()) { + char ch = mimeType.charAt(nextIndex); + if (ch == ';') { + if (!quoted) { + break; + } + } + else if (ch == '"') { + quoted = !quoted; + } + nextIndex++; + } + String parameter = mimeType.substring(index + 1, nextIndex).trim(); + if (parameter.length() > 0) { + if (parameters == null) { + parameters = new LinkedHashMap<>(4); + } int eqIndex = parameter.indexOf('='); - if (eqIndex != -1) { + if (eqIndex >= 0) { String attribute = parameter.substring(0, eqIndex); String value = parameter.substring(eqIndex + 1, parameter.length()); parameters.put(attribute, value); } } + index = nextIndex; } + while (index < mimeType.length()); try { return new MimeType(type, subtype, parameters); @@ -350,11 +374,4 @@ public abstract class MimeTypeUtils { return new String(generateMultipartBoundary(), StandardCharsets.US_ASCII); } - - - /** - * Comparator used by {@link #sortBySpecificity(List)}. - */ - public static final Comparator SPECIFICITY_COMPARATOR = new SpecificityComparator<>(); - } diff --git a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java index dc797f88cd..49961078f1 100644 --- a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java +++ b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java @@ -87,6 +87,16 @@ public class MimeTypeTests { assertEquals("Invalid charset", StandardCharsets.UTF_8, mimeType.getCharset()); } + @Test + public void parseQuotedSeparator() { + String s = "application/xop+xml;charset=utf-8;type=\"application/soap+xml;action=\\\"http://x.y.z\\\"\""; + MimeType mimeType = MimeType.valueOf(s); + assertEquals("Invalid type", "application", mimeType.getType()); + assertEquals("Invalid subtype", "xop+xml", mimeType.getSubtype()); + assertEquals("Invalid charset", StandardCharsets.UTF_8, mimeType.getCharset()); + assertEquals("\"application/soap+xml;action=\\\"http://x.y.z\\\"\"", mimeType.getParameter("type")); + } + @Test public void withConversionService() { ConversionService conversionService = new DefaultConversionService();