//----------------------------------------------------------------------- // // Copyright (c) Outercurve Foundation. All rights reserved. // //----------------------------------------------------------------------- namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; using System.Reflection; using System.Xml; using DotNetOpenAuth.Messaging.Reflection; using Validation; /// /// Serializes/deserializes OAuth messages for/from transit. /// internal class MessageSerializer { /// /// The specific -derived type /// that will be serialized and deserialized using this class. /// private readonly Type messageType; /// /// Initializes a new instance of the MessageSerializer class. /// /// The specific -derived type /// that will be serialized and deserialized using this class. private MessageSerializer(Type messageType) { RequiresEx.NotNullSubtype(messageType, "messageType"); this.messageType = messageType; } /// /// Creates or reuses a message serializer for a given message type. /// /// The type of message that will be serialized/deserialized. /// A message serializer for the given message type. internal static MessageSerializer Get(Type messageType) { RequiresEx.NotNullSubtype(messageType, "messageType"); return new MessageSerializer(messageType); } /// /// Reads JSON as a flat dictionary into a message. /// /// The message dictionary to fill with the JSON-deserialized data. /// The JSON reader. internal static void DeserializeJsonAsFlatDictionary(IDictionary messageDictionary, XmlDictionaryReader reader) { Requires.NotNull(messageDictionary, "messageDictionary"); Requires.NotNull(reader, "reader"); reader.Read(); // one extra one to skip the root node. while (reader.Read()) { if (reader.NodeType == XmlNodeType.EndElement) { // This is likely the closing tag. continue; } string key = reader.Name; reader.Read(); string value = reader.ReadContentAsString(); messageDictionary[key] = value; } } /// /// Reads the data from a message instance and writes an XML/JSON encoding of it. /// /// The message to be serialized. /// The writer to use for the serialized form. /// /// Use /// to create the instance capable of emitting JSON. /// [Pure] internal static void Serialize(MessageDictionary messageDictionary, XmlDictionaryWriter writer) { Requires.NotNull(messageDictionary, "messageDictionary"); Requires.NotNull(writer, "writer"); writer.WriteStartElement("root"); writer.WriteAttributeString("type", "object"); foreach (var pair in messageDictionary) { bool include = false; string type = "string"; MessagePart partDescription; if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { Assumes.True(partDescription != null); if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { include = true; Type formattingType = partDescription.PreferredFormattingType; if (IsNumeric(formattingType)) { type = "number"; } else if (formattingType.IsAssignableFrom(typeof(bool))) { type = "boolean"; } } } else { // This is extra data. We always write it out. include = true; } if (include) { writer.WriteStartElement(pair.Key); writer.WriteAttributeString("type", type); writer.WriteString(pair.Value); writer.WriteEndElement(); } } writer.WriteEndElement(); } /// /// Reads XML/JSON into a message dictionary. /// /// The message to deserialize into. /// The XML/JSON to read into the message. /// Thrown when protocol rules are broken by the incoming message. /// /// Use /// to create the instance capable of reading JSON. /// internal static void Deserialize(MessageDictionary messageDictionary, XmlDictionaryReader reader) { Requires.NotNull(messageDictionary, "messageDictionary"); Requires.NotNull(reader, "reader"); DeserializeJsonAsFlatDictionary(messageDictionary, reader); // Make sure all the required parts are present and valid. messageDictionary.Description.EnsureMessagePartsPassBasicValidation(messageDictionary); messageDictionary.Message.EnsureValidMessage(); } /// /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message. /// /// The message to be serialized. /// The dictionary of values to send for the message. [Pure] [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Parallel design with Deserialize method.")] internal IDictionary Serialize(MessageDictionary messageDictionary) { Requires.NotNull(messageDictionary, "messageDictionary"); // Rather than hand back the whole message dictionary (which // includes keys with blank values), create a new dictionary // that only has required keys, and optional keys whose // values are not empty (or default). var result = new Dictionary(); foreach (var pair in messageDictionary) { MessagePart partDescription; if (messageDictionary.Description.Mapping.TryGetValue(pair.Key, out partDescription)) { Assumes.True(partDescription != null); if (partDescription.IsRequired || partDescription.IsNondefaultValueSet(messageDictionary.Message)) { result.Add(pair.Key, pair.Value); } } else { // This is extra data. We always write it out. result.Add(pair.Key, pair.Value); } } return result; } /// /// Reads name=value pairs into a message. /// /// The name=value pairs that were read in from the transport. /// The message to deserialize into. /// Thrown when protocol rules are broken by the incoming message. internal void Deserialize(IDictionary fields, MessageDictionary messageDictionary) { Requires.NotNull(fields, "fields"); Requires.NotNull(messageDictionary, "messageDictionary"); var messageDescription = messageDictionary.Description; // Before we deserialize the message, make sure all the required parts are present. messageDescription.EnsureMessagePartsPassBasicValidation(fields); try { foreach (var pair in fields) { messageDictionary[pair.Key] = pair.Value; } } catch (ArgumentException ex) { throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorDeserializingMessage, this.messageType.Name); } messageDictionary.Message.EnsureValidMessage(); var originalPayloadMessage = messageDictionary.Message as IMessageOriginalPayload; if (originalPayloadMessage != null) { originalPayloadMessage.OriginalPayload = fields; } } /// /// Determines whether the specified type is numeric. /// /// The type to test. /// /// true if the specified type is numeric; otherwise, false. /// private static bool IsNumeric(Type type) { return type.IsAssignableFrom(typeof(double)) || type.IsAssignableFrom(typeof(float)) || type.IsAssignableFrom(typeof(short)) || type.IsAssignableFrom(typeof(int)) || type.IsAssignableFrom(typeof(long)) || type.IsAssignableFrom(typeof(ushort)) || type.IsAssignableFrom(typeof(uint)) || type.IsAssignableFrom(typeof(ulong)); } #if CONTRACTS_FULL /// /// Verifies conditions that should be true for any valid state of this object. /// [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Called by code contracts.")] [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Called by code contracts.")] [ContractInvariantMethod] private void ObjectInvariant() { Contract.Invariant(this.messageType != null); } #endif } }