diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-11-06 08:12:32 -0800 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-11-06 08:12:32 -0800 |
commit | 15ffce6c077b52339dd3ba1b792b2005c61c7e84 (patch) | |
tree | 82f3c027917d9c089fa4448e53c65d012677dac1 /src | |
parent | 462e19abd9034c11a12cad30e9899740f2bef8ff (diff) | |
download | DotNetOpenAuth-15ffce6c077b52339dd3ba1b792b2005c61c7e84.zip DotNetOpenAuth-15ffce6c077b52339dd3ba1b792b2005c61c7e84.tar.gz DotNetOpenAuth-15ffce6c077b52339dd3ba1b792b2005c61c7e84.tar.bz2 |
Lots of Messaging plumbing work to support DotNetOpenId associate messages.
Diffstat (limited to 'src')
16 files changed, 914 insertions, 20 deletions
diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs index c3c69c2..cdc5361 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { using System; using System.Linq; using System.Reflection; + using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; using DotNetOpenAuth.Test.Mocks; @@ -62,12 +63,44 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { Assert.AreEqual("8", part.GetValue(message)); } + [TestMethod] + public void Base64Member() { + var message = new MessageWithBase64EncodedString(); + message.LastName = "andrew"; + MessagePart part = GetMessagePart(message.GetType(), "nameBytes"); + Assert.AreEqual("YW5kcmV3", part.GetValue(message)); + part.SetValue(message, "YXJub3R0"); + Assert.AreEqual("arnott", message.LastName); + } + + [TestMethod] + public void ConstantFieldMemberValidValues() { + var message = new MessageWithConstantField(); + MessagePart part = GetMessagePart(message.GetType(), "ConstantField"); + Assert.AreEqual("abc", part.GetValue(message)); + part.SetValue(message, "abc"); + Assert.AreEqual("abc", part.GetValue(message)); + } + + [TestMethod, ExpectedException(typeof(ArgumentException))] + public void ConstantFieldMemberInvalidValues() { + var message = new MessageWithConstantField(); + MessagePart part = GetMessagePart(message.GetType(), "ConstantField"); + part.SetValue(message, "def"); + } + [TestMethod, ExpectedException(typeof(ArgumentException))] public void NonFieldOrPropertyMember() { MemberInfo method = typeof(MessageWithNullableOptionalStruct).GetMethod("Equals", BindingFlags.Public | BindingFlags.Instance); new MessagePart(method, new MessagePartAttribute()); } + private static MessagePart GetMessagePart(Type messageType, string memberName) { + FieldInfo field = messageType.GetField(memberName, BindingFlags.NonPublic | BindingFlags.Instance); + MessagePartAttribute attribute = field.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>().Single(); + return new MessagePart(field, attribute); + } + private MessagePart ParameterizedMessageTypeTest(Type messageType) { PropertyInfo field = messageType.GetProperty("OptionalInt", BindingFlags.NonPublic | BindingFlags.Instance); MessagePartAttribute attribute = field.GetCustomAttributes(typeof(MessagePartAttribute), true).OfType<MessagePartAttribute>().Single(); @@ -97,5 +130,22 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { [MessagePart(IsRequired = true)] private int? OptionalInt { get; set; } } + + private class MessageWithBase64EncodedString : TestMessage { + [MessagePart] + private byte[] nameBytes; + + public string LastName { + get { return this.nameBytes != null ? Encoding.UTF8.GetString(this.nameBytes) : null; } + set { this.nameBytes = value != null ? Encoding.UTF8.GetBytes(value) : null; } + } + } + + private class MessageWithConstantField : TestMessage { + [MessagePart(IsRequired = true)] +#pragma warning disable 0414 // read by reflection + private readonly string ConstantField = "abc"; +#pragma warning restore 0414 + } } } diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index cf155b9..26030ce 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -63,6 +63,11 @@ </Reference> </ItemGroup> <ItemGroup> + <Compile Include="Messaging\EmptyDictionary.cs" /> + <Compile Include="Messaging\EmptyEnumerator.cs" /> + <Compile Include="Messaging\EmptyList.cs" /> + <Compile Include="Messaging\ErrorUtilities.cs" /> + <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> <Compile Include="OAuth\ChannelElements\OAuthConsumerMessageTypeProvider.cs" /> <Compile Include="OAuth\ChannelElements\ITokenGenerator.cs" /> <Compile Include="OAuth\ChannelElements\ITokenManager.cs" /> @@ -169,4 +174,4 @@ </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> -</Project> +</Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index a4ec142..e1e1b06 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -552,7 +552,7 @@ namespace DotNetOpenAuth.Messaging { MessageDictionary dictionary = new MessageDictionary(message); MessageDescription description = MessageDescription.Get(message.GetType()); - description.EnsureRequiredMessagePartsArePresent(dictionary.Keys); + description.EnsureMessagePartsPassBasicValidation(dictionary); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs b/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs new file mode 100644 index 0000000..df2f5a7 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/EmptyDictionary.cs @@ -0,0 +1,247 @@ +//----------------------------------------------------------------------- +// <copyright file="EmptyDictionary.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Linq; + + /// <summary> + /// An empty dictionary. Useful for avoiding memory allocations in creating new dictionaries to represent empty ones. + /// </summary> + /// <typeparam name="TKey">The type of the key.</typeparam> + /// <typeparam name="TValue">The type of the value.</typeparam> + internal class EmptyDictionary<TKey, TValue> : IDictionary<TKey, TValue> { + /// <summary> + /// The singleton instance of the empty dictionary. + /// </summary> + internal static readonly EmptyDictionary<TKey, TValue> Instance = new EmptyDictionary<TKey, TValue>(); + + /// <summary> + /// Prevents a default instance of the EmptyDictionary class from being created. + /// </summary> + private EmptyDictionary() { + } + + /// <summary> + /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </summary> + /// <value></value> + /// <returns> + /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the values in the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </returns> + public ICollection<TValue> Values { + get { return EmptyList<TValue>.Instance; } + } + + /// <summary> + /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <value></value> + /// <returns> + /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </returns> + public int Count { + get { return 0; } + } + + /// <summary> + /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </summary> + /// <value></value> + /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false. + /// </returns> + public bool IsReadOnly { + get { return true; } + } + + /// <summary> + /// Gets an <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </summary> + /// <value></value> + /// <returns> + /// An <see cref="T:System.Collections.Generic.ICollection`1"/> containing the keys of the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </returns> + public ICollection<TKey> Keys { + get { return EmptyList<TKey>.Instance; } + } + + /// <summary> + /// Gets or sets the value with the specified key. + /// </summary> + /// <param name="key">The key being read or written.</param> + public TValue this[TKey key] { + get { throw new KeyNotFoundException(); } + set { throw new NotSupportedException(); } + } + + /// <summary> + /// Adds an element with the provided key and value to the <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </summary> + /// <param name="key">The object to use as the key of the element to add.</param> + /// <param name="value">The object to use as the value of the element to add.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="key"/> is null. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// An element with the same key already exists in the <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </exception> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only. + /// </exception> + public void Add(TKey key, TValue value) { + throw new NotSupportedException(); + } + + /// <summary> + /// Determines whether the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key. + /// </summary> + /// <param name="key">The key to locate in the <see cref="T:System.Collections.Generic.IDictionary`2"/>.</param> + /// <returns> + /// true if the <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the key; otherwise, false. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="key"/> is null. + /// </exception> + public bool ContainsKey(TKey key) { + return false; + } + + /// <summary> + /// Removes the element with the specified key from the <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </summary> + /// <param name="key">The key of the element to remove.</param> + /// <returns> + /// true if the element is successfully removed; otherwise, false. This method also returns false if <paramref name="key"/> was not found in the original <see cref="T:System.Collections.Generic.IDictionary`2"/>. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="key"/> is null. + /// </exception> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.IDictionary`2"/> is read-only. + /// </exception> + public bool Remove(TKey key) { + return false; + } + + /// <summary> + /// Gets the value associated with the specified key. + /// </summary> + /// <param name="key">The key whose value to get.</param> + /// <param name="value">When this method returns, the value associated with the specified key, if the key is found; otherwise, the default value for the type of the <paramref name="value"/> parameter. This parameter is passed uninitialized.</param> + /// <returns> + /// true if the object that implements <see cref="T:System.Collections.Generic.IDictionary`2"/> contains an element with the specified key; otherwise, false. + /// </returns> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="key"/> is null. + /// </exception> + public bool TryGetValue(TKey key, out TValue value) { + value = default(TValue); + return false; + } + + #region ICollection<KeyValuePair<TKey,TValue>> Members + + /// <summary> + /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + public void Add(KeyValuePair<TKey, TValue> item) { + throw new NotSupportedException(); + } + + /// <summary> + /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + public void Clear() { + throw new NotSupportedException(); + } + + /// <summary> + /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value. + /// </summary> + /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> + /// <returns> + /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. + /// </returns> + public bool Contains(KeyValuePair<TKey, TValue> item) { + return false; + } + + /// <summary> + /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index. + /// </summary> + /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param> + /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="array"/> is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// <paramref name="array"/> is multidimensional. + /// -or- + /// <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>. + /// -or- + /// The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>. + /// -or- + /// Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>. + /// </exception> + public void CopyTo(KeyValuePair<TKey, TValue>[] array, int arrayIndex) { + } + + /// <summary> + /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> + /// <returns> + /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </returns> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + public bool Remove(KeyValuePair<TKey, TValue> item) { + return false; + } + + #endregion + + #region IEnumerable<KeyValuePair<TKey,TValue>> Members + + /// <summary> + /// Returns an enumerator that iterates through the collection. + /// </summary> + /// <returns> + /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection. + /// </returns> + public IEnumerator<KeyValuePair<TKey, TValue>> GetEnumerator() { + return Enumerable.Empty<KeyValuePair<TKey, TValue>>().GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return EmptyEnumerator.Instance; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/Messaging/EmptyEnumerator.cs b/src/DotNetOpenAuth/Messaging/EmptyEnumerator.cs new file mode 100644 index 0000000..f37e3d4 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/EmptyEnumerator.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="EmptyEnumerator.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System.Collections; + + /// <summary> + /// An enumerator that always generates zero elements. + /// </summary> + internal class EmptyEnumerator : IEnumerator { + /// <summary> + /// The singleton instance of this empty enumerator. + /// </summary> + internal static readonly EmptyEnumerator Instance = new EmptyEnumerator(); + + /// <summary> + /// Prevents a default instance of the <see cref="EmptyEnumerator"/> class from being created. + /// </summary> + private EmptyEnumerator() { + } + + #region IEnumerator Members + + /// <summary> + /// Gets the current element in the collection. + /// </summary> + /// <value></value> + /// <returns> + /// The current element in the collection. + /// </returns> + /// <exception cref="T:System.InvalidOperationException"> + /// The enumerator is positioned before the first element of the collection or after the last element. + /// </exception> + public object Current { + get { return null; } + } + + /// <summary> + /// Advances the enumerator to the next element of the collection. + /// </summary> + /// <returns> + /// true if the enumerator was successfully advanced to the next element; false if the enumerator has passed the end of the collection. + /// </returns> + /// <exception cref="T:System.InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + public bool MoveNext() { + return false; + } + + /// <summary> + /// Sets the enumerator to its initial position, which is before the first element in the collection. + /// </summary> + /// <exception cref="T:System.InvalidOperationException"> + /// The collection was modified after the enumerator was created. + /// </exception> + public void Reset() { + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/Messaging/EmptyList.cs b/src/DotNetOpenAuth/Messaging/EmptyList.cs new file mode 100644 index 0000000..e9a83e9 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/EmptyList.cs @@ -0,0 +1,207 @@ +//----------------------------------------------------------------------- +// <copyright file="EmptyList.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + + /// <summary> + /// An empty, read-only list. + /// </summary> + /// <typeparam name="T">The type the list claims to include.</typeparam> + internal class EmptyList<T> : IList<T> { + /// <summary> + /// The singleton instance of the empty list. + /// </summary> + internal static readonly EmptyList<T> Instance = new EmptyList<T>(); + + /// <summary> + /// Prevents a default instance of the EmptyList class from being created. + /// </summary> + private EmptyList() { + } + + /// <summary> + /// Gets the number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <value></value> + /// <returns> + /// The number of elements contained in the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </returns> + public int Count { + get { return 0; } + } + + /// <summary> + /// Gets a value indicating whether the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </summary> + /// <value></value> + /// <returns>true if the <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only; otherwise, false. + /// </returns> + public bool IsReadOnly { + get { return true; } + } + + #region IList<T> Members + + /// <summary> + /// Gets or sets the <typeparamref name="T"/> at the specified index. + /// </summary> + /// <param name="index">The index of the element in the list to change.</param> + public T this[int index] { + get { + throw new ArgumentOutOfRangeException("index"); + } + + set { + throw new ArgumentOutOfRangeException("index"); + } + } + + /// <summary> + /// Determines the index of a specific item in the <see cref="T:System.Collections.Generic.IList`1"/>. + /// </summary> + /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.IList`1"/>.</param> + /// <returns> + /// The index of <paramref name="item"/> if found in the list; otherwise, -1. + /// </returns> + public int IndexOf(T item) { + return -1; + } + + /// <summary> + /// Inserts an item to the <see cref="T:System.Collections.Generic.IList`1"/> at the specified index. + /// </summary> + /// <param name="index">The zero-based index at which <paramref name="item"/> should be inserted.</param> + /// <param name="item">The object to insert into the <see cref="T:System.Collections.Generic.IList`1"/>.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>. + /// </exception> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.IList`1"/> is read-only. + /// </exception> + public void Insert(int index, T item) { + throw new NotSupportedException(); + } + + /// <summary> + /// Removes the <see cref="T:System.Collections.Generic.IList`1"/> item at the specified index. + /// </summary> + /// <param name="index">The zero-based index of the item to remove.</param> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="index"/> is not a valid index in the <see cref="T:System.Collections.Generic.IList`1"/>. + /// </exception> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.IList`1"/> is read-only. + /// </exception> + public void RemoveAt(int index) { + throw new ArgumentOutOfRangeException("index"); + } + + #endregion + + #region ICollection<T> Members + + /// <summary> + /// Adds an item to the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <param name="item">The object to add to the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + public void Add(T item) { + throw new NotSupportedException(); + } + + /// <summary> + /// Removes all items from the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + public void Clear() { + throw new NotSupportedException(); + } + + /// <summary> + /// Determines whether the <see cref="T:System.Collections.Generic.ICollection`1"/> contains a specific value. + /// </summary> + /// <param name="item">The object to locate in the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> + /// <returns> + /// true if <paramref name="item"/> is found in the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. + /// </returns> + public bool Contains(T item) { + return false; + } + + /// <summary> + /// Copies the elements of the <see cref="T:System.Collections.Generic.ICollection`1"/> to an <see cref="T:System.Array"/>, starting at a particular <see cref="T:System.Array"/> index. + /// </summary> + /// <param name="array">The one-dimensional <see cref="T:System.Array"/> that is the destination of the elements copied from <see cref="T:System.Collections.Generic.ICollection`1"/>. The <see cref="T:System.Array"/> must have zero-based indexing.</param> + /// <param name="arrayIndex">The zero-based index in <paramref name="array"/> at which copying begins.</param> + /// <exception cref="T:System.ArgumentNullException"> + /// <paramref name="array"/> is null. + /// </exception> + /// <exception cref="T:System.ArgumentOutOfRangeException"> + /// <paramref name="arrayIndex"/> is less than 0. + /// </exception> + /// <exception cref="T:System.ArgumentException"> + /// <paramref name="array"/> is multidimensional. + /// -or- + /// <paramref name="arrayIndex"/> is equal to or greater than the length of <paramref name="array"/>. + /// -or- + /// The number of elements in the source <see cref="T:System.Collections.Generic.ICollection`1"/> is greater than the available space from <paramref name="arrayIndex"/> to the end of the destination <paramref name="array"/>. + /// -or- + /// Type <paramref name="T"/> cannot be cast automatically to the type of the destination <paramref name="array"/>. + /// </exception> + public void CopyTo(T[] array, int arrayIndex) { + } + + /// <summary> + /// Removes the first occurrence of a specific object from the <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </summary> + /// <param name="item">The object to remove from the <see cref="T:System.Collections.Generic.ICollection`1"/>.</param> + /// <returns> + /// true if <paramref name="item"/> was successfully removed from the <see cref="T:System.Collections.Generic.ICollection`1"/>; otherwise, false. This method also returns false if <paramref name="item"/> is not found in the original <see cref="T:System.Collections.Generic.ICollection`1"/>. + /// </returns> + /// <exception cref="T:System.NotSupportedException"> + /// The <see cref="T:System.Collections.Generic.ICollection`1"/> is read-only. + /// </exception> + public bool Remove(T item) { + return false; + } + + #endregion + + #region IEnumerable<T> Members + + /// <summary> + /// Returns an enumerator that iterates through the collection. + /// </summary> + /// <returns> + /// A <see cref="T:System.Collections.Generic.IEnumerator`1"/> that can be used to iterate through the collection. + /// </returns> + public IEnumerator<T> GetEnumerator() { + return System.Linq.Enumerable.Empty<T>().GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + /// <summary> + /// Returns an enumerator that iterates through a collection. + /// </summary> + /// <returns> + /// An <see cref="T:System.Collections.IEnumerator"/> object that can be used to iterate through the collection. + /// </returns> + System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { + return EmptyEnumerator.Instance; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs new file mode 100644 index 0000000..b16cb0e --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs @@ -0,0 +1,54 @@ +//----------------------------------------------------------------------- +// <copyright file="ErrorUtilities.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Globalization; + + /// <summary> + /// A collection of error checking and reporting methods. + /// </summary> + internal class ErrorUtilities { + /// <summary> + /// Wraps an exception in a new <see cref="ProtocolException"/>. + /// </summary> + /// <param name="inner">The inner exception to wrap.</param> + /// <param name="errorMessage">The error message for the outer exception.</param> + /// <param name="args">The string formatting arguments, if any.</param> + /// <returns>The newly constructed (unthrown) exception.</returns> + internal static Exception Wrap(Exception inner, string errorMessage, params object[] args) { + return new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), inner); + } + + /// <summary> + /// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false. + /// </summary> + /// <param name="condition">True to do nothing; false to throw the exception.</param> + /// <param name="faultedMessage">The message being processed that would be responsible for the exception if thrown.</param> + /// <param name="errorMessage">The error message for the exception.</param> + /// <param name="args">The string formatting arguments, if any.</param> + internal static void Verify(bool condition, IProtocolMessage faultedMessage, string errorMessage, params object[] args) { + if (!condition) { + throw new ProtocolException(string.Format(CultureInfo.CurrentCulture, errorMessage, args), faultedMessage); + } + } + + /// <summary> + /// Throws a <see cref="ProtocolException"/> if some <paramref name="condition"/> evaluates to false. + /// </summary> + /// <param name="condition">True to do nothing; false to throw the exception.</param> + /// <param name="message">The error message for the exception.</param> + /// <param name="args">The string formatting arguments, if any.</param> + internal static void Verify(bool condition, string message, params object[] args) { + if (!condition) { + throw new ProtocolException(string.Format( + CultureInfo.CurrentCulture, + message, + args)); + } + } + } +} diff --git a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs index 8812f38..248cb01 100644 --- a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs +++ b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs @@ -23,6 +23,7 @@ namespace DotNetOpenAuth.Messaging { /// Initializes a new instance of the <see cref="MessagePartAttribute"/> class. /// </summary> public MessagePartAttribute() { + this.AllowEmpty = true; } /// <summary> @@ -32,7 +33,7 @@ namespace DotNetOpenAuth.Messaging { /// A special name to give the value of this member in the serialized message. /// When null or empty, the name of the member will be used in the serialized message. /// </param> - public MessagePartAttribute(string name) { + public MessagePartAttribute(string name) : this() { this.Name = name; } @@ -53,5 +54,16 @@ namespace DotNetOpenAuth.Messaging { /// Gets or sets a value indicating whether this member is a required part of the serialized message. /// </summary> public bool IsRequired { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the string value is allowed to be empty in the serialized message. + /// </summary> + /// <value>Default is true.</value> + public bool AllowEmpty { get; set; } + + /// <summary> + /// Gets or sets a custom encoder to use to translate the applied member to and from a string. + /// </summary> + public Type Encoder { get; set; } } } diff --git a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs index cc26096..999e251 100644 --- a/src/DotNetOpenAuth/Messaging/MessageSerializer.cs +++ b/src/DotNetOpenAuth/Messaging/MessageSerializer.cs @@ -79,18 +79,23 @@ namespace DotNetOpenAuth.Messaging { /// <param name="fields">The name=value pairs that were read in from the transport.</param> /// <param name="recipient">The recipient of the message.</param> /// <returns>The instantiated and initialized <see cref="IProtocolMessage"/> instance.</returns> + /// <exception cref="ProtocolException">Thrown when protocol rules are broken by the incoming message.</exception> internal IProtocolMessage Deserialize(IDictionary<string, string> fields, MessageReceivingEndpoint recipient) { if (fields == null) { throw new ArgumentNullException("fields"); } // Before we deserialize the message, make sure all the required parts are present. - MessageDescription.Get(this.messageType).EnsureRequiredMessagePartsArePresent(fields.Keys); + MessageDescription.Get(this.messageType).EnsureMessagePartsPassBasicValidation(fields); IProtocolMessage result = this.CreateMessage(recipient); - foreach (var pair in fields) { - IDictionary<string, string> dictionary = new MessageDictionary(result); - dictionary[pair.Key] = pair.Value; + try { + foreach (var pair in fields) { + IDictionary<string, string> dictionary = new MessageDictionary(result); + dictionary[pair.Key] = pair.Value; + } + } catch (ArgumentException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.ErrorDeserializingMessage, this.messageType.Name); } result.EnsureValidMessage(); return result; diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs index b79b846..5733fc9 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs @@ -115,6 +115,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to Error while deserializing message {0}.. + /// </summary> + internal static string ErrorDeserializingMessage { + get { + return ResourceManager.GetString("ErrorDeserializingMessage", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Error occurred while sending a direct message or getting the response.. /// </summary> internal static string ErrorInRequestReplyMessage { @@ -214,6 +223,42 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to The value for {0}.{1} on member {1} was expected to derive from {2} but was {3}.. + /// </summary> + internal static string MessagePartEncoderWrongType { + get { + return ResourceManager.GetString("MessagePartEncoderWrongType", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Error while reading message '{0}' parameter '{1}' with value '{2}'.. + /// </summary> + internal static string MessagePartReadFailure { + get { + return ResourceManager.GetString("MessagePartReadFailure", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Message parameter '{0}' with value '{1}' failed to base64 decode.. + /// </summary> + internal static string MessagePartValueBase64DecodingFault { + get { + return ResourceManager.GetString("MessagePartValueBase64DecodingFault", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Error while preparing message '{0}' parameter '{1}' for sending.. + /// </summary> + internal static string MessagePartWriteFailure { + get { + return ResourceManager.GetString("MessagePartWriteFailure", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to A message response is already queued for sending in the response stream.. /// </summary> internal static string QueuedMessageResponseAlreadyExists { @@ -241,6 +286,15 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to The following required non-empty parameters were empty in the {0} message: {1}. + /// </summary> + internal static string RequiredNonEmptyParameterWasEmpty { + get { + return ResourceManager.GetString("RequiredNonEmptyParameterWasEmpty", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The following required parameters were missing from the {0} message: {1}. /// </summary> internal static string RequiredParametersMissing { @@ -322,6 +376,24 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Looks up a localized string similar to Message parameter '{0}' had unexpected value '{1}'.. + /// </summary> + internal static string UnexpectedMessagePartValue { + get { + return ResourceManager.GetString("UnexpectedMessagePartValue", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to Expected message {0} parameter '{1}' to have value '{2}' but had '{3}' instead.. + /// </summary> + internal static string UnexpectedMessagePartValueForConstant { + get { + return ResourceManager.GetString("UnexpectedMessagePartValueForConstant", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Expected message {0} but received {1} instead.. /// </summary> internal static string UnexpectedMessageReceived { diff --git a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx index 5fd4e27..262d084 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingStrings.resx +++ b/src/DotNetOpenAuth/Messaging/MessagingStrings.resx @@ -135,6 +135,9 @@ <data name="DirectedMessageMissingRecipient" xml:space="preserve"> <value>The directed message's Recipient property must not be null.</value> </data> + <data name="ErrorDeserializingMessage" xml:space="preserve"> + <value>Error while deserializing message {0}.</value> + </data> <data name="ErrorInRequestReplyMessage" xml:space="preserve"> <value>Error occurred while sending a direct message or getting the response.</value> </data> @@ -168,6 +171,18 @@ <data name="KeyAlreadyExists" xml:space="preserve"> <value>An item with the same key has already been added.</value> </data> + <data name="MessagePartEncoderWrongType" xml:space="preserve"> + <value>The value for {0}.{1} on member {1} was expected to derive from {2} but was {3}.</value> + </data> + <data name="MessagePartReadFailure" xml:space="preserve"> + <value>Error while reading message '{0}' parameter '{1}' with value '{2}'.</value> + </data> + <data name="MessagePartValueBase64DecodingFault" xml:space="preserve"> + <value>Message parameter '{0}' with value '{1}' failed to base64 decode.</value> + </data> + <data name="MessagePartWriteFailure" xml:space="preserve"> + <value>Error while preparing message '{0}' parameter '{1}' for sending.</value> + </data> <data name="QueuedMessageResponseAlreadyExists" xml:space="preserve"> <value>A message response is already queued for sending in the response stream.</value> </data> @@ -177,6 +192,9 @@ <data name="ReplayProtectionNotSupported" xml:space="preserve"> <value>This channel does not support replay protection.</value> </data> + <data name="RequiredNonEmptyParameterWasEmpty" xml:space="preserve"> + <value>The following required non-empty parameters were empty in the {0} message: {1}</value> + </data> <data name="RequiredParametersMissing" xml:space="preserve"> <value>The following required parameters were missing from the {0} message: {1}</value> </data> @@ -204,6 +222,12 @@ <data name="TooManyBindingsOfferingSameProtection" xml:space="preserve"> <value>Expected at most 1 binding element offering the {0} protection, but found {1}.</value> </data> + <data name="UnexpectedMessagePartValue" xml:space="preserve"> + <value>Message parameter '{0}' had unexpected value '{1}'.</value> + </data> + <data name="UnexpectedMessagePartValueForConstant" xml:space="preserve"> + <value>Expected message {0} parameter '{1}' to have value '{2}' but had '{3}' instead.</value> + </data> <data name="UnexpectedMessageReceived" xml:space="preserve"> <value>Expected message {0} but received {1} instead.</value> </data> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs new file mode 100644 index 0000000..7da25a5 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="IMessagePartEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + + /// <summary> + /// An interface describing how various objects can be serialized and deserialized between their object and string forms. + /// </summary> + /// <remarks> + /// Implementations of this interface must include a default constructor and must be thread-safe. + /// </remarks> + internal interface IMessagePartEncoder { + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns>The <paramref name="value"/> in string form, ready for message transport.</returns> + string Encode(object value); + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns>The deserialized form of the given string.</returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + object Decode(string value); + } +} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs index 3c668c5..31b6c41 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs @@ -106,12 +106,21 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> + /// Ensures the message parts pass basic validation. + /// </summary> + /// <param name="parts">The key/value pairs of the serialzied message.</param> + internal void EnsureMessagePartsPassBasicValidation(IDictionary<string, string> parts) { + this.EnsureRequiredMessagePartsArePresent(parts.Keys); + this.EnsureRequiredProtocolMessagePartsAreNotEmpty(parts); + } + + /// <summary> /// Verifies that a given set of keys include all the required parameters /// for this message type or throws an exception. /// </summary> /// <param name="keys">The names of all parameters included in a message.</param> /// <exception cref="ProtocolException">Thrown when required parts of a message are not in <paramref name="keys"/></exception> - internal void EnsureRequiredMessagePartsArePresent(IEnumerable<string> keys) { + private void EnsureRequiredMessagePartsArePresent(IEnumerable<string> keys) { var missingKeys = (from part in Mapping.Values where part.IsRequired && !keys.Contains(part.Name) select part.Name).ToArray(); @@ -124,5 +133,24 @@ namespace DotNetOpenAuth.Messaging.Reflection { string.Join(", ", missingKeys))); } } + + /// <summary> + /// Ensures the protocol message parts that must not be empty are in fact not empty. + /// </summary> + /// <param name="partValues">A dictionary of key/value pairs that make up the serialized message.</param> + private void EnsureRequiredProtocolMessagePartsAreNotEmpty(IDictionary<string, string> partValues) { + string value; + var emptyValuedKeys = (from part in Mapping.Values + where !part.AllowEmpty && partValues.TryGetValue(part.Name, out value) && value.Length == 0 + select part.Name).ToArray(); + if (emptyValuedKeys.Length > 0) { + throw new ProtocolException( + string.Format( + CultureInfo.CurrentCulture, + MessagingStrings.RequiredNonEmptyParameterWasEmpty, + this.messageType.FullName, + string.Join(", ", emptyValuedKeys))); + } + } } } diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs index 923c772..45399aa 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs @@ -126,6 +126,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// that type member is set. Otherwise the key/value is stored in a /// dictionary for extra (weakly typed) strings. /// </remarks> + /// <exception cref="ArgumentException">Thrown when setting a value that is not allowed for a given <paramref name="key"/>.</exception> public string this[string key] { get { MessagePart part; diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index d662b25..b39d582 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -23,6 +23,11 @@ namespace DotNetOpenAuth.Messaging.Reflection { private static readonly Dictionary<Type, ValueMapping> converters = new Dictionary<Type, ValueMapping>(); /// <summary> + /// A map of instantiated custom encoders used to encode/decode message parts. + /// </summary> + private static readonly Dictionary<Type, IMessagePartEncoder> encoders = new Dictionary<Type, IMessagePartEncoder>(); + + /// <summary> /// The string-object conversion routines to use for this individual message part. /// </summary> private ValueMapping converter; @@ -54,6 +59,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { static MessagePart() { Map<Uri>(uri => uri.AbsoluteUri, str => new Uri(str)); Map<DateTime>(dt => XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc), str => XmlConvert.ToDateTime(str, XmlDateTimeSerializationMode.Utc)); + Map<byte[]>(bytes => Convert.ToBase64String(bytes), str => Convert.FromBase64String(str)); } /// <summary> @@ -91,13 +97,27 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.Name = attribute.Name ?? member.Name; this.RequiredProtection = attribute.RequiredProtection; this.IsRequired = attribute.IsRequired; + this.AllowEmpty = attribute.AllowEmpty; this.memberDeclaredType = (this.field != null) ? this.field.FieldType : this.property.PropertyType; this.defaultMemberValue = DeriveDefaultValue(this.memberDeclaredType); - if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) { + if (attribute.Encoder == null) { + if (!converters.TryGetValue(this.memberDeclaredType, out this.converter)) { + this.converter = new ValueMapping( + obj => obj != null ? obj.ToString() : null, + str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); + } + } else { + var encoder = GetEncoder(attribute.Encoder); this.converter = new ValueMapping( - obj => obj != null ? obj.ToString() : null, - str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); + obj => encoder.Encode(obj), + str => encoder.Decode(str)); + } + + if (this.field != null && (this.field.Attributes & FieldAttributes.InitOnly) != 0) { + this.IsConstantValue = true; + } else if (this.property != null && !this.property.CanWrite) { + this.IsConstantValue = true; } // Validate a sane combination of settings @@ -121,16 +141,47 @@ namespace DotNetOpenAuth.Messaging.Reflection { internal bool IsRequired { get; set; } /// <summary> + /// Gets or sets a value indicating whether the string value is allowed to be empty in the serialized message. + /// </summary> + internal bool AllowEmpty { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether the field or property must remain its default value. + /// </summary> + internal bool IsConstantValue { get; set; } + + /// <summary> /// Sets the member of a given message to some given value. /// Used in deserialization. /// </summary> /// <param name="message">The message instance containing the member whose value should be set.</param> /// <param name="value">The string representation of the value to set.</param> internal void SetValue(IProtocolMessage message, string value) { - if (this.property != null) { - this.property.SetValue(message, this.ToValue(value), null); - } else { - this.field.SetValue(message, this.ToValue(value)); + if (message == null) { + throw new ArgumentNullException("message"); + } + + try { + if (this.IsConstantValue) { + string constantValue = this.GetValue(message); + if (!string.Equals(constantValue, value)) { + throw new ArgumentException(string.Format( + CultureInfo.CurrentCulture, + MessagingStrings.UnexpectedMessagePartValueForConstant, + message.GetType().Name, + this.Name, + constantValue, + value)); + } + } else { + if (this.property != null) { + this.property.SetValue(message, this.ToValue(value), null); + } else { + this.field.SetValue(message, this.ToValue(value)); + } + } + } catch (FormatException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartReadFailure, message.GetType(), this.Name, value); } } @@ -141,7 +192,12 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="message">The message instance to read the value from.</param> /// <returns>The string representation of the member's value.</returns> internal string GetValue(IProtocolMessage message) { - return this.ToString(this.GetValueAsObject(message)); + try { + object value = this.GetValueAsObject(message); + return this.ToString(value); + } catch (FormatException ex) { + throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartWriteFailure, message.GetType(), this.Name); + } } /// <summary> @@ -200,21 +256,39 @@ namespace DotNetOpenAuth.Messaging.Reflection { } /// <summary> + /// Retrieves a previously instantiated encoder of a given type, or creates a new one and stores it for later retrieval as well. + /// </summary> + /// <param name="messagePartEncoder">The message part encoder type.</param> + /// <returns>An instance of the desired encoder.</returns> + private static IMessagePartEncoder GetEncoder(Type messagePartEncoder) { + IMessagePartEncoder encoder; + if (!encoders.TryGetValue(messagePartEncoder, out encoder)) { + encoder = encoders[messagePartEncoder] = (IMessagePartEncoder)Activator.CreateInstance(messagePartEncoder); + } + + return encoder; + } + + /// <summary> /// Converts a string representation of the member's value to the appropriate type. /// </summary> /// <param name="value">The string representation of the member's value.</param> - /// <returns>An instance of the appropriate type for setting the member.</returns> + /// <returns> + /// An instance of the appropriate type for setting the member. + /// </returns> private object ToValue(string value) { - return this.converter.StringToValue(value); + return value == null ? null : this.converter.StringToValue(value); } /// <summary> /// Converts the member's value to its string representation. /// </summary> /// <param name="value">The value of the member.</param> - /// <returns>The string representation of the member's value.</returns> + /// <returns> + /// The string representation of the member's value. + /// </returns> private string ToString(object value) { - return this.converter.ValueToString(value); + return value == null ? null : this.converter.ValueToString(value); } /// <summary> diff --git a/src/DotNetOpenAuth/UriUtil.cs b/src/DotNetOpenAuth/UriUtil.cs index 04bbb0f..63f666d 100644 --- a/src/DotNetOpenAuth/UriUtil.cs +++ b/src/DotNetOpenAuth/UriUtil.cs @@ -27,5 +27,20 @@ namespace DotNetOpenAuth { NameValueCollection nvc = HttpUtility.ParseQueryString(uri.Query); return nvc.Keys.OfType<string>().Any(key => key.StartsWith(OAuth.Protocol.V10.ParameterPrefix, StringComparison.Ordinal)); } + + /// <summary> + /// Determines whether some <see cref="Uri"/> is using HTTPS. + /// </summary> + /// <param name="uri">The Uri being tested for security.</param> + /// <returns> + /// <c>true</c> if the URI represents an encrypted request; otherwise, <c>false</c>. + /// </returns> + internal static bool IsTransportSecure(this Uri uri) { + if (uri == null) { + throw new ArgumentNullException("uri"); + } + + return string.Equals(uri.Scheme, "https", StringComparison.OrdinalIgnoreCase); + } } } |