Proxy : also save multipart as string in mapping file (#264)

* ExactObjectMatcher

* BytesEncodingUtils

* BodyParser

* Encoding.ASCII
This commit is contained in:
Stef Heyenrath
2019-04-05 14:51:29 +02:00
committed by GitHub
parent 409d55350f
commit 3d845d5be5
11 changed files with 379 additions and 34 deletions

View File

@@ -1,5 +1,5 @@
using System.Linq;
using JetBrains.Annotations;
using JetBrains.Annotations;
using System.Linq;
using WireMock.Validation;
namespace WireMock.Matchers
@@ -10,8 +10,9 @@ namespace WireMock.Matchers
/// <seealso cref="IObjectMatcher" />
public class ExactObjectMatcher : IObjectMatcher
{
private readonly object _object;
private readonly byte[] _bytes;
public object ValueAsObject { get; }
public byte[] ValueAsBytes { get; }
/// <inheritdoc cref="IMatcher.MatchBehaviour"/>
public MatchBehaviour MatchBehaviour { get; }
@@ -33,7 +34,7 @@ namespace WireMock.Matchers
{
Check.NotNull(value, nameof(value));
_object = value;
ValueAsObject = value;
MatchBehaviour = matchBehaviour;
}
@@ -54,14 +55,14 @@ namespace WireMock.Matchers
{
Check.NotNull(value, nameof(value));
_bytes = value;
ValueAsBytes = value;
MatchBehaviour = matchBehaviour;
}
/// <inheritdoc cref="IObjectMatcher.IsMatch"/>
public double IsMatch(object input)
{
bool equals = _object != null ? Equals(_object, input) : _bytes.SequenceEqual((byte[])input);
bool equals = ValueAsObject != null ? Equals(ValueAsObject, input) : ValueAsBytes.SequenceEqual((byte[])input);
return MatchBehaviourHelper.Convert(MatchBehaviour, MatchScores.ToScore(equals));
}

View File

@@ -9,7 +9,10 @@ namespace WireMock
internal static class ResponseMessageBuilder
{
private static string ContentTypeJson = "application/json";
private static readonly IDictionary<string, WireMockList<string>> ContentTypeJsonHeaders = new Dictionary<string, WireMockList<string>> { { HttpKnownHeaderNames.ContentType, new WireMockList<string> { ContentTypeJson } } };
private static readonly IDictionary<string, WireMockList<string>> ContentTypeJsonHeaders = new Dictionary<string, WireMockList<string>>
{
{ HttpKnownHeaderNames.ContentType, new WireMockList<string> { ContentTypeJson } }
};
internal static ResponseMessage Create(string message, int statusCode = 200, Guid? guid = null)
{

View File

@@ -1,8 +1,8 @@
using System;
using JetBrains.Annotations;
using SimMetrics.Net;
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using SimMetrics.Net;
using WireMock.Admin.Mappings;
using WireMock.Matchers;
@@ -32,6 +32,10 @@ namespace WireMock.Serialization
case "ExactMatcher":
return new ExactMatcher(matchBehaviour, stringPatterns);
case "ExactObjectMatcher":
var bytePattern = Convert.FromBase64String(stringPatterns[0]);
return new ExactObjectMatcher(matchBehaviour, bytePattern);
case "RegexMatcher":
return new RegexMatcher(matchBehaviour, stringPatterns, matcher.IgnoreCase == true);
@@ -73,20 +77,33 @@ namespace WireMock.Serialization
return null;
}
// If the matcher is a IStringMatcher, get the patterns.
// If the matcher is a IValueMatcher, get the value (can be string or object).
// Else empty array
object[] patterns = matcher is IStringMatcher stringMatcher ?
stringMatcher.GetPatterns().Cast<object>().ToArray() :
matcher is IValueMatcher valueMatcher ? new[] { valueMatcher.Value } :
new object[0];
bool? ignorecase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null;
object[] patterns = new object[0]; // Default empty array
switch (matcher)
{
// If the matcher is a IStringMatcher, get the patterns.
case IStringMatcher stringMatcher:
patterns = stringMatcher.GetPatterns().Cast<object>().ToArray();
break;
// If the matcher is a IValueMatcher, get the value (can be string or object).
case IValueMatcher valueMatcher:
patterns = new[] { valueMatcher.Value };
break;
// If the matcher is a ExactObjectMatcher, get the ValueAsObject or ValueAsBytes.
case ExactObjectMatcher exactObjectMatcher:
patterns = new[] { exactObjectMatcher.ValueAsObject ?? exactObjectMatcher.ValueAsBytes };
break;
}
bool? ignoreCase = matcher is IIgnoreCaseMatcher ignoreCaseMatcher ? ignoreCaseMatcher.IgnoreCase : (bool?)null;
bool? rejectOnMatch = matcher.MatchBehaviour == MatchBehaviour.RejectOnMatch ? true : (bool?)null;
return new MatcherModel
{
RejectOnMatch = rejectOnMatch,
IgnoreCase = ignorecase,
IgnoreCase = ignoreCase,
Name = matcher.Name,
Pattern = patterns.Length == 1 ? patterns.First() : null,
Patterns = patterns.Length > 1 ? patterns : null

View File

@@ -277,13 +277,19 @@ namespace WireMock.Server
}
});
if (requestMessage.BodyData?.DetectedBodyType == BodyType.Json)
switch (requestMessage.BodyData?.DetectedBodyType)
{
request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson));
}
else if (requestMessage.BodyData?.DetectedBodyType == BodyType.String)
{
request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsString));
case BodyType.Json:
request.WithBody(new JsonMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsJson));
break;
case BodyType.String:
request.WithBody(new ExactMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsString));
break;
case BodyType.Bytes:
request.WithBody(new ExactObjectMatcher(MatchBehaviour.AcceptOnMatch, requestMessage.BodyData.BodyAsBytes));
break;
}
var response = Response.Create(responseMessage);

View File

@@ -14,6 +14,7 @@ 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.
@@ -91,9 +92,19 @@ namespace WireMock.Util
DetectedBodyTypeFromContentType = DetectBodyTypeFromContentType(contentType)
};
// In case of MultiPart: never try to read as String but keep as-is
// 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;
}

View File

@@ -0,0 +1,230 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace WireMock.Util
{
/// <summary>
/// Based on:
/// http://utf8checker.codeplex.com
/// https://github.com/0x53A/Mvvm/blob/master/src/Mvvm/src/Utf8Checker.cs
///
/// References:
/// http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335
/// http://www.cl.cam.ac.uk/~mgk25/ucs/ISO-10646-UTF-8.html
/// http://www.unicode.org/versions/corrigendum1.html
/// http://www.ietf.org/rfc/rfc2279.txt
/// </summary>
public static class BytesEncodingUtils
{
/// <summary>
/// Tries the get the Encoding from an array of bytes.
/// </summary>
/// <param name="bytes">The bytes.</param>
/// <param name="encoding">The output encoding.</param>
public static bool TryGetEncoding(byte[] bytes, out Encoding encoding)
{
encoding = null;
if (bytes.All(b => b < 80))
{
encoding = Encoding.ASCII;
return true;
}
if (StartsWith(bytes, new byte[] { 0xff, 0xfe, 0x00, 0x00 }))
{
encoding = Encoding.UTF32;
return true;
}
if (StartsWith(bytes, new byte[] { 0xfe, 0xff }))
{
encoding = Encoding.BigEndianUnicode;
return true;
}
if (StartsWith(bytes, new byte[] { 0xff, 0xfe }))
{
encoding = Encoding.Unicode;
return true;
}
if (StartsWith(bytes, new byte[] { 0xef, 0xbb, 0xbf }))
{
encoding = Encoding.UTF8;
return true;
}
if (IsUtf8(bytes, bytes.Length))
{
encoding = new UTF8Encoding(false);
return true;
}
return false;
}
private static bool StartsWith(IEnumerable<byte> data, IReadOnlyCollection<byte> other)
{
byte[] arraySelf = data.Take(other.Count).ToArray();
return other.SequenceEqual(arraySelf);
}
private static bool IsUtf8(IReadOnlyList<byte> buffer, int length)
{
int position = 0;
int bytes = 0;
while (position < length)
{
if (!IsValid(buffer, position, length, ref bytes))
{
return false;
}
position += bytes;
}
return true;
}
#pragma warning disable S3776 // Cognitive Complexity of methods should not be too high
private static bool IsValid(IReadOnlyList<byte> buffer, int position, int length, ref int bytes)
{
if (length > buffer.Count)
{
throw new ArgumentException("Invalid length");
}
if (position > length - 1)
{
bytes = 0;
return true;
}
byte ch = buffer[position];
if (ch <= 0x7F)
{
bytes = 1;
return true;
}
if (ch >= 0xc2 && ch <= 0xdf)
{
if (position >= length - 2)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 2;
return true;
}
if (ch == 0xe0)
{
if (position >= length - 3)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0xa0 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 3;
return true;
}
if (ch >= 0xe1 && ch <= 0xef)
{
if (position >= length - 3)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 3;
return true;
}
if (ch == 0xf0)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x90 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
if (ch == 0xf4)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0x8f ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
if (ch >= 0xf1 && ch <= 0xf3)
{
if (position >= length - 4)
{
bytes = 0;
return false;
}
if (buffer[position + 1] < 0x80 || buffer[position + 1] > 0xbf ||
buffer[position + 2] < 0x80 || buffer[position + 2] > 0xbf ||
buffer[position + 3] < 0x80 || buffer[position + 3] > 0xbf)
{
bytes = 0;
return false;
}
bytes = 4;
return true;
}
return false;
}
}
#pragma warning restore S3776 // Cognitive Complexity of methods should not be too high
}