summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2008-11-06 08:12:32 -0800
committerAndrew <andrewarnott@gmail.com>2008-11-06 08:12:32 -0800
commit15ffce6c077b52339dd3ba1b792b2005c61c7e84 (patch)
tree82f3c027917d9c089fa4448e53c65d012677dac1 /src
parent462e19abd9034c11a12cad30e9899740f2bef8ff (diff)
downloadDotNetOpenAuth-15ffce6c077b52339dd3ba1b792b2005c61c7e84.zip
DotNetOpenAuth-15ffce6c077b52339dd3ba1b792b2005c61c7e84.tar.gz
DotNetOpenAuth-15ffce6c077b52339dd3ba1b792b2005c61c7e84.tar.bz2
Lots of Messaging plumbing work to support DotNetOpenId associate messages.
Diffstat (limited to 'src')
-rw-r--r--src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs50
-rw-r--r--src/DotNetOpenAuth/DotNetOpenAuth.csproj7
-rw-r--r--src/DotNetOpenAuth/Messaging/Channel.cs2
-rw-r--r--src/DotNetOpenAuth/Messaging/EmptyDictionary.cs247
-rw-r--r--src/DotNetOpenAuth/Messaging/EmptyEnumerator.cs65
-rw-r--r--src/DotNetOpenAuth/Messaging/EmptyList.cs207
-rw-r--r--src/DotNetOpenAuth/Messaging/ErrorUtilities.cs54
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs14
-rw-r--r--src/DotNetOpenAuth/Messaging/MessageSerializer.cs13
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.Designer.cs72
-rw-r--r--src/DotNetOpenAuth/Messaging/MessagingStrings.resx24
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/IMessagePartEncoder.cs35
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/MessageDescription.cs30
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/MessageDictionary.cs1
-rw-r--r--src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs98
-rw-r--r--src/DotNetOpenAuth/UriUtil.cs15
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 &apos;{0}&apos; parameter &apos;{1}&apos; with value &apos;{2}&apos;..
+ /// </summary>
+ internal static string MessagePartReadFailure {
+ get {
+ return ResourceManager.GetString("MessagePartReadFailure", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Message parameter &apos;{0}&apos; with value &apos;{1}&apos; 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 &apos;{0}&apos; parameter &apos;{1}&apos; 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 &apos;{0}&apos; had unexpected value &apos;{1}&apos;..
+ /// </summary>
+ internal static string UnexpectedMessagePartValue {
+ get {
+ return ResourceManager.GetString("UnexpectedMessagePartValue", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Expected message {0} parameter &apos;{1}&apos; to have value &apos;{2}&apos; but had &apos;{3}&apos; 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);
+ }
}
}