diff --git a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs
index bfda2aac81..32074b44cc 100644
--- a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs
+++ b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValue.cs
@@ -11,10 +11,22 @@ using Microsoft.Extensions.Primitives;
namespace Microsoft.Net.Http.Headers
{
+ ///
+ /// Representation of the media type header. See .
+ ///
public class MediaTypeHeaderValue
{
- private const string CharsetString = "charset";
private const string BoundaryString = "boundary";
+ private const string CharsetString = "charset";
+ private const string MatchesAllString = "*/*";
+ private const string QualityString = "q";
+ private const string WildcardString = "*";
+
+ private const char ForwardSlashCharacter = '/';
+ private const char PeriodCharacter = '.';
+ private const char PlusCharacter = '+';
+
+ private static readonly char[] PeriodCharacterArray = new char[] { PeriodCharacter };
private static readonly HttpHeaderParser SingleValueParser
= new GenericHeaderParser(false, GetMediaTypeLength);
@@ -31,18 +43,33 @@ namespace Microsoft.Net.Http.Headers
// Used by the parser to create a new instance of this type.
}
+ ///
+ /// Initializes a instance.
+ ///
+ /// A representation of a media type.
+ /// The text provided must be a single media type without parameters.
public MediaTypeHeaderValue(StringSegment mediaType)
{
- CheckMediaTypeFormat(mediaType, "mediaType");
+ CheckMediaTypeFormat(mediaType, nameof(mediaType));
_mediaType = mediaType;
}
+ ///
+ /// Initializes a instance.
+ ///
+ /// A representation of a media type.
+ /// The text provided must be a single media type without parameters.
+ /// The with the quality of the media type.
public MediaTypeHeaderValue(StringSegment mediaType, double quality)
: this(mediaType)
{
Quality = quality;
}
+ ///
+ /// Gets or sets the value of the charset parameter. Returns
+ /// if there is no charset.
+ ///
public StringSegment Charset
{
get
@@ -77,6 +104,10 @@ namespace Microsoft.Net.Http.Headers
}
}
+ ///
+ /// Gets or sets the value of the Encoding parameter. Setting the Encoding will set
+ /// the to .
+ ///
public Encoding Encoding
{
get
@@ -109,6 +140,10 @@ namespace Microsoft.Net.Http.Headers
}
}
+ ///
+ /// Gets or sets the value of the boundary parameter. Returns
+ /// if there is no boundary.
+ ///
public StringSegment Boundary
{
get
@@ -141,6 +176,10 @@ namespace Microsoft.Net.Http.Headers
}
}
+ ///
+ /// Gets or sets the media type's parameters. Returns an empty
+ /// if there are no parameters.
+ ///
public IList Parameters
{
get
@@ -160,6 +199,10 @@ namespace Microsoft.Net.Http.Headers
}
}
+ ///
+ /// Gets or sets the value of the quality parameter. Returns null
+ /// if there is no quality.
+ ///
public double? Quality
{
get { return HeaderUtilities.GetQuality(_parameters); }
@@ -170,55 +213,155 @@ namespace Microsoft.Net.Http.Headers
}
}
+ ///
+ /// Gets or sets the value of the media type. Returns
+ /// if there is no media type.
+ ///
+ ///
+ /// For the media type "application/json", the property gives the value
+ /// "application/json".
+ ///
public StringSegment MediaType
{
get { return _mediaType; }
set
{
HeaderUtilities.ThrowIfReadOnly(IsReadOnly);
- CheckMediaTypeFormat(value, "value");
+ CheckMediaTypeFormat(value, nameof(value));
_mediaType = value;
}
}
+ ///
+ /// Gets the type of the .
+ ///
+ ///
+ /// For the media type "application/json", the property gives the value "application".
+ ///
+ /// See for more details on the type.
public StringSegment Type
{
get
{
- return _mediaType.Subsegment(0, _mediaType.IndexOf('/'));
+ return _mediaType.Subsegment(0, _mediaType.IndexOf(ForwardSlashCharacter));
}
}
+ ///
+ /// Gets the subtype of the .
+ ///
+ ///
+ /// For the media type "application/vnd.example+json", the property gives the value
+ /// "vnd.example+json".
+ ///
+ /// See for more details on the subtype.
public StringSegment SubType
{
get
{
- return _mediaType.Subsegment(_mediaType.IndexOf('/') + 1);
+ return _mediaType.Subsegment(_mediaType.IndexOf(ForwardSlashCharacter) + 1);
}
}
///
- /// MediaType = "*/*"
+ /// Gets subtype of the , excluding any structured syntax suffix. Returns
+ /// if there is no subtype without suffix.
///
- public bool MatchesAllTypes
+ ///
+ /// For the media type "application/vnd.example+json", the property gives the value
+ /// "vnd.example".
+ ///
+ public StringSegment SubTypeWithoutSuffix
{
get
{
- return MediaType.Equals("*/*", StringComparison.Ordinal);
+ var subType = SubType;
+ var startOfSuffix = subType.LastIndexOf(PlusCharacter);
+ if (startOfSuffix == -1)
+ {
+ return subType;
+ }
+ else
+ {
+ return subType.Subsegment(0, startOfSuffix);
+ }
}
}
///
- /// SubType = "*"
+ /// Gets the structured syntax suffix of the if it has one.
+ /// See The RFC documentation on structured syntaxes.
///
- public bool MatchesAllSubTypes
+ ///
+ /// For the media type "application/vnd.example+json", the property gives the value
+ /// "json".
+ ///
+ public StringSegment Suffix
{
get
{
- return SubType.Equals("*", StringComparison.Ordinal);
+ var subType = SubType;
+ var startOfSuffix = subType.LastIndexOf(PlusCharacter);
+ if (startOfSuffix == -1)
+ {
+ return default(StringSegment);
+ }
+ else
+ {
+ return subType.Subsegment(startOfSuffix + 1);
+ }
}
}
+
+ ///
+ /// Get a of facets of the . Facets are a
+ /// period separated list of StringSegments in the .
+ /// See The RFC documentation on facets.
+ ///
+ ///
+ /// For the media type "application/vnd.example+json", the property gives the value:
+ /// {"vnd", "example"}
+ ///
+ public IEnumerable Facets
+ {
+ get
+ {
+ return SubTypeWithoutSuffix.Split(PeriodCharacterArray);
+ }
+ }
+
+ ///
+ /// Gets whether this matches all types.
+ ///
+ public bool MatchesAllTypes => MediaType.Equals(MatchesAllString, StringComparison.Ordinal);
+
+ ///
+ /// Gets whether this matches all subtypes.
+ ///
+ ///
+ /// For the media type "application/*", this property is true.
+ ///
+ ///
+ /// For the media type "application/json", this property is false.
+ ///
+ public bool MatchesAllSubTypes => SubType.Equals(WildcardString, StringComparison.Ordinal);
+
+ ///
+ /// Gets whether this matches all subtypes, ignoring any structured syntax suffix.
+ ///
+ ///
+ /// For the media type "application/*+json", this property is true.
+ ///
+ ///
+ /// For the media type "application/vnd.example+json", this property is false.
+ ///
+ public bool MatchesAllSubTypesWithoutSuffix =>
+ SubTypeWithoutSuffix.Equals(WildcardString, StringComparison.OrdinalIgnoreCase);
+
+ ///
+ /// Gets whether the is readonly.
+ ///
public bool IsReadOnly
{
get { return _isReadOnly; }
@@ -247,56 +390,14 @@ namespace Microsoft.Net.Http.Headers
}
// "text/plain" is a subset of "text/plain", "text/*" and "*/*". "*/*" is a subset only of "*/*".
- if (!Type.Equals(otherMediaType.Type, comparisonType: StringComparison.OrdinalIgnoreCase))
- {
- if (!otherMediaType.MatchesAllTypes)
- {
- return false;
- }
- }
- else if (!SubType.Equals(otherMediaType.SubType, comparisonType: StringComparison.OrdinalIgnoreCase))
- {
- if (!otherMediaType.MatchesAllSubTypes)
- {
- return false;
- }
- }
-
- // "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)
- {
- // 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)
- {
- if (parameter.Name.Equals("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 (!StringSegment.Equals(parameter.Value, localParameter.Value, StringComparison.OrdinalIgnoreCase))
- {
- return false;
- }
- }
- }
-
- return true;
+ return MatchesType(otherMediaType) &&
+ MatchesSubtype(otherMediaType) &&
+ MatchesParameters(otherMediaType);
}
///
/// Performs a deep copy of this object and all of it's NameValueHeaderValue sub components,
- /// while avoiding the cost of revalidating the components.
+ /// while avoiding the cost of re-validating the components.
///
/// A deep copy.
public MediaTypeHeaderValue Copy()
@@ -314,7 +415,7 @@ namespace Microsoft.Net.Http.Headers
///
/// Performs a deep copy of this object and all of it's NameValueHeaderValue sub components,
- /// while avoiding the cost of revalidating the components. This copy is read-only.
+ /// while avoiding the cost of re-validating the components. This copy is read-only.
///
/// A deep, read-only, copy.
public MediaTypeHeaderValue CopyAsReadOnly()
@@ -362,33 +463,67 @@ namespace Microsoft.Net.Http.Headers
return StringSegmentComparer.OrdinalIgnoreCase.GetHashCode(_mediaType) ^ NameValueHeaderValue.GetHashCode(_parameters);
}
+ ///
+ /// Takes a media type and parses it into the and its associated parameters.
+ ///
+ /// The with the media type.
+ /// The parsed .
public static MediaTypeHeaderValue Parse(StringSegment input)
{
var index = 0;
return SingleValueParser.ParseValue(input, ref index);
}
+ ///
+ /// Takes a media type, which can include parameters, and parses it into the and its associated parameters.
+ ///
+ /// The with the media type. The media type constructed here must not have an y
+ /// The parsed
+ /// True if the value was successfully parsed.
public static bool TryParse(StringSegment input, out MediaTypeHeaderValue parsedValue)
{
var index = 0;
return SingleValueParser.TryParseValue(input, ref index, out parsedValue);
}
+ ///
+ /// Takes an of and parses it into the and its associated parameters.
+ ///
+ /// A list of media types
+ /// The parsed .
public static IList ParseList(IList inputs)
{
return MultipleValueParser.ParseValues(inputs);
}
+ ///
+ /// Takes an of and parses it into the and its associated parameters.
+ /// Throws if there is invalid data in a string.
+ ///
+ /// A list of media types
+ /// The parsed .
public static IList ParseStrictList(IList inputs)
{
return MultipleValueParser.ParseStrictValues(inputs);
}
+ ///
+ /// Takes an of and parses it into the and its associated parameters.
+ ///
+ /// A list of media types
+ /// The parsed .
+ /// True if the value was successfully parsed.
public static bool TryParseList(IList inputs, out IList parsedValues)
{
return MultipleValueParser.TryParseValues(inputs, out parsedValues);
}
+ ///
+ /// Takes an of and parses it into the and its associated parameters.
+ ///
+ /// A list of media types
+ /// The parsed .
+ /// True if the value was successfully parsed.
public static bool TryParseStrictList(IList inputs, out IList parsedValues)
{
return MultipleValueParser.TryParseStrictValues(inputs, out parsedValues);
@@ -481,7 +616,7 @@ namespace Microsoft.Net.Http.Headers
}
else
{
- mediaType = input.Substring(startIndex, typeLength) + "/" + input.Substring(current, subtypeLength);
+ mediaType = input.Substring(startIndex, typeLength) + ForwardSlashCharacter + input.Substring(current, subtypeLength);
}
return mediaTypeLength;
@@ -502,5 +637,85 @@ namespace Microsoft.Net.Http.Headers
throw new FormatException(string.Format(CultureInfo.InvariantCulture, "Invalid media type '{0}'.", mediaType));
}
}
+
+ private bool MatchesType(MediaTypeHeaderValue set)
+ {
+ return set.MatchesAllTypes ||
+ set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool MatchesSubtype(MediaTypeHeaderValue set)
+ {
+ if (set.MatchesAllSubTypes)
+ {
+ return true;
+ }
+ if (set.Suffix.HasValue)
+ {
+ if (Suffix.HasValue)
+ {
+ return MatchesSubtypeWithoutSuffix(set) && MatchesSubtypeSuffix(set);
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else
+ {
+ return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+
+ private bool MatchesSubtypeWithoutSuffix(MediaTypeHeaderValue set)
+ {
+ return set.MatchesAllSubTypesWithoutSuffix ||
+ set.SubTypeWithoutSuffix.Equals(SubTypeWithoutSuffix, StringComparison.OrdinalIgnoreCase);
+ }
+
+ private bool MatchesParameters(MediaTypeHeaderValue set)
+ {
+ if (set._parameters != null && set._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 set._parameters)
+ {
+ if (parameter.Name.Equals(WildcardString, StringComparison.OrdinalIgnoreCase))
+ {
+ // A parameter named "*" has no effect on media type matching, as it is only used as an indication
+ // that the entire media type string should be treated as a wildcard.
+ continue;
+ }
+
+ if (parameter.Name.Equals(QualityString, 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 (!StringSegment.Equals(parameter.Value, localParameter.Value, StringComparison.OrdinalIgnoreCase))
+ {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private bool MatchesSubtypeSuffix(MediaTypeHeaderValue set)
+ {
+ // We don't have support for wildcards on suffixes alone (e.g., "application/entity+*")
+ // because there's no clear use case for it.
+ return set.Suffix.Equals(Suffix, StringComparison.OrdinalIgnoreCase);
+ }
}
}
diff --git a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValueComparer.cs b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValueComparer.cs
index 21ab21f71e..cc34640988 100644
--- a/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValueComparer.cs
+++ b/src/Microsoft.Net.Http.Headers/MediaTypeHeaderValueComparer.cs
@@ -28,11 +28,15 @@ namespace Microsoft.Net.Http.Headers
///
/// Performs comparisons based on the arguments' quality values
/// (aka their "q-value"). Values with identical q-values are considered equal (i.e. the result is 0)
- /// with the exception that subtype wildcards are considered less than specific media types and full
- /// wildcards are considered less than subtype wildcards. This allows callers to sort a sequence of
- /// following their q-values in the order of specific
- /// media types, subtype wildcards, and last any full wildcards.
+ /// with the exception that suffixed subtype wildcards are considered less than subtype wildcards, subtype wildcards
+ /// are considered less than specific media types and full wildcards are considered less than
+ /// subtype wildcards. This allows callers to sort a sequence of following
+ /// their q-values in the order of specific media types, subtype wildcards, and last any full wildcards.
///
+ ///
+ /// If we had a list of media types (comma separated): { text/*;q=0.8, text/*+json;q=0.8, */*;q=1, */*;q=0.8, text/plain;q=0.8 }
+ /// Sorting them using Compare would return: { */*;q=0.8, text/*;q=0.8, text/*+json;q=0.8, text/plain;q=0.8, */*;q=1 }
+ ///
public int Compare(MediaTypeHeaderValue mediaType1, MediaTypeHeaderValue mediaType2)
{
if (object.ReferenceEquals(mediaType1, mediaType2))
@@ -62,6 +66,14 @@ namespace Microsoft.Net.Http.Headers
{
return 1;
}
+ else if (mediaType1.MatchesAllSubTypesWithoutSuffix && !mediaType2.MatchesAllSubTypesWithoutSuffix)
+ {
+ return -1;
+ }
+ else if (!mediaType1.MatchesAllSubTypesWithoutSuffix && mediaType2.MatchesAllSubTypesWithoutSuffix)
+ {
+ return 1;
+ }
}
else if (!mediaType1.SubType.Equals(mediaType2.SubType, StringComparison.OrdinalIgnoreCase))
{
@@ -73,6 +85,25 @@ namespace Microsoft.Net.Http.Headers
{
return 1;
}
+ else if (mediaType1.MatchesAllSubTypesWithoutSuffix && !mediaType2.MatchesAllSubTypesWithoutSuffix)
+ {
+ return -1;
+ }
+ else if (!mediaType1.MatchesAllSubTypesWithoutSuffix && mediaType2.MatchesAllSubTypesWithoutSuffix)
+ {
+ return 1;
+ }
+ }
+ else if (!mediaType1.Suffix.Equals(mediaType2.Suffix, StringComparison.OrdinalIgnoreCase))
+ {
+ if (mediaType1.MatchesAllSubTypesWithoutSuffix)
+ {
+ return -1;
+ }
+ else if (mediaType2.MatchesAllSubTypesWithoutSuffix)
+ {
+ return 1;
+ }
}
}
diff --git a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueComparerTests.cs b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueComparerTests.cs
index 44d723cd9e..3ce2702ec6 100644
--- a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueComparerTests.cs
+++ b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueComparerTests.cs
@@ -18,8 +18,10 @@ namespace Microsoft.Net.Http.Headers
{
"application/*",
"text/plain",
+ "text/*+json;q=0.8",
"text/plain;q=1.0",
"text/plain",
+ "text/*+json;q=0.6",
"text/plain;q=0",
"*/*;q=0.8",
"*/*;q=1",
@@ -27,6 +29,7 @@ namespace Microsoft.Net.Http.Headers
"text/plain;q=0.8",
"text/*;q=0.8",
"text/*;q=0.6",
+ "text/*+json;q=0.4",
"text/*;q=1.0",
"*/*;q=0.4",
"text/plain;q=0.6",
@@ -43,10 +46,13 @@ namespace Microsoft.Net.Http.Headers
"text/*;q=1.0",
"*/*;q=1",
"text/plain;q=0.8",
+ "text/*+json;q=0.8",
"text/*;q=0.8",
"*/*;q=0.8",
"text/plain;q=0.6",
+ "text/*+json;q=0.6",
"text/*;q=0.6",
+ "text/*+json;q=0.4",
"*/*;q=0.4",
"text/plain;q=0",
}
diff --git a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs
index 1d5c9e9f65..75cccabc9c 100644
--- a/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs
+++ b/test/Microsoft.Net.Http.Headers.Tests/MediaTypeHeaderValueTest.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Microsoft.Extensions.Primitives;
using Xunit;
namespace Microsoft.Net.Http.Headers
@@ -39,6 +40,68 @@ namespace Microsoft.Net.Http.Headers
AssertFormatException("text/plain;charset=utf-8"); // ctor takes only media-type name, no parameters
}
+ public static TheoryData MediaTypesWithSuffixes =>
+ new TheoryData
+ {
+ // See https://tools.ietf.org/html/rfc6838#section-4.2 for allowed names spec
+ { "application/json", "json", null },
+ { "application/json+", "json", "" },
+ { "application/+json", "", "json" },
+ { "application/entitytype+json", "entitytype", "json" },
+ { "applica+tion/entitytype+json", "entitytype", "json" },
+ };
+
+ [Theory]
+ [MemberData(nameof(MediaTypesWithSuffixes))]
+ public void Ctor_CanParseSuffixedMediaTypes(string mediaType, string expectedSubTypeWithoutSuffix, string expectedSubTypeSuffix)
+ {
+ var result = new MediaTypeHeaderValue(mediaType);
+
+ Assert.Equal(new StringSegment(expectedSubTypeWithoutSuffix), result.SubTypeWithoutSuffix); // TODO consider overloading to have SubTypeWithoutSuffix?
+ Assert.Equal(new StringSegment(expectedSubTypeSuffix), result.Suffix);
+ }
+
+ public static TheoryData MediaTypesWithSuffixesAndSpaces =>
+ new TheoryData
+ {
+ // See https://tools.ietf.org/html/rfc6838#section-4.2 for allowed names spec
+ { " application / json+xml", "json", "xml" },
+ { " application / vnd.com-pany.some+entity!.v2+js.#$&^_n ; q=\"0.3+1\"", "vnd.com-pany.some+entity!.v2", "js.#$&^_n"},
+ { " application/ +json", "", "json" },
+ { " application/ entitytype+json ", "entitytype", "json" },
+ { " applica+tion/ entitytype+json ", "entitytype", "json" }
+ };
+
+ [Theory]
+ [MemberData(nameof(MediaTypesWithSuffixesAndSpaces))]
+ public void Parse_CanParseSuffixedMediaTypes(string mediaType, string expectedSubTypeWithoutSuffix, string expectedSubTypeSuffix)
+ {
+ var result = MediaTypeHeaderValue.Parse(mediaType);
+
+ Assert.Equal(new StringSegment(expectedSubTypeWithoutSuffix), result.SubTypeWithoutSuffix); // TODO consider overloading to have SubTypeWithoutSuffix?
+ Assert.Equal(new StringSegment(expectedSubTypeSuffix), result.Suffix);
+ }
+
+ [Theory]
+ [InlineData("*/*", true)]
+ [InlineData("text/*", true)]
+ [InlineData("text/*+suffix", true)]
+ [InlineData("text/*+", true)]
+ [InlineData("text/*+*", true)]
+ [InlineData("text/json+suffix", false)]
+ [InlineData("*/json+*", false)]
+ public void MatchesAllSubTypesWithoutSuffix_ReturnsExpectedResult(string value, bool expectedReturnValue)
+ {
+ // Arrange
+ var mediaType = new MediaTypeHeaderValue(value);
+
+ // Act
+ var result = mediaType.MatchesAllSubTypesWithoutSuffix;
+
+ // Assert
+ Assert.Equal(expectedReturnValue, result);
+ }
+
[Fact]
public void Ctor_MediaTypeValidFormat_SuccessfullyCreated()
{
@@ -644,6 +707,8 @@ namespace Microsoft.Net.Http.Headers
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/plain;foo=bar;q=0.0;charset=utf-8")] // different order of parameters
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "text/*;charset=utf-8;foo=bar;q=0.0")]
[InlineData("text/plain;charset=utf-8;foo=bar;q=0.0", "*/*;charset=utf-8;foo=bar;q=0.0")]
+ [InlineData("application/json;v=2", "application/json;*")]
+ [InlineData("application/json;v=2;charset=utf-8", "application/json;v=2;*")]
public void IsSubsetOf_PositiveCases(string mediaType1, string mediaType2)
{
// Arrange
@@ -681,6 +746,74 @@ namespace Microsoft.Net.Http.Headers
Assert.False(isSubset);
}
+ [Theory]
+ [InlineData("application/entity+json", "application/entity+json")]
+ [InlineData("application/*+json", "application/entity+json")]
+ [InlineData("application/*+json", "application/*+json")]
+ [InlineData("application/*", "application/*+JSON")]
+ [InlineData("application/vnd.github+json", "application/vnd.github+json")]
+ [InlineData("application/*", "application/entity+JSON")]
+ [InlineData("*/*", "application/entity+json")]
+ public void IsSubsetOfWithSuffixes_PositiveCases(string set, string subset)
+ {
+ // Arrange
+ var setMediaType = MediaTypeHeaderValue.Parse(set);
+ var subSetMediaType = MediaTypeHeaderValue.Parse(subset);
+
+ // Act
+ var result = subSetMediaType.IsSubsetOf(setMediaType);
+
+ // Assert
+ Assert.True(result);
+ }
+
+ [Theory]
+ [InlineData("application/entity+json", "application/entity+txt")]
+ [InlineData("application/entity+json", "application/entity.v2+json")]
+ [InlineData("application/*+json", "application/entity+txt")]
+ [InlineData("application/*+*", "application/json")]
+ [InlineData("application/entity+*", "application/entity+json")] // We don't allow suffixes to be wildcards
+ [InlineData("application/*+*", "application/entity+json")] // We don't allow suffixes to be wildcards
+ public void IsSubSetOfWithSuffixes_NegativeCases(string set, string subset)
+ {
+ // Arrange
+ var setMediaType = MediaTypeHeaderValue.Parse(set);
+ var subSetMediaType = MediaTypeHeaderValue.Parse(subset);
+
+ // Act
+ var result = subSetMediaType.IsSubsetOf(setMediaType);
+
+ // Assert
+ Assert.False(result);
+ }
+
+ public static TheoryData> MediaTypesWithFacets =>
+ new TheoryData>
+ {
+ { "application/vdn.github",
+ new List(){ "vdn", "github" } },
+ { "application/vdn.github+json",
+ new List(){ "vdn", "github" } },
+ { "application/vdn.github.v3+json",
+ new List(){ "vdn", "github", "v3" } },
+ { "application/vdn.github.+json",
+ new List(){ "vdn", "github", "" } },
+ };
+
+ [Theory]
+ [MemberData(nameof(MediaTypesWithFacets))]
+ public void Facets_TestPositiveCases(string input, List expected)
+ {
+ // Arrange
+ var mediaType = MediaTypeHeaderValue.Parse(input);
+
+ // Act
+ var result = mediaType.Facets;
+
+ // Assert
+ Assert.Equal(expected, result);
+ }
+
private void CheckValidParse(string input, MediaTypeHeaderValue expectedResult)
{
var result = MediaTypeHeaderValue.Parse(input);