From b30e4faab611c1438f3227d167589a7d71bd81fc Mon Sep 17 00:00:00 2001 From: Stef Heyenrath Date: Sun, 19 Mar 2023 10:21:47 +0100 Subject: [PATCH] Fix issue with application/x-www-form-urlencoded and ExactMatcher (#907) --- .../Http/HttpRequestMessageHelper.cs | 5 +-- .../Request/RequestMessageBodyMatcher.cs | 5 +-- .../Owin/Mappers/OwinResponseMapper.cs | 1 + .../Serialization/LogEntryMapper.cs | 5 +++ .../Serialization/MappingConverter.cs | 2 ++ .../Serialization/ProxyMappingConverter.cs | 1 + .../Serialization/WebhookMapper.cs | 1 + .../Server/WireMockServer.Admin.cs | 14 +++++--- .../Server/WireMockServer.AdminFiles.cs | 2 +- src/WireMock.Net/Transformers/Transformer.cs | 22 ++++++++---- .../WireMockServerTests.WithBody.cs | 34 ++++++++++++++++++- 11 files changed, 74 insertions(+), 18 deletions(-) diff --git a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs index 71428546..44fb3745 100644 --- a/src/WireMock.Net/Http/HttpRequestMessageHelper.cs +++ b/src/WireMock.Net/Http/HttpRequestMessageHelper.cs @@ -28,7 +28,7 @@ internal static class HttpRequestMessageHelper switch (requestMessage.BodyData?.DetectedBodyType) { case BodyType.Bytes: - httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes, contentType); + httpRequestMessage.Content = ByteArrayContentHelper.Create(requestMessage.BodyData.BodyAsBytes!, contentType); break; case BodyType.Json: @@ -36,7 +36,8 @@ internal static class HttpRequestMessageHelper break; case BodyType.String: - httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString, contentType); + case BodyType.FormUrlEncoded: + httpRequestMessage.Content = StringContentHelper.Create(requestMessage.BodyData.BodyAsString!, contentType); break; } diff --git a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs index cf46d77a..580902f2 100644 --- a/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs +++ b/src/WireMock.Net/Matchers/Request/RequestMessageBodyMatcher.cs @@ -157,6 +157,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher { case BodyType.Json: case BodyType.String: + case BodyType.FormUrlEncoded: return notNullOrEmptyMatcher.IsMatch(requestMessage.BodyData.BodyAsString); case BodyType.Bytes: @@ -171,7 +172,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher { // If the body is a byte array, try to match. var detectedBodyType = requestMessage.BodyData?.DetectedBodyType; - if (detectedBodyType is BodyType.Bytes or BodyType.String) + if (detectedBodyType is BodyType.Bytes or BodyType.String or BodyType.FormUrlEncoded) { return exactObjectMatcher.IsMatch(requestMessage.BodyData?.BodyAsBytes); } @@ -197,7 +198,7 @@ public class RequestMessageBodyMatcher : IRequestMatcher if (matcher is IStringMatcher stringMatcher) { // If the body is a Json or a String, use the BodyAsString to match on. - if (requestMessage?.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String) + if (requestMessage?.BodyData?.DetectedBodyType is BodyType.Json or BodyType.String or BodyType.FormUrlEncoded) { return stringMatcher.IsMatch(requestMessage.BodyData.BodyAsString); } diff --git a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs index c26c2f78..3f0e1394 100644 --- a/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs +++ b/src/WireMock.Net/Owin/Mappers/OwinResponseMapper.cs @@ -130,6 +130,7 @@ namespace WireMock.Owin.Mappers switch (responseMessage.BodyData?.DetectedBodyType) { case BodyType.String: + case BodyType.FormUrlEncoded: return (responseMessage.BodyData.Encoding ?? _utf8NoBom).GetBytes(responseMessage.BodyData.BodyAsString!); case BodyType.Json: diff --git a/src/WireMock.Net/Serialization/LogEntryMapper.cs b/src/WireMock.Net/Serialization/LogEntryMapper.cs index d2dfe243..7fbc84b4 100644 --- a/src/WireMock.Net/Serialization/LogEntryMapper.cs +++ b/src/WireMock.Net/Serialization/LogEntryMapper.cs @@ -44,6 +44,7 @@ internal class LogEntryMapper switch (logEntry.RequestMessage.BodyData.DetectedBodyType) { case BodyType.String: + case BodyType.FormUrlEncoded: logRequestModel.Body = logEntry.RequestMessage.BodyData.BodyAsString; break; @@ -120,6 +121,7 @@ internal class LogEntryMapper switch (logEntry.ResponseMessage.BodyData!.DetectedBodyType) { case BodyType.String: + case BodyType.FormUrlEncoded: if (!string.IsNullOrEmpty(logEntry.ResponseMessage.BodyData.IsFuncUsed) && _options.DoNotSaveDynamicResponseInLogEntry == true) { logResponseModel.Body = logEntry.ResponseMessage.BodyData.IsFuncUsed; @@ -142,6 +144,9 @@ internal class LogEntryMapper logResponseModel.BodyAsFile = logEntry.ResponseMessage.BodyData.BodyAsFile; logResponseModel.BodyAsFileIsCached = logEntry.ResponseMessage.BodyData.BodyAsFileIsCached; break; + + default: + break; } } diff --git a/src/WireMock.Net/Serialization/MappingConverter.cs b/src/WireMock.Net/Serialization/MappingConverter.cs index 62356cae..cba301c4 100644 --- a/src/WireMock.Net/Serialization/MappingConverter.cs +++ b/src/WireMock.Net/Serialization/MappingConverter.cs @@ -131,6 +131,7 @@ internal class MappingConverter switch (response.ResponseMessage.BodyData.DetectedBodyType) { case BodyType.String: + case BodyType.FormUrlEncoded: sb.AppendLine($" .WithBody(\"{response.ResponseMessage.BodyData.BodyAsString}\")"); break; } @@ -325,6 +326,7 @@ internal class MappingConverter switch (response.ResponseMessage.BodyData?.DetectedBodyType) { case BodyType.String: + case BodyType.FormUrlEncoded: mappingModel.Response.Body = response.ResponseMessage.BodyData.BodyAsString; break; diff --git a/src/WireMock.Net/Serialization/ProxyMappingConverter.cs b/src/WireMock.Net/Serialization/ProxyMappingConverter.cs index 1531a0ce..49a6f6ea 100644 --- a/src/WireMock.Net/Serialization/ProxyMappingConverter.cs +++ b/src/WireMock.Net/Serialization/ProxyMappingConverter.cs @@ -141,6 +141,7 @@ internal class ProxyMappingConverter break; case BodyType.String: + case BodyType.FormUrlEncoded: newRequest.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, true, throwExceptionWhenMatcherFails, MatchOperator.Or, requestMessage.BodyData.BodyAsString!)); break; diff --git a/src/WireMock.Net/Serialization/WebhookMapper.cs b/src/WireMock.Net/Serialization/WebhookMapper.cs index 5550190f..bcd45c9f 100644 --- a/src/WireMock.Net/Serialization/WebhookMapper.cs +++ b/src/WireMock.Net/Serialization/WebhookMapper.cs @@ -97,6 +97,7 @@ internal static class WebhookMapper switch (webhook.Request.BodyData.DetectedBodyType) { case BodyType.String: + case BodyType.FormUrlEncoded: model.Request.Body = webhook.Request.BodyData.BodyAsString; break; diff --git a/src/WireMock.Net/Server/WireMockServer.Admin.cs b/src/WireMock.Net/Server/WireMockServer.Admin.cs index 56e7d2b4..90294f68 100644 --- a/src/WireMock.Net/Server/WireMockServer.Admin.cs +++ b/src/WireMock.Net/Server/WireMockServer.Admin.cs @@ -767,14 +767,18 @@ public partial class WireMockServer private static T DeserializeObject(IRequestMessage requestMessage) where T : new() { - return requestMessage.BodyData?.DetectedBodyType switch + switch (requestMessage.BodyData?.DetectedBodyType) { - BodyType.String => JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString!), + case BodyType.String: + case BodyType.FormUrlEncoded: + return JsonUtils.DeserializeObject(requestMessage.BodyData.BodyAsString!); - BodyType.Json when requestMessage.BodyData?.BodyAsJson != null => ((JObject)requestMessage.BodyData.BodyAsJson).ToObject()!, + case BodyType.Json when requestMessage.BodyData?.BodyAsJson != null: + return ((JObject)requestMessage.BodyData.BodyAsJson).ToObject()!; - _ => throw new NotSupportedException() - }; + default: + throw new NotSupportedException(); + } } private static T[] DeserializeRequestMessageToArray(IRequestMessage requestMessage) diff --git a/src/WireMock.Net/Server/WireMockServer.AdminFiles.cs b/src/WireMock.Net/Server/WireMockServer.AdminFiles.cs index e7b0f4be..6541cb78 100644 --- a/src/WireMock.Net/Server/WireMockServer.AdminFiles.cs +++ b/src/WireMock.Net/Server/WireMockServer.AdminFiles.cs @@ -65,7 +65,7 @@ namespace WireMock.Server } }; - if (BytesEncodingUtils.TryGetEncoding(bytes, out Encoding encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any()) + if (BytesEncodingUtils.TryGetEncoding(bytes, out var encoding) && FileBodyIsString.Select(x => x.Equals(encoding)).Any()) { response.BodyData.DetectedBodyType = BodyType.String; response.BodyData.BodyAsString = encoding.GetString(bytes); diff --git a/src/WireMock.Net/Transformers/Transformer.cs b/src/WireMock.Net/Transformers/Transformer.cs index 74cd6992..bea06a52 100644 --- a/src/WireMock.Net/Transformers/Transformer.cs +++ b/src/WireMock.Net/Transformers/Transformer.cs @@ -85,7 +85,7 @@ internal class Transformer : ITransformer { responseMessage.BodyData = TransformBodyData(transformerContext, options, model, original.BodyData, useTransformerForBodyAsFile); - if (original.BodyData.DetectedBodyType == BodyType.String) + if (original.BodyData.DetectedBodyType is BodyType.String or BodyType.FormUrlEncoded) { responseMessage.BodyOriginal = original.BodyData.BodyAsString; } @@ -123,13 +123,21 @@ internal class Transformer : ITransformer private IBodyData? TransformBodyData(ITransformerContext transformerContext, ReplaceNodeOptions options, TransformModel model, IBodyData original, bool useTransformerForBodyAsFile) { - return original.DetectedBodyType switch + switch (original.DetectedBodyType) { - BodyType.Json => TransformBodyAsJson(transformerContext, options, model, original), - BodyType.File => TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile), - BodyType.String => TransformBodyAsString(transformerContext, model, original), - _ => null - }; + case BodyType.Json: + return TransformBodyAsJson(transformerContext, options, model, original); + + case BodyType.File: + return TransformBodyAsFile(transformerContext, model, original, useTransformerForBodyAsFile); + + case BodyType.String: + case BodyType.FormUrlEncoded: + return TransformBodyAsString(transformerContext, model, original); + + default: + return null; + } } private static IDictionary> TransformHeaders(ITransformerContext transformerContext, TransformModel model, IDictionary>? original) diff --git a/test/WireMock.Net.Tests/WireMockServerTests.WithBody.cs b/test/WireMock.Net.Tests/WireMockServerTests.WithBody.cs index 65457bec..a891dfd3 100644 --- a/test/WireMock.Net.Tests/WireMockServerTests.WithBody.cs +++ b/test/WireMock.Net.Tests/WireMockServerTests.WithBody.cs @@ -94,6 +94,38 @@ public partial class WireMockServerTests server.Stop(); } -} + [Fact] + public async Task WireMockServer_WithBodyAsFormUrlEncoded_Using_PostAsync_And_WithExactMatcher() + { + // Arrange + var server = WireMockServer.Start(); + server.Given( + Request.Create() + .UsingPost() + .WithPath("/foo") + .WithHeader("Content-Type", "application/x-www-form-urlencoded") + .WithBody(new ExactMatcher("name=John+Doe&email=johndoe%40example.com") + ) + ) + .RespondWith( + Response.Create() + ); + + // Act + var content = new FormUrlEncodedContent(new[] + { + new KeyValuePair("name", "John Doe"), + new KeyValuePair("email", "johndoe@example.com") + }); + var response = await new HttpClient() + .PostAsync($"{server.Url}/foo", content) + .ConfigureAwait(false); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + + server.Stop(); + } +} #endif \ No newline at end of file