From 38aa48651f9d8c2efd812016b99707c40da7262e Mon Sep 17 00:00:00 2001 From: joheredi Date: Thu, 18 Aug 2016 15:04:14 -0700 Subject: [PATCH 1/2] Fix for Issue#5150 --- .../Formatters/MediaType.cs | 2 +- .../Internal/AcceptHeaderParserTest.cs | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs index 09c2e67b3d..0d20e5be36 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs @@ -308,7 +308,7 @@ public static MediaTypeSegmentWithQuality CreateMediaTypeSegmentWithQuality(stri // 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) + if (!parser.ParsingFailed && parser.CurrentOffset >= start) { return new MediaTypeSegmentWithQuality( new StringSegment(mediaType, start, parser.CurrentOffset - start), diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs index 36a167cc55..b8a470cff6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs @@ -1,6 +1,7 @@ // 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 Microsoft.Extensions.Primitives; using Xunit; @@ -48,6 +49,21 @@ public void ParseAcceptHeader_ParsesSimpleHeaderWithMultipleValues() } } + [Fact] + public void ParseAcceptHeader_ParsesSimpleHeaderWithMultipleValues_InvalidFormat() + { + // Arrange + var header = "application/json, application/xml,;q=0.8"; + var expectedException = "\"Invalid values ';q=0.8'.\""; + + // Act + var ex = Assert.Throws( + () => { AcceptHeaderParser.ParseAcceptHeader(new List { header }); }); + + // Assert + Assert.Equal(expectedException, ex.Message); + } + [Fact] public void ParseAcceptHeader_ParsesMultipleHeaderValues() { From 957980630688898d8cc6de357f658d98231b5e35 Mon Sep 17 00:00:00 2001 From: jacalvar Date: Mon, 3 Oct 2016 14:57:45 -0700 Subject: [PATCH 2/2] [Fixes #5150] parsing issue on asp.net when request quality factor is specified --- .../Formatters/MediaType.cs | 11 +++- .../Internal/AcceptHeaderParser.cs | 35 ++++++++---- .../Internal/AcceptHeaderParserTest.cs | 53 ++++++++++++++++--- .../ContentNegotiationTest.cs | 20 +++++++ 4 files changed, 101 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs index 0d20e5be36..e30b52408e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs @@ -292,6 +292,12 @@ public static Encoding GetEncoding(StringSegment mediaType) 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 @@ public static MediaTypeSegmentWithQuality CreateMediaTypeSegmentWithQuality(stri } } - // 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 @@ public bool ParseNextParameter(out MediaTypeParameter result) { if (_mediaTypeBuffer == null) { + ParsingFailed = true; result = default(MediaTypeParameter); return false; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs index 33eae84fdd..45e1800067 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs @@ -29,7 +29,6 @@ public static void ParseAcceptHeader(IList acceptHeaders, IList acceptHeaders, IList + { + new MediaTypeSegmentWithQuality(new StringSegment("application/json"),1.0), + new MediaTypeSegmentWithQuality(new StringSegment("application/xml"),1.0), + }; + + // Act + var mediaTypes = AcceptHeaderParser.ParseAcceptHeader(new List { header }); + + // Assert + Assert.Equal(expectedMediaTypes, mediaTypes); + } + + public static TheoryData ParseAcceptHeaderWithInvalidMediaTypesData => + new TheoryData + { + { 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 ex = Assert.Throws( - () => { AcceptHeaderParser.ParseAcceptHeader(new List { header }); }); + var parsed = AcceptHeaderParser.ParseAcceptHeader(acceptHeader); // Assert - Assert.Equal(expectedException, ex.Message); + Assert.Equal(expectedMediaTypes, parsed); } [Fact] @@ -70,8 +111,8 @@ 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) + new MediaTypeSegmentWithQuality(new StringSegment("application/json"), 1.0), + new MediaTypeSegmentWithQuality(new StringSegment("application/xml;q=0.8"), 0.8) }; // Act diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs index c94f6047de..41cd535e49 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs @@ -85,6 +85,26 @@ public async Task NoProducesAttribute_ActionReturningAnyObject_RunsUsingDefaultF 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() {