Refactor media types parsing improvements

Issue: SPR-17459
This commit is contained in:
Rossen Stoyanchev
2018-11-13 22:43:29 -05:00
parent f4b05dc2e7
commit ba3fef3e8a
4 changed files with 59 additions and 136 deletions

View File

@@ -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;
@@ -249,32 +250,53 @@ 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<MimeType> parseMimeTypes(String mimeTypes) {
if (!StringUtils.hasLength(mimeTypes)) {
return Collections.emptyList();
}
boolean isQuoted = false;
int nextBeginIndex = 0;
List<MimeType> tokens = new ArrayList<>();
for(int i = 0; i < mimeTypes.length() - 1; i++) {
//tokenizing on commas that are not within double quotes
if(mimeTypes.charAt(i) == ',' && !isQuoted) {
tokens.add(parseMimeType(mimeTypes.substring(nextBeginIndex,i)));
nextBeginIndex = i + 1;
//ignoring escaped double quote within double quotes
} else if(isQuoted && mimeTypes.charAt(i) == '"' && mimeTypes.charAt(i-1) == '\\') {
continue;
} else if(mimeTypes.charAt(i) == '"') {
isQuoted = !isQuoted;
}
return tokenize(mimeTypes).stream()
.map(MimeTypeUtils::parseMimeType).collect(Collectors.toList());
}
/**
* Tokenize the given comma-separated string of {@code MimeType} objects
* into a {@code List<String>}. 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<String> tokenize(String mimeTypes) {
if (!StringUtils.hasLength(mimeTypes)) {
return Collections.emptyList();
}
//either the last part of the tokenization or the original string
tokens.add(parseMimeType(mimeTypes.substring(nextBeginIndex)));
List<String> 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++;
}
tokens.add(mimeTypes.substring(startIndex));
return tokens;
}

View File

@@ -277,54 +277,23 @@ public class MimeTypeTests {
assertEquals("Invalid amount of mime types", 0, mimeTypes.size());
}
// SPR-17459
@Test
public void parseMimeTypesWithOddNumberOfDoubleQuotedCommas() {
String s = "foo/bar;param=\",\"";
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes(s);
assertEquals("Invalid amount of mime types", 1, mimeTypes.size());
assertEquals("Comma should be part of the mime type", s, mimeTypes.get(0).toString());
@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=\"\\,\\\"");
}
// SPR-17459
@Test
public void parseMimeTypesWithEvenNumberOfDoubleQuotedCommas() {
String s = "foo/bar;param=\"s,a,\"";
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes(s);
assertEquals("Invalid amount of mime types", 1, mimeTypes.size());
assertEquals("Comma should be part of the mime type", s, mimeTypes.get(0).toString());
}
// SPR-17459
@Test
public void parseMimeTypesWithAndWithoutDoubleQuotedCommas() {
String s = "foo/bar;param=\"s,\", text/x-c";
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes(s);
assertEquals("Invalid amount of mime types", 2, mimeTypes.size());
assertEquals("Comma should be part of the mime type", "foo/bar;param=\"s,\"", mimeTypes.get(0).toString());
}
// SPR-17459
@Test
public void parseMimeTypesIgnoreEscapedDoubleQuoteWithinDoubleQuotes() {
String s = "foo/bar;param=\"a\\\"b,c\"";
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes(s);
assertEquals("Invalid amount of mime types", 1, mimeTypes.size());
assertEquals("Escaped quote within quotes should be ignored when considering comma tokenization", s, mimeTypes.get(0).toString());
}
// SPR-17459
@Test
public void parseMimeTypesIgnoreEscapedBackslash() {
String s = "foo/bar;param=\"\\\\\"";
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes(s);
assertEquals("Invalid amount of mime types", 1, mimeTypes.size());
assertEquals("Escaped backslash should be ignored when considering comma tokenization", s, mimeTypes.get(0).toString());
s = "foo/bar;param=\"\\,\\\"";
mimeTypes = MimeTypeUtils.parseMimeTypes(s);
assertEquals("Invalid amount of mime types", 1, mimeTypes.size());
assertEquals("Escaped backslash should be ignored when considering comma tokenization", s, mimeTypes.get(0).toString());
private void testWithQuotedParameters(String... mimeTypes) {
String s = String.join(",", mimeTypes);
List<MimeType> 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