From c0a43ed20475778ca0661a18c3e8d00305382c54 Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sat, 31 Aug 2019 19:01:44 +0000 Subject: [PATCH] remove MimeKitLite and use MediaTypeHeaderValue (#338) --- Directory.Build.props | 2 +- GitHubReleaseNotes.txt | 2 +- src/WireMock.Net/Http/HttpClientHelper.cs | 2 +- .../Http/HttpRequestMessageHelper.cs | 6 +- .../Http/HttpResponseMessageHelper.cs | 2 +- src/WireMock.Net/Http/StringContentHelper.cs | 23 +- src/WireMock.Net/Util/BodyParser.cs | 308 +++++++++--------- src/WireMock.Net/WireMock.Net.csproj | 1 - .../Http/HttpRequestMessageHelperTests.cs | 4 +- .../Http/StringContentHelperTests.cs | 39 +++ .../WireMock.Net.Tests.csproj | 1 - 11 files changed, 205 insertions(+), 185 deletions(-) create mode 100644 test/WireMock.Net.Tests/Http/StringContentHelperTests.cs diff --git a/Directory.Build.props b/Directory.Build.props index 988d7385..32a01573 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -4,7 +4,7 @@ - 1.0.29 + 1.0.30 diff --git a/GitHubReleaseNotes.txt b/GitHubReleaseNotes.txt index a39fdfc4..17ebdce2 100644 --- a/GitHubReleaseNotes.txt +++ b/GitHubReleaseNotes.txt @@ -1,3 +1,3 @@ https://github.com/StefH/GitHubReleaseNotes -GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --exclude-labels question, invalid --version 1.0.29.0 \ No newline at end of file +GitHubReleaseNotes.exe --output CHANGELOG.md --skip-empty-releases --exclude-labels question, invalid --version 1.0.30.0 \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpClientHelper.cs b/src/WireMock.Net/Http/HttpClientHelper.cs index 883cb897..4b5b05e3 100644 --- a/src/WireMock.Net/Http/HttpClientHelper.cs +++ b/src/WireMock.Net/Http/HttpClientHelper.cs @@ -72,7 +72,7 @@ namespace WireMock.Http var httpResponseMessage = await client.SendAsync(httpRequestMessage, HttpCompletionOption.ResponseContentRead); // Create ResponseMessage - return await HttpResponseMessageHelper.Create(httpResponseMessage, requiredUri, originalUri); + return await HttpResponseMessageHelper.CreateAsync(httpResponseMessage, requiredUri, originalUri); } } } \ No newline at end of file diff --git a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs index 7f892b00..456a3bd2 100644 --- a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs +++ b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Linq; using System.Net.Http; +using System.Net.Http.Headers; using JetBrains.Annotations; -using MimeKit; using Newtonsoft.Json; using WireMock.Util; using WireMock.Validation; @@ -19,11 +19,11 @@ namespace WireMock.Http var httpRequestMessage = new HttpRequestMessage(new HttpMethod(requestMessage.Method), url); - ContentType contentType = null; + MediaTypeHeaderValue contentType = null; if (requestMessage.Headers != null && requestMessage.Headers.ContainsKey(HttpKnownHeaderNames.ContentType)) { var value = requestMessage.Headers[HttpKnownHeaderNames.ContentType].FirstOrDefault(); - ContentType.TryParse(value, out contentType); + MediaTypeHeaderValue.TryParse(value, out contentType); } switch (requestMessage.BodyData?.DetectedBodyType) diff --git a/src/WireMock.Net/Http/HttpResponseMessageHelper.cs b/src/WireMock.Net/Http/HttpResponseMessageHelper.cs index fa134e17..f23c5acb 100644 --- a/src/WireMock.Net/Http/HttpResponseMessageHelper.cs +++ b/src/WireMock.Net/Http/HttpResponseMessageHelper.cs @@ -9,7 +9,7 @@ namespace WireMock.Http { internal static class HttpResponseMessageHelper { - public static async Task Create(HttpResponseMessage httpResponseMessage, Uri requiredUri, Uri originalUri) + public static async Task CreateAsync(HttpResponseMessage httpResponseMessage, Uri requiredUri, Uri originalUri) { var responseMessage = new ResponseMessage { StatusCode = (int)httpResponseMessage.StatusCode }; diff --git a/src/WireMock.Net/Http/StringContentHelper.cs b/src/WireMock.Net/Http/StringContentHelper.cs index 2d9446f8..f71b55e4 100644 --- a/src/WireMock.Net/Http/StringContentHelper.cs +++ b/src/WireMock.Net/Http/StringContentHelper.cs @@ -1,8 +1,6 @@ using System.Net.Http; using System.Net.Http.Headers; -using System.Text; using JetBrains.Annotations; -using MimeKit; using WireMock.Validation; namespace WireMock.Http @@ -10,29 +8,18 @@ namespace WireMock.Http internal static class StringContentHelper { /// - /// Creates a StringContent object. Note that the Encoding is only set when it's also set on the original header. + /// Creates a StringContent object. /// /// The string content (cannot be null) /// The ContentType (can be null) /// StringContent - internal static StringContent Create([NotNull] string content, [CanBeNull] ContentType contentType) + internal static StringContent Create([NotNull] string content, [CanBeNull] MediaTypeHeaderValue contentType) { Check.NotNull(content, nameof(content)); - if (contentType == null) - { - return new StringContent(content); - } - - if (contentType.Charset == null) - { - var stringContent = new StringContent(content); - stringContent.Headers.ContentType = new MediaTypeHeaderValue(contentType.MimeType); - return stringContent; - } - - var encoding = Encoding.GetEncoding(contentType.Charset); - return new StringContent(content, encoding, contentType.MimeType); + var stringContent = new StringContent(content); + stringContent.Headers.ContentType = contentType; + return stringContent; } } } \ No newline at end of file diff --git a/src/WireMock.Net/Util/BodyParser.cs b/src/WireMock.Net/Util/BodyParser.cs index f31901be..fea3110d 100644 --- a/src/WireMock.Net/Util/BodyParser.cs +++ b/src/WireMock.Net/Util/BodyParser.cs @@ -1,33 +1,32 @@ -using JetBrains.Annotations; -using MimeKit; -using Newtonsoft.Json; -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using WireMock.Matchers; -using WireMock.Validation; - -namespace WireMock.Util -{ - internal static class BodyParser - { - private static readonly Encoding DefaultEncoding = Encoding.UTF8; - private static readonly Encoding[] SupportedBodyAsStringEncodingForMultipart = { Encoding.UTF8, Encoding.ASCII }; - - /* - HEAD - No defined body semantics. - GET - No defined body semantics. - PUT - Body supported. - POST - Body supported. - DELETE - No defined body semantics. - TRACE - Body not supported. - OPTIONS - Body supported but no semantics on usage (maybe in the future). - CONNECT - No defined body semantics - PATCH - Body supported. - */ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http.Headers; +using System.Text; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Newtonsoft.Json; +using WireMock.Matchers; +using WireMock.Validation; + +namespace WireMock.Util +{ + internal static class BodyParser + { + private static readonly Encoding DefaultEncoding = Encoding.UTF8; + private static readonly Encoding[] SupportedBodyAsStringEncodingForMultipart = { Encoding.UTF8, Encoding.ASCII }; + + /* + HEAD - No defined body semantics. + GET - No defined body semantics. + PUT - Body supported. + POST - Body supported. + DELETE - No defined body semantics. + TRACE - Body not supported. + OPTIONS - Body supported but no semantics on usage (maybe in the future). + CONNECT - No defined body semantics + PATCH - Body supported. + */ private static readonly IDictionary BodyAllowedForMethods = new Dictionary { { "HEAD", false }, @@ -39,134 +38,131 @@ namespace WireMock.Util { "OPTIONS", true }, { "CONNECT", false }, { "PATCH", true } - }; - - private static readonly IStringMatcher[] MultipartContentTypesMatchers = { - new WildcardMatcher("multipart/*", true) - }; - - private static readonly IStringMatcher[] JsonContentTypesMatchers = { - new WildcardMatcher("application/json", true), - new WildcardMatcher("application/vnd.*+json", true) - }; - - private static readonly IStringMatcher[] TextContentTypeMatchers = - { - new WildcardMatcher("text/*", true), - new RegexMatcher("^application\\/(java|type)script$", true), - new WildcardMatcher("application/*xml", true), - new WildcardMatcher("application/x-www-form-urlencoded", true) - }; - - public static bool ParseBodyAsIsValid([CanBeNull] string parseBodyAs) - { - return Enum.TryParse(parseBodyAs, out BodyType _); - } - - public static bool ShouldParseBody([CanBeNull] string method) - { - if (String.IsNullOrEmpty(method)) + }; + + private static readonly IStringMatcher[] MultipartContentTypesMatchers = { + new WildcardMatcher("multipart/*", true) + }; + + private static readonly IStringMatcher[] JsonContentTypesMatchers = { + new WildcardMatcher("application/json", true), + new WildcardMatcher("application/vnd.*+json", true) + }; + + private static readonly IStringMatcher[] TextContentTypeMatchers = + { + new WildcardMatcher("text/*", true), + new RegexMatcher("^application\\/(java|type)script$", true), + new WildcardMatcher("application/*xml", true), + new WildcardMatcher("application/x-www-form-urlencoded", true) + }; + + public static bool ShouldParseBody([CanBeNull] string method) + { + if (string.IsNullOrEmpty(method)) { return false; - } - if (BodyAllowedForMethods.TryGetValue(method.ToUpper(), out var allowed)) + } + + if (BodyAllowedForMethods.TryGetValue(method.ToUpper(), out bool allowed)) { return allowed; - } - // If we don't have any knowledge of this method, we should assume that a body *may* - // be present, so we should parse it if it is. Therefore, if a new method is added to - // the HTTP Method Registry, we only really need to add it to BodyAllowedForMethods if - // we want to make it clear that a body is *not* allowed. - return true; - } - - public static BodyType DetectBodyTypeFromContentType([CanBeNull] string contentTypeValue) - { - if (string.IsNullOrEmpty(contentTypeValue) || !ContentType.TryParse(contentTypeValue, out ContentType contentType)) - { - return BodyType.Bytes; - } - - if (TextContentTypeMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MimeType)))) - { - return BodyType.String; - } - - if (JsonContentTypesMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MimeType)))) - { - return BodyType.Json; - } - - if (MultipartContentTypesMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MimeType)))) - { - return BodyType.MultiPart; - } - - return BodyType.Bytes; - } - - public static async Task Parse([NotNull] Stream stream, [CanBeNull] string contentType) - { - Check.NotNull(stream, nameof(stream)); - - var data = new BodyData - { - BodyAsBytes = await ReadBytesAsync(stream), - DetectedBodyType = BodyType.Bytes, - DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(contentType) - }; - - // In case of MultiPart: check if the BodyAsBytes is a valid UTF8 or ASCII string, in that case read as String else keep as-is - if (data.DetectedBodyTypeFromContentType == BodyType.MultiPart) - { - if (BytesEncodingUtils.TryGetEncoding(data.BodyAsBytes, out Encoding encoding) && - SupportedBodyAsStringEncodingForMultipart.Select(x => x.Equals(encoding)).Any()) - { - data.BodyAsString = encoding.GetString(data.BodyAsBytes); - data.Encoding = encoding; - data.DetectedBodyType = BodyType.String; - - return data; - } - - return data; - } - - // Try to get the body as String - try - { - data.BodyAsString = DefaultEncoding.GetString(data.BodyAsBytes); - data.Encoding = DefaultEncoding; - data.DetectedBodyType = BodyType.String; - - // If string is not null or empty, try to get as Json - if (!string.IsNullOrEmpty(data.BodyAsString)) - { - try - { - data.BodyAsJson = JsonConvert.DeserializeObject(data.BodyAsString, new JsonSerializerSettings { Formatting = Formatting.Indented }); - data.DetectedBodyType = BodyType.Json; - } - catch - { - // JsonConvert failed, just ignore. - } - } - } - catch - { - // Reading as string failed, just ignore - } - - return data; - } - private static async Task ReadBytesAsync(Stream stream) - { - using (var memoryStream = new MemoryStream()) - { - await stream.CopyToAsync(memoryStream); - return memoryStream.ToArray(); - } - } - } + } + + // If we don't have any knowledge of this method, we should assume that a body *may* + // be present, so we should parse it if it is. Therefore, if a new method is added to + // the HTTP Method Registry, we only really need to add it to BodyAllowedForMethods if + // we want to make it clear that a body is *not* allowed. + return true; + } + + public static BodyType DetectBodyTypeFromContentType([CanBeNull] string contentTypeValue) + { + if (string.IsNullOrEmpty(contentTypeValue) || !MediaTypeHeaderValue.TryParse(contentTypeValue, out MediaTypeHeaderValue contentType)) + { + return BodyType.Bytes; + } + + if (TextContentTypeMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType)))) + { + return BodyType.String; + } + + if (JsonContentTypesMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType)))) + { + return BodyType.Json; + } + + if (MultipartContentTypesMatchers.Any(matcher => MatchScores.IsPerfect(matcher.IsMatch(contentType.MediaType)))) + { + return BodyType.MultiPart; + } + + return BodyType.Bytes; + } + + public static async Task Parse([NotNull] Stream stream, [CanBeNull] string contentType) + { + Check.NotNull(stream, nameof(stream)); + + var data = new BodyData + { + BodyAsBytes = await ReadBytesAsync(stream), + DetectedBodyType = BodyType.Bytes, + DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(contentType) + }; + + // In case of MultiPart: check if the BodyAsBytes is a valid UTF8 or ASCII string, in that case read as String else keep as-is + if (data.DetectedBodyTypeFromContentType == BodyType.MultiPart) + { + if (BytesEncodingUtils.TryGetEncoding(data.BodyAsBytes, out Encoding encoding) && + SupportedBodyAsStringEncodingForMultipart.Select(x => x.Equals(encoding)).Any()) + { + data.BodyAsString = encoding.GetString(data.BodyAsBytes); + data.Encoding = encoding; + data.DetectedBodyType = BodyType.String; + + return data; + } + + return data; + } + + // Try to get the body as String + try + { + data.BodyAsString = DefaultEncoding.GetString(data.BodyAsBytes); + data.Encoding = DefaultEncoding; + data.DetectedBodyType = BodyType.String; + + // If string is not null or empty, try to get as Json + if (!string.IsNullOrEmpty(data.BodyAsString)) + { + try + { + data.BodyAsJson = JsonConvert.DeserializeObject(data.BodyAsString, new JsonSerializerSettings { Formatting = Formatting.Indented }); + data.DetectedBodyType = BodyType.Json; + } + catch + { + // JsonConvert failed, just ignore. + } + } + } + catch + { + // Reading as string failed, just ignore + } + + return data; + } + private static async Task ReadBytesAsync(Stream stream) + { + using (var memoryStream = new MemoryStream()) + { + await stream.CopyToAsync(memoryStream); + return memoryStream.ToArray(); + } + } + } } \ No newline at end of file diff --git a/src/WireMock.Net/WireMock.Net.csproj b/src/WireMock.Net/WireMock.Net.csproj index 815e5f4c..bfe61902 100644 --- a/src/WireMock.Net/WireMock.Net.csproj +++ b/src/WireMock.Net/WireMock.Net.csproj @@ -57,7 +57,6 @@ - all runtime; build; native; contentfiles; analyzers diff --git a/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs b/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs index ff28b539..dd645b5f 100644 --- a/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs +++ b/test/WireMock.Net.Tests/Http/HttpRequestMessageHelperTests.cs @@ -137,7 +137,7 @@ namespace WireMock.Net.Tests.Http var message = HttpRequestMessageHelper.Create(request, "http://url"); // Assert - Check.That(message.Content.Headers.GetValues("Content-Type")).ContainsExactly("application/xml; charset=utf-8"); + Check.That(message.Content.Headers.GetValues("Content-Type")).ContainsExactly("application/xml; charset=UTF-8"); } [Fact] @@ -156,7 +156,7 @@ namespace WireMock.Net.Tests.Http var message = HttpRequestMessageHelper.Create(request, "http://url"); // Assert - Check.That(message.Content.Headers.GetValues("Content-Type")).ContainsExactly("application/xml; charset=us-ascii"); + Check.That(message.Content.Headers.GetValues("Content-Type")).ContainsExactly("application/xml; charset=Ascii"); } } } \ No newline at end of file diff --git a/test/WireMock.Net.Tests/Http/StringContentHelperTests.cs b/test/WireMock.Net.Tests/Http/StringContentHelperTests.cs new file mode 100644 index 00000000..5d3e08f5 --- /dev/null +++ b/test/WireMock.Net.Tests/Http/StringContentHelperTests.cs @@ -0,0 +1,39 @@ +using System.Net.Http.Headers; +using FluentAssertions; +using WireMock.Http; +using Xunit; + +namespace WireMock.Net.Tests.Http +{ + public class StringContentHelperTests + { + [Fact] + public void StringContentHelper_Create_WithNullContentType() + { + // Act + var result = StringContentHelper.Create("test", null); + + // Assert + result.Headers.ContentType.Should().BeNull(); + result.ReadAsStringAsync().Result.Should().Be("test"); + } + + [Theory] + [InlineData("application/json", "application/json")] + [InlineData("application/soap+xml", "application/soap+xml")] + [InlineData("application/soap+xml;charset=UTF-8", "application/soap+xml; charset=UTF-8")] + [InlineData("application/soap+xml;charset=UTF-8;action=\"http://myCompany.Customer.Contract/ICustomerService/GetSomeConfiguration\"", "application/soap+xml; charset=UTF-8; action=\"http://myCompany.Customer.Contract/ICustomerService/GetSomeConfiguration\"")] + public void StringContentHelper_Create(string test, string expected) + { + // Arrange + var contentType = MediaTypeHeaderValue.Parse(test); + + // Act + var result = StringContentHelper.Create("test", contentType); + + // Assert + result.Headers.ContentType.ToString().Should().Be(expected); + result.ReadAsStringAsync().Result.Should().Be("test"); + } + } +} diff --git a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj index 26b90ea4..dbd2c7e8 100644 --- a/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj +++ b/test/WireMock.Net.Tests/WireMock.Net.Tests.csproj @@ -39,7 +39,6 @@ -