[Fixes #5150] parsing issue on asp.net when request quality factor is specified

This commit is contained in:
jacalvar 2016-10-03 14:57:45 -07:00
parent 38aa48651f
commit 9579806306
4 changed files with 101 additions and 18 deletions

View File

@ -292,6 +292,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
public static MediaTypeSegmentWithQuality CreateMediaTypeSegmentWithQuality(string mediaType, int start)
{
var parsedMediaType = new MediaType(mediaType, start, length: null);
if (parsedMediaType.Type.Equals(default(StringSegment)) ||
parsedMediaType.SubType.Equals(default(StringSegment)))
{
return default(MediaTypeSegmentWithQuality);
}
var parser = parsedMediaType._parameterParser;
double quality = 1.0d;
@ -306,9 +312,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
}
}
// We check if the parsed media type has value at this stage when we have iterated
// We check if the parsed media type has a value at this stage when we have iterated
// over all the parameters and we know if the parsing was sucessful.
if (!parser.ParsingFailed && parser.CurrentOffset >= start)
if (!parser.ParsingFailed)
{
return new MediaTypeSegmentWithQuality(
new StringSegment(mediaType, start, parser.CurrentOffset - start),
@ -395,6 +401,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters
{
if (_mediaTypeBuffer == null)
{
ParsingFailed = true;
result = default(MediaTypeParameter);
return false;
}

View File

@ -29,7 +29,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Internal
{
throw new ArgumentNullException(nameof(parsedValues));
}
for (var i = 0; i < acceptHeaders.Count; i++)
{
var charIndex = 0;
@ -46,13 +45,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Internal
parsedValues.Add(output);
}
}
else
{
var invalidValuesError = Resources.FormatAcceptHeaderParser_ParseAcceptHeader_InvalidValues(
value.Substring(charIndex));
throw new FormatException(invalidValuesError);
}
}
}
}
@ -80,11 +72,34 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Internal
return true;
}
MediaTypeSegmentWithQuality result;
var length = GetMediaTypeWithQualityLength(value, currentIndex, out result);
// We deliberately want to ignore media types that we are not capable of parsing.
// This is due to the fact that some browsers will send invalid media types like
// ; q=0.9 or */;q=0.2, etc.
// In this scenario, our recovery action consists of advancing the pointer to the
// next separator and moving on.
// In case we don't find the next separator, we simply advance the cursor to the
// end of the string to signal that we are done parsing.
var result = default(MediaTypeSegmentWithQuality);
var length = 0;
try
{
length = GetMediaTypeWithQualityLength(value, currentIndex, out result);
}
catch
{
length = 0;
}
if (length == 0)
{
// The parsing failed.
currentIndex = value.IndexOf(',', currentIndex);
if (currentIndex == -1)
{
index = value.Length;
return false;
}
index = currentIndex;
return false;
}

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Extensions.Primitives;
using Xunit;
@ -54,14 +55,54 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Internal
{
// Arrange
var header = "application/json, application/xml,;q=0.8";
var expectedException = "\"Invalid values ';q=0.8'.\"";
var expectedMediaTypes = new List<MediaTypeSegmentWithQuality>
{
new MediaTypeSegmentWithQuality(new StringSegment("application/json"),1.0),
new MediaTypeSegmentWithQuality(new StringSegment("application/xml"),1.0),
};
// Act
var ex = Assert.Throws<FormatException>(
() => { AcceptHeaderParser.ParseAcceptHeader(new List<string> { header }); });
var mediaTypes = AcceptHeaderParser.ParseAcceptHeader(new List<string> { header });
// Assert
Assert.Equal(expectedException, ex.Message);
Assert.Equal(expectedMediaTypes, mediaTypes);
}
public static TheoryData<string[], string[]> ParseAcceptHeaderWithInvalidMediaTypesData =>
new TheoryData<string[], string[]>
{
{ new [] { ";q=0.9" }, new string[] { } },
{ new [] { "/" }, new string[] { } },
{ new [] { "*/" }, new string[] { } },
{ new [] { "/*" }, new string[] { } },
{ new [] { "/;q=0.9" }, new string[] { } },
{ new [] { "*/;q=0.9" }, new string[] { } },
{ new [] { "/*;q=0.9" }, new string[] { } },
{ new [] { "/;q=0.9,text/html" }, new string[] { "text/html" } },
{ new [] { "*/;q=0.9,text/html" }, new string[] { "text/html" } },
{ new [] { "/*;q=0.9,text/html" }, new string[] { "text/html" } },
{ new [] { "img/png,/;q=0.9,text/html" }, new string[] { "img/png", "text/html" } },
{ new [] { "img/png,*/;q=0.9,text/html" }, new string[] { "img/png", "text/html" } },
{ new [] { "img/png,/*;q=0.9,text/html" }, new string[] { "img/png", "text/html" } },
{ new [] { "img/png, /;q=0.9" }, new string[] { "img/png", } },
{ new [] { "img/png, */;q=0.9" }, new string[] { "img/png", } },
{ new [] { "img/png;q=1.0, /*;q=0.9" }, new string[] { "img/png;q=1.0", } },
};
[Theory]
[MemberData(nameof(ParseAcceptHeaderWithInvalidMediaTypesData))]
public void ParseAcceptHeader_GracefullyRecoversFromInvalidMediaTypeValues_AndReturnsValidMediaTypes(
string[] acceptHeader,
string[] expected)
{
// Arrange
var expectedMediaTypes = expected.Select(e => new MediaTypeSegmentWithQuality(new StringSegment(e), 1.0)).ToList();
// Act
var parsed = AcceptHeaderParser.ParseAcceptHeader(acceptHeader);
// Assert
Assert.Equal(expectedMediaTypes, parsed);
}
[Fact]
@ -70,8 +111,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Internal
// Arrange
var expected = new List<MediaTypeSegmentWithQuality>
{
new MediaTypeSegmentWithQuality(new StringSegment("application/json"),1.0),
new MediaTypeSegmentWithQuality(new StringSegment("application/xml;q=0.8"),0.8)
new MediaTypeSegmentWithQuality(new StringSegment("application/json"), 1.0),
new MediaTypeSegmentWithQuality(new StringSegment("application/xml;q=0.8"), 0.8)
};
// Act

View File

@ -85,6 +85,26 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
Assert.Equal(expectedContentType, response.Content.Headers.ContentType);
}
[Theory]
[InlineData("/;q=0.9")]
[InlineData("/;q=0.9, invalid;q=0.5;application/json;q=0.1")]
[InlineData("/invalid;q=0.9, application/json;q=0.1,invalid;q=0.5")]
[InlineData("text/html, application/json, image/jpeg, *; q=.2, */*; q=.2")]
public async Task ContentNegotiationWithPartiallyValidAcceptHeader_SkipsInvalidEntries(string acceptHeader)
{
// Arrange
var expectedContentType = MediaTypeHeaderValue.Parse("application/json;charset=utf-8");
var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/ContentNegotiation/UserInfo_ProducesWithTypeOnly");
request.Headers.TryAddWithoutValidation("Accept", acceptHeader);
// Act
var response = await Client.SendAsync(request);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.Equal(expectedContentType, response.Content.Headers.ContentType);
}
[Fact]
public async Task ProducesAttributeWithTypeOnly_RunsRegularContentNegotiation()
{