diff --git a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs index aebf80e00b..23ed8b0cec 100644 --- a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs +++ b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs @@ -219,6 +219,21 @@ namespace Microsoft.Net.Http.Headers get { return _isReadOnly; } } + /// + /// Gets a value indicating whether this is a subset of + /// . A "subset" is defined as the same or a more specific media type + /// according to the precedence described in https://www.ietf.org/rfc/rfc2068.txt section 14.1, Accept. + /// + /// The to compare. + /// + /// A value indicating whether this is a subset of + /// . + /// + /// + /// For example "multipart/mixed; boundary=1234" is a subset of "multipart/mixed; boundary=1234", + /// "multipart/mixed", "multipart/*", and "*/*" but not "multipart/mixed; boundary=2345" or + /// "multipart/message; boundary=1234". + /// public bool IsSubsetOf(MediaTypeHeaderValue otherMediaType) { if (otherMediaType == null) @@ -226,6 +241,7 @@ namespace Microsoft.Net.Http.Headers return false; } + // "text/plain" is a subset of "text/plain", "text/*" and "*/*". "*/*" is a subset only of "*/*". if (!Type.Equals(otherMediaType.Type, StringComparison.OrdinalIgnoreCase)) { if (!otherMediaType.MatchesAllTypes) @@ -241,22 +257,29 @@ namespace Microsoft.Net.Http.Headers } } - if (Parameters != null) + // "text/plain; charset=utf-8; level=1" is a subset of "text/plain; charset=utf-8". In turn + // "text/plain; charset=utf-8" is a subset of "text/plain". + if (otherMediaType._parameters != null && otherMediaType._parameters.Count != 0) { - if (Parameters.Count != 0 && (otherMediaType.Parameters == null || otherMediaType.Parameters.Count == 0)) + // Make sure all parameters in the potential superset are included locally. Fine to have additional + // parameters locally; they make this one more specific. + foreach (var parameter in otherMediaType._parameters) { - return false; - } - - // Make sure all parameters listed locally are listed in the other one. The other one may have additional parameters. - foreach (var param in _parameters) - { - var otherParam = NameValueHeaderValue.Find(otherMediaType._parameters, param.Name); - if (otherParam == null) + if (string.Equals(parameter.Name, "q", StringComparison.OrdinalIgnoreCase)) { + // "q" and later parameters are not involved in media type matching. Quoting the RFC: The first + // "q" parameter (if any) separates the media-range parameter(s) from the accept-params. + break; + } + + var localParameter = NameValueHeaderValue.Find(_parameters, parameter.Name); + if (localParameter == null) + { + // Not found. return false; } - if (!string.Equals(param.Value, otherParam.Value, StringComparison.OrdinalIgnoreCase)) + + if (!string.Equals(parameter.Value, localParameter.Value, StringComparison.OrdinalIgnoreCase)) { return false; } @@ -364,7 +387,7 @@ namespace Microsoft.Net.Http.Headers return 0; } - // Caller must remove leading whitespaces. If not, we'll return 0. + // Caller must remove leading whitespace. If not, we'll return 0. string mediaType = null; var mediaTypeLength = MediaTypeHeaderValue.GetMediaTypeExpressionLength(input, startIndex, out mediaType); @@ -432,7 +455,7 @@ namespace Microsoft.Net.Http.Headers return 0; } - // If there are no whitespaces between and in / get the media type using + // If there is no whitespace between and in / get the media type using // one Substring call. Otherwise get substrings for and and combine them. var mediatTypeLength = current + subtypeLength - startIndex; if (typeLength + subtypeLength + 1 == mediatTypeLength) @@ -454,8 +477,8 @@ namespace Microsoft.Net.Http.Headers throw new ArgumentException("An empty string is not allowed.", parameterName); } - // When adding values using strongly typed objects, no leading/trailing LWS (whitespaces) are allowed. - // Also no LWS between type and subtype are allowed. + // When adding values using strongly typed objects, no leading/trailing LWS (whitespace) is allowed. + // Also no LWS between type and subtype is allowed. string tempMediaType; var mediaTypeLength = GetMediaTypeExpressionLength(mediaType, 0, out tempMediaType); if ((mediaTypeLength == 0) || (tempMediaType.Length != mediaType.Length)) diff --git a/test/Microsoft.Extensions.BufferedHtmlContent.Test/Microsoft.Extensions.BufferedHtmlContent.Test.xproj b/test/Microsoft.Extensions.BufferedHtmlContent.Test/Microsoft.Extensions.BufferedHtmlContent.Test.xproj index a091460b76..f0c4c7f35a 100644 --- a/test/Microsoft.Extensions.BufferedHtmlContent.Test/Microsoft.Extensions.BufferedHtmlContent.Test.xproj +++ b/test/Microsoft.Extensions.BufferedHtmlContent.Test/Microsoft.Extensions.BufferedHtmlContent.Test.xproj @@ -1,4 +1,4 @@ - + 14.0 @@ -11,9 +11,11 @@ ..\..\artifacts\obj\$(MSBuildProjectName) ..\..\artifacts\bin\$(MSBuildProjectName)\ - 2.0 + + + \ No newline at end of file diff --git a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs index 9ca74a3632..48a4bfab12 100644 --- a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs +++ b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs @@ -542,13 +542,16 @@ namespace Microsoft.Net.Http.Headers [Theory] [InlineData("*/*;", "*/*")] - [InlineData("text/*;", "text/*")] + [InlineData("text/*", "text/*")] + [InlineData("text/*;", "*/*")] [InlineData("text/plain;", "text/plain")] - [InlineData("*/*;", "*/*;charset=utf-8;")] - [InlineData("text/*;", "*/*;charset=utf-8;")] - [InlineData("text/plain;", "*/*;charset=utf-8;")] - [InlineData("text/plain;", "text/*;charset=utf-8;")] - [InlineData("text/plain;", "text/plain;charset=utf-8;")] + [InlineData("text/plain", "text/*")] + [InlineData("text/plain;", "*/*")] + [InlineData("*/*;missingparam=4", "*/*")] + [InlineData("text/*;missingparam=4;", "*/*;")] + [InlineData("text/plain;missingparam=4", "*/*;")] + [InlineData("text/plain;missingparam=4", "text/*")] + [InlineData("text/plain;charset=utf-8", "text/plain;charset=utf-8")] [InlineData("text/plain;version=v1", "Text/plain;Version=v1")] [InlineData("text/plain;version=v1", "tExT/plain;version=V1")] [InlineData("text/plain;version=v1", "TEXT/PLAIN;VERSION=V1")] @@ -558,26 +561,38 @@ namespace Microsoft.Net.Http.Headers [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "*/*;charset=utf-8;foo=bar;q=0.0")] public void IsSubsetOf_PositiveCases(string mediaType1, string mediaType2) { + // Arrange var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1); var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2); + // Act var isSubset = parsedMediaType1.IsSubsetOf(parsedMediaType2); + + // Assert Assert.True(isSubset); } [Theory] + [InlineData("application/html", "text/*")] + [InlineData("application/json", "application/html")] [InlineData("text/plain;version=v1", "text/plain;version=")] [InlineData("*/*;", "text/plain;charset=utf-8;foo=bar;q=0.0")] [InlineData("text/*;", "text/plain;charset=utf-8;foo=bar;q=0.0")] - [InlineData("text/plain;missingparam=4;", "text/plain;charset=utf-8;foo=bar;q=0.0")] - [InlineData("text/plain;missingparam=4;", "text/*;charset=utf-8;foo=bar;q=0.0")] - [InlineData("text/plain;missingparam=4;", "*/*;charset=utf-8;foo=bar;q=0.0")] + [InlineData("text/*;charset=utf-8;foo=bar;q=0.0", "text/plain;missingparam=4;")] + [InlineData("*/*;charset=utf-8;foo=bar;q=0.0", "text/plain;missingparam=4;")] + [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;missingparam=4;")] + [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/*;missingparam=4;")] + [InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "*/*;missingparam=4;")] public void IsSubsetOf_NegativeCases(string mediaType1, string mediaType2) { + // Arrange var parsedMediaType1 = MediaTypeHeaderValue.Parse(mediaType1); var parsedMediaType2 = MediaTypeHeaderValue.Parse(mediaType2); + // Act var isSubset = parsedMediaType1.IsSubsetOf(parsedMediaType2); + + // Assert Assert.False(isSubset); }