diff --git a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs
index 588e13db40..1bb82eec15 100644
--- a/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs
+++ b/src/Microsoft.AspNet.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs
@@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
public abstract class OutputFormatterCanWriteContext
{
///
- /// Gets or sets the of the content type to write to the response.
+ /// Gets or sets the content type to write to the response.
///
///
/// An can set this value when its
diff --git a/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs
index 7b44014b56..f2ce65cb80 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ConsumesAttribute.cs
@@ -68,14 +68,27 @@ namespace Microsoft.AspNet.Mvc
// Confirm the request's content type is more specific than a media type this action supports e.g. OK
// if client sent "text/plain" data and this action supports "text/*".
- if (requestContentType != null &&
- !ContentTypes.Any(contentType => MediaTypeComparisons.IsSubsetOf(contentType, requestContentType)))
+ if (requestContentType != null && !IsSubsetOfAnyContentType(requestContentType))
{
context.Result = new UnsupportedMediaTypeResult();
}
}
}
+ private bool IsSubsetOfAnyContentType(string requestMediaType)
+ {
+ var parsedRequestMediaType = new MediaType(requestMediaType);
+ for (var i = 0; i < ContentTypes.Count; i++)
+ {
+ var contentTypeMediaType = new MediaType(ContentTypes[i]);
+ if (parsedRequestMediaType.IsSubsetOf(contentTypeMediaType))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
///
public void OnResourceExecuted(ResourceExecutedContext context)
{
@@ -111,9 +124,9 @@ namespace Microsoft.AspNet.Mvc
return !isActionWithoutConsumeConstraintPresent;
}
- // Confirm the request's content type is more specific than a media type this action supports e.g. OK
+ // Confirm the request's content type is more specific than (a media type this action supports e.g. OK
// if client sent "text/plain" data and this action supports "text/*".
- if (ContentTypes.Any(contentType => MediaTypeComparisons.IsSubsetOf(contentType, requestContentType)))
+ if (IsSubsetOfAnyContentType(requestContentType))
{
return true;
}
@@ -181,8 +194,9 @@ namespace Microsoft.AspNet.Mvc
var contentTypes = new MediaTypeCollection();
foreach (var arg in completeArgs)
{
- if (MediaTypeComparisons.MatchesAllSubtypes(arg) ||
- MediaTypeComparisons.MatchesAllTypes(arg))
+ var mediaType = new MediaType(arg);
+ if (mediaType.MatchesAllSubTypes ||
+ mediaType.MatchesAllTypes)
{
throw new InvalidOperationException(
Resources.FormatMatchAllContentTypeIsNotAllowed(arg));
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/AcceptHeaderParser.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/AcceptHeaderParser.cs
new file mode 100644
index 0000000000..bda6fc6988
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/AcceptHeaderParser.cs
@@ -0,0 +1,153 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using Microsoft.AspNet.Mvc.Core;
+
+namespace Microsoft.AspNet.Mvc.Formatters
+{
+ public static class AcceptHeaderParser
+ {
+ public static IList ParseAcceptHeader(IList acceptHeaders)
+ {
+ var parsedValues = new List();
+ ParseAcceptHeader(acceptHeaders, parsedValues);
+
+ return parsedValues;
+ }
+
+ public static void ParseAcceptHeader(IList acceptHeaders, IList parsedValues)
+ {
+ if (acceptHeaders == null)
+ {
+ throw new ArgumentNullException(nameof(acceptHeaders));
+ }
+
+ if (parsedValues == null)
+ {
+ throw new ArgumentNullException(nameof(parsedValues));
+ }
+
+ for (var i = 0; i < acceptHeaders.Count; i++)
+ {
+ var charIndex = 0;
+ var value = acceptHeaders[i];
+
+ while (!string.IsNullOrEmpty(value) && charIndex < value.Length)
+ {
+ MediaTypeSegmentWithQuality output;
+ if (TryParseValue(value, ref charIndex, out output))
+ {
+ // The entry may not contain an actual value, like Accept: application/json, , */*
+ if (output.MediaType.HasValue)
+ {
+ parsedValues.Add(output);
+ }
+ }
+ else
+ {
+ var invalidValuesError = Resources.FormatAcceptHeaderParser_ParseAcceptHeader_InvalidValues(
+ value.Substring(charIndex));
+
+ throw new FormatException(invalidValuesError);
+ }
+ }
+ }
+ }
+
+ private static bool TryParseValue(string value, ref int index, out MediaTypeSegmentWithQuality parsedValue)
+ {
+ parsedValue = default(MediaTypeSegmentWithQuality);
+
+ // The accept header may be added multiple times to the request/response message. E.g.
+ // Accept: text/xml; q=1
+ // Accept:
+ // Accept: text/plain; q=0.2
+ // In this case, do not fail parsing in case one of the values is the empty string.
+ if (string.IsNullOrEmpty(value) || (index == value.Length))
+ {
+ return true;
+ }
+
+ var separatorFound = false;
+ var currentIndex = GetNextNonEmptyOrWhitespaceIndex(value, index, out separatorFound);
+
+ if (currentIndex == value.Length)
+ {
+ index = currentIndex;
+ return true;
+ }
+
+ MediaTypeSegmentWithQuality result;
+ var length = GetMediaTypeWithQualityLength(value, currentIndex, out result);
+
+ if (length == 0)
+ {
+ return false;
+ }
+
+ currentIndex = currentIndex + length;
+ currentIndex = GetNextNonEmptyOrWhitespaceIndex(value, currentIndex, out separatorFound);
+
+ // If we've not reached the end of the string, then we must have a separator.
+ // E. g application/json, text/plain <- We must be at ',' otherwise, we've failed parsing.
+ if (!separatorFound && (currentIndex < value.Length))
+ {
+ return false;
+ }
+
+ index = currentIndex;
+ parsedValue = result;
+ return true;
+ }
+
+ private static int GetNextNonEmptyOrWhitespaceIndex(
+ string input,
+ int startIndex,
+ out bool separatorFound)
+ {
+ Debug.Assert(input != null);
+ Debug.Assert(startIndex <= input.Length); // it's OK if index == value.Length.
+
+ separatorFound = false;
+ var current = startIndex + HttpTokenParsingRules.GetWhitespaceLength(input, startIndex);
+
+ if ((current == input.Length) || (input[current] != ','))
+ {
+ return current;
+ }
+
+ // If we have a separator, skip the separator and all following whitespaces, and
+ // continue until the current character is neither a separator nor a whitespace.
+ separatorFound = true;
+ current++; // skip delimiter.
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+
+ while ((current < input.Length) && (input[current] == ','))
+ {
+ current++; // skip delimiter.
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+ }
+
+ return current;
+ }
+
+ private static int GetMediaTypeWithQualityLength(
+ string input,
+ int start,
+ out MediaTypeSegmentWithQuality result)
+ {
+ result = MediaType.CreateMediaTypeSegmentWithQuality(input, start);
+ if (result.MediaType.HasValue)
+ {
+ return result.MediaType.Length;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs
index b64bc64d9f..199aa55fc1 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/FormatFilter.cs
@@ -89,13 +89,28 @@ namespace Microsoft.AspNet.Mvc.Formatters
// request's format and IApiResponseMetadataProvider-provided content types similarly to an Accept
// header and an output formatter's SupportedMediaTypes: Confirm action supports a more specific media
// type than requested e.g. OK if "text/*" requested and action supports "text/plain".
- if (!supportedMediaTypes.Any(c => MediaTypeComparisons.IsSubsetOf(contentType, c)))
+ if (!IsSuperSetOfAnySupportedMediaType(contentType, supportedMediaTypes))
{
context.Result = new HttpNotFoundResult();
}
}
}
+ private bool IsSuperSetOfAnySupportedMediaType(string contentType, MediaTypeCollection supportedMediaTypes)
+ {
+ var parsedContentType = new MediaType(contentType);
+ for (var i = 0; i < supportedMediaTypes.Count; i++)
+ {
+ var supportedMediaType = new MediaType(supportedMediaTypes[i]);
+ if (supportedMediaType.IsSubsetOf(parsedContentType))
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
///
public void OnResourceExecuted(ResourceExecutedContext context)
{
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpParseResult.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpParseResult.cs
new file mode 100644
index 0000000000..86a4fec97c
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpParseResult.cs
@@ -0,0 +1,12 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+namespace Microsoft.AspNet.Mvc.Formatters
+{
+ internal enum HttpParseResult
+ {
+ Parsed,
+ NotParsed,
+ InvalidFormat,
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpTokenParsingRules.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpTokenParsingRules.cs
new file mode 100644
index 0000000000..10f9ec0844
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/HttpTokenParsingRules.cs
@@ -0,0 +1,273 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics.Contracts;
+using System.Globalization;
+using System.Text;
+
+namespace Microsoft.AspNet.Mvc.Formatters
+{
+ internal static class HttpTokenParsingRules
+ {
+ private static readonly bool[] TokenChars;
+ private const int MaxNestedCount = 5;
+
+ internal const char CR = '\r';
+ internal const char LF = '\n';
+ internal const char SP = ' ';
+ internal const char Tab = '\t';
+ internal const int MaxInt64Digits = 19;
+ internal const int MaxInt32Digits = 10;
+
+ // iso-8859-1, Western European (ISO)
+ internal static readonly Encoding DefaultHttpEncoding = Encoding.GetEncoding(28591);
+
+ static HttpTokenParsingRules()
+ {
+ // token = 1*
+ // CTL =
+
+ TokenChars = new bool[128]; // everything is false
+
+ for (int i = 33; i < 127; i++) // skip Space (32) & DEL (127)
+ {
+ TokenChars[i] = true;
+ }
+
+ // remove separators: these are not valid token characters
+ TokenChars[(byte)'('] = false;
+ TokenChars[(byte)')'] = false;
+ TokenChars[(byte)'<'] = false;
+ TokenChars[(byte)'>'] = false;
+ TokenChars[(byte)'@'] = false;
+ TokenChars[(byte)','] = false;
+ TokenChars[(byte)';'] = false;
+ TokenChars[(byte)':'] = false;
+ TokenChars[(byte)'\\'] = false;
+ TokenChars[(byte)'"'] = false;
+ TokenChars[(byte)'/'] = false;
+ TokenChars[(byte)'['] = false;
+ TokenChars[(byte)']'] = false;
+ TokenChars[(byte)'?'] = false;
+ TokenChars[(byte)'='] = false;
+ TokenChars[(byte)'{'] = false;
+ TokenChars[(byte)'}'] = false;
+ }
+
+ internal static bool IsTokenChar(char character)
+ {
+ // Must be between 'space' (32) and 'DEL' (127)
+ if (character > 127)
+ {
+ return false;
+ }
+
+ return TokenChars[character];
+ }
+
+ internal static int GetTokenLength(string input, int startIndex)
+ {
+ Contract.Requires(input != null);
+ Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex)));
+
+ if (startIndex >= input.Length)
+ {
+ return 0;
+ }
+
+ var current = startIndex;
+
+ while (current < input.Length)
+ {
+ if (!IsTokenChar(input[current]))
+ {
+ return current - startIndex;
+ }
+ current++;
+ }
+ return input.Length - startIndex;
+ }
+
+ internal static int GetWhitespaceLength(string input, int startIndex)
+ {
+ Contract.Requires(input != null);
+ Contract.Ensures((Contract.Result() >= 0) && (Contract.Result() <= (input.Length - startIndex)));
+
+ if (startIndex >= input.Length)
+ {
+ return 0;
+ }
+
+ var current = startIndex;
+
+ char c;
+ while (current < input.Length)
+ {
+ c = input[current];
+
+ if ((c == SP) || (c == Tab))
+ {
+ current++;
+ continue;
+ }
+
+ if (c == CR)
+ {
+ // If we have a #13 char, it must be followed by #10 and then at least one SP or HT.
+ if ((current + 2 < input.Length) && (input[current + 1] == LF))
+ {
+ char spaceOrTab = input[current + 2];
+ if ((spaceOrTab == SP) || (spaceOrTab == Tab))
+ {
+ current += 3;
+ continue;
+ }
+ }
+ }
+
+ return current - startIndex;
+ }
+
+ // All characters between startIndex and the end of the string are LWS characters.
+ return input.Length - startIndex;
+ }
+
+ internal static HttpParseResult GetQuotedStringLength(string input, int startIndex, out int length)
+ {
+ var nestedCount = 0;
+ return GetExpressionLength(input, startIndex, '"', '"', false, ref nestedCount, out length);
+ }
+
+ // quoted-pair = "\" CHAR
+ // CHAR =
+ internal static HttpParseResult GetQuotedPairLength(string input, int startIndex, out int length)
+ {
+ Contract.Requires(input != null);
+ Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
+ Contract.Ensures((Contract.ValueAtReturn(out length) >= 0) &&
+ (Contract.ValueAtReturn(out length) <= (input.Length - startIndex)));
+
+ length = 0;
+
+ if (input[startIndex] != '\\')
+ {
+ return HttpParseResult.NotParsed;
+ }
+
+ // Quoted-char has 2 characters. Check wheter there are 2 chars left ('\' + char)
+ // If so, check whether the character is in the range 0-127. If not, it's an invalid value.
+ if ((startIndex + 2 > input.Length) || (input[startIndex + 1] > 127))
+ {
+ return HttpParseResult.InvalidFormat;
+ }
+
+ // We don't care what the char next to '\' is.
+ length = 2;
+ return HttpParseResult.Parsed;
+ }
+
+ // TEXT =
+ // LWS = [CRLF] 1*( SP | HT )
+ // CTL =
+ //
+ // Since we don't really care about the content of a quoted string or comment, we're more tolerant and
+ // allow these characters. We only want to find the delimiters ('"' for quoted string and '(', ')' for comment).
+ //
+ // 'nestedCount': Comments can be nested. We allow a depth of up to 5 nested comments, i.e. something like
+ // "(((((comment)))))". If we wouldn't define a limit an attacker could send a comment with hundreds of nested
+ // comments, resulting in a stack overflow exception. In addition having more than 1 nested comment (if any)
+ // is unusual.
+ private static HttpParseResult GetExpressionLength(
+ string input,
+ int startIndex,
+ char openChar,
+ char closeChar,
+ bool supportsNesting,
+ ref int nestedCount,
+ out int length)
+ {
+ Contract.Requires(input != null);
+ Contract.Requires((startIndex >= 0) && (startIndex < input.Length));
+ Contract.Ensures((Contract.Result() != HttpParseResult.Parsed) ||
+ (Contract.ValueAtReturn(out length) > 0));
+
+ length = 0;
+
+ if (input[startIndex] != openChar)
+ {
+ return HttpParseResult.NotParsed;
+ }
+
+ var current = startIndex + 1; // Start parsing with the character next to the first open-char
+ while (current < input.Length)
+ {
+ // Only check whether we have a quoted char, if we have at least 3 characters left to read (i.e.
+ // quoted char + closing char). Otherwise the closing char may be considered part of the quoted char.
+ var quotedPairLength = 0;
+ if ((current + 2 < input.Length) &&
+ (GetQuotedPairLength(input, current, out quotedPairLength) == HttpParseResult.Parsed))
+ {
+ // We ignore invalid quoted-pairs. Invalid quoted-pairs may mean that it looked like a quoted pair,
+ // but we actually have a quoted-string: e.g. "\ü" ('\' followed by a char >127 - quoted-pair only
+ // allows ASCII chars after '\'; qdtext allows both '\' and >127 chars).
+ current = current + quotedPairLength;
+ continue;
+ }
+
+ // If we support nested expressions and we find an open-char, then parse the nested expressions.
+ if (supportsNesting && (input[current] == openChar))
+ {
+ nestedCount++;
+ try
+ {
+ // Check if we exceeded the number of nested calls.
+ if (nestedCount > MaxNestedCount)
+ {
+ return HttpParseResult.InvalidFormat;
+ }
+
+ var nestedLength = 0;
+ HttpParseResult nestedResult = GetExpressionLength(input, current, openChar, closeChar,
+ supportsNesting, ref nestedCount, out nestedLength);
+
+ switch (nestedResult)
+ {
+ case HttpParseResult.Parsed:
+ current += nestedLength; // add the length of the nested expression and continue.
+ break;
+
+ case HttpParseResult.NotParsed:
+ Contract.Assert(false, "'NotParsed' is unexpected: We started nested expression " +
+ "parsing, because we found the open-char. So either it's a valid nested " +
+ "expression or it has invalid format.");
+ break;
+
+ case HttpParseResult.InvalidFormat:
+ // If the nested expression is invalid, we can't continue, so we fail with invalid format.
+ return HttpParseResult.InvalidFormat;
+
+ default:
+ Contract.Assert(false, "Unknown enum result: " + nestedResult);
+ break;
+ }
+ }
+ finally
+ {
+ nestedCount--;
+ }
+ }
+
+ if (input[current] == closeChar)
+ {
+ length = current - startIndex + 1;
+ return HttpParseResult.Parsed;
+ }
+ current++;
+ }
+
+ // We didn't see the final quote, therefore we have an invalid expression string.
+ return HttpParseResult.InvalidFormat;
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
index 7330c0ef9d..751ff68729 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/InputFormatter.cs
@@ -69,10 +69,21 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Confirm the request's content type is more specific than a media type this formatter supports e.g. OK if
// client sent "text/plain" data and this formatter supports "text/*".
- return SupportedMediaTypes.Any(supportedMediaType =>
+ return IsSubsetOfAnySupportedContentType(contentType);
+ }
+
+ private bool IsSubsetOfAnySupportedContentType(string contentType)
+ {
+ var parsedContentType = new MediaType(contentType);
+ for (var i = 0; i < SupportedMediaTypes.Count; i++)
{
- return MediaTypeComparisons.IsSubsetOf(supportedMediaType, contentType);
- });
+ var supportedMediaType = new MediaType(SupportedMediaTypes[i]);
+ if (parsedContentType.IsSubsetOf(supportedMediaType))
+ {
+ return true;
+ }
+ }
+ return false;
}
///
@@ -120,7 +131,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
if (request.ContentType != null)
{
- var encoding = MediaTypeEncoding.GetEncoding(request.ContentType);
+ var encoding = MediaType.GetEncoding(request.ContentType);
if (encoding != null)
{
foreach (var supportedEncoding in SupportedEncodings)
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaType.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaType.cs
new file mode 100644
index 0000000000..3bf928958d
--- /dev/null
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaType.cs
@@ -0,0 +1,551 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Globalization;
+using System.Text;
+using Microsoft.Extensions.Internal;
+using Microsoft.Extensions.Primitives;
+
+namespace Microsoft.AspNet.Mvc.Formatters
+{
+ ///
+ /// A media type value.
+ ///
+ public struct MediaType
+ {
+ private static readonly StringSegment QualityParameter = new StringSegment("q");
+
+ private MediaTypeParameterParser _parameterParser;
+
+ ///
+ /// Initializes a instance.
+ ///
+ /// The with the media type.
+ public MediaType(string mediaType)
+ : this(mediaType, 0, mediaType.Length)
+ {
+ }
+
+ ///
+ /// Initializes a instance.
+ ///
+ /// The with the media type.
+ public MediaType(StringSegment mediaType)
+ : this(mediaType.Buffer, mediaType.Offset, mediaType.Length)
+ {
+ }
+
+ ///
+ /// Initializes a instance.
+ ///
+ /// The with the media type.
+ /// The offset in the where the parsing starts.
+ /// The of the media type to parse if provided.
+ public MediaType(string mediaType, int offset, int? length)
+ {
+ if (mediaType == null)
+ {
+ throw new ArgumentNullException(nameof(mediaType));
+ }
+
+ if (offset < 0 || offset >= mediaType.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(offset));
+ }
+
+ if (length != null && offset + length > mediaType.Length)
+ {
+ throw new ArgumentOutOfRangeException(nameof(length));
+ }
+
+ _parameterParser = default(MediaTypeParameterParser);
+
+ StringSegment type;
+ var typeLength = GetTypeLength(mediaType, offset, out type);
+ if (typeLength == 0)
+ {
+ Type = new StringSegment();
+ SubType = new StringSegment();
+ return;
+ }
+ else
+ {
+ Type = type;
+ }
+
+ StringSegment subType;
+ var subTypeLength = GetSubtypeLength(mediaType, offset + typeLength, out subType);
+ if (subTypeLength == 0)
+ {
+ SubType = new StringSegment();
+ return;
+ }
+ else
+ {
+ SubType = subType;
+ }
+
+ _parameterParser = new MediaTypeParameterParser(mediaType, offset + typeLength + subTypeLength, length);
+ }
+
+ // All GetXXXLength methods work in the same way. They expect to be on the right position for
+ // the token they are parsing, for example, the beginning of the media type or the delimiter
+ // from a previous token, like '/', ';' or '='.
+ // Each method consumes the delimiter token if any, the leading whitespace, then the given token
+ // itself, and finally the trailing whitespace.
+ private static int GetTypeLength(string input, int offset, out StringSegment type)
+ {
+ if (offset < 0 || offset >= input.Length)
+ {
+ type = default(StringSegment);
+ return 0;
+ }
+
+ // Parse the type, i.e. in media type string "/; param1=value1; param2=value2"
+ var typeLength = HttpTokenParsingRules.GetTokenLength(input, offset);
+
+ if (typeLength == 0)
+ {
+ type = default(StringSegment);
+ return 0;
+ }
+
+ type = new StringSegment(input, offset, typeLength);
+
+ var current = offset + typeLength;
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+
+ return current - offset;
+ }
+
+ private static int GetSubtypeLength(string input, int offset, out StringSegment subType)
+ {
+ var current = offset;
+ // Parse the separator between type and subtype
+ if (current < 0 || current >= input.Length || input[current] != '/')
+ {
+ subType = default(StringSegment);
+ return 0;
+ }
+
+ current++; // skip delimiter.
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+
+ var subtypeLength = HttpTokenParsingRules.GetTokenLength(input, current);
+
+ if (subtypeLength == 0)
+ {
+ subType = default(StringSegment);
+ return 0;
+ }
+
+ subType = new StringSegment(input, current, subtypeLength);
+
+ current = current + subtypeLength;
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+
+ return current - offset;
+ }
+
+ ///
+ /// Gets the type of the .
+ ///
+ public StringSegment Type { get; }
+
+ ///
+ /// Gets whether this matches all types.
+ ///
+ public bool MatchesAllTypes => Type.Equals("*", StringComparison.OrdinalIgnoreCase);
+
+ ///
+ /// Gets the subtype of the .
+ ///
+ public StringSegment SubType { get; private set; }
+
+ ///
+ /// Gets whether this matches all subtypes.
+ ///
+ public bool MatchesAllSubTypes => SubType.Equals("*", StringComparison.OrdinalIgnoreCase);
+
+ ///
+ /// Gets the of the if it has one.
+ ///
+ public Encoding Encoding => GetEncodingFromCharset(GetParameter("charset"));
+
+ ///
+ /// Gets the charset parameter of the if it has one.
+ ///
+ public StringSegment Charset => GetParameter("charset");
+
+ ///
+ /// Determines whether the current is a subset of the .
+ ///
+ /// The set .
+ ///
+ /// true if this is a subset of ; otherwisefalse.
+ ///
+ public bool IsSubsetOf(MediaType set)
+ {
+ return (set.MatchesAllTypes || set.Type.Equals(Type, StringComparison.OrdinalIgnoreCase)) &&
+ (set.MatchesAllSubTypes || set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase)) &&
+ ContainsAllParameters(set._parameterParser);
+ }
+
+ ///
+ /// Gets the parameter of the media type.
+ ///
+ /// The name of the parameter to retrieve.
+ /// The for the given if found; otherwisenull.
+ public StringSegment GetParameter(string parameterName)
+ {
+ return GetParameter(new StringSegment(parameterName));
+ }
+
+ ///
+ /// Gets the parameter of the media type.
+ ///
+ /// The name of the parameter to retrieve.
+ /// The for the given if found; otherwisenull.
+ public StringSegment GetParameter(StringSegment parameterName)
+ {
+ var parametersParser = _parameterParser;
+
+ MediaTypeParameter parameter;
+ while (parametersParser.ParseNextParameter(out parameter))
+ {
+ if (parameter.HasName(parameterName))
+ {
+ return parameter.Value;
+ }
+ }
+
+ return new StringSegment();
+ }
+
+ ///
+ /// Replaces the encoding of the given with the provided
+ /// .
+ ///
+ /// The media type whose encoding will be replaced.
+ /// The encoding that will replace the encoding in the
+ /// A media type with the replaced encoding.
+ public static string ReplaceEncoding(string mediaType, Encoding encoding)
+ {
+ return ReplaceEncoding(new StringSegment(mediaType), encoding);
+ }
+
+ ///
+ /// Replaces the encoding of the given with the provided
+ /// .
+ ///
+ /// The media type whose encoding will be replaced.
+ /// The encoding that will replace the encoding in the
+ /// A media type with the replaced encoding.
+ public static string ReplaceEncoding(StringSegment mediaType, Encoding encoding)
+ {
+ var parsedMediaType = new MediaType(mediaType);
+ var charset = parsedMediaType.GetParameter("charset");
+
+ if (charset.HasValue &&
+ charset.Equals(encoding.WebName, StringComparison.OrdinalIgnoreCase))
+ {
+ return mediaType.Value;
+ }
+
+ if (!charset.HasValue)
+ {
+ return CreateMediaTypeWithEncoding(mediaType, encoding);
+ }
+
+ var charsetOffset = charset.Offset - mediaType.Offset;
+ var restOffset = charsetOffset + charset.Length;
+ var restLength = mediaType.Length - restOffset;
+ var finalLength = charsetOffset + encoding.WebName.Length + restLength;
+
+ var builder = new StringBuilder(mediaType.Buffer, mediaType.Offset, charsetOffset, finalLength);
+ builder.Append(encoding.WebName);
+ builder.Append(mediaType.Buffer, restOffset, restLength);
+
+ return builder.ToString();
+ }
+
+ public static Encoding GetEncoding(string mediaType)
+ {
+ return GetEncoding(new StringSegment(mediaType));
+ }
+
+ public static Encoding GetEncoding(StringSegment mediaType)
+ {
+ var parsedMediaType = new MediaType(mediaType);
+ return parsedMediaType.Encoding;
+ }
+
+ ///
+ /// Creates an containing the media type in
+ /// and its associated quality.
+ ///
+ /// The media type to parse.
+ /// The position at which the parsing starts.
+ /// The parsed media type with its associated quality.
+ public static MediaTypeSegmentWithQuality CreateMediaTypeSegmentWithQuality(string mediaType, int start)
+ {
+ var parsedMediaType = new MediaType(mediaType, start, length: null);
+ var parser = parsedMediaType._parameterParser;
+
+ double quality = 1.0d;
+ MediaTypeParameter parameter;
+ while (parser.ParseNextParameter(out parameter))
+ {
+ if (parameter.HasName(QualityParameter))
+ {
+ quality = double.Parse(
+ parameter.Value.Value, NumberStyles.AllowDecimalPoint,
+ NumberFormatInfo.InvariantInfo);
+ }
+ }
+
+ // We check if the parsed media type has value at this stage when we have iterated
+ // over all the parameters and we know if the parsing was sucessful.
+ if (!parser.ParsingFailed)
+ {
+ return new MediaTypeSegmentWithQuality(
+ new StringSegment(mediaType, start, parser.CurrentOffset - start),
+ quality);
+ }
+ else
+ {
+ return default(MediaTypeSegmentWithQuality);
+ }
+ }
+
+ private static Encoding GetEncodingFromCharset(StringSegment charset)
+ {
+ if (charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase))
+ {
+ // This is an optimization for utf-8 that prevents the Substring caused by
+ // charset.Value
+ return Encoding.UTF8;
+ }
+
+ try
+ {
+ // charset.Value might be an invalid encoding name as in charset=invalid.
+ // For that reason, we catch the exception thrown by Encoding.GetEncoding
+ // and return null instead.
+ return charset.HasValue ? Encoding.GetEncoding(charset.Value) : null;
+ }
+ catch (Exception)
+ {
+ return null;
+ }
+ }
+
+ private static string CreateMediaTypeWithEncoding(StringSegment mediaType, Encoding encoding)
+ {
+ return $"{mediaType.Value}; charset={encoding.WebName}";
+ }
+
+ private bool ContainsAllParameters(MediaTypeParameterParser setParameters)
+ {
+ var parameterFound = true;
+ MediaTypeParameter setParameter;
+ while (setParameters.ParseNextParameter(out setParameter) && parameterFound)
+ {
+ if (setParameter.HasName("q"))
+ {
+ // "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;
+ }
+
+ // Copy the parser as we need to iterate multiple times over it.
+ // We can do this because it's a struct
+ var subSetParameters = _parameterParser;
+ parameterFound = false;
+ MediaTypeParameter subSetParameter;
+ while (subSetParameters.ParseNextParameter(out subSetParameter) && !parameterFound)
+ {
+ parameterFound = subSetParameter.Equals(setParameter);
+ }
+ }
+
+ return parameterFound;
+ }
+
+ private struct MediaTypeParameterParser
+ {
+ private string _mediaTypeBuffer;
+ private int? _length;
+
+ public MediaTypeParameterParser(string mediaTypeBuffer, int offset, int? length)
+ {
+ _mediaTypeBuffer = mediaTypeBuffer;
+ _length = length;
+ CurrentOffset = offset;
+ ParsingFailed = false;
+ }
+
+ public int CurrentOffset { get; private set; }
+
+ public bool ParsingFailed { get; private set; }
+
+ public bool ParseNextParameter(out MediaTypeParameter result)
+ {
+ if (_mediaTypeBuffer == null)
+ {
+ result = default(MediaTypeParameter);
+ return false;
+ }
+
+ var parameterLength = GetParameterLength(_mediaTypeBuffer, CurrentOffset, out result);
+ CurrentOffset = CurrentOffset + parameterLength;
+
+ if (parameterLength == 0)
+ {
+ ParsingFailed = _length != null && CurrentOffset < _length;
+ return false;
+ }
+
+ return true;
+ }
+
+ private static int GetParameterLength(string input, int startIndex, out MediaTypeParameter parsedValue)
+ {
+ if (OffsetIsOutOfRange(startIndex, input.Length) ||
+ input[startIndex] != ';')
+ {
+ parsedValue = default(MediaTypeParameter);
+ return 0;
+ }
+
+ StringSegment name;
+ var nameLength = GetNameLength(input, startIndex, out name);
+
+ var current = startIndex + nameLength;
+
+ if (nameLength == 0 || OffsetIsOutOfRange(current, input.Length) || input[current] != '=')
+ {
+ parsedValue = default(MediaTypeParameter);
+ return 0;
+ }
+
+ StringSegment value;
+ var valueLength = GetValueLength(input, current, out value);
+
+ parsedValue = new MediaTypeParameter(name, value);
+
+ current = current + valueLength;
+ return current - startIndex;
+ }
+
+ private static int GetNameLength(string input, int startIndex, out StringSegment name)
+ {
+ var current = startIndex;
+
+ current++; // skip ';'
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+
+ var nameLength = HttpTokenParsingRules.GetTokenLength(input, current);
+
+ if (nameLength == 0)
+ {
+ name = default(StringSegment);
+ return 0;
+ }
+
+ name = new StringSegment(input, current, nameLength);
+
+ current = current + nameLength;
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+ return current - startIndex;
+ }
+
+ private static int GetValueLength(string input, int startIndex, out StringSegment value)
+ {
+ var current = startIndex;
+
+ current++; // skip '='.
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+
+ var valueLength = HttpTokenParsingRules.GetTokenLength(input, current);
+
+ if (valueLength == 0)
+ {
+ // A value can either be a token or a quoted string. Check if it is a quoted string.
+ if (HttpTokenParsingRules.GetQuotedStringLength(input, current, out valueLength) != HttpParseResult.Parsed)
+ {
+ // We have an invalid value. Reset the name and return.
+ value = default(StringSegment);
+ return 0;
+ }
+ }
+
+ value = new StringSegment(input, current, valueLength);
+
+ current = current + valueLength;
+ current = current + HttpTokenParsingRules.GetWhitespaceLength(input, current);
+
+ return current - startIndex;
+ }
+
+ private static bool OffsetIsOutOfRange(int offset, int length)
+ {
+ return offset < 0 || offset >= length;
+ }
+ }
+
+ private struct MediaTypeParameter : IEquatable
+ {
+ public static readonly StringSegment Type = new StringSegment("type");
+ public static readonly StringSegment Subtype = new StringSegment("subtype");
+
+ public MediaTypeParameter(StringSegment name, StringSegment value)
+ {
+ Name = name;
+ Value = value;
+ }
+
+ public StringSegment Name { get; }
+
+ public StringSegment Value { get; }
+
+ public bool HasName(string name)
+ {
+ return HasName(new StringSegment(name));
+ }
+
+ public bool HasName(StringSegment name)
+ {
+ return Name.Equals(name, StringComparison.OrdinalIgnoreCase);
+ }
+
+ public bool Equals(MediaTypeParameter other)
+ {
+ return HasName(other.Name) &&
+ Value.Equals(other.Value, StringComparison.OrdinalIgnoreCase);
+ }
+
+ ///
+ public override bool Equals(object obj)
+ {
+ if (ReferenceEquals(null, obj))
+ {
+ return false;
+ }
+
+ return obj is MediaTypeParameter && Equals((MediaTypeParameter)obj);
+ }
+
+ public override int GetHashCode()
+ {
+ HashCodeCombiner hashCode = HashCodeCombiner.Start();
+ hashCode.Add(Name.Value);
+ hashCode.Add(Value.Value);
+ return hashCode;
+ }
+
+ public override string ToString() => $"{Name}={Value}";
+ }
+ }
+}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeComparisons.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeComparisons.cs
deleted file mode 100644
index 33775d5ff4..0000000000
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeComparisons.cs
+++ /dev/null
@@ -1,120 +0,0 @@
-// Copyright (c) .NET Foundation. All rights reserved.
-// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
-
-using System;
-using Microsoft.Extensions.Primitives;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNet.Mvc.Formatters
-{
- ///
- /// Different types of tests against media type values.
- ///
- public static class MediaTypeComparisons
- {
- ///
- /// Determines if the media type is a subset of the media type
- /// without taking into account the quality parameter.
- ///
- /// The more general media type.
- /// The more specific media type.
- /// true if is a more general media type than ;
- /// otherwise false.
- public static bool IsSubsetOf(StringSegment set, string subset)
- {
- return IsSubsetOf(set, new StringSegment(subset));
- }
-
- ///
- /// Determines if the media type is a subset of the media type
- /// without taking into account the quality parameter.
- ///
- /// The more general media type.
- /// The more specific media type.
- /// true if is a more general media type than ;
- /// otherwise false.
- public static bool IsSubsetOf(string set, string subset)
- {
- return IsSubsetOf(new StringSegment(set), new StringSegment(subset));
- }
-
- ///
- /// Determines if the media type is a subset of the media type.
- /// Two media types are compatible if one is a subset of the other ignoring any charset
- /// parameter.
- ///
- /// The more general media type.
- /// The more specific media type.
- /// Whether or not we should skip checking the quality parameter.
- /// true if is a more general media type than ;
- /// otherwise false.
- public static bool IsSubsetOf(StringSegment set, StringSegment subset)
- {
- if (!set.HasValue || !subset.HasValue)
- {
- return false;
- }
-
- MediaTypeHeaderValue setMediaType;
- MediaTypeHeaderValue subSetMediaType;
-
- return MediaTypeHeaderValue.TryParse(set.Value, out setMediaType) &&
- MediaTypeHeaderValue.TryParse(subset.Value, out subSetMediaType) &&
- subSetMediaType.IsSubsetOf(setMediaType);
- }
-
- ///
- /// Determines if the type of a given matches all types, E.g, */*.
- ///
- /// The media type to check
- /// true if the matches all subtypes; otherwise false.
- public static bool MatchesAllTypes(string mediaType)
- {
- return MatchesAllTypes(new StringSegment(mediaType));
- }
-
- ///
- /// Determines if the type of a given matches all types, E.g, */*.
- ///
- /// The media type to check
- /// true if the matches all subtypes; otherwise false.
- public static bool MatchesAllTypes(StringSegment mediaType)
- {
- if (!mediaType.HasValue)
- {
- return false;
- }
-
- MediaTypeHeaderValue parsedMediaType;
- return MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType) &&
- parsedMediaType.MatchesAllTypes;
- }
-
- ///
- /// Determines if the given matches all subtypes, E.g, text/*.
- ///
- /// The media type to check
- /// true if the matches all subtypes; otherwise false.
- public static bool MatchesAllSubtypes(string mediaType)
- {
- return MatchesAllSubtypes(new StringSegment(mediaType));
- }
-
- ///
- /// Determines if the given matches all subtypes, E.g, text/*.
- ///
- /// The media type to check
- /// true if the matches all subtypes; otherwise false.
- public static bool MatchesAllSubtypes(StringSegment mediaType)
- {
- if (!mediaType.HasValue)
- {
- return false;
- }
-
- MediaTypeHeaderValue parsedMediaType;
- return MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType) &&
- parsedMediaType.MatchesAllSubTypes;
- }
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeEncoding.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeEncoding.cs
deleted file mode 100644
index c516f92f49..0000000000
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/MediaTypeEncoding.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using System;
-using System.Text;
-using Microsoft.Extensions.Primitives;
-using Microsoft.Net.Http.Headers;
-
-namespace Microsoft.AspNet.Mvc.Formatters
-{
- ///
- /// A set of operations to manipulate the encoding of a media type value.
- ///
- public class MediaTypeEncoding
- {
- ///
- /// Gets the for the given if it exists.
- ///
- /// The media type from which to get the charset parameter.
- /// The of the media type if it exists; otherwise null.
- public static Encoding GetEncoding(StringSegment mediaType)
- {
- var charset = GetCharsetParameter(mediaType);
- return GetEncodingFromCharset(charset);
- }
-
- ///
- /// Gets the for the given if it exists.
- ///
- /// The media type from which to get the charset parameter.
- /// The of the media type if it exists or a without value if not.
- public static Encoding GetEncoding(string mediaType)
- {
- var charset = GetCharsetParameter(new StringSegment(mediaType));
- return GetEncodingFromCharset(charset);
- }
-
- ///
- /// Gets the charset parameter of the given if it exists.
- ///
- /// The media type from which to get the charset parameter.
- /// The charset of the media type if it exists or a without value if not.
- public static StringSegment GetCharsetParameter(StringSegment mediaType)
- {
- MediaTypeHeaderValue parsedMediaType;
- if (MediaTypeHeaderValue.TryParse(mediaType.Value, out parsedMediaType))
- {
- return new StringSegment(parsedMediaType.Charset);
- }
- return new StringSegment();
- }
-
- ///
- /// Replaces the encoding of the given with the provided
- /// .
- ///
- /// The media type whose encoding will be replaced.
- /// The encoding that will replace the encoding in the
- /// A media type with the replaced encoding.
- public static string ReplaceEncoding(string mediaType, Encoding encoding)
- {
- return ReplaceEncoding(new StringSegment(mediaType), encoding);
- }
-
- ///
- /// Replaces the encoding of the given with the provided
- /// .
- ///
- /// The media type whose encoding will be replaced.
- /// The encoding that will replace the encoding in the
- /// A media type with the replaced encoding.
- public static string ReplaceEncoding(StringSegment mediaType, Encoding encoding)
- {
- var parsedMediaType = MediaTypeHeaderValue.Parse(mediaType.Value);
-
- if (string.Equals(parsedMediaType.Encoding?.WebName, encoding?.WebName, StringComparison.OrdinalIgnoreCase))
- {
- return mediaType.Value;
- }
-
- parsedMediaType.Encoding = encoding;
- return parsedMediaType.ToString();
- }
-
- private static Encoding GetEncodingFromCharset(StringSegment charset)
- {
- if (charset.Equals("utf-8", StringComparison.OrdinalIgnoreCase))
- {
- // This is an optimization for utf-8 that prevents the Substring caused by
- // charset.Value
- return Encoding.UTF8;
- }
-
- try
- {
- // charset.Value might be an invalid encoding name as in charset=invalid.
- // For that reason, we catch the exception thrown by Encoding.GetEncoding
- // and return null instead.
- return charset.HasValue ? Encoding.GetEncoding(charset.Value) : null;
- }
- catch (Exception)
- {
- return null;
- }
- }
- }
-}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
index 9d538122ef..35bfd1e16a 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/OutputFormatter.cs
@@ -51,7 +51,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
var cache = new Dictionary();
foreach (var mediaType in SupportedMediaTypes)
{
- cache.Add(mediaType, MediaTypeEncoding.ReplaceEncoding(mediaType, Encoding.UTF8));
+ cache.Add(mediaType, MediaType.ReplaceEncoding(mediaType, Encoding.UTF8));
}
// Safe race condition, worst case scenario we initialize the field multiple times with dictionaries containing
@@ -93,11 +93,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
{
List mediaTypes = null;
+ var parsedContentType = new MediaType(contentType);
+
// Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*"
// requested and formatter supports "text/plain". Treat contentType like it came from an Accept header.
foreach (var mediaType in SupportedMediaTypes)
{
- if (MediaTypeComparisons.IsSubsetOf(new StringSegment(contentType), mediaType))
+ var parsedMediaType = new MediaType(mediaType);
+ if (parsedMediaType.IsSubsetOf(parsedContentType))
{
if (mediaTypes == null)
{
@@ -135,13 +138,14 @@ namespace Microsoft.AspNet.Mvc.Formatters
if (context.ContentType.HasValue)
{
- var contentTypeEncoding = MediaTypeEncoding.GetCharsetParameter(context.ContentType);
- if (contentTypeEncoding.HasValue)
+ var parsedContentType = new MediaType(context.ContentType);
+ var contentTypeCharset = parsedContentType.Charset;
+ if (contentTypeCharset.HasValue)
{
for (var i = 0; i < SupportedEncodings.Count; i++)
{
var supportedEncoding = SupportedEncodings[i];
- if (contentTypeEncoding.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
+ if (contentTypeCharset.Equals(supportedEncoding.WebName, StringComparison.OrdinalIgnoreCase))
{
// This is supported.
return SupportedEncodings[i];
@@ -186,11 +190,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
// Confirm this formatter supports a more specific media type than requested e.g. OK if "text/*"
// requested and formatter supports "text/plain". contentType is typically what we got in an Accept
// header.
- var contentType = context.ContentType;
+ var parsedContentType = new MediaType(context.ContentType);
for (var i = 0; i < SupportedMediaTypes.Count; i++)
{
- var supportedMediaType = SupportedMediaTypes[i];
- if (MediaTypeComparisons.IsSubsetOf(contentType, supportedMediaType))
+ var supportedMediaType = new MediaType(SupportedMediaTypes[i]);
+ if (supportedMediaType.IsSubsetOf(parsedContentType))
{
context.ContentType = new StringSegment(SupportedMediaTypes[i]);
return true;
@@ -272,11 +276,11 @@ namespace Microsoft.AspNet.Mvc.Formatters
{
if (string.Equals(encoding.WebName, Encoding.UTF8.WebName, StringComparison.OrdinalIgnoreCase) &&
OutputMediaTypeCache.ContainsKey(mediaType))
- {
- return OutputMediaTypeCache[mediaType];
- }
+ {
+ return OutputMediaTypeCache[mediaType];
+ }
- return MediaTypeEncoding.ReplaceEncoding(mediaType, encoding);
+ return MediaType.ReplaceEncoding(mediaType, encoding);
}
private Encoding MatchAcceptCharacterEncoding(IList acceptCharsetHeaders)
diff --git a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs
index a8d0265096..6a8bb81bee 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Formatters/StringOutputFormatter.cs
@@ -55,10 +55,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
var response = context.HttpContext.Response;
-
- return response.WriteAsync(
- valueAsString,
- MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8);
+ return response.WriteAsync(valueAsString, MediaType.GetEncoding(response.ContentType) ?? Encoding.UTF8);
}
}
}
diff --git a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs
index 2f6cee1935..c07a263ccb 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Infrastructure/ObjectResultExecutor.cs
@@ -256,19 +256,11 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
HttpRequest request)
{
var result = new List();
- var parsedHeaders = request.GetTypedHeaders().Accept;
- for (var i = 0; i < parsedHeaders?.Count; i++)
+ AcceptHeaderParser.ParseAcceptHeader(request.Headers[HeaderNames.Accept], result);
+ for (int i = 0; i < result.Count; i++)
{
- result.Add(new MediaTypeSegmentWithQuality(
- new StringSegment(parsedHeaders[i].ToString()),
- parsedHeaders[i].Quality ?? 1.0));
- }
-
- for (var i = 0; i < result.Count; i++)
- {
- if (!RespectBrowserAcceptHeader &&
- MediaTypeComparisons.MatchesAllTypes(result[i].MediaType) &&
- MediaTypeComparisons.MatchesAllSubtypes(result[i].MediaType))
+ var mediaType = new MediaType(result[i].MediaType);
+ if (!RespectBrowserAcceptHeader && mediaType.MatchesAllSubTypes && mediaType.MatchesAllTypes)
{
result.Clear();
return result;
@@ -292,9 +284,11 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
return true;
}
+ var parsedMediaType = new MediaType(mediaType);
for (int i = 0; i < acceptableMediaTypes.Count; i++)
{
- if (MediaTypeComparisons.IsSubsetOf(mediaType, acceptableMediaTypes[i]))
+ var acceptableMediaType = new MediaType(acceptableMediaTypes[i]);
+ if (acceptableMediaType.IsSubsetOf(parsedMediaType))
{
return true;
}
@@ -378,7 +372,7 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
{
var mediaType = sortedAcceptHeaders[i];
formatterContext.ContentType = mediaType.MediaType;
- for(var j = 0;j < formatters.Count;j++)
+ for (var j = 0; j < formatters.Count; j++)
{
var formatter = formatters[j];
if (formatter.CanWriteResult(formatterContext))
@@ -450,8 +444,9 @@ namespace Microsoft.AspNet.Mvc.Infrastructure
for (var i = 0; i < contentTypes.Count; i++)
{
var contentType = contentTypes[i];
- if (MediaTypeComparisons.MatchesAllTypes(contentType) ||
- MediaTypeComparisons.MatchesAllSubtypes(contentType))
+ var parsedContentType = new MediaType(contentType);
+ if (parsedContentType.MatchesAllTypes ||
+ parsedContentType.MatchesAllSubTypes)
{
var message = Resources.FormatObjectResult_MatchAllContentType(
contentType,
diff --git a/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs b/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs
index ab7b078d2f..54f3520af1 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Internal/ResponseContentTypeHelper.cs
@@ -37,14 +37,14 @@ namespace Microsoft.AspNet.Mvc.Internal
{
Debug.Assert(defaultContentType != null);
- var defaultContentTypeEncoding = MediaTypeEncoding.GetEncoding(defaultContentType);
+ var defaultContentTypeEncoding = MediaType.GetEncoding(defaultContentType);
Debug.Assert(defaultContentTypeEncoding != null);
// 1. User sets the ContentType property on the action result
if (actionResultContentType != null)
{
resolvedContentType = actionResultContentType;
- var actionResultEncoding = MediaTypeEncoding.GetEncoding(actionResultContentType);
+ var actionResultEncoding = MediaType.GetEncoding(actionResultContentType);
resolvedContentTypeEncoding = actionResultEncoding ?? defaultContentTypeEncoding;
return;
}
@@ -52,7 +52,7 @@ namespace Microsoft.AspNet.Mvc.Internal
// 2. User sets the ContentType property on the http response directly
if (!string.IsNullOrEmpty(httpResponseContentType))
{
- var mediaTypeEncoding = MediaTypeEncoding.GetEncoding(httpResponseContentType);
+ var mediaTypeEncoding = MediaType.GetEncoding(httpResponseContentType);
if (mediaTypeEncoding != null)
{
resolvedContentType = httpResponseContentType;
diff --git a/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs b/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs
index 82fdb42719..6210b5b247 100644
--- a/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/ProducesAttribute.cs
@@ -92,15 +92,15 @@ namespace Microsoft.AspNet.Mvc
var contentTypes = new MediaTypeCollection();
foreach (var arg in completeArgs)
{
- var contentType = arg;
- if (MediaTypeComparisons.MatchesAllSubtypes(contentType)||
- MediaTypeComparisons.MatchesAllTypes(contentType))
+ var contentType = new MediaType(arg);
+ if (contentType.MatchesAllTypes ||
+ contentType.MatchesAllSubTypes)
{
throw new InvalidOperationException(
Resources.FormatMatchAllContentTypeIsNotAllowed(arg));
}
- contentTypes.Add(contentType);
+ contentTypes.Add(arg);
}
return contentTypes;
diff --git a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
index 9307dd86d2..671585b669 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNet.Mvc.Core/Properties/Resources.Designer.cs
@@ -1050,6 +1050,22 @@ namespace Microsoft.AspNet.Mvc.Core
return string.Format(CultureInfo.CurrentCulture, GetString("FormatFormatterMappings_GetMediaTypeMappingForFormat_InvalidFormat"), p0);
}
+ ///
+ /// "Invalid values '{0}'."
+ ///
+ internal static string AcceptHeaderParser_ParseAcceptHeader_InvalidValues
+ {
+ get { return GetString("AcceptHeaderParser_ParseAcceptHeader_InvalidValues"); }
+ }
+
+ ///
+ /// "Invalid values '{0}'."
+ ///
+ internal static string FormatAcceptHeaderParser_ParseAcceptHeader_InvalidValues(object p0)
+ {
+ return string.Format(CultureInfo.CurrentCulture, GetString("AcceptHeaderParser_ParseAcceptHeader_InvalidValues"), p0);
+ }
+
private static string GetString(string name, params string[] formatterNames)
{
var value = _resourceManager.GetString(name);
diff --git a/src/Microsoft.AspNet.Mvc.Core/Resources.resx b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
index 946db5d5f7..9f56f1705f 100644
--- a/src/Microsoft.AspNet.Mvc.Core/Resources.resx
+++ b/src/Microsoft.AspNet.Mvc.Core/Resources.resx
@@ -322,4 +322,7 @@
The argument '{0}' is invalid. Empty or null formats are not supported.
+
+ "Invalid values '{0}'."
+
\ No newline at end of file
diff --git a/src/Microsoft.AspNet.Mvc.Core/project.json b/src/Microsoft.AspNet.Mvc.Core/project.json
index e16c385e12..6e9f7cf26e 100644
--- a/src/Microsoft.AspNet.Mvc.Core/project.json
+++ b/src/Microsoft.AspNet.Mvc.Core/project.json
@@ -26,6 +26,10 @@
"type": "build"
},
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
+ "Microsoft.Extensions.HashCodeCombiner.Sources": {
+ "type": "build",
+ "version": "1.0.0-*"
+ },
"Microsoft.Extensions.PropertyActivator.Sources": {
"version": "1.0.0-*",
"type": "build"
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs
index 6a927c3de7..1bafa87b6a 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Json/JsonOutputFormatter.cs
@@ -140,7 +140,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
var response = context.HttpContext.Response;
- var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
+ var selectedEncoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8;
using (var writer = context.WriterFactory(response.Body, selectedEncoding))
{
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs
index e17638161d..f58f5a2c60 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs
@@ -188,7 +188,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
}
var writerSettings = WriterSettings.Clone();
- writerSettings.Encoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
+ writerSettings.Encoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8;
// Wrap the object only if there is a wrapping type.
var value = context.Object;
diff --git a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs
index 414de4d775..7ead7c4c1f 100644
--- a/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs
+++ b/src/Microsoft.AspNet.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs
@@ -165,7 +165,7 @@ namespace Microsoft.AspNet.Mvc.Formatters
var response = context.HttpContext.Response;
var writerSettings = WriterSettings.Clone();
- writerSettings.Encoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
+ writerSettings.Encoding = MediaType.GetEncoding(context.ContentType) ?? Encoding.UTF8;
// Wrap the object only if there is a wrapping type.
var value = context.Object;
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs
index c00f95e894..eadfc83df8 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/ControllerBaseTest.cs
@@ -977,7 +977,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
// Assert
Assert.IsType(actualContentResult);
Assert.Equal("TestContent", actualContentResult.Content);
- Assert.Null(MediaTypeEncoding.GetEncoding(actualContentResult.ContentType));
+ Assert.Null(MediaType.GetEncoding(actualContentResult.ContentType));
Assert.Equal("text/plain", actualContentResult.ContentType.ToString());
}
@@ -993,7 +993,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
// Assert
Assert.IsType(actualContentResult);
Assert.Equal("TestContent", actualContentResult.Content);
- Assert.Same(Encoding.UTF8, MediaTypeEncoding.GetEncoding(actualContentResult.ContentType));
+ Assert.Same(Encoding.UTF8, MediaType.GetEncoding(actualContentResult.ContentType));
Assert.Equal("text/plain; charset=utf-8", actualContentResult.ContentType.ToString());
}
@@ -1026,7 +1026,7 @@ namespace Microsoft.AspNet.Mvc.Core.Test
Assert.NotNull(contentResult.ContentType);
Assert.Equal(contentType, contentResult.ContentType.ToString());
// The default encoding of ContentResult is used when this result is executed.
- Assert.Null(MediaTypeEncoding.GetEncoding(contentResult.ContentType));
+ Assert.Null(MediaType.GetEncoding(contentResult.ContentType));
}
[Fact]
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/AcceptHeaderParserTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/AcceptHeaderParserTest.cs
new file mode 100644
index 0000000000..4ef9ec7854
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/AcceptHeaderParserTest.cs
@@ -0,0 +1,69 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Collections.Generic;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Formatters
+{
+ public class AcceptHeaderParserTest
+ {
+ [Fact]
+ public void ParseAcceptHeader_ParsesSimpleHeader()
+ {
+ // Arrange
+ var header = "application/json";
+ var expected = new List
+ {
+ new MediaTypeSegmentWithQuality(new StringSegment("application/json"),1.0)
+ };
+
+ // Act
+ var parsed = AcceptHeaderParser.ParseAcceptHeader(new List { header });
+
+ // Assert
+ Assert.Equal(expected, parsed);
+ }
+
+ [Fact]
+ public void ParseAcceptHeader_ParsesSimpleHeaderWithMultipleValues()
+ {
+ // Arrange
+ var header = "application/json, application/xml;q=0.8";
+ var expected = new List
+ {
+ new MediaTypeSegmentWithQuality(new StringSegment("application/json"),1.0),
+ new MediaTypeSegmentWithQuality(new StringSegment("application/xml;q=0.8"),0.8)
+ };
+
+ // Act
+ var parsed = AcceptHeaderParser.ParseAcceptHeader(new List { header });
+
+ // Assert
+ Assert.Equal(expected, parsed);
+ foreach (var mediaType in parsed)
+ {
+ Assert.Same(header, mediaType.MediaType.Buffer);
+ }
+ }
+
+ [Fact]
+ public void ParseAcceptHeader_ParsesMultipleHeaderValues()
+ {
+ // Arrange
+ var expected = new List
+ {
+ new MediaTypeSegmentWithQuality(new StringSegment("application/json"),1.0),
+ new MediaTypeSegmentWithQuality(new StringSegment("application/xml;q=0.8"),0.8)
+ };
+
+ // Act
+ var parsed = AcceptHeaderParser.ParseAcceptHeader(
+ new List { "application/json", "", "application/xml;q=0.8" });
+
+ // Assert
+ Assert.Equal(expected, parsed);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs
index 0fa93d3a2a..d16c4a4704 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeComparisonsTest.cs
@@ -9,57 +9,6 @@ namespace Microsoft.AspNet.Mvc.Formatters
{
public class MediaTypeComparisonsTest
{
- [Theory]
- [InlineData("application/json", "application/json", true)]
- [InlineData("application/json", "application/json;charset=utf-8", true)]
- [InlineData("application/json;charset=utf-8", "application/json", false)]
- [InlineData("application/json;q=0.8", "application/json;q=0.9", true)]
- [InlineData("application/json;q=0.8;charset=utf-7", "application/json;charset=utf-8;q=0.9", true)]
- [InlineData("application/json;format=indent;charset=utf-8", "application/json", false)]
- [InlineData("application/json", "application/json;format=indent;charset=utf-8", true)]
- [InlineData("application/json;format=indent;charset=utf-8", "application/json;format=indent;charset=utf-8", true)]
- [InlineData("application/json;charset=utf-8;format=indent", "application/json;format=indent;charset=utf-8", true)]
- public void IsSubsetOf(string set, string subset, bool expectedResult)
- {
- // Arrange & Act
- var result = MediaTypeComparisons.IsSubsetOf(
- new StringSegment(set),
- new StringSegment(subset));
- // Assert
- Assert.Equal(expectedResult, result);
- }
-
- [Theory]
- [InlineData("*/*", true)]
- [InlineData("text/*", false)]
- [InlineData("text/plain", false)]
- public void MatchesAllTypes(string value, bool expectedResult)
- {
- // Arrange
- var mediaType = new StringSegment(value);
-
- // Act
- var result = MediaTypeComparisons.MatchesAllTypes(mediaType);
-
- // Assert
- Assert.Equal(expectedResult, result);
- }
-
- [Theory]
- [InlineData("*/*", true)]
- [InlineData("text/*", true)]
- [InlineData("text/plain", false)]
- public void MatchesAllSubtypes(string value, bool expectedResult)
- {
- // Arrange
- var mediaType = new StringSegment(value);
-
- // Act
- var result = MediaTypeComparisons.MatchesAllSubtypes(mediaType);
-
- // Assert
- Assert.Equal(expectedResult, result);
- }
}
}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeTest.cs
new file mode 100644
index 0000000000..618a431bdd
--- /dev/null
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Formatters/MediaTypeTest.cs
@@ -0,0 +1,153 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System.Linq;
+using Microsoft.Extensions.Primitives;
+using Xunit;
+
+namespace Microsoft.AspNet.Mvc.Formatters
+{
+ public class MediaTypeTest
+ {
+ [Theory]
+ [InlineData("application/json")]
+ [InlineData("application /json")]
+ public void CanParse_ParameterlessMediaTypes(string mediaType)
+ {
+ // Arrange & Act
+ var result = new MediaType(mediaType, 0, mediaType.Length);
+
+ // Assert
+ Assert.Equal(new StringSegment("application"), result.Type);
+ Assert.Equal(new StringSegment("json"), result.SubType);
+ }
+
+ [Theory]
+ [InlineData("application/json;format=pretty;charset=utf-8;q=0.8")]
+ [InlineData("application/json;format=pretty;charset=utf-8; q=0.8 ")]
+ [InlineData("application/json;format=pretty;charset=utf-8 ; q=0.8 ")]
+ [InlineData("application/json;format=pretty; charset=utf-8 ; q=0.8 ")]
+ [InlineData("application/json;format=pretty ; charset=utf-8 ; q=0.8 ")]
+ [InlineData("application/json; format=pretty ; charset=utf-8 ; q=0.8 ")]
+ [InlineData("application/json; format=pretty ; charset=utf-8 ; q= 0.8 ")]
+ [InlineData("application/json; format=pretty ; charset=utf-8 ; q = 0.8 ")]
+ public void CanParse_MediaTypesWithParameters(string mediaType)
+ {
+ // Arrange & Act
+ var result = new MediaType(mediaType, 0, mediaType.Length);
+
+ // Assert
+ Assert.Equal(new StringSegment("application"), result.Type);
+ Assert.Equal(new StringSegment("json"), result.SubType);
+ Assert.Equal(new StringSegment("pretty"), result.GetParameter("format"));
+ Assert.Equal(new StringSegment("0.8"), result.GetParameter("q"));
+ Assert.Equal(new StringSegment("utf-8"), result.GetParameter("charset"));
+ }
+
+ [Theory]
+ [InlineData("application/json;charset=utf-8")]
+ [InlineData("application/json;format=indent;q=0.8;charset=utf-8")]
+ [InlineData("application/json;format=indent;charset=utf-8;q=0.8")]
+ [InlineData("application/json;charset=utf-8;format=indent;q=0.8")]
+ public void GetParameter_ReturnsParameter_IfParameterIsInMediaType(string mediaType)
+ {
+ // Arrange
+ var expectedParameter = new StringSegment("utf-8");
+
+ var parsedMediaType = new MediaType(mediaType, 0, mediaType.Length);
+
+ // Act
+ var result = parsedMediaType.GetParameter("charset");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(expectedParameter, result);
+ }
+
+ [Fact]
+ public void GetParameter_IsCaseInsensitive()
+ {
+ // Arrange
+ var mediaType = "application/json;charset=utf-8";
+ var expectedParameter = new StringSegment("utf-8");
+
+ var parsedMediaType = new MediaType(mediaType);
+
+ // Act
+ var result = parsedMediaType.GetParameter("CHARSET");
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(expectedParameter, result);
+ }
+
+ [Theory]
+ [InlineData("application/json", "application/json", true)]
+ [InlineData("application/json", "application/json;charset=utf-8", true)]
+ [InlineData("application/json;charset=utf-8", "application/json", false)]
+ [InlineData("application/json;q=0.8", "application/json;q=0.9", true)]
+ [InlineData("application/json;q=0.8;charset=utf-7", "application/json;charset=utf-8;q=0.9", true)]
+ [InlineData("application/json;format=indent;charset=utf-8", "application/json", false)]
+ [InlineData("application/json", "application/json;format=indent;charset=utf-8", true)]
+ [InlineData("application/json;format=indent;charset=utf-8", "application/json;format=indent;charset=utf-8", true)]
+ [InlineData("application/json;charset=utf-8;format=indent", "application/json;format=indent;charset=utf-8", true)]
+ public void IsSubsetOf(string set, string subset, bool expectedResult)
+ {
+ // Arrange
+ var setMediaType = new MediaType(set);
+ var subSetMediaType = new MediaType(subset);
+
+ // Act
+ var result = subSetMediaType.IsSubsetOf(setMediaType);
+
+ // Assert
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData("*/*", true)]
+ [InlineData("text/*", false)]
+ [InlineData("text/plain", false)]
+ public void MatchesAllTypes(string value, bool expectedResult)
+ {
+ // Arrange
+ var mediaType = new MediaType(value);
+
+ // Act
+ var result = mediaType.MatchesAllTypes;
+
+ // Assert
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Theory]
+ [InlineData("*/*", true)]
+ [InlineData("text/*", true)]
+ [InlineData("text/plain", false)]
+ public void MatchesAllSubtypes(string value, bool expectedResult)
+ {
+ // Arrange
+ var mediaType = new MediaType(value);
+
+ // Act
+ var result = mediaType.MatchesAllSubTypes;
+
+ // Assert
+ Assert.Equal(expectedResult, result);
+ }
+
+ [Fact]
+ public void GetParameter_ReturnsNull_IfParameterIsNotInMediaType()
+ {
+ var mediaType = "application/json;charset=utf-8;format=indent;q=0.8";
+
+ var parsedMediaType = new MediaType(mediaType, 0, mediaType.Length);
+
+ // Act
+ var result = parsedMediaType.GetParameter("other");
+
+ // Assert
+ Assert.False(result.HasValue);
+ }
+ }
+}
diff --git a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs
index a7c69f3358..c32ffe4f23 100644
--- a/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs
+++ b/test/Microsoft.AspNet.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs
@@ -138,7 +138,6 @@ namespace Microsoft.AspNet.Mvc.Internal
out resolvedContentTypeEncoding);
// Assert
-
Assert.Equal(expectedContentType, resolvedContentType);
Assert.Equal(Encoding.UTF8, resolvedContentTypeEncoding);
}
diff --git a/test/Microsoft.AspNet.Mvc.FunctionalTests/ContentNegotiationTest.cs b/test/Microsoft.AspNet.Mvc.FunctionalTests/ContentNegotiationTest.cs
index 7e834e97f7..2441ab91ea 100644
--- a/test/Microsoft.AspNet.Mvc.FunctionalTests/ContentNegotiationTest.cs
+++ b/test/Microsoft.AspNet.Mvc.FunctionalTests/ContentNegotiationTest.cs
@@ -404,6 +404,50 @@ END:VCARD
Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
}
+ [Fact]
+ public async Task InvalidResponseContentType_WithNotMatchingAcceptHeader_Returns406()
+ {
+ // Arrange
+ var targetUri = "http://localhost/InvalidContentType/SetResponseContentTypeJson";
+ var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
+ request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/custom1"));
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task InvalidResponseContentType_WithMatchingAcceptHeader_Returns406()
+ {
+ // Arrange
+ var targetUri = "http://localhost/InvalidContentType/SetResponseContentTypeJson";
+ var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
+ request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/json"));
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
+ }
+
+ [Fact]
+ public async Task InvalidResponseContentType_WithoutAcceptHeader_Returns406()
+ {
+ // Arrange
+ var targetUri = "http://localhost/InvalidContentType/SetResponseContentTypeJson";
+ var request = new HttpRequestMessage(HttpMethod.Get, targetUri);
+
+ // Act
+ var response = await Client.SendAsync(request);
+
+ // Assert
+ Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode);
+ }
+
[Fact]
public async Task ProducesAttribute_And_FormatFilterAttribute_Conflicting()
{
diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs
index 977c997a5e..3e87a7b784 100644
--- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs
+++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs
@@ -91,7 +91,7 @@ namespace Microsoft.AspNet.Mvc
var contentResult = Assert.IsType(result);
Assert.Equal(content, contentResult.Content);
Assert.Equal("text/asp; charset=us-ascii", contentResult.ContentType.ToString());
- Assert.Equal(encoding, MediaTypeEncoding.GetEncoding(contentResult.ContentType));
+ Assert.Equal(encoding, MediaType.GetEncoding(contentResult.ContentType));
}
[Theory]
diff --git a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs
index a07a8cfe7a..bf6e129ef1 100644
--- a/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs
+++ b/test/Microsoft.AspNet.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs
@@ -282,7 +282,7 @@ namespace Microsoft.AspNet.Mvc.ViewFeatures
// Check if the original instance provided by the user has not changed.
// Since we do not have access to the new instance created within the view executor,
// check if at least the content is the same.
- Assert.Null(MediaTypeEncoding.GetEncoding(contentType));
+ Assert.Null(MediaType.GetEncoding(contentType));
}
[Fact]
diff --git a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs
index f5aa58af41..7e73d99c42 100644
--- a/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs
+++ b/test/Microsoft.AspNet.Mvc.WebApiCompatShimTest/ApiControllerTest.cs
@@ -265,7 +265,7 @@ namespace System.Web.Http
var jsonResult = Assert.IsType(result);
Assert.Same(product, jsonResult.Value);
- Assert.Same(Encoding.UTF8, MediaTypeEncoding.GetEncoding(jsonResult.ContentType));
+ Assert.Same(Encoding.UTF8, MediaType.GetEncoding(jsonResult.ContentType));
}
[Fact]
diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/InvalidContentTypeController.cs b/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/InvalidContentTypeController.cs
new file mode 100644
index 0000000000..36ee0dbc28
--- /dev/null
+++ b/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/InvalidContentTypeController.cs
@@ -0,0 +1,14 @@
+using Microsoft.AspNet.Mvc;
+
+namespace BasicWebSite.Controllers.ContentNegotiation
+{
+ public class InvalidContentTypeController : Controller
+ {
+ [HttpGet("InvalidContentType/SetResponseContentTypeJson")]
+ public IActionResult SetResponseContentTypeJson()
+ {
+ HttpContext.Response.ContentType = "json";
+ return Ok(0);
+ }
+ }
+}
diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs
index 4a1225efbe..94bf957ec7 100644
--- a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs
+++ b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs
@@ -38,7 +38,7 @@ namespace BasicWebSite.Formatters
builder.AppendLine();
builder.AppendLine("END:VCARD");
- var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
+ var selectedEncoding = new MediaType(context.ContentType).Encoding ?? Encoding.UTF8;
await context.HttpContext.Response.WriteAsync(
builder.ToString(),
diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs
index 875c695db6..e84a50e499 100644
--- a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs
+++ b/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs
@@ -41,7 +41,7 @@ namespace BasicWebSite.Formatters
builder.AppendLine();
builder.AppendLine("END:VCARD");
- var selectedEncoding = MediaTypeEncoding.GetEncoding(context.ContentType) ?? Encoding.UTF8;
+ var selectedEncoding = new MediaType(context.ContentType).Encoding ?? Encoding.UTF8;
await context.HttpContext.Response.WriteAsync(
builder.ToString(),