mirror of
https://github.com/spring-projects/spring-framework.git
synced 2026-09-17 08:24:13 +00:00
Improve MimeType parser for RFC compliance
Prior to this commit, the `MimeType` class would compare raw parameter values for the equals/hashcode contract. This went against the RFC which states that quoted and unquoted parameter values are equivalent. This commit rewrote the entire `MimeType` parser in `MimeTypeUtils` as a state parser to improve robustness and performance. The `MimeType` equals, compareTo and hascode contracts now unquote parameter values before comparing them. This change also optimizes the `tokenize` function that splits many comma-separated MIME types into a list. Now that this method isn't used anywhere else, it is also deprecated as of 7.1. This method was initially made public to be reused within Spring Framework and has no particular use in Spring applications in general. Finally, this also makes `MediaType` and `MimeType` leverage the `MimeType` LRU cache as much as possible, including when parsing `Accept:` HTTP headers. Closes gh-36729
This commit is contained in:
@@ -107,6 +107,8 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
|
||||
private volatile @Nullable String toStringValue;
|
||||
|
||||
private volatile int hash;
|
||||
|
||||
|
||||
/**
|
||||
* Create a new {@code MimeType} for the given primary type.
|
||||
@@ -207,6 +209,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
this.parameters = other.parameters;
|
||||
this.resolvedCharset = other.resolvedCharset;
|
||||
this.toStringValue = other.toStringValue;
|
||||
this.hash = other.hash;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -254,8 +257,29 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
return ((s.startsWith("\"") && s.endsWith("\"")) || (s.startsWith("'") && s.endsWith("'")));
|
||||
}
|
||||
|
||||
/**
|
||||
* Unquote the given string, resolving any quoted-pair escapes it might
|
||||
* contain (for example, {@code "1\"2"} becomes {@code 1"2}) in the process.
|
||||
* <p>Returns the given string as-is if it is not a quoted string.
|
||||
*/
|
||||
protected String unquote(String s) {
|
||||
return (isQuotedString(s) ? s.substring(1, s.length() - 1) : s);
|
||||
if (!isQuotedString(s)) {
|
||||
return s;
|
||||
}
|
||||
String inner = s.substring(1, s.length() - 1);
|
||||
if (inner.indexOf('\\') == -1) {
|
||||
return inner;
|
||||
}
|
||||
StringBuilder sb = new StringBuilder(inner.length());
|
||||
for (int i = 0; i < inner.length(); i++) {
|
||||
char c = inner.charAt(i);
|
||||
if (c == '\\' && i + 1 < inner.length()) {
|
||||
i++;
|
||||
c = inner.charAt(i);
|
||||
}
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -462,7 +486,9 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
/**
|
||||
* Determine if the parameters in this {@code MimeType} and the supplied
|
||||
* {@code MimeType} are equal, performing case-insensitive comparisons
|
||||
* for {@link Charset Charsets}.
|
||||
* for {@link Charset Charsets} and disregarding quoting of parameter
|
||||
* values, so that, for example, {@code spring="framework"} and
|
||||
* {@code spring=framework} are considered equal.
|
||||
* @since 4.2
|
||||
*/
|
||||
private boolean parametersAreEqual(MimeType other) {
|
||||
@@ -480,7 +506,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!ObjectUtils.nullSafeEquals(entry.getValue(), other.parameters.get(key))) {
|
||||
else if (!ObjectUtils.nullSafeEquals(unquote(entry.getValue()), unquote(other.parameters.get(key)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -490,9 +516,28 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.type.hashCode();
|
||||
result = 31 * result + this.subtype.hashCode();
|
||||
result = 31 * result + this.parameters.hashCode();
|
||||
int result = this.hash;
|
||||
if (result == 0) {
|
||||
result = this.type.hashCode();
|
||||
result = 31 * result + this.subtype.hashCode();
|
||||
result = 31 * result + parametersHashCode();
|
||||
this.hash = result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute a hash code for the parameters map, consistent with
|
||||
* {@link #parametersAreEqual}: normalizing {@link Charset Charsets} and
|
||||
* disregarding quoting of parameter values.
|
||||
*/
|
||||
private int parametersHashCode() {
|
||||
int result = 0;
|
||||
for (Map.Entry<String, String> entry : this.parameters.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = (PARAM_CHARSET.equals(key) ? getCharset() : unquote(entry.getValue()));
|
||||
result += key.hashCode() ^ ObjectUtils.nullSafeHashCode(value);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -580,7 +625,7 @@ public class MimeType implements Comparable<MimeType>, Serializable {
|
||||
if (otherValue == null) {
|
||||
otherValue = "";
|
||||
}
|
||||
comp = thisValue.compareTo(otherValue);
|
||||
comp = unquote(thisValue).compareTo(unquote(otherValue));
|
||||
if (comp != 0) {
|
||||
return comp;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.jspecify.annotations.Nullable;
|
||||
|
||||
@@ -204,72 +203,7 @@ public abstract class MimeTypeUtils {
|
||||
}
|
||||
|
||||
private static MimeType parseMimeTypeInternal(String mimeType) {
|
||||
int index = mimeType.indexOf(';');
|
||||
String fullType = (index >= 0 ? mimeType.substring(0, index) : mimeType).trim();
|
||||
if (fullType.isEmpty()) {
|
||||
throw new InvalidMimeTypeException(mimeType, "'mimeType' must not be empty");
|
||||
}
|
||||
|
||||
// java.net.HttpURLConnection returns a *; q=.2 Accept header
|
||||
if (MimeType.WILDCARD_TYPE.equals(fullType)) {
|
||||
fullType = "*/*";
|
||||
}
|
||||
int subIndex = fullType.indexOf('/');
|
||||
if (subIndex == -1) {
|
||||
throw new InvalidMimeTypeException(mimeType, "does not contain '/'");
|
||||
}
|
||||
if (subIndex == fullType.length() - 1) {
|
||||
throw new InvalidMimeTypeException(mimeType, "does not contain subtype after '/'");
|
||||
}
|
||||
String type = fullType.substring(0, subIndex);
|
||||
String subtype = fullType.substring(subIndex + 1);
|
||||
if (MimeType.WILDCARD_TYPE.equals(type) && !MimeType.WILDCARD_TYPE.equals(subtype)) {
|
||||
throw new InvalidMimeTypeException(mimeType, "wildcard type is legal only in '*/*' (all mime types)");
|
||||
}
|
||||
|
||||
Map<String, String> parameters = null;
|
||||
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 == '"' && (nextIndex == 0 || mimeType.charAt(nextIndex - 1) != '\\')) {
|
||||
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 >= 0) {
|
||||
String attribute = parameter.substring(0, eqIndex).trim();
|
||||
String value = parameter.substring(eqIndex + 1).trim();
|
||||
if (parameters.put(attribute, value) != null) {
|
||||
throw new InvalidMimeTypeException(mimeType, "duplicate parameter '" + parameter + "'");
|
||||
}
|
||||
}
|
||||
}
|
||||
index = nextIndex;
|
||||
}
|
||||
while (index < mimeType.length());
|
||||
|
||||
try {
|
||||
return new MimeType(type, subtype, parameters);
|
||||
}
|
||||
catch (UnsupportedCharsetException ex) {
|
||||
throw new InvalidMimeTypeException(mimeType, "unsupported charset '" + ex.getCharsetName() + "'");
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new InvalidMimeTypeException(mimeType, ex.getMessage());
|
||||
}
|
||||
return new MimeTypeParser(mimeType).parse();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -278,14 +212,17 @@ public abstract class MimeTypeUtils {
|
||||
* @return the list of mime types
|
||||
* @throws InvalidMimeTypeException if the string cannot be parsed
|
||||
*/
|
||||
@SuppressWarnings("removal")
|
||||
public static List<MimeType> parseMimeTypes(String mimeTypes) {
|
||||
if (!StringUtils.hasLength(mimeTypes)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return tokenize(mimeTypes).stream()
|
||||
.filter(StringUtils::hasText)
|
||||
.map(MimeTypeUtils::parseMimeType)
|
||||
.collect(Collectors.toList());
|
||||
List<String> tokens = tokenize(mimeTypes);
|
||||
List<MimeType> result = new ArrayList<>(tokens.size());
|
||||
for (String token : tokens) {
|
||||
result.add(parseMimeType(token));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,11 +232,18 @@ public abstract class MimeTypeUtils {
|
||||
* @param mimeTypes the string to tokenize
|
||||
* @return the list of tokens
|
||||
* @since 5.1.3
|
||||
* @deprecated as of 7.1.0 without any replacement.
|
||||
* Use {@link #parseMimeType(String)} and {@link #parseMimeTypes(String)} instead.
|
||||
*/
|
||||
@Deprecated(since = "7.1.0", forRemoval = true)
|
||||
public static List<String> tokenize(String mimeTypes) {
|
||||
if (!StringUtils.hasLength(mimeTypes)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// Without quoted parameter values, there is no need to track quotes
|
||||
if (mimeTypes.indexOf('"') == -1) {
|
||||
return splitByComma(mimeTypes);
|
||||
}
|
||||
List<String> tokens = new ArrayList<>();
|
||||
boolean inQuotes = false;
|
||||
int startIndex = 0;
|
||||
@@ -309,7 +253,10 @@ public abstract class MimeTypeUtils {
|
||||
case '"' -> inQuotes = !inQuotes;
|
||||
case ',' -> {
|
||||
if (!inQuotes) {
|
||||
tokens.add(mimeTypes.substring(startIndex, i));
|
||||
String trimmed = mimeTypes.substring(startIndex, i).trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
tokens.add(trimmed);
|
||||
}
|
||||
startIndex = i + 1;
|
||||
}
|
||||
}
|
||||
@@ -317,7 +264,28 @@ public abstract class MimeTypeUtils {
|
||||
}
|
||||
i++;
|
||||
}
|
||||
tokens.add(mimeTypes.substring(startIndex));
|
||||
String trimmed = mimeTypes.substring(startIndex).trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
tokens.add(trimmed);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
private static List<String> splitByComma(String mimeTypes) {
|
||||
List<String> tokens = new ArrayList<>();
|
||||
int startIndex = 0;
|
||||
int commaIndex;
|
||||
while ((commaIndex = mimeTypes.indexOf(',', startIndex)) != -1) {
|
||||
String trimmed = mimeTypes.substring(startIndex, commaIndex).trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
tokens.add(trimmed);
|
||||
}
|
||||
startIndex = commaIndex + 1;
|
||||
}
|
||||
String trimmed = mimeTypes.substring(startIndex).trim();
|
||||
if (!trimmed.isEmpty()) {
|
||||
tokens.add(trimmed);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
@@ -411,4 +379,263 @@ public abstract class MimeTypeUtils {
|
||||
return new String(generateMultipartBoundary(), StandardCharsets.US_ASCII);
|
||||
}
|
||||
|
||||
private static final class MimeTypeParser {
|
||||
|
||||
private final String input;
|
||||
|
||||
private int index = 0;
|
||||
|
||||
private int mark = 0;
|
||||
|
||||
private String type = "";
|
||||
|
||||
private String subtype = "";
|
||||
|
||||
private @Nullable Map<String, String> parameters;
|
||||
|
||||
private @Nullable String paramName;
|
||||
|
||||
private @Nullable MimeType parsed;
|
||||
|
||||
|
||||
private MimeTypeParser(String input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the entire input as exactly one MIME type: any leftover
|
||||
* content the state machine cannot make sense of (for example, a
|
||||
* comma-separated second MIME type) is rejected rather than ignored.
|
||||
*/
|
||||
private MimeType parse() {
|
||||
ParserState state = ParserState.INITIAL;
|
||||
for (; this.index < this.input.length(); this.index++) {
|
||||
char c = this.input.charAt(this.index);
|
||||
state = state.process(c, this);
|
||||
}
|
||||
state.onEof(this);
|
||||
if (this.parsed == null) {
|
||||
throw new InvalidMimeTypeException(this.input, "'mimeType' must not be empty");
|
||||
}
|
||||
return this.parsed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a type for which no '/' was found before a terminator (or
|
||||
* the end of input) was reached. The only valid case is the bare
|
||||
* {@code *} some clients (for example, {@code java.net.HttpURLConnection})
|
||||
* send as shorthand for the {@code *; q=.2}-style wildcard
|
||||
* (*/*) Accept header; anything else is rejected.
|
||||
*/
|
||||
private void resolveBareType(String candidate) {
|
||||
if (!MimeType.WILDCARD_TYPE.equals(candidate)) {
|
||||
throw new InvalidMimeTypeException(this.input, "does not contain '/'");
|
||||
}
|
||||
this.type = MimeType.WILDCARD_TYPE;
|
||||
this.subtype = MimeType.WILDCARD_TYPE;
|
||||
}
|
||||
|
||||
private void putParameter(String name, String value) {
|
||||
if (this.parameters == null) {
|
||||
this.parameters = new LinkedHashMap<>(4);
|
||||
}
|
||||
if (this.parameters.put(name, value) != null) {
|
||||
throw new InvalidMimeTypeException(this.input, "duplicate parameter '" + name + "=" + value + "'");
|
||||
}
|
||||
}
|
||||
|
||||
private void emitMimeType() {
|
||||
this.parsed = buildMimeType();
|
||||
}
|
||||
|
||||
private MimeType buildMimeType() {
|
||||
if (MimeType.WILDCARD_TYPE.equals(this.type) && !MimeType.WILDCARD_TYPE.equals(this.subtype)) {
|
||||
throw new InvalidMimeTypeException(this.input, "wildcard type is legal only in '*/*' (all mime types)");
|
||||
}
|
||||
try {
|
||||
return new MimeType(this.type, this.subtype, this.parameters);
|
||||
}
|
||||
catch (UnsupportedCharsetException ex) {
|
||||
throw new InvalidMimeTypeException(this.input, "unsupported charset '" + ex.getCharsetName() + "'");
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new InvalidMimeTypeException(this.input, ex.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum ParserState {
|
||||
|
||||
INITIAL {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == ' ' || c == '\t') {
|
||||
return this;
|
||||
}
|
||||
parser.mark = parser.index;
|
||||
return TYPE;
|
||||
}
|
||||
},
|
||||
|
||||
TYPE {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == '/') {
|
||||
parser.type = parser.input.substring(parser.mark, parser.index);
|
||||
if (parser.type.isEmpty()) {
|
||||
throw new InvalidMimeTypeException(parser.input, "'type' must not be empty");
|
||||
}
|
||||
parser.mark = parser.index + 1;
|
||||
return SUBTYPE;
|
||||
}
|
||||
if (c == ';' || c == ' ' || c == '\t') {
|
||||
parser.resolveBareType(parser.input.substring(parser.mark, parser.index));
|
||||
return WHITESPACE;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onEof(MimeTypeParser parser) {
|
||||
parser.resolveBareType(parser.input.substring(parser.mark));
|
||||
parser.emitMimeType();
|
||||
}
|
||||
},
|
||||
|
||||
SUBTYPE {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == ';' || c == ' ' || c == '\t') {
|
||||
parser.subtype = parser.input.substring(parser.mark, parser.index);
|
||||
return WHITESPACE;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
void onEof(MimeTypeParser parser) {
|
||||
parser.subtype = parser.input.substring(parser.mark);
|
||||
parser.emitMimeType();
|
||||
}
|
||||
},
|
||||
|
||||
WHITESPACE {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == ' ' || c == '\t' || c == ';') {
|
||||
return this;
|
||||
}
|
||||
parser.mark = parser.index;
|
||||
return PARAM_NAME;
|
||||
}
|
||||
@Override
|
||||
void onEof(MimeTypeParser parser) {
|
||||
if (!parser.type.isEmpty()) {
|
||||
parser.emitMimeType();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
PARAM_NAME {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == '=') {
|
||||
parser.paramName = parser.input.substring(parser.mark, parser.index);
|
||||
return PARAM_VALUE_START;
|
||||
}
|
||||
if (c == ' ' || c == '\t') {
|
||||
parser.paramName = parser.input.substring(parser.mark, parser.index);
|
||||
return PARAM_NAME_END;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
},
|
||||
|
||||
PARAM_NAME_END {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == ' ' || c == '\t') {
|
||||
return this;
|
||||
}
|
||||
if (c == '=') {
|
||||
return PARAM_VALUE_START;
|
||||
}
|
||||
throw new InvalidMimeTypeException(parser.input, "Unexpected character '" + c + "' after parameter name");
|
||||
}
|
||||
},
|
||||
|
||||
PARAM_VALUE_START {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == ' ' || c == '\t') {
|
||||
return this;
|
||||
}
|
||||
if (c == '"') {
|
||||
parser.mark = parser.index;
|
||||
return PARAM_VALUE_QUOTED;
|
||||
}
|
||||
parser.mark = parser.index;
|
||||
return PARAM_VALUE_TOKEN;
|
||||
}
|
||||
},
|
||||
|
||||
PARAM_VALUE_TOKEN {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == ';' || c == ' ' || c == '\t') {
|
||||
extractParameter(parser, parser.input.substring(parser.mark, parser.index));
|
||||
return WHITESPACE;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
void onEof(MimeTypeParser parser) {
|
||||
extractParameter(parser, parser.input.substring(parser.mark));
|
||||
parser.emitMimeType();
|
||||
}
|
||||
},
|
||||
|
||||
PARAM_VALUE_QUOTED {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
if (c == '"') {
|
||||
extractParameter(parser, parser.input.substring(parser.mark, parser.index + 1));
|
||||
return WHITESPACE;
|
||||
}
|
||||
if (c == '\\') {
|
||||
return PARAM_VALUE_ESCAPED;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@Override
|
||||
void onEof(MimeTypeParser parser) {
|
||||
extractParameter(parser, parser.input.substring(parser.mark));
|
||||
parser.emitMimeType();
|
||||
}
|
||||
},
|
||||
|
||||
PARAM_VALUE_ESCAPED {
|
||||
@Override
|
||||
ParserState process(char c, MimeTypeParser parser) {
|
||||
return PARAM_VALUE_QUOTED;
|
||||
}
|
||||
@Override
|
||||
void onEof(MimeTypeParser parser) {
|
||||
extractParameter(parser, parser.input.substring(parser.mark));
|
||||
parser.emitMimeType();
|
||||
}
|
||||
};
|
||||
|
||||
private static void extractParameter(MimeTypeParser parser, String input) {
|
||||
Assert.hasText(parser.paramName, "'paramName' must not be empty");
|
||||
parser.putParameter(parser.paramName, input);
|
||||
}
|
||||
|
||||
abstract ParserState process(char c, MimeTypeParser parser);
|
||||
|
||||
void onEof(MimeTypeParser parser) {
|
||||
// Only specific states need to flush here.
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
@@ -44,86 +45,415 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
*/
|
||||
class MimeTypeTests {
|
||||
|
||||
@Test
|
||||
void slashInSubtype() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new MimeType("text", "/"));
|
||||
@Nested
|
||||
class InvalidMimeTypeTests {
|
||||
|
||||
@Test
|
||||
void parseMimeTypeNull() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfSlashInSubtype() {
|
||||
assertThatIllegalArgumentException().isThrownBy(() ->
|
||||
new MimeType("text", "/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfNoSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfNoSubtypeOnlySlash() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfIllegalType() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio(/basic"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfIllegalSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio/basic)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfIllegalCharset() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("text/html; charset=foo-bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeNoSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeNoSubtypeSlash() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeTypeRange() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("*/json"));
|
||||
}
|
||||
|
||||
@Test // gh-36729
|
||||
void parseMimeTypeRejectsSecondCommaSeparatedType() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("text/plain,text/html"));
|
||||
}
|
||||
|
||||
@Test // gh-36729
|
||||
void parseMimeTypeRejectsLeadingComma() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType(",text/plain"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalType() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio(/basic"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/basic)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeMissingTypeAndSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType(" ;a=b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeEmptyParameterAttribute() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;=value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeEmptyParameterValue() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr="));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalParameterAttribute() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr<=value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalParameterValue() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr=v>alue"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalCharset() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("text/html; charset=foo-bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfDuplicateParameter() {
|
||||
assertThatThrownBy(() -> MimeType.valueOf("text/plain;dupe=\"1\";dupe=\"2\"")).isInstanceOf(InvalidMimeTypeException.class)
|
||||
.hasMessageContaining("Invalid mime type \"text/plain;dupe=\"1\";dupe=\"2\"\": duplicate parameter 'dupe=\"2\"'");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfNoSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio"));
|
||||
|
||||
@Nested
|
||||
class QuotePairsTests {
|
||||
|
||||
@Test
|
||||
void parseQuotedCharset() {
|
||||
MimeType mimeType = MimeType.valueOf("application/xml;charset=\"utf-8\"");
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("xml");
|
||||
assertThat(mimeType.getCharset()).as("Invalid charset").isEqualTo(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseQuotedParameterValue() {
|
||||
MimeType mimeType = MimeType.valueOf("application/xop+xml;charset=utf-8;type=\"application/soap+xml;action=\\\"https://x.y.z\\\"\"");
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("xop+xml");
|
||||
assertThat(mimeType.getCharset()).as("Invalid charset").isEqualTo(StandardCharsets.UTF_8);
|
||||
assertThat(mimeType.getParameter("type")).isEqualTo("\"application/soap+xml;action=\\\"https://x.y.z\\\"\"");
|
||||
}
|
||||
|
||||
@Test // gh-36730
|
||||
void parseParameterWithQuotedPair() {
|
||||
MimeType mimeType = MimeType.valueOf("text/plain;twelve=\"1\\\"2\"");
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("text");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("plain");
|
||||
assertThat(mimeType.getParameter("twelve")).isEqualTo("\"1\\\"2\"");
|
||||
}
|
||||
|
||||
@Test // SPR-8917
|
||||
void parseMimeTypeQuotedParameterValue() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("audio/*;attr=\"v>alue\"");
|
||||
assertThat(mimeType.getParameter("attr")).isEqualTo("\"v>alue\"");
|
||||
}
|
||||
|
||||
@Test // SPR-8917
|
||||
void parseMimeTypeSingleQuotedParameterValue() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("audio/*;attr='v>alue'");
|
||||
assertThat(mimeType.getParameter("attr")).isEqualTo("'v>alue'");
|
||||
}
|
||||
|
||||
@Test // SPR-16630
|
||||
void parseMimeTypeWithSpacesAroundEqualsAndQuotedValue() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("text/plain; foo = \" bar \" ");
|
||||
assertThat(mimeType.getParameter("foo")).isEqualTo("\" bar \"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalQuotedParameterValue() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr=\""));
|
||||
}
|
||||
|
||||
@Test // gh-36971
|
||||
void parseMimeTypeWrappedInQuotes() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class)
|
||||
.isThrownBy(() -> MimeTypeUtils.parseMimeType("\"application/xml\""))
|
||||
.withMessageContaining("Invalid token character '\"'");
|
||||
}
|
||||
|
||||
@Test // SPR-17459
|
||||
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<MimeType> actual = MimeTypeUtils.parseMimeTypes(s);
|
||||
|
||||
assertThat(actual).hasSameSizeAs(mimeTypes);
|
||||
for (int i = 0; i < mimeTypes.length; i++) {
|
||||
assertThat(actual.get(i).toString()).isEqualTo(mimeTypes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfNoSubtypeSlash() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio/"));
|
||||
}
|
||||
@Nested
|
||||
class SpecificityTests {
|
||||
|
||||
@Test
|
||||
void valueOfIllegalType() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio(/basic"));
|
||||
}
|
||||
@Test
|
||||
void includes() {
|
||||
MimeType textPlain = MimeTypeUtils.TEXT_PLAIN;
|
||||
assertThat(textPlain.includes(textPlain)).as("Equal types is not inclusive").isTrue();
|
||||
MimeType allText = new MimeType("text");
|
||||
|
||||
@Test
|
||||
void valueOfIllegalSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("audio/basic)"));
|
||||
}
|
||||
assertThat(allText.includes(textPlain)).as("All subtypes is not inclusive").isTrue();
|
||||
assertThat(textPlain.includes(allText)).as("All subtypes is inclusive").isFalse();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.includes(textPlain)).as("All types is not inclusive").isTrue();
|
||||
assertThat(textPlain.includes(MimeTypeUtils.ALL)).as("All types is inclusive").isFalse();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.includes(textPlain)).as("All types is not inclusive").isTrue();
|
||||
assertThat(textPlain.includes(MimeTypeUtils.ALL)).as("All types is inclusive").isFalse();
|
||||
|
||||
MimeType applicationSoapXml = new MimeType("application", "soap+xml");
|
||||
MimeType applicationWildcardXml = new MimeType("application", "*+xml");
|
||||
MimeType suffixXml = new MimeType("application", "x.y+z+xml"); // SPR-15795
|
||||
|
||||
assertThat(applicationSoapXml.includes(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.includes(applicationWildcardXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.includes(suffixXml)).isTrue();
|
||||
|
||||
assertThat(applicationWildcardXml.includes(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationSoapXml.includes(applicationWildcardXml)).isFalse();
|
||||
assertThat(suffixXml.includes(applicationWildcardXml)).isFalse();
|
||||
|
||||
assertThat(applicationWildcardXml.includes(MimeTypeUtils.APPLICATION_JSON)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isCompatible() {
|
||||
MimeType textPlain = MimeTypeUtils.TEXT_PLAIN;
|
||||
assertThat(textPlain.isCompatibleWith(textPlain)).as("Equal types is not compatible").isTrue();
|
||||
MimeType allText = new MimeType("text");
|
||||
|
||||
assertThat(allText.isCompatibleWith(textPlain)).as("All subtypes is not compatible").isTrue();
|
||||
assertThat(textPlain.isCompatibleWith(allText)).as("All subtypes is not compatible").isTrue();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.isCompatibleWith(textPlain)).as("All types is not compatible").isTrue();
|
||||
assertThat(textPlain.isCompatibleWith(MimeTypeUtils.ALL)).as("All types is not compatible").isTrue();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.isCompatibleWith(textPlain)).as("All types is not compatible").isTrue();
|
||||
assertThat(textPlain.isCompatibleWith(MimeTypeUtils.ALL)).as("All types is compatible").isTrue();
|
||||
|
||||
MimeType applicationSoapXml = new MimeType("application", "soap+xml");
|
||||
MimeType applicationWildcardXml = new MimeType("application", "*+xml");
|
||||
MimeType suffixXml = new MimeType("application", "x.y+z+xml"); // SPR-15795
|
||||
|
||||
assertThat(applicationSoapXml.isCompatibleWith(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(applicationWildcardXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(suffixXml)).isTrue();
|
||||
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationSoapXml.isCompatibleWith(applicationWildcardXml)).isTrue();
|
||||
assertThat(suffixXml.isCompatibleWith(applicationWildcardXml)).isTrue();
|
||||
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(MimeTypeUtils.APPLICATION_JSON)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void compareTo() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
// equal
|
||||
assertThat(audioBasic.compareTo(audioBasic)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(audio.compareTo(audio)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(audioBasicLevel.compareTo(audioBasicLevel)).as("Invalid comparison result").isEqualTo(0);
|
||||
|
||||
assertThat(audioBasicLevel.compareTo(audio)).as("Invalid comparison result").isGreaterThan(0);
|
||||
|
||||
List<MimeType> expected = new ArrayList<>();
|
||||
expected.add(audio);
|
||||
expected.add(audioBasic);
|
||||
expected.add(audioBasicLevel);
|
||||
expected.add(audioWave);
|
||||
|
||||
List<MimeType> result = new ArrayList<>(expected);
|
||||
Random rnd = new Random();
|
||||
|
||||
// shuffle & sort 10 times
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Collections.shuffle(result, rnd);
|
||||
Collections.sort(result);
|
||||
|
||||
for (int j = 0; j < result.size(); j++) {
|
||||
assertThat(result.get(j)).as("Invalid media type at " + j + ", run " + i).isSameAs(expected.get(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void compareToCaseSensitivity() {
|
||||
MimeType m1 = new MimeType("audio", "basic");
|
||||
MimeType m2 = new MimeType("Audio", "Basic");
|
||||
assertThat(m1.compareTo(m2)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(m2.compareTo(m1)).as("Invalid comparison result").isEqualTo(0);
|
||||
|
||||
m1 = new MimeType("audio", "basic", singletonMap("foo", "bar"));
|
||||
m2 = new MimeType("audio", "basic", singletonMap("Foo", "bar"));
|
||||
assertThat(m1.compareTo(m2)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(m2.compareTo(m1)).as("Invalid comparison result").isEqualTo(0);
|
||||
|
||||
m1 = new MimeType("audio", "basic", singletonMap("foo", "bar"));
|
||||
m2 = new MimeType("audio", "basic", singletonMap("foo", "Bar"));
|
||||
assertThat(m1.compareTo(m2)).as("Invalid comparison result").isNotEqualTo(0);
|
||||
assertThat(m2.compareTo(m1)).as("Invalid comparison result").isNotEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMoreSpecific() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
assertThat(audioBasic.isMoreSpecific(audioBasicLevel)).isFalse();
|
||||
assertThat(audioBasicLevel.isMoreSpecific(audioBasic)).isTrue();
|
||||
|
||||
assertThat(audio.isMoreSpecific(MimeTypeUtils.ALL)).isTrue();
|
||||
assertThat(MimeTypeUtils.ALL.isMoreSpecific(audio)).isFalse();
|
||||
|
||||
assertThat(audioBasicLevel.isMoreSpecific(audioBasic)).isTrue();
|
||||
assertThat(audioBasic.isMoreSpecific(audioBasicLevel)).isFalse();
|
||||
|
||||
assertThat(audioBasic.isMoreSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
assertThat(audioBasic.isMoreSpecific(audioWave)).isFalse();
|
||||
assertThat(audioBasicLevel.isMoreSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLessSpecific() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
assertThat(audioBasic.isLessSpecific(audioBasicLevel)).isTrue();
|
||||
assertThat(audioBasicLevel.isLessSpecific(audioBasic)).isFalse();
|
||||
|
||||
assertThat(audio.isLessSpecific(MimeTypeUtils.ALL)).isFalse();
|
||||
assertThat(MimeTypeUtils.ALL.isLessSpecific(audio)).isTrue();
|
||||
|
||||
assertThat(audioBasicLevel.isLessSpecific(audioBasic)).isFalse();
|
||||
assertThat(audioBasic.isLessSpecific(audioBasicLevel)).isTrue();
|
||||
|
||||
assertThat(audioBasic.isLessSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
assertThat(audioBasic.isLessSpecific(audioWave)).isFalse();
|
||||
assertThat(audioBasicLevel.isLessSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortBySpecificity() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
List<MimeType> mimeTypes = new ArrayList<>(
|
||||
List.of(MimeTypeUtils.ALL, audio, audioWave, audioBasic, audioBasicLevel));
|
||||
|
||||
MimeTypeUtils.sortBySpecificity(mimeTypes);
|
||||
|
||||
assertThat(mimeTypes).containsExactly(audioWave, audioBasicLevel, audioBasic, audio, MimeTypeUtils.ALL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bubbleSort() {
|
||||
List<Integer> list = new ArrayList<>(List.of(10, 9, 8, 7, 6, 5, 4, 3, 2, 1));
|
||||
MimeTypeUtils.bubbleSort(list, (i1, i2) -> i1 > i2);
|
||||
assertThat(list).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
}
|
||||
|
||||
@Test // gh-25350
|
||||
void wildcardSubtypeCompatibleWithSuffix() {
|
||||
MimeType applicationStar = new MimeType("application", "*");
|
||||
MimeType applicationVndJson = new MimeType("application", "vnd.something+json");
|
||||
assertThat(applicationStar.isCompatibleWith(applicationVndJson)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void valueOfIllegalCharset() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeType.valueOf("text/html; charset=foo-bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseCharset() {
|
||||
String s = "text/html; charset=iso-8859-1";
|
||||
MimeType mimeType = MimeType.valueOf(s);
|
||||
MimeType mimeType = MimeType.valueOf("text/html; charset=iso-8859-1");
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("text");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("html");
|
||||
assertThat(mimeType.getCharset()).as("Invalid charset").isEqualTo(StandardCharsets.ISO_8859_1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseQuotedCharset() {
|
||||
String s = "application/xml;charset=\"utf-8\"";
|
||||
MimeType mimeType = MimeType.valueOf(s);
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("xml");
|
||||
assertThat(mimeType.getCharset()).as("Invalid charset").isEqualTo(StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseQuotedParameterValue() {
|
||||
String s = "application/xop+xml;charset=utf-8;type=\"application/soap+xml;action=\\\"https://x.y.z\\\"\"";
|
||||
MimeType mimeType = MimeType.valueOf(s);
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("application");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("xop+xml");
|
||||
assertThat(mimeType.getCharset()).as("Invalid charset").isEqualTo(StandardCharsets.UTF_8);
|
||||
assertThat(mimeType.getParameter("type")).isEqualTo("\"application/soap+xml;action=\\\"https://x.y.z\\\"\"");
|
||||
}
|
||||
|
||||
@Test // gh-36730
|
||||
void parseParameterWithQuotedPair() {
|
||||
String s = "text/plain;twelve=\"1\\\"2\"";
|
||||
MimeType mimeType = MimeType.valueOf(s);
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("text");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("plain");
|
||||
assertThat(mimeType.getParameter("twelve")).isEqualTo("\"1\\\"2\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsDuplicateParameter() {
|
||||
String s = "text/plain;dupe=\"1\";dupe=\"2\"";
|
||||
assertThatThrownBy(() -> MimeType.valueOf(s)).isInstanceOf(InvalidMimeTypeException.class)
|
||||
.hasMessageContaining("Invalid mime type \"text/plain;dupe=\"1\";dupe=\"2\"\": duplicate parameter 'dupe=\"2\"'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void withConversionService() {
|
||||
ConversionService conversionService = new DefaultConversionService();
|
||||
@@ -132,66 +462,6 @@ class MimeTypeTests {
|
||||
assertThat(conversionService.convert("application/xml", MimeType.class)).isEqualTo(mimeType);
|
||||
}
|
||||
|
||||
@Test
|
||||
void includes() {
|
||||
MimeType textPlain = MimeTypeUtils.TEXT_PLAIN;
|
||||
assertThat(textPlain.includes(textPlain)).as("Equal types is not inclusive").isTrue();
|
||||
MimeType allText = new MimeType("text");
|
||||
|
||||
assertThat(allText.includes(textPlain)).as("All subtypes is not inclusive").isTrue();
|
||||
assertThat(textPlain.includes(allText)).as("All subtypes is inclusive").isFalse();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.includes(textPlain)).as("All types is not inclusive").isTrue();
|
||||
assertThat(textPlain.includes(MimeTypeUtils.ALL)).as("All types is inclusive").isFalse();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.includes(textPlain)).as("All types is not inclusive").isTrue();
|
||||
assertThat(textPlain.includes(MimeTypeUtils.ALL)).as("All types is inclusive").isFalse();
|
||||
|
||||
MimeType applicationSoapXml = new MimeType("application", "soap+xml");
|
||||
MimeType applicationWildcardXml = new MimeType("application", "*+xml");
|
||||
MimeType suffixXml = new MimeType("application", "x.y+z+xml"); // SPR-15795
|
||||
|
||||
assertThat(applicationSoapXml.includes(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.includes(applicationWildcardXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.includes(suffixXml)).isTrue();
|
||||
|
||||
assertThat(applicationWildcardXml.includes(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationSoapXml.includes(applicationWildcardXml)).isFalse();
|
||||
assertThat(suffixXml.includes(applicationWildcardXml)).isFalse();
|
||||
|
||||
assertThat(applicationWildcardXml.includes(MimeTypeUtils.APPLICATION_JSON)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isCompatible() {
|
||||
MimeType textPlain = MimeTypeUtils.TEXT_PLAIN;
|
||||
assertThat(textPlain.isCompatibleWith(textPlain)).as("Equal types is not compatible").isTrue();
|
||||
MimeType allText = new MimeType("text");
|
||||
|
||||
assertThat(allText.isCompatibleWith(textPlain)).as("All subtypes is not compatible").isTrue();
|
||||
assertThat(textPlain.isCompatibleWith(allText)).as("All subtypes is not compatible").isTrue();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.isCompatibleWith(textPlain)).as("All types is not compatible").isTrue();
|
||||
assertThat(textPlain.isCompatibleWith(MimeTypeUtils.ALL)).as("All types is not compatible").isTrue();
|
||||
|
||||
assertThat(MimeTypeUtils.ALL.isCompatibleWith(textPlain)).as("All types is not compatible").isTrue();
|
||||
assertThat(textPlain.isCompatibleWith(MimeTypeUtils.ALL)).as("All types is compatible").isTrue();
|
||||
|
||||
MimeType applicationSoapXml = new MimeType("application", "soap+xml");
|
||||
MimeType applicationWildcardXml = new MimeType("application", "*+xml");
|
||||
MimeType suffixXml = new MimeType("application", "x.y+z+xml"); // SPR-15795
|
||||
|
||||
assertThat(applicationSoapXml.isCompatibleWith(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(applicationWildcardXml)).isTrue();
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(suffixXml)).isTrue();
|
||||
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(applicationSoapXml)).isTrue();
|
||||
assertThat(applicationSoapXml.isCompatibleWith(applicationWildcardXml)).isTrue();
|
||||
assertThat(suffixXml.isCompatibleWith(applicationWildcardXml)).isTrue();
|
||||
|
||||
assertThat(applicationWildcardXml.isCompatibleWith(MimeTypeUtils.APPLICATION_JSON)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void toStringOutput() {
|
||||
MimeType mimeType = new MimeType("text", "plain");
|
||||
@@ -200,122 +470,34 @@ class MimeTypeTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeType() {
|
||||
String s = "audio/*";
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType(s);
|
||||
void parseWildcardSubtype() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("audio/*");
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("audio");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("*");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeNoSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio"));
|
||||
@Test // gh-36729
|
||||
void parseBareWildcardShorthand() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("*");
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("*");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("*");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeNoSubtypeSlash() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/"));
|
||||
@Test // gh-36729
|
||||
void parseBareWildcardShorthandWithParameter() {
|
||||
// java.net.HttpURLConnection returns a "*; q=.2" Accept header
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("*; q=.2");
|
||||
assertThat(mimeType.getType()).as("Invalid type").isEqualTo("*");
|
||||
assertThat(mimeType.getSubtype()).as("Invalid subtype").isEqualTo("*");
|
||||
assertThat(mimeType.getParameter("q")).isEqualTo(".2");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeTypeRange() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("*/json"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalType() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio(/basic"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/basic)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeMissingTypeAndSubtype() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType(" ;a=b"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeEmptyParameterAttribute() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;=value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeEmptyParameterValue() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr="));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalParameterAttribute() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr<=value"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalParameterValue() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr=v>alue"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalCharset() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("text/html; charset=foo-bar"));
|
||||
}
|
||||
|
||||
@Test // SPR-8917
|
||||
void parseMimeTypeQuotedParameterValue() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("audio/*;attr=\"v>alue\"");
|
||||
assertThat(mimeType.getParameter("attr")).isEqualTo("\"v>alue\"");
|
||||
}
|
||||
|
||||
@Test // SPR-8917
|
||||
void parseMimeTypeSingleQuotedParameterValue() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("audio/*;attr='v>alue'");
|
||||
assertThat(mimeType.getParameter("attr")).isEqualTo("'v>alue'");
|
||||
}
|
||||
|
||||
@Test // SPR-16630
|
||||
@Test // SPR-16630
|
||||
void parseMimeTypeWithSpacesAroundEquals() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("multipart/x-mixed-replace;boundary = --myboundary");
|
||||
assertThat(mimeType.getParameter("boundary")).isEqualTo("--myboundary");
|
||||
}
|
||||
|
||||
@Test // SPR-16630
|
||||
void parseMimeTypeWithSpacesAroundEqualsAndQuotedValue() {
|
||||
MimeType mimeType = MimeTypeUtils.parseMimeType("text/plain; foo = \" bar \" ");
|
||||
assertThat(mimeType.getParameter("foo")).isEqualTo("\" bar \"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeIllegalQuotedParameterValue() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType("audio/*;attr=\""));
|
||||
}
|
||||
|
||||
@Test // gh-36971
|
||||
void parseMimeTypeWrappedInQuotes() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class)
|
||||
.isThrownBy(() -> MimeTypeUtils.parseMimeType("\"application/xml\""))
|
||||
.withMessageContaining("Invalid token character '\"'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypeNull() {
|
||||
assertThatExceptionOfType(InvalidMimeTypeException.class).isThrownBy(() ->
|
||||
MimeTypeUtils.parseMimeType(null));
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseMimeTypes() {
|
||||
String s = "text/plain, text/html, text/x-dvi, text/x-c";
|
||||
@@ -328,23 +510,13 @@ class MimeTypeTests {
|
||||
assertThat(mimeTypes).as("Invalid amount of mime types").isEmpty();
|
||||
}
|
||||
|
||||
@Test // gh-23241
|
||||
@Test // gh-23241
|
||||
void parseMimeTypesWithTrailingComma() {
|
||||
List<MimeType> mimeTypes = MimeTypeUtils.parseMimeTypes("text/plain, text/html,");
|
||||
assertThat(mimeTypes).as("No mime types returned").isNotNull();
|
||||
assertThat(mimeTypes).as("Incorrect number of mime types").hasSize(2);
|
||||
}
|
||||
|
||||
@Test // SPR-17459
|
||||
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=\"\\,\\\"");
|
||||
}
|
||||
|
||||
@Test
|
||||
void parseSubtypeSuffix() {
|
||||
MimeType type = new MimeType("application", "vdn.something+json");
|
||||
@@ -357,140 +529,7 @@ class MimeTypeTests {
|
||||
assertThat(type.getSubtypeSuffix()).isEqualTo("json");
|
||||
}
|
||||
|
||||
@Test // gh-25350
|
||||
void wildcardSubtypeCompatibleWithSuffix() {
|
||||
MimeType applicationStar = new MimeType("application", "*");
|
||||
MimeType applicationVndJson = new MimeType("application", "vnd.something+json");
|
||||
assertThat(applicationStar.isCompatibleWith(applicationVndJson)).isTrue();
|
||||
}
|
||||
|
||||
private void testWithQuotedParameters(String... mimeTypes) {
|
||||
String s = String.join(",", mimeTypes);
|
||||
List<MimeType> actual = MimeTypeUtils.parseMimeTypes(s);
|
||||
|
||||
assertThat(actual).hasSameSizeAs(mimeTypes);
|
||||
for (int i = 0; i < mimeTypes.length; i++) {
|
||||
assertThat(actual.get(i).toString()).isEqualTo(mimeTypes[i]);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void compareTo() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
// equal
|
||||
assertThat(audioBasic.compareTo(audioBasic)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(audio.compareTo(audio)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(audioBasicLevel.compareTo(audioBasicLevel)).as("Invalid comparison result").isEqualTo(0);
|
||||
|
||||
assertThat(audioBasicLevel.compareTo(audio)).as("Invalid comparison result").isGreaterThan(0);
|
||||
|
||||
List<MimeType> expected = new ArrayList<>();
|
||||
expected.add(audio);
|
||||
expected.add(audioBasic);
|
||||
expected.add(audioBasicLevel);
|
||||
expected.add(audioWave);
|
||||
|
||||
List<MimeType> result = new ArrayList<>(expected);
|
||||
Random rnd = new Random();
|
||||
|
||||
// shuffle & sort 10 times
|
||||
for (int i = 0; i < 10; i++) {
|
||||
Collections.shuffle(result, rnd);
|
||||
Collections.sort(result);
|
||||
|
||||
for (int j = 0; j < result.size(); j++) {
|
||||
assertThat(result.get(j)).as("Invalid media type at " + j + ", run " + i).isSameAs(expected.get(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void compareToCaseSensitivity() {
|
||||
MimeType m1 = new MimeType("audio", "basic");
|
||||
MimeType m2 = new MimeType("Audio", "Basic");
|
||||
assertThat(m1.compareTo(m2)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(m2.compareTo(m1)).as("Invalid comparison result").isEqualTo(0);
|
||||
|
||||
m1 = new MimeType("audio", "basic", singletonMap("foo", "bar"));
|
||||
m2 = new MimeType("audio", "basic", singletonMap("Foo", "bar"));
|
||||
assertThat(m1.compareTo(m2)).as("Invalid comparison result").isEqualTo(0);
|
||||
assertThat(m2.compareTo(m1)).as("Invalid comparison result").isEqualTo(0);
|
||||
|
||||
m1 = new MimeType("audio", "basic", singletonMap("foo", "bar"));
|
||||
m2 = new MimeType("audio", "basic", singletonMap("foo", "Bar"));
|
||||
assertThat(m1.compareTo(m2)).as("Invalid comparison result").isNotEqualTo(0);
|
||||
assertThat(m2.compareTo(m1)).as("Invalid comparison result").isNotEqualTo(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
void isMoreSpecific() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
assertThat(audioBasic.isMoreSpecific(audioBasicLevel)).isFalse();
|
||||
assertThat(audioBasicLevel.isMoreSpecific(audioBasic)).isTrue();
|
||||
|
||||
assertThat(audio.isMoreSpecific(MimeTypeUtils.ALL)).isTrue();
|
||||
assertThat(MimeTypeUtils.ALL.isMoreSpecific(audio)).isFalse();
|
||||
|
||||
assertThat(audioBasicLevel.isMoreSpecific(audioBasic)).isTrue();
|
||||
assertThat(audioBasic.isMoreSpecific(audioBasicLevel)).isFalse();
|
||||
|
||||
assertThat(audioBasic.isMoreSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
assertThat(audioBasic.isMoreSpecific(audioWave)).isFalse();
|
||||
assertThat(audioBasicLevel.isMoreSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isLessSpecific() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
assertThat(audioBasic.isLessSpecific(audioBasicLevel)).isTrue();
|
||||
assertThat(audioBasicLevel.isLessSpecific(audioBasic)).isFalse();
|
||||
|
||||
assertThat(audio.isLessSpecific(MimeTypeUtils.ALL)).isFalse();
|
||||
assertThat(MimeTypeUtils.ALL.isLessSpecific(audio)).isTrue();
|
||||
|
||||
assertThat(audioBasicLevel.isLessSpecific(audioBasic)).isFalse();
|
||||
assertThat(audioBasic.isLessSpecific(audioBasicLevel)).isTrue();
|
||||
|
||||
assertThat(audioBasic.isLessSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
assertThat(audioBasic.isLessSpecific(audioWave)).isFalse();
|
||||
assertThat(audioBasicLevel.isLessSpecific(MimeTypeUtils.TEXT_HTML)).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
void sortBySpecificity() {
|
||||
MimeType audioBasic = new MimeType("audio", "basic");
|
||||
MimeType audio = new MimeType("audio");
|
||||
MimeType audioWave = new MimeType("audio", "wave");
|
||||
MimeType audioBasicLevel = new MimeType("audio", "basic", singletonMap("level", "1"));
|
||||
|
||||
List<MimeType> mimeTypes = new ArrayList<>(
|
||||
List.of(MimeTypeUtils.ALL, audio, audioWave, audioBasic, audioBasicLevel));
|
||||
|
||||
MimeTypeUtils.sortBySpecificity(mimeTypes);
|
||||
|
||||
assertThat(mimeTypes).containsExactly(audioWave, audioBasicLevel, audioBasic, audio, MimeTypeUtils.ALL);
|
||||
}
|
||||
|
||||
@Test
|
||||
void bubbleSort() {
|
||||
List<Integer> list = new ArrayList<>(List.of(10, 9, 8, 7, 6, 5, 4, 3, 2, 1));
|
||||
MimeTypeUtils.bubbleSort(list, (i1, i2) -> i1 > i2);
|
||||
assertThat(list).containsExactly(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
|
||||
}
|
||||
|
||||
@Test // SPR-13157
|
||||
@Test // SPR-13157
|
||||
void equalsIsCaseInsensitiveForCharsets() {
|
||||
MimeType m1 = new MimeType("text", "plain", singletonMap("charset", "UTF-8"));
|
||||
MimeType m2 = new MimeType("text", "plain", singletonMap("charset", "utf-8"));
|
||||
@@ -500,7 +539,18 @@ class MimeTypeTests {
|
||||
assertThat(m2.compareTo(m1)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test // gh-26127
|
||||
@Test // gh-36729
|
||||
void equalsIgnoresParameterValueQuoting() {
|
||||
MimeType m1 = MimeTypeUtils.parseMimeType("text/plain; spring=\"framework\"");
|
||||
MimeType m2 = MimeTypeUtils.parseMimeType("text/plain; spring=framework");
|
||||
assertThat(m1).isEqualTo(m2);
|
||||
assertThat(m2).isEqualTo(m1);
|
||||
assertThat(m1.hashCode()).isEqualTo(m2.hashCode());
|
||||
assertThat(m1.compareTo(m2)).isEqualTo(0);
|
||||
assertThat(m2.compareTo(m1)).isEqualTo(0);
|
||||
}
|
||||
|
||||
@Test // gh-26127
|
||||
void serialize() throws Exception {
|
||||
MimeType original = new MimeType("text", "plain", StandardCharsets.UTF_8);
|
||||
MimeType deserialized = SerializationTestUtils.serializeAndDeserialize(original);
|
||||
|
||||
@@ -701,12 +701,20 @@ public class MediaType extends MimeType implements Serializable {
|
||||
if (!StringUtils.hasLength(mediaTypes)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// Avoid using java.util.stream.Stream in hot paths
|
||||
List<String> tokenizedTypes = MimeTypeUtils.tokenize(mediaTypes);
|
||||
List<MediaType> result = new ArrayList<>(tokenizedTypes.size());
|
||||
for (String type : tokenizedTypes) {
|
||||
if (StringUtils.hasText(type)) {
|
||||
result.add(parseMediaType(type));
|
||||
List<MimeType> mimeTypes;
|
||||
try {
|
||||
mimeTypes = MimeTypeUtils.parseMimeTypes(mediaTypes);
|
||||
}
|
||||
catch (InvalidMimeTypeException ex) {
|
||||
throw new InvalidMediaTypeException(ex);
|
||||
}
|
||||
List<MediaType> result = new ArrayList<>(mimeTypes.size());
|
||||
for (MimeType mimeType : mimeTypes) {
|
||||
try {
|
||||
result.add(new MediaType(mimeType));
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new InvalidMediaTypeException(mimeType.toString(), ex.getMessage());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
||||
Reference in New Issue
Block a user