MimeType parsing properly handles quoted semicolons

Issue:  SPR-14986
This commit is contained in:
Juergen Hoeller 2016-12-09 15:04:26 +01:00
parent a7ec6dc0af
commit 7714eeccf3
3 changed files with 48 additions and 21 deletions

View File

@ -165,8 +165,8 @@ public class MimeType implements Comparable<MimeType>, Serializable {
* @throws IllegalArgumentException if any of the parameters contains illegal characters * @throws IllegalArgumentException if any of the parameters contains illegal characters
*/ */
public MimeType(String type, String subtype, Map<String, String> parameters) { public MimeType(String type, String subtype, Map<String, String> parameters) {
Assert.hasLength(type, "type must not be empty"); Assert.hasLength(type, "'type' must not be empty");
Assert.hasLength(subtype, "subtype must not be empty"); Assert.hasLength(subtype, "'subtype' must not be empty");
checkToken(type); checkToken(type);
checkToken(subtype); checkToken(subtype);
this.type = type.toLowerCase(Locale.ENGLISH); this.type = type.toLowerCase(Locale.ENGLISH);
@ -202,8 +202,8 @@ public class MimeType implements Comparable<MimeType>, Serializable {
} }
protected void checkParameters(String attribute, String value) { protected void checkParameters(String attribute, String value) {
Assert.hasLength(attribute, "parameter attribute must not be empty"); Assert.hasLength(attribute, "'attribute' must not be empty");
Assert.hasLength(value, "parameter value must not be empty"); Assert.hasLength(value, "'value' must not be empty");
checkToken(attribute); checkToken(attribute);
if (PARAM_CHARSET.equals(attribute)) { if (PARAM_CHARSET.equals(attribute)) {
value = unquote(value); value = unquote(value);
@ -277,8 +277,8 @@ public class MimeType implements Comparable<MimeType>, Serializable {
* @since 4.3 * @since 4.3
*/ */
public Charset getCharset() { public Charset getCharset() {
String charSet = getParameter(PARAM_CHARSET); String charset = getParameter(PARAM_CHARSET);
return (charSet != null ? Charset.forName(unquote(charSet)) : null); return (charset != null ? Charset.forName(unquote(charset)) : null);
} }
/** /**

View File

@ -47,6 +47,11 @@ public abstract class MimeTypeUtils {
private static final Random RND = new Random(); private static final Random RND = new Random();
/**
* Comparator used by {@link #sortBySpecificity(List)}.
*/
public static final Comparator<MimeType> SPECIFICITY_COMPARATOR = new SpecificityComparator<>();
/** /**
* Public constant mime type that includes all media ranges (i.e. "&#42;/&#42;"). * Public constant mime type that includes all media ranges (i.e. "&#42;/&#42;").
*/ */
@ -216,12 +221,13 @@ public abstract class MimeTypeUtils {
if (!StringUtils.hasLength(mimeType)) { if (!StringUtils.hasLength(mimeType)) {
throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty"); 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"); throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
} }
String fullType = parts[0].trim();
// java.net.HttpURLConnection returns a *; q=.2 Accept header // java.net.HttpURLConnection returns a *; q=.2 Accept header
if (MimeType.WILDCARD_TYPE.equals(fullType)) { if (MimeType.WILDCARD_TYPE.equals(fullType)) {
fullType = "*/*"; fullType = "*/*";
@ -240,18 +246,36 @@ public abstract class MimeTypeUtils {
} }
Map<String, String> parameters = null; Map<String, String> parameters = null;
if (parts.length > 1) { do {
parameters = new LinkedHashMap<>(parts.length - 1); int nextIndex = index + 1;
for (int i = 1; i < parts.length; i++) { boolean quoted = false;
String parameter = parts[i]; 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('='); int eqIndex = parameter.indexOf('=');
if (eqIndex != -1) { if (eqIndex >= 0) {
String attribute = parameter.substring(0, eqIndex); String attribute = parameter.substring(0, eqIndex);
String value = parameter.substring(eqIndex + 1, parameter.length()); String value = parameter.substring(eqIndex + 1, parameter.length());
parameters.put(attribute, value); parameters.put(attribute, value);
} }
} }
index = nextIndex;
} }
while (index < mimeType.length());
try { try {
return new MimeType(type, subtype, parameters); return new MimeType(type, subtype, parameters);
@ -350,11 +374,4 @@ public abstract class MimeTypeUtils {
return new String(generateMultipartBoundary(), StandardCharsets.US_ASCII); return new String(generateMultipartBoundary(), StandardCharsets.US_ASCII);
} }
/**
* Comparator used by {@link #sortBySpecificity(List)}.
*/
public static final Comparator<MimeType> SPECIFICITY_COMPARATOR = new SpecificityComparator<>();
} }

View File

@ -87,6 +87,16 @@ public class MimeTypeTests {
assertEquals("Invalid charset", StandardCharsets.UTF_8, mimeType.getCharset()); 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 @Test
public void withConversionService() { public void withConversionService() {
ConversionService conversionService = new DefaultConversionService(); ConversionService conversionService = new DefaultConversionService();