using System; using System.Collections.Generic; using System.Globalization; using System.IO; using System.Linq; using System.Text; using Amazon.Runtime; using Amazon.Runtime.Internal.Util; using Amazon.SQS.Model; using Amazon.Runtime.Internal; namespace Amazon.SQS.Internal { /// /// Custom pipeline handler /// public class ValidationResponseHandler : PipelineHandler { /// /// Calls the post invoke logic after calling the next handler /// in the pipeline. /// /// The execution context which contains both the /// requests and response context. public override void InvokeSync(IExecutionContext executionContext) { base.InvokeSync(executionContext); PostInvoke(executionContext); } #if AWS_ASYNC_API /// /// Calls the and post invoke logic after calling the next handler /// in the pipeline. /// /// The response type for the current request. /// The execution context, it contains the /// request and response context. /// A task that represents the asynchronous operation. public override async System.Threading.Tasks.Task InvokeAsync(IExecutionContext executionContext) { var response = await base.InvokeAsync(executionContext).ConfigureAwait(false); PostInvoke(executionContext); return response; } #elif AWS_APM_API /// /// Calls the PostInvoke methods after calling the next handler /// in the pipeline. /// /// The execution context, it contains the /// request and response context. protected override void InvokeAsyncCallback(IAsyncExecutionContext executionContext) { // Process the response if an exception hasn't occured if (executionContext.ResponseContext.AsyncResult.Exception == null) { PostInvoke(ExecutionContext.CreateFromAsyncContext(executionContext)); } base.InvokeAsyncCallback(executionContext); } #endif /// /// Custom pipeline handler /// /// protected virtual void PostInvoke(IExecutionContext executionContext) { var request = executionContext.RequestContext.Request; var response = executionContext.ResponseContext.Response; ReceiveMessageResponse receiveMessageResponse = response as ReceiveMessageResponse; if (receiveMessageResponse != null) { ValidateReceiveMessage(receiveMessageResponse); } SendMessageResponse sendMessageResponse = response as SendMessageResponse; SendMessageRequest sendMessageRequest = request.OriginalRequest as SendMessageRequest; if (sendMessageRequest != null && sendMessageResponse != null) { ValidateSendMessage(sendMessageRequest, sendMessageResponse); } SendMessageBatchRequest sendMessageBatchRequest = request.OriginalRequest as SendMessageBatchRequest; SendMessageBatchResponse sendMessageBatchResponse = response as SendMessageBatchResponse; if (sendMessageBatchRequest != null && sendMessageBatchResponse != null) { ValidateSendMessageBatch(sendMessageBatchRequest, sendMessageBatchResponse); } } private const byte STRING_TYPE = 1; private const byte BINARY_TYPE = 2; private const byte STRING_LIST_TYPE = 3; private const byte BINARY_LIST_TYPE = 4; private class SQSWriter : IDisposable { private BinaryWriter writer; private Encoding utf8 = Encoding.UTF8; private bool shouldReverseInts; public SQSWriter(Stream stream) { writer = new BinaryWriter(stream); shouldReverseInts = BitConverter.IsLittleEndian; } public void Write(string value) { if (value == null) throw new ArgumentNullException("value"); var valueUtf8 = utf8.GetBytes(value); Write(valueUtf8.Length); Write(valueUtf8); } public void Write(MemoryStream ms) { if (ms == null) throw new ArgumentNullException("ms"); Write((int)ms.Length); var bytes = ms.ToArray(); Write(bytes); } public void Write(int value) { var bytes = BitConverter.GetBytes(value); if (shouldReverseInts) bytes = bytes.Reverse().ToArray(); Write(bytes); } public void Write(byte value) { writer.Write(value); } public void Write(byte[] bytes) { writer.Write(bytes); } public void Dispose() { #if PCL || NETSTANDARD writer.Dispose(); #else writer.Close(); #endif } } /// /// Calculate the MD5 of the message attributes /// /// /// public static string CalculateMD5(Dictionary attributes) { List> orderedAttributes = new List>(); foreach (var kvp in attributes) { orderedAttributes.Add(kvp); } orderedAttributes.Sort(delegate(KeyValuePair kvp1, KeyValuePair kvp2) { return string.CompareOrdinal(kvp1.Key, kvp2.Key); }); var ms = new MemoryStream(); using (var writer = new SQSWriter(ms)) { foreach (var kvp in orderedAttributes) { var name = kvp.Key; var value = kvp.Value; writer.Write(name); writer.Write(value.DataType); if (value.StringValue != null) { writer.Write(STRING_TYPE); writer.Write(value.StringValue); } else if (value.BinaryValue != null) { writer.Write(BINARY_TYPE); writer.Write(value.BinaryValue); } else if (value.StringListValues != null) { writer.Write(STRING_LIST_TYPE); foreach (var item in value.StringListValues) { writer.Write(item); } } else if (value.BinaryListValues != null) { writer.Write(BINARY_LIST_TYPE); foreach (var item in value.BinaryListValues) { writer.Write(item); } } } } var bytes = ms.ToArray(); return CalculateMD5(bytes); } /// /// Calculate the MD5 /// /// /// public static string CalculateMD5(string message) { var messageBytes = System.Text.Encoding.UTF8.GetBytes(message); return CalculateMD5(messageBytes); } /// /// Calculate the MD5 /// /// /// public static string CalculateMD5(byte[] bytes) { var md5Hash = Amazon.Util.CryptoUtilFactory.CryptoInstance.ComputeMD5Hash(bytes); var calculatedMd5 = BitConverter.ToString(md5Hash).Replace("-", string.Empty).ToLowerInvariant(); return calculatedMd5; } /// /// Compare the MD5 /// /// /// /// public static bool CompareMD5(string message, string md5FromService) { var calculatedMd5 = CalculateMD5(message); return (string.Equals(calculatedMd5, md5FromService, StringComparison.OrdinalIgnoreCase)); } /// /// Compare the MD5 /// /// /// /// public static bool CompareMD5(Dictionary attributes, string md5FromService) { var calculatedMd5 = CalculateMD5(attributes); return (string.Equals(calculatedMd5, md5FromService, StringComparison.OrdinalIgnoreCase)); } /// /// Validate the MD5 /// /// /// public static void ValidateMD5(string message, string md5FromService) { if (!CompareMD5(message, md5FromService)) throw new AmazonSQSException("MD5 hash mismatch"); } /// /// Validate the MD5 /// /// /// public static void ValidateMD5(Dictionary attributes, string md5FromService) { if (!CompareMD5(attributes, md5FromService)) throw new AmazonSQSException("Attribute MD5 hash mismatch"); } /// /// Validate the MD5 /// /// /// /// public static void ValidateMD5(string message, string messageId, string md5FromService) { if (!CompareMD5(message, md5FromService)) throw new AmazonSQSException(string.Format(CultureInfo.InvariantCulture, "MD5 hash mismatch for message id {0}", messageId)); } /// /// Validate the MD5 /// /// /// /// public static void ValidateMD5(Dictionary attributes, string messageId, string md5FromService) { if (!CompareMD5(attributes, md5FromService)) throw new AmazonSQSException(string.Format(CultureInfo.InvariantCulture, "Attribute MD5 hash mismatch for message id {0}", messageId)); } /// /// Validate the MD5 /// /// public static void ValidateMD5(Message message) { ValidateMD5(message.Body, message.MessageId, message.MD5OfBody); if (message.MessageAttributes != null && message.MessageAttributes.Count > 0 && !string.IsNullOrEmpty(message.MD5OfMessageAttributes)) ValidateMD5(message.MessageAttributes, message.MessageId, message.MD5OfMessageAttributes); } /// /// Validate the Message /// /// public static void ValidateReceiveMessage(ReceiveMessageResponse response) { if (response != null && response.Messages != null && response.Messages.Count > 0) { foreach (Message message in response.Messages) { ValidateMD5(message); } } } /// /// Validate the Message /// /// /// public static void ValidateSendMessage(SendMessageRequest request, SendMessageResponse response) { if (request != null && response != null && request.MessageBody != null && !string.IsNullOrEmpty(request.MessageBody) && !string.IsNullOrEmpty(response.MD5OfMessageBody)) { ValidateMD5(request.MessageBody, response.MD5OfMessageBody); } if (request != null && response != null && request.MessageAttributes != null && request.MessageAttributes.Count > 0 && !string.IsNullOrEmpty(response.MD5OfMessageAttributes)) { ValidateMD5(request.MessageAttributes, response.MD5OfMessageAttributes); } } /// /// Validate the Message /// /// /// public static void ValidateSendMessageBatch(SendMessageBatchRequest request, SendMessageBatchResponse response) { if (response != null && response.Successful != null && response.Successful.Count > 0) { Dictionary requestMessages = request.Entries.ToDictionary(entry => entry.Id, StringComparer.Ordinal); List resultEntries = response.Successful; foreach (SendMessageBatchResultEntry entry in resultEntries) { var message = requestMessages[entry.Id]; string id = entry.MessageId; var body = message.MessageBody; var bodyMd5 = entry.MD5OfMessageBody; ValidateMD5(body, id, bodyMd5); var attributes = message.MessageAttributes; var attributesMd5 = entry.MD5OfMessageAttributes; if (attributes != null && attributes.Count > 0 && !string.IsNullOrEmpty(attributesMd5)) ValidateMD5(attributes, id, attributesMd5); } } } } }