From f7a014d7dc577ad5327a11c71169f3be122d5ae7 Mon Sep 17 00:00:00 2001 From: Brian Clozel Date: Tue, 8 Sep 2020 14:39:21 +0200 Subject: [PATCH] Improve MIME type subtype suffix handling Prior to this commit, the subtype suffix of a MIME type (see RFC 6839) was not properly taken into account when checking compatibility between MIME types. For example, `"application/*"` was not considered as compatible with `"application/vnd.io.spring+json"`. This commit adds a new `MimeType#getSubtypeSuffix()` method to easily extract the subtype suffix information. This method is then reused in the `isCompatibleWith` implementation to better handle these cases. Fixes gh-25350 --- .../org/springframework/util/MimeType.java | 37 ++++++++++++------- .../springframework/util/MimeTypeTests.java | 21 ++++++++++- 2 files changed, 44 insertions(+), 14 deletions(-) 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);