diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs index 3faf8c718..b73e7e3a7 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs @@ -145,17 +145,17 @@ private double CalculateMatchScore(RequestMessage requestMessage) if (Func != null) { - return MatchScores.ToScore(requestMessage?.BodyData?.DetectedBodyType == BodyType.String && Func(requestMessage.BodyData.BodyAsString)); + return MatchScores.ToScore(Func(requestMessage?.BodyData?.BodyAsString)); } if (JsonFunc != null) { - return MatchScores.ToScore(requestMessage?.BodyData?.DetectedBodyType == BodyType.Json && JsonFunc(requestMessage.BodyData.BodyAsJson)); + return MatchScores.ToScore(JsonFunc(requestMessage?.BodyData?.BodyAsJson)); } if (DataFunc != null) { - return MatchScores.ToScore(requestMessage?.BodyData?.DetectedBodyType == BodyType.Bytes && DataFunc(requestMessage.BodyData.BodyAsBytes)); + return MatchScores.ToScore(DataFunc(requestMessage?.BodyData?.BodyAsBytes)); } return MatchScores.Mismatch; diff --git a/src/WireMock.Net/Util/BodyParser.cs b/src/WireMock.Net/Util/BodyParser.cs index fea3110df..bda8c7c5a 100644 --- a/src/WireMock.Net/Util/BodyParser.cs +++ b/src/WireMock.Net/Util/BodyParser.cs @@ -13,8 +13,8 @@ namespace WireMock.Util { internal static class BodyParser { - private static readonly Encoding DefaultEncoding = Encoding.UTF8; - private static readonly Encoding[] SupportedBodyAsStringEncodingForMultipart = { Encoding.UTF8, Encoding.ASCII }; + private static readonly Encoding DefaultEncoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); + private static readonly Encoding[] SupportedBodyAsStringEncodingForMultipart = { DefaultEncoding, Encoding.ASCII }; /* HEAD - No defined body semantics. diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs b/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs index be00d225f..bc1da82a3 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.Proxy.cs @@ -6,6 +6,7 @@ using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using FluentAssertions; using WireMock.Matchers.Request; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; @@ -308,6 +309,43 @@ public async Task FluentMockServer_Proxy_Should_preserve_cookie_header_in_proxie Check.That(receivedRequest.Cookies).ContainsPair("name", "value"); } + /// + /// Send some binary content in a request through the proxy and check that the same content + /// arrived at the target. As example a JPEG/JIFF header is used, which is not representable + /// in UTF8 and breaks if it is not treated as binary content. + /// + [Fact] + public async Task FluentMockServer_Proxy_Should_preserve_binary_request_content() + { + // arrange + var jpegHeader = new byte[] {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00}; + var brokenJpegHeader = new byte[] + {0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0xEF, 0xBF, 0xBD, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00}; + + bool HasCorrectHeader(byte[] bytes) => bytes.SequenceEqual(jpegHeader); + bool HasBrokenHeader(byte[] bytes) => bytes.SequenceEqual(brokenJpegHeader); + + var serverForProxyForwarding = FluentMockServer.Start(); + serverForProxyForwarding + .Given(Request.Create().WithBody(HasCorrectHeader)) + .RespondWith(Response.Create().WithSuccess()); + + serverForProxyForwarding + .Given(Request.Create().WithBody(HasBrokenHeader)) + .RespondWith(Response.Create().WithStatusCode(HttpStatusCode.InternalServerError)); + + var server = FluentMockServer.Start(); + server + .Given(Request.Create()) + .RespondWith(Response.Create().WithProxy(serverForProxyForwarding.Urls[0])); + + // act + var response = await new HttpClient().PostAsync(server.Urls[0], new ByteArrayContent(jpegHeader)); + + // assert + Check.That(response.StatusCode).IsEqualTo(HttpStatusCode.OK); + } + [Fact] public async Task FluentMockServer_Proxy_Should_set_BodyAsJson_in_proxied_response() { diff --git a/test/WireMock.Net.Tests/FluentMockServerTests.cs b/test/WireMock.Net.Tests/FluentMockServerTests.cs index 97bdead27..22ddc1d79 100644 --- a/test/WireMock.Net.Tests/FluentMockServerTests.cs +++ b/test/WireMock.Net.Tests/FluentMockServerTests.cs @@ -187,7 +187,7 @@ public async Task FluentMockServer_Should_exclude_body_for_methods_where_body_is var server = FluentMockServer.Start(); server - .Given(Request.Create().WithBody(b => true)) + .Given(Request.Create().WithBody((byte[] bodyBytes) => bodyBytes != null)) .AtPriority(0) .RespondWith(Response.Create().WithStatusCode(400)); server diff --git a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs index f21730240..a860aebc0 100644 --- a/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs +++ b/test/WireMock.Net.Tests/RequestMatchers/RequestMessageBodyMatcherTests.cs @@ -1,5 +1,10 @@ -using System.Linq; +using System; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; using Moq; +using Newtonsoft.Json; using NFluent; using WireMock.Matchers; using WireMock.Matchers.Request; @@ -208,5 +213,74 @@ public void RequestMessageBodyMatcher_GetMatchingScore_BodyAsBytes_IObjectMatche // Verify objectMatcherMock.Verify(m => m.IsMatch(It.IsAny()), Times.Once); } + + [Theory] + [MemberData(nameof(MatchingScoreData))] + public async Task RequestMessageBodyMatcher_GetMatchingScore_Funcs_Matching(object body, RequestMessageBodyMatcher matcher, bool shouldMatch) + { + // assign + BodyData bodyData; + if (body is byte[] b) + bodyData = await BodyParser.Parse(new MemoryStream(b), null); + else if (body is string s) + bodyData = await BodyParser.Parse(new MemoryStream(Encoding.UTF8.GetBytes(s)), null); + else + throw new Exception(); + + var requestMessage = new RequestMessage(new UrlDetails("http://localhost"), "GET", "127.0.0.1", bodyData); + + // act + var result = new RequestMatchResult(); + var score = matcher.GetMatchingScore(requestMessage, result); + + // assert + Check.That(score).IsEqualTo(shouldMatch ? 1d : 0d); + } + + public static TheoryData MatchingScoreData + { + get + { + var json = "{'a':'b'}"; + var str = "HelloWorld"; + var bytes = new byte[] {0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00}; + + return new TheoryData + { + // JSON match +++ + {json, new RequestMessageBodyMatcher((object o) => ((dynamic) o).a == "b"), true}, + {json, new RequestMessageBodyMatcher((string s) => s == json), true}, + {json, new RequestMessageBodyMatcher((byte[] b) => b.SequenceEqual(Encoding.UTF8.GetBytes(json))), true}, + + // JSON no match --- + {json, new RequestMessageBodyMatcher((object o) => false), false}, + {json, new RequestMessageBodyMatcher((string s) => false), false}, + {json, new RequestMessageBodyMatcher((byte[] b) => false), false}, + {json, new RequestMessageBodyMatcher(), false }, + + // string match +++ + {str, new RequestMessageBodyMatcher((object o) => o == null), true}, + {str, new RequestMessageBodyMatcher((string s) => s == str), true}, + {str, new RequestMessageBodyMatcher((byte[] b) => b.SequenceEqual(Encoding.UTF8.GetBytes(str))), true}, + + // string no match --- + {str, new RequestMessageBodyMatcher((object o) => false), false}, + {str, new RequestMessageBodyMatcher((string s) => false), false}, + {str, new RequestMessageBodyMatcher((byte[] b) => false), false}, + {str, new RequestMessageBodyMatcher(), false }, + + // binary match +++ + {bytes, new RequestMessageBodyMatcher((object o) => o == null), true}, + {bytes, new RequestMessageBodyMatcher((string s) => s == null), true}, + {bytes, new RequestMessageBodyMatcher((byte[] b) => b.SequenceEqual(bytes)), true}, + + // binary no match --- + {bytes, new RequestMessageBodyMatcher((object o) => false), false}, + {bytes, new RequestMessageBodyMatcher((string s) => false), false}, + {bytes, new RequestMessageBodyMatcher((byte[] b) => false), false}, + {bytes, new RequestMessageBodyMatcher(), false }, + }; + } + } } } diff --git a/test/WireMock.Net.Tests/Util/BodyParserTests.cs b/test/WireMock.Net.Tests/Util/BodyParserTests.cs index 436407c41..d64374cd2 100644 --- a/test/WireMock.Net.Tests/Util/BodyParserTests.cs +++ b/test/WireMock.Net.Tests/Util/BodyParserTests.cs @@ -50,6 +50,22 @@ public async Task BodyParser_Parse_ContentTypeString(string contentType, string Check.That(body.DetectedBodyTypeFromContentType).IsEqualTo(detectedBodyTypeFromContentType); } + [Theory] + [InlineData(new byte[] {34, 97, 34}, BodyType.Json)] + [InlineData(new byte[] {97}, BodyType.String)] + [InlineData(new byte[] {0xFF, 0xD8, 0xFF, 0xE0}, BodyType.Bytes)] + public async Task BodyParser_Parse_DetectedBodyType(byte[] content, BodyType detectedBodyType) + { + // arrange + var memoryStream = new MemoryStream(content); + + // act + var body = await BodyParser.Parse(memoryStream, null); + + // assert + Check.That(body.DetectedBodyType).IsEqualTo(detectedBodyType); + } + [Fact] public async Task BodyParser_Parse_WithUTF8EncodingAndContentTypeMultipart_DetectedBodyTypeEqualsString() {