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 d6581241a30..d09ee8c4a2f 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java +++ b/spring-core/src/main/java/org/springframework/util/MimeTypeUtils.java @@ -28,6 +28,7 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Random; +import java.util.stream.Collectors; import org.springframework.lang.Nullable; import org.springframework.util.MimeType.SpecificityComparator; @@ -37,6 +38,7 @@ import org.springframework.util.MimeType.SpecificityComparator; * * @author Arjen Poutsma * @author Rossen Stoyanchev + * @author Dimitrios Liapis * @since 4.0 */ public abstract class MimeTypeUtils { @@ -248,21 +250,54 @@ public abstract class MimeTypeUtils { } /** - * Parse the given, comma-separated string into a list of {@code MimeType} objects. + * Parse the comma-separated string into a list of {@code MimeType} objects. * @param mimeTypes the string to parse * @return the list of mime types - * @throws IllegalArgumentException if the string cannot be parsed + * @throws InvalidMimeTypeException if the string cannot be parsed */ public static List parseMimeTypes(String mimeTypes) { if (!StringUtils.hasLength(mimeTypes)) { return Collections.emptyList(); } - String[] tokens = StringUtils.tokenizeToStringArray(mimeTypes, ","); - List result = new ArrayList<>(tokens.length); - for (String token : tokens) { - result.add(parseMimeType(token)); + return tokenize(mimeTypes).stream() + .map(MimeTypeUtils::parseMimeType).collect(Collectors.toList()); + } + + /** + * Tokenize the given comma-separated string of {@code MimeType} objects + * into a {@code List}. Unlike simple tokenization by ",", this + * method takes into account quoted parameters. + * @param mimeTypes the string to tokenize + * @return the list of tokens + * @since 5.1.3 + */ + public static List tokenize(String mimeTypes) { + if (!StringUtils.hasLength(mimeTypes)) { + return Collections.emptyList(); + } + List tokens = new ArrayList<>(); + boolean inQuotes = false; + int startIndex = 0; + int i = 0; + while (i < mimeTypes.length()) { + switch (mimeTypes.charAt(i)) { + case '"': + inQuotes = !inQuotes; + break; + case ',': + if (!inQuotes) { + tokens.add(mimeTypes.substring(startIndex, i)); + startIndex = i + 1; + } + break; + case '\\': + i++; + break; + } + i++; } - return result; + tokens.add(mimeTypes.substring(startIndex)); + return tokens; } /** 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 904f230820b..07e3b543589 100644 --- a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java +++ b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java @@ -36,6 +36,7 @@ import static org.junit.Assert.*; * @author Arjen Poutsma * @author Juergen Hoeller * @author Sam Brannen + * @author Dimitrios Liapis */ public class MimeTypeTests { @@ -276,6 +277,25 @@ public class MimeTypeTests { assertEquals("Invalid amount of mime types", 0, mimeTypes.size()); } + @Test // SPR-17459 + public void parseMimeTypesWithQuotedParameters() { + testWithQuotedParameters("foo/bar;param=\",\""); + testWithQuotedParameters("foo/bar;param=\"s,a,\""); + testWithQuotedParameters("foo/bar;param=\"s,\"", "text/x-c"); + testWithQuotedParameters("foo/bar;param=\"a\\\"b,c\""); + testWithQuotedParameters("foo/bar;param=\"\\\\\""); + testWithQuotedParameters("foo/bar;param=\"\\,\\\""); + } + + private void testWithQuotedParameters(String... mimeTypes) { + String s = String.join(",", mimeTypes); + List actual = MimeTypeUtils.parseMimeTypes(s); + assertEquals(mimeTypes.length, actual.size()); + for (int i=0; i < mimeTypes.length; i++) { + assertEquals(mimeTypes[i], actual.get(i).toString()); + } + } + @Test public void compareTo() { MimeType audioBasic = new MimeType("audio", "basic"); diff --git a/spring-web/src/main/java/org/springframework/http/MediaType.java b/spring-web/src/main/java/org/springframework/http/MediaType.java index d7962f39294..3c6bfea9815 100644 --- a/spring-web/src/main/java/org/springframework/http/MediaType.java +++ b/spring-web/src/main/java/org/springframework/http/MediaType.java @@ -542,7 +542,7 @@ public class MediaType extends MimeType implements Serializable { } /** - * Parse the given comma-separated string into a list of {@code MediaType} objects. + * Parse the comma-separated string into a list of {@code MediaType} objects. *

This method can be used to parse an Accept or Content-Type header. * @param mediaTypes the string to parse * @return the list of media types @@ -552,12 +552,8 @@ public class MediaType extends MimeType implements Serializable { if (!StringUtils.hasLength(mediaTypes)) { return Collections.emptyList(); } - String[] tokens = StringUtils.tokenizeToStringArray(mediaTypes, ","); - List result = new ArrayList<>(tokens.length); - for (String token : tokens) { - result.add(parseMediaType(token)); - } - return result; + return MimeTypeUtils.tokenize(mediaTypes).stream() + .map(MediaType::parseMediaType).collect(Collectors.toList()); } /**