Update `MediaTypeHeaderValue.IsSubsetOf()` to perform consistent checks
- aspnet/Mvc#3138 part 1/2 - check parameters with same polarity as type and subtype - ignore quality factors - bug was obscured because MVC has no formatters supporting wildcard media types nits: - add doc comments - spelling - correct typo in a `project.json` file
This commit is contained in:
parent
a0b29e3f2b
commit
0581bcf008
|
|
@ -219,6 +219,21 @@ namespace Microsoft.Net.Http.Headers
|
|||
get { return _isReadOnly; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether this <see cref="MediaTypeHeaderValue"/> is a subset of
|
||||
/// <paramref name="otherMediaType"/>. 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.
|
||||
/// </summary>
|
||||
/// <param name="otherMediaType">The <see cref="MediaTypeHeaderValue"/> to compare.</param>
|
||||
/// <returns>
|
||||
/// A value indicating whether this <see cref="MediaTypeHeaderValue"/> is a subset of
|
||||
/// <paramref name="otherMediaType"/>.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// 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".
|
||||
/// </remarks>
|
||||
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 <type> and <subtype> in <type>/<subtype> get the media type using
|
||||
// If there is no whitespace between <type> and <subtype> in <type>/<subtype> get the media type using
|
||||
// one Substring call. Otherwise get substrings for <type> and <subtype> 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))
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<VisualStudioVersion Condition="'$(VisualStudioVersion)' == ''">14.0</VisualStudioVersion>
|
||||
|
|
@ -11,9 +11,11 @@
|
|||
<BaseIntermediateOutputPath Condition="'$(BaseIntermediateOutputPath)'=='' ">..\..\artifacts\obj\$(MSBuildProjectName)</BaseIntermediateOutputPath>
|
||||
<OutputPath Condition="'$(OutputPath)'=='' ">..\..\artifacts\bin\$(MSBuildProjectName)\</OutputPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<SchemaVersion>2.0</SchemaVersion>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Service Include="{82a7f48d-3b50-4b1e-b82e-3ada8210c358}" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" Condition="'$(VSToolsPath)' != ''" />
|
||||
</Project>
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue