Fix case sensitivity issues

- Web hook method must be exactly "OPTIONS"
- Kafka header keys are case sensitive

Fixes #90

Signed-off-by: Jon Skeet <jonskeet@google.com>
This commit is contained in:
Jon Skeet 2021-05-21 15:37:46 +01:00 committed by Jon Skeet
parent e197ef0e36
commit 62da9c0779
4 changed files with 31 additions and 17 deletions

View File

@ -23,8 +23,6 @@ namespace CloudNative.CloudEvents.Kafka
private const string KafkaContentTypeAttributeName = "content-type"; private const string KafkaContentTypeAttributeName = "content-type";
private const string SpecVersionKafkaHeader = KafkaHeaderPrefix + "specversion"; private const string SpecVersionKafkaHeader = KafkaHeaderPrefix + "specversion";
// TODO: Avoid all the byte[] -> string conversions? If we didn't care about case-sensitivity, we could prepare byte arrays to perform comparisons with.
/// <summary> /// <summary>
/// Indicates whether this message holds a single CloudEvent. /// Indicates whether this message holds a single CloudEvent.
/// </summary> /// </summary>
@ -35,7 +33,7 @@ namespace CloudNative.CloudEvents.Kafka
/// <returns>true, if the request is a CloudEvent</returns> /// <returns>true, if the request is a CloudEvent</returns>
public static bool IsCloudEvent(this Message<string, byte[]> message) => public static bool IsCloudEvent(this Message<string, byte[]> message) =>
GetHeaderValue(message, SpecVersionKafkaHeader) is object || GetHeaderValue(message, SpecVersionKafkaHeader) is object ||
MimeUtilities.IsCloudEventsContentType(ExtractContentType(message)); MimeUtilities.IsCloudEventsContentType(GetHeaderValue(message, KafkaContentTypeAttributeName));
/// <summary> /// <summary>
/// Converts this Kafka message into a CloudEvent object. /// Converts this Kafka message into a CloudEvent object.
@ -66,7 +64,7 @@ namespace CloudNative.CloudEvents.Kafka
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
var contentType = ExtractContentType(message); var contentType = GetHeaderValue(message, KafkaContentTypeAttributeName);
CloudEvent cloudEvent; CloudEvent cloudEvent;
@ -78,11 +76,10 @@ namespace CloudNative.CloudEvents.Kafka
else else
{ {
// Binary mode // Binary mode
if (!(GetHeaderValue(message, SpecVersionKafkaHeader) is byte[] versionIdBytes)) if (!(GetHeaderValue(message, SpecVersionKafkaHeader) is string versionId))
{ {
throw new ArgumentException("Request is not a CloudEvent"); throw new ArgumentException("Request is not a CloudEvent");
} }
string versionId = Encoding.UTF8.GetString(versionIdBytes);
CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionId) CloudEventsSpecVersion version = CloudEventsSpecVersion.FromVersionId(versionId)
?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(message)); ?? throw new ArgumentException($"Unknown CloudEvents spec version '{versionId}'", nameof(message));
@ -115,12 +112,6 @@ namespace CloudNative.CloudEvents.Kafka
return Validation.CheckCloudEventArgument(cloudEvent, nameof(message)); return Validation.CheckCloudEventArgument(cloudEvent, nameof(message));
} }
private static string ExtractContentType(Message<string, byte[]> message)
{
var headerValue = GetHeaderValue(message, KafkaContentTypeAttributeName);
return headerValue is null ? null : Encoding.UTF8.GetString(headerValue);
}
private static void InitPartitioningKey(Message<string, byte[]> message, CloudEvent cloudEvent) private static void InitPartitioningKey(Message<string, byte[]> message, CloudEvent cloudEvent)
{ {
if (!string.IsNullOrEmpty(message.Key)) if (!string.IsNullOrEmpty(message.Key))
@ -129,9 +120,13 @@ namespace CloudNative.CloudEvents.Kafka
} }
} }
private static byte[] GetHeaderValue(MessageMetadata message, string headerName) => /// <summary>
message.Headers.FirstOrDefault(x => string.Equals(x.Key, headerName, StringComparison.InvariantCultureIgnoreCase)) /// Returns the last header value with the given name, decoded using UTF-8, or null if there is no such header.
?.GetValueBytes(); /// </summary>
private static string GetHeaderValue(MessageMetadata message, string headerName) =>
Validation.CheckNotNull(message, nameof(message)).Headers is null
? null
: message.Headers.TryGetLastBytes(headerName, out var bytes) ? Encoding.UTF8.GetString(bytes) : null;
/// <summary> /// <summary>
/// Converts a CloudEvent to a Kafka message. /// Converts a CloudEvent to a Kafka message.

View File

@ -108,7 +108,7 @@ namespace CloudNative.CloudEvents.Http
/// Indicates whether this HttpListenerRequest is a web hook validation request /// Indicates whether this HttpListenerRequest is a web hook validation request
/// </summary> /// </summary>
public static bool IsWebHookValidationRequest(this HttpRequestMessage httpRequestMessage) => public static bool IsWebHookValidationRequest(this HttpRequestMessage httpRequestMessage) =>
httpRequestMessage.Method.Method.Equals("options", StringComparison.InvariantCultureIgnoreCase) && httpRequestMessage.Method.Method == "OPTIONS" &&
httpRequestMessage.Headers.Contains("WebHook-Request-Origin"); httpRequestMessage.Headers.Contains("WebHook-Request-Origin");
/// <summary> /// <summary>

View File

@ -140,7 +140,7 @@ namespace CloudNative.CloudEvents.Http
/// Indicates whether this HttpListenerRequest is a web hook validation request /// Indicates whether this HttpListenerRequest is a web hook validation request
/// </summary> /// </summary>
public static bool IsWebHookValidationRequest(this HttpListenerRequest httpRequestMessage) => public static bool IsWebHookValidationRequest(this HttpListenerRequest httpRequestMessage) =>
httpRequestMessage.HttpMethod.Equals("options", StringComparison.InvariantCultureIgnoreCase) && httpRequestMessage.HttpMethod == "OPTIONS" &&
httpRequestMessage.Headers["WebHook-Request-Origin"] is object; httpRequestMessage.Headers["WebHook-Request-Origin"] is object;
/// <summary> /// <summary>

View File

@ -17,6 +17,25 @@ namespace CloudNative.CloudEvents.Kafka.UnitTests
{ {
public class KafkaTest public class KafkaTest
{ {
[Theory]
[InlineData("content-type", "application/cloudevents", true)]
[InlineData("content-type", "APPLICATION/CLOUDEVENTS", true)]
[InlineData("CONTENT-TYPE", "application/cloudevents", false)]
[InlineData("ce_specversion", "1.0", true)]
[InlineData("CE_SPECVERSION", "1.0", false)]
public void IsCloudEvent(string headerName, string headerValue, bool expectedResult)
{
var message = new Message<string, byte[]>
{
Headers = new Headers { { headerName, Encoding.UTF8.GetBytes(headerValue) } }
};
Assert.Equal(expectedResult, message.IsCloudEvent());
}
[Fact]
public void IsCloudEvent_NoHeaders() =>
Assert.False(new Message<string, byte[]>().IsCloudEvent());
[Fact] [Fact]
public void KafkaStructuredMessageTest() public void KafkaStructuredMessageTest()
{ {