diff --git a/spring-core/src/main/java/org/springframework/util/MimeType.java b/spring-core/src/main/java/org/springframework/util/MimeType.java index 539be7f561..88af6498cd 100644 --- a/spring-core/src/main/java/org/springframework/util/MimeType.java +++ b/spring-core/src/main/java/org/springframework/util/MimeType.java @@ -284,6 +284,19 @@ public class MimeType implements Comparable, Serializable { return this.subtype; } + /** + * Return the subtype suffix as defined in RFC 6839. + * @since 5.3 + */ + @Nullable + public String getSubtypeSuffix() { + int suffixIndex = this.subtype.lastIndexOf('+'); + if (suffixIndex != -1 && this.subtype.length() > suffixIndex) { + return this.subtype.substring(suffixIndex + 1); + } + return null; + } + /** * Return the character set, as indicated by a {@code charset} parameter, if any. * @return the character set, or {@code null} if not available @@ -377,22 +390,20 @@ public class MimeType implements Comparable, Serializable { if (getSubtype().equals(other.getSubtype())) { return true; } - // Wildcard with suffix? e.g. application/*+xml if (isWildcardSubtype() || other.isWildcardSubtype()) { - int thisPlusIdx = getSubtype().lastIndexOf('+'); - int otherPlusIdx = other.getSubtype().lastIndexOf('+'); - if (thisPlusIdx == -1 && otherPlusIdx == -1) { + String thisSuffix = getSubtypeSuffix(); + String otherSuffix = other.getSubtypeSuffix(); + if (getSubtype().equals(WILDCARD_TYPE) + || other.getSubtype().equals(WILDCARD_TYPE)) { return true; } - else if (thisPlusIdx != -1 && otherPlusIdx != -1) { - String thisSubtypeNoSuffix = getSubtype().substring(0, thisPlusIdx); - String otherSubtypeNoSuffix = other.getSubtype().substring(0, otherPlusIdx); - String thisSubtypeSuffix = getSubtype().substring(thisPlusIdx + 1); - String otherSubtypeSuffix = other.getSubtype().substring(otherPlusIdx + 1); - if (thisSubtypeSuffix.equals(otherSubtypeSuffix) && - (WILDCARD_TYPE.equals(thisSubtypeNoSuffix) || WILDCARD_TYPE.equals(otherSubtypeNoSuffix))) { - return true; - } + else if (isWildcardSubtype() && thisSuffix != null) { + return thisSuffix.equals(other.getSubtype()) + || thisSuffix.equals(otherSuffix); + } + else if (other.isWildcardSubtype() && otherSuffix != null) { + return this.getSubtype().equals(otherSuffix) + || otherSuffix.equals(thisSuffix); } } } 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 aa5fefc2e3..7eb86378a5 100644 --- a/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java +++ b/spring-core/src/test/java/org/springframework/util/MimeTypeTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2020 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -320,6 +320,25 @@ class MimeTypeTests { testWithQuotedParameters("foo/bar;param=\"\\,\\\""); } + @Test + void parseSubtypeSuffix() { + MimeType type = new MimeType("application", "vdn.something+json"); + assertThat(type.getSubtypeSuffix()).isEqualTo("json"); + type = new MimeType("application", "vdn.something"); + assertThat(type.getSubtypeSuffix()).isNull(); + type = new MimeType("application", "vdn.something+"); + assertThat(type.getSubtypeSuffix()).isEqualTo(""); + type = new MimeType("application", "vdn.some+thing+json"); + 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 actual = MimeTypeUtils.parseMimeTypes(s);