MediaType parsing supports comma inside quotes

Issue: SPR-17459
This commit is contained in:
Dimitrios Liapis 2018-11-07 00:03:20 +01:00 committed by Rossen Stoyanchev
parent 46a5fb7a91
commit f4b05dc2e7
4 changed files with 139 additions and 11 deletions

View File

@ -37,6 +37,7 @@ import org.springframework.util.MimeType.SpecificityComparator;
* *
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Dimitrios Liapis
* @since 4.0 * @since 4.0
*/ */
public abstract class MimeTypeUtils { public abstract class MimeTypeUtils {
@ -257,12 +258,24 @@ public abstract class MimeTypeUtils {
if (!StringUtils.hasLength(mimeTypes)) { if (!StringUtils.hasLength(mimeTypes)) {
return Collections.emptyList(); return Collections.emptyList();
} }
String[] tokens = StringUtils.tokenizeToStringArray(mimeTypes, ","); boolean isQuoted = false;
List<MimeType> result = new ArrayList<>(tokens.length); int nextBeginIndex = 0;
for (String token : tokens) { List<MimeType> tokens = new ArrayList<>();
result.add(parseMimeType(token)); 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 result; }
//either the last part of the tokenization or the original string
tokens.add(parseMimeType(mimeTypes.substring(nextBeginIndex)));
return tokens;
} }
/** /**

View File

@ -36,6 +36,7 @@ import static org.junit.Assert.*;
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Sam Brannen * @author Sam Brannen
* @author Dimitrios Liapis
*/ */
public class MimeTypeTests { public class MimeTypeTests {
@ -276,6 +277,56 @@ public class MimeTypeTests {
assertEquals("Invalid amount of mime types", 0, mimeTypes.size()); 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());
}
// 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());
}
@Test @Test
public void compareTo() { public void compareTo() {
MimeType audioBasic = new MimeType("audio", "basic"); MimeType audioBasic = new MimeType("audio", "basic");

View File

@ -44,6 +44,7 @@ import org.springframework.util.StringUtils;
* @author Rossen Stoyanchev * @author Rossen Stoyanchev
* @author Sebastien Deleuze * @author Sebastien Deleuze
* @author Kazuki Shimizu * @author Kazuki Shimizu
* @author Dimitrios Liapis
* @since 3.0 * @since 3.0
* @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.1"> * @see <a href="http://tools.ietf.org/html/rfc7231#section-3.1.1.1">
* HTTP 1.1: Semantics and Content, section 3.1.1.1</a> * HTTP 1.1: Semantics and Content, section 3.1.1.1</a>
@ -552,12 +553,24 @@ public class MediaType extends MimeType implements Serializable {
if (!StringUtils.hasLength(mediaTypes)) { if (!StringUtils.hasLength(mediaTypes)) {
return Collections.emptyList(); return Collections.emptyList();
} }
String[] tokens = StringUtils.tokenizeToStringArray(mediaTypes, ","); boolean isQuoted = false;
List<MediaType> result = new ArrayList<>(tokens.length); int nextBeginIndex = 0;
for (String token : tokens) { List<MediaType> tokens = new ArrayList<>();
result.add(parseMediaType(token)); for(int i = 0; i < mediaTypes.length() - 1; i++) {
//tokenizing on commas that are not within double quotes
if(mediaTypes.charAt(i) == ',' && !isQuoted) {
tokens.add(parseMediaType(mediaTypes.substring(nextBeginIndex, i)));
nextBeginIndex = i + 1;
//ignoring escaped double quote within double quotes
} else if(isQuoted && mediaTypes.charAt(i) == '"' && mediaTypes.charAt(i-1) == '\\') {
continue;
} else if(mediaTypes.charAt(i) == '"') {
isQuoted = !isQuoted;
} }
return result; }
//either the last part of the tokenization or the original string
tokens.add(parseMediaType(mediaTypes.substring(nextBeginIndex)));
return tokens;
} }
/** /**

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2016 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -32,6 +32,7 @@ import static org.junit.Assert.*;
/** /**
* @author Arjen Poutsma * @author Arjen Poutsma
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Dimitrios Liapis
*/ */
public class MediaTypeTests { public class MediaTypeTests {
@ -143,6 +144,56 @@ public class MediaTypeTests {
assertEquals("Invalid amount of media types", 0, mediaTypes.size()); assertEquals("Invalid amount of media types", 0, mediaTypes.size());
} }
// SPR-17459
@Test
public void parseMediaTypesWithOddNumberOfDoubleQuotedCommas() {
String s = "foo/bar;param=\",\"";
List<MediaType> mediaTypes = MediaType.parseMediaTypes(s);
assertEquals("Invalid amount of media types", 1, mediaTypes.size());
assertEquals("Comma should be part of the media type", s, mediaTypes.get(0).toString());
}
// SPR-17459
@Test
public void parseMediaTypesWithEvenNumberOfDoubleQuotedCommas() {
String s = "foo/bar;param=\"s,a,\"";
List<MediaType> mediaTypes = MediaType.parseMediaTypes(s);
assertEquals("Invalid amount of media types", 1, mediaTypes.size());
assertEquals("Comma should be part of the media type", s, mediaTypes.get(0).toString());
}
// SPR-17459
@Test
public void parseMediaTypesWithAndWithoutDoubleQuotedCommas() {
String s = "foo/bar;param=\"s,\", text/x-c";
List<MediaType> mediaTypes = MediaType.parseMediaTypes(s);
assertEquals("Invalid amount of media types", 2, mediaTypes.size());
assertEquals("Comma should be part of the media type", "foo/bar;param=\"s,\"", mediaTypes.get(0).toString());
}
// SPR-17459
@Test
public void parseMediaTypesIgnoreEscapedDoubleQuoteWithinDoubleQuotes() {
String s = "foo/bar;param=\"a\\\"b,c\"";
List<MediaType> mediaTypes = MediaType.parseMediaTypes(s);
assertEquals("Invalid amount of media types", 1, mediaTypes.size());
assertEquals("Escaped quote within quotes should be ignored when considering comma tokenization", s, mediaTypes.get(0).toString());
}
// SPR-17459
@Test
public void parseMediaTypesIgnoreEscapedBackslash() {
String s = "foo/bar;param=\"\\\\\"";
List<MediaType> mediaTypes = MediaType.parseMediaTypes(s);
assertEquals("Invalid amount of media types", 1, mediaTypes.size());
assertEquals("Escaped quote within quotes should be ignored when considering comma tokenization", s, mediaTypes.get(0).toString());
s = "foo/bar;param=\"\\,\\\"";
mediaTypes = MediaType.parseMediaTypes(s);
assertEquals("Invalid amount of media types", 1, mediaTypes.size());
assertEquals("Escaped quote within quotes should be ignored when considering comma tokenization", s, mediaTypes.get(0).toString());
}
@Test @Test
public void compareTo() { public void compareTo() {
MediaType audioBasic = new MediaType("audio", "basic"); MediaType audioBasic = new MediaType("audio", "basic");