summaryrefslogtreecommitdiffstats
path: root/src/DotNetOAuth/Messaging
diff options
context:
space:
mode:
Diffstat (limited to 'src/DotNetOAuth/Messaging')
-rw-r--r--src/DotNetOAuth/Messaging/Channel.cs354
-rw-r--r--src/DotNetOAuth/Messaging/DictionaryXmlReader.cs91
-rw-r--r--src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs273
-rw-r--r--src/DotNetOAuth/Messaging/HttpRequestInfo.cs137
-rw-r--r--src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs20
-rw-r--r--src/DotNetOAuth/Messaging/IMessageTypeProvider.cs41
-rw-r--r--src/DotNetOAuth/Messaging/IProtocolMessage.cs42
-rw-r--r--src/DotNetOAuth/Messaging/MessageScheme.cs35
-rw-r--r--src/DotNetOAuth/Messaging/MessageSerializer.cs167
-rw-r--r--src/DotNetOAuth/Messaging/MessageTransport.cs22
-rw-r--r--src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs108
-rw-r--r--src/DotNetOAuth/Messaging/MessagingStrings.resx135
-rw-r--r--src/DotNetOAuth/Messaging/MessagingUtilities.cs119
-rw-r--r--src/DotNetOAuth/Messaging/Response.cs73
14 files changed, 1617 insertions, 0 deletions
diff --git a/src/DotNetOAuth/Messaging/Channel.cs b/src/DotNetOAuth/Messaging/Channel.cs
new file mode 100644
index 0000000..40261b1
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/Channel.cs
@@ -0,0 +1,354 @@
+//-----------------------------------------------------------------------
+// <copyright file="Channel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+
+ /// <summary>
+ /// Manages sending direct messages to a remote party and receiving responses.
+ /// </summary>
+ internal abstract class Channel {
+ /// <summary>
+ /// The maximum allowable size for a 301 Redirect response before we send
+ /// a 200 OK response with a scripted form POST with the parameters instead
+ /// in order to ensure successfully sending a large payload to another server
+ /// that might have a maximum allowable size restriction on its GET request.
+ /// </summary>
+ private static int indirectMessageGetToPostThreshold = 2 * 1024; // 2KB, recommended by OpenID group
+
+ /// <summary>
+ /// The template for indirect messages that require form POST to forward through the user agent.
+ /// </summary>
+ /// <remarks>
+ /// We are intentionally using " instead of the html single quote ' below because
+ /// the HtmlEncode'd values that we inject will only escape the double quote, so
+ /// only the double-quote used around these values is safe.
+ /// </remarks>
+ private static string indirectMessageFormPostFormat = @"
+<html>
+<body onload=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()"">
+<form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;"">
+{1}
+ <input id=""submit_button"" type=""submit"" value=""Continue"" />
+</form>
+</body>
+</html>
+";
+
+ /// <summary>
+ /// A tool that can figure out what kind of message is being received
+ /// so it can be deserialized.
+ /// </summary>
+ private IMessageTypeProvider messageTypeProvider;
+
+ /// <summary>
+ /// Gets or sets the HTTP response to send as a reply to the current incoming HTTP request.
+ /// </summary>
+ private Response queuedIndirectOrResponseMessage;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Channel"/> class.
+ /// </summary>
+ /// <param name="messageTypeProvider">
+ /// A class prepared to analyze incoming messages and indicate what concrete
+ /// message types can deserialize from it.
+ /// </param>
+ protected Channel(IMessageTypeProvider messageTypeProvider) {
+ if (messageTypeProvider == null) {
+ throw new ArgumentNullException("messageTypeProvider");
+ }
+
+ this.messageTypeProvider = messageTypeProvider;
+ }
+
+ /// <summary>
+ /// Gets a tool that can figure out what kind of message is being received
+ /// so it can be deserialized.
+ /// </summary>
+ protected IMessageTypeProvider MessageTypeProvider {
+ get { return this.messageTypeProvider; }
+ }
+
+ /// <summary>
+ /// Retrieves the stored response for sending and clears it from the channel.
+ /// </summary>
+ /// <returns>The response to send as the HTTP response.</returns>
+ internal Response DequeueIndirectOrResponseMessage() {
+ Response response = this.queuedIndirectOrResponseMessage;
+ this.queuedIndirectOrResponseMessage = null;
+ return response;
+ }
+
+ /// <summary>
+ /// Queues an indirect message (either a request or response)
+ /// or direct message response for transmission to a remote party.
+ /// </summary>
+ /// <param name="message">The one-way message to send</param>
+ internal void Send(IProtocolMessage message) {
+ this.Send(message, null);
+ }
+
+ /// <summary>
+ /// Queues an indirect message (either a request or response)
+ /// or direct message response for transmission to a remote party.
+ /// </summary>
+ /// <param name="message">The one-way message to send</param>
+ /// <param name="inResponseTo">
+ /// If <paramref name="message"/> is a response to an incoming message, this is the incoming message.
+ /// This is useful for error scenarios in deciding just how to send the response message.
+ /// May be null.
+ /// </param>
+ internal void Send(IProtocolMessage message, IProtocolMessage inResponseTo) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ var directedMessage = message as IDirectedProtocolMessage;
+ if (directedMessage == null) {
+ // This is a response to a direct message.
+ this.SendDirectMessageResponse(message);
+ } else {
+ if (directedMessage.Recipient != null) {
+ // This is an indirect message request or reply.
+ this.SendIndirectMessage(directedMessage);
+ } else {
+ ProtocolException exception = message as ProtocolException;
+ if (exception != null) {
+ if (inResponseTo is IDirectedProtocolMessage) {
+ this.ReportErrorAsDirectResponse(exception);
+ } else {
+ this.ReportErrorToUser(exception);
+ }
+ } else {
+ throw new InvalidOperationException(MessagingStrings.DirectedMessageMissingRecipient);
+ }
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the protocol message embedded in the given HTTP request, if present.
+ /// </summary>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ /// <remarks>
+ /// Requires an HttpContext.Current context.
+ /// </remarks>
+ internal IProtocolMessage ReadFromRequest() {
+ return this.ReadFromRequest(new HttpRequestInfo(HttpContext.Current.Request));
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be embedded in the given HTTP request.
+ /// </summary>
+ /// <param name="request">The request to search for an embedded message.</param>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ protected internal virtual IProtocolMessage ReadFromRequest(HttpRequestInfo request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ // Search Form data first, and if nothing is there search the QueryString
+ var fields = request.Form.ToDictionary();
+ if (fields.Count == 0) {
+ fields = request.QueryString.ToDictionary();
+ }
+
+ return this.Receive(fields);
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response stream.
+ /// </summary>
+ /// <param name="responseStream">The response that is anticipated to contain an OAuth message.</param>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ protected internal abstract IProtocolMessage ReadFromResponse(Stream responseStream);
+
+ /// <summary>
+ /// Sends a direct message to a remote party and waits for the response.
+ /// </summary>
+ /// <param name="request">The message to send.</param>
+ /// <returns>The remote party's response.</returns>
+ protected internal abstract IProtocolMessage Request(IDirectedProtocolMessage request);
+
+ /// <summary>
+ /// Deserializes a dictionary of values into a message.
+ /// </summary>
+ /// <param name="fields">The dictionary of values that were read from an HTTP request or response.</param>
+ /// <returns>The deserialized message.</returns>
+ protected virtual IProtocolMessage Receive(Dictionary<string, string> fields) {
+ Type messageType = null;
+ if (fields != null) {
+ messageType = this.MessageTypeProvider.GetRequestMessageType(fields);
+ }
+
+ // If there was no data, or we couldn't recognize it as a message, abort.
+ if (messageType == null) {
+ return null;
+ }
+
+ // We have a message! Assemble it.
+ var serializer = MessageSerializer.Get(messageType);
+ IProtocolMessage message = serializer.Deserialize(fields);
+
+ return message;
+ }
+
+ /// <summary>
+ /// Takes a message and temporarily stores it for sending as the hosting site's
+ /// HTTP response to the current request.
+ /// </summary>
+ /// <param name="response">The message to store for sending.</param>
+ protected void QueueIndirectOrResponseMessage(Response response) {
+ if (response == null) {
+ throw new ArgumentNullException("response");
+ }
+ if (this.queuedIndirectOrResponseMessage != null) {
+ throw new InvalidOperationException(MessagingStrings.QueuedMessageResponseAlreadyExists);
+ }
+
+ this.queuedIndirectOrResponseMessage = response;
+ }
+
+ /// <summary>
+ /// Queues an indirect message for transmittal via the user agent.
+ /// </summary>
+ /// <param name="message">The message to send.</param>
+ protected virtual void SendIndirectMessage(IDirectedProtocolMessage message) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ var serializer = MessageSerializer.Get(message.GetType());
+ var fields = serializer.Serialize(message);
+ Response response;
+ if (CalculateSizeOfPayload(fields) > indirectMessageGetToPostThreshold) {
+ response = this.CreateFormPostResponse(message, fields);
+ } else {
+ response = this.Create301RedirectResponse(message, fields);
+ }
+
+ this.QueueIndirectOrResponseMessage(response);
+ }
+
+ /// <summary>
+ /// Encodes an HTTP response that will instruct the user agent to forward a message to
+ /// some remote third party using a 301 Redirect GET method.
+ /// </summary>
+ /// <param name="message">The message to forward.</param>
+ /// <param name="fields">The pre-serialized fields from the message.</param>
+ /// <returns>The encoded HTTP response.</returns>
+ protected virtual Response Create301RedirectResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ WebHeaderCollection headers = new WebHeaderCollection();
+ UriBuilder builder = new UriBuilder(message.Recipient);
+ MessagingUtilities.AppendQueryArgs(builder, fields);
+ headers.Add(HttpResponseHeader.Location, builder.Uri.AbsoluteUri);
+ Logger.DebugFormat("Redirecting to {0}", builder.Uri.AbsoluteUri);
+ Response response = new Response {
+ Status = HttpStatusCode.Redirect,
+ Headers = headers,
+ Body = new byte[0],
+ OriginalMessage = message
+ };
+
+ return response;
+ }
+
+ /// <summary>
+ /// Encodes an HTTP response that will instruct the user agent to forward a message to
+ /// some remote third party using a form POST method.
+ /// </summary>
+ /// <param name="message">The message to forward.</param>
+ /// <param name="fields">The pre-serialized fields from the message.</param>
+ /// <returns>The encoded HTTP response.</returns>
+ protected virtual Response CreateFormPostResponse(IDirectedProtocolMessage message, IDictionary<string, string> fields) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ WebHeaderCollection headers = new WebHeaderCollection();
+ MemoryStream body = new MemoryStream();
+ StreamWriter bodyWriter = new StreamWriter(body);
+ StringBuilder hiddenFields = new StringBuilder();
+ foreach (var field in fields) {
+ hiddenFields.AppendFormat(
+ "\t<input type=\"hidden\" name=\"{0}\" value=\"{1}\" />\r\n",
+ HttpUtility.HtmlEncode(field.Key),
+ HttpUtility.HtmlEncode(field.Value));
+ }
+ bodyWriter.WriteLine(
+ indirectMessageFormPostFormat,
+ HttpUtility.HtmlEncode(message.Recipient.AbsoluteUri),
+ hiddenFields);
+ bodyWriter.Flush();
+ Response response = new Response {
+ Status = HttpStatusCode.Redirect,
+ Headers = headers,
+ Body = body.ToArray(),
+ OriginalMessage = message
+ };
+
+ return response;
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream where the fields
+ /// are sent in the response stream in querystring style.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <remarks>
+ /// This method implements spec V1.0 section 5.3.
+ /// </remarks>
+ protected abstract void SendDirectMessageResponse(IProtocolMessage response);
+
+ /// <summary>
+ /// Reports an error to the user via the user agent.
+ /// </summary>
+ /// <param name="exception">The error information.</param>
+ protected abstract void ReportErrorToUser(ProtocolException exception);
+
+ /// <summary>
+ /// Sends an error result directly to the calling remote party according to the
+ /// rules of the protocol.
+ /// </summary>
+ /// <param name="exception">The error information.</param>
+ protected abstract void ReportErrorAsDirectResponse(ProtocolException exception);
+
+ /// <summary>
+ /// Calculates a fairly accurate estimation on the size of a message that contains
+ /// a given set of fields.
+ /// </summary>
+ /// <param name="fields">The fields that would be included in a message.</param>
+ /// <returns>The size (in bytes) of the message payload.</returns>
+ private static int CalculateSizeOfPayload(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ int size = 0;
+ foreach (var field in fields) {
+ size += field.Key.Length;
+ size += field.Value.Length;
+ size += 2; // & and =
+ }
+ return size;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/DictionaryXmlReader.cs b/src/DotNetOAuth/Messaging/DictionaryXmlReader.cs
new file mode 100644
index 0000000..7a5dc21
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/DictionaryXmlReader.cs
@@ -0,0 +1,91 @@
+//-----------------------------------------------------------------------
+// <copyright file="DictionaryXmlReader.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.IO;
+ using System.Linq;
+ using System.Xml;
+ using System.Xml.Linq;
+
+ /// <summary>
+ /// An XmlReader-looking object that actually reads from a dictionary.
+ /// </summary>
+ internal class DictionaryXmlReader {
+ /// <summary>
+ /// Creates an XmlReader that reads data out of a dictionary instead of XML.
+ /// </summary>
+ /// <param name="rootElement">The name of the root XML element.</param>
+ /// <param name="fields">The dictionary to read data from.</param>
+ /// <returns>The XmlReader that will read the data out of the given dictionary.</returns>
+ internal static XmlReader Create(XName rootElement, IDictionary<string, string> fields) {
+ if (rootElement == null) {
+ throw new ArgumentNullException("rootElement");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ return CreateRoundtripReader(rootElement, fields);
+ }
+
+ /// <summary>
+ /// Creates an <see cref="XmlReader"/> that will read values out of a dictionary.
+ /// </summary>
+ /// <param name="rootElement">The surrounding root XML element to generate.</param>
+ /// <param name="fields">The dictionary to list values from.</param>
+ /// <returns>The generated <see cref="XmlReader"/>.</returns>
+ private static XmlReader CreateRoundtripReader(XName rootElement, IDictionary<string, string> fields) {
+ if (rootElement == null) {
+ throw new ArgumentNullException("rootElement");
+ }
+
+ MemoryStream stream = new MemoryStream();
+ XmlWriter writer = XmlWriter.Create(stream);
+ SerializeDictionaryToXml(writer, rootElement, fields);
+ writer.Flush();
+ stream.Seek(0, SeekOrigin.Begin);
+
+ // For debugging purposes.
+ StreamReader sr = new StreamReader(stream);
+ Trace.WriteLine(sr.ReadToEnd());
+ stream.Seek(0, SeekOrigin.Begin);
+
+ return XmlReader.Create(stream);
+ }
+
+ /// <summary>
+ /// Writes out the values in a dictionary as XML.
+ /// </summary>
+ /// <param name="writer">The <see cref="XmlWriter"/> to write out the XML to.</param>
+ /// <param name="rootElement">The name of the root element to use to surround the dictionary values.</param>
+ /// <param name="fields">The dictionary with values to serialize.</param>
+ private static void SerializeDictionaryToXml(XmlWriter writer, XName rootElement, IDictionary<string, string> fields) {
+ if (writer == null) {
+ throw new ArgumentNullException("writer");
+ }
+ if (rootElement == null) {
+ throw new ArgumentNullException("rootElement");
+ }
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ writer.WriteStartElement(rootElement.LocalName, rootElement.NamespaceName);
+
+ // The elements must be serialized in alphabetical order so the DataContractSerializer will see them.
+ foreach (var pair in fields.OrderBy(pair => pair.Key, StringComparer.Ordinal)) {
+ writer.WriteStartElement(pair.Key, rootElement.NamespaceName);
+ writer.WriteValue(pair.Value);
+ writer.WriteEndElement();
+ }
+
+ writer.WriteEndElement();
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs b/src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs
new file mode 100644
index 0000000..3be043f
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/DictionaryXmlWriter.cs
@@ -0,0 +1,273 @@
+//-----------------------------------------------------------------------
+// <copyright file="DictionaryXmlWriter.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using System.Xml;
+
+ /// <summary>
+ /// An XmlWriter-looking object that actually saves data to a dictionary.
+ /// </summary>
+ internal class DictionaryXmlWriter {
+ /// <summary>
+ /// Creates an <see cref="XmlWriter"/> that actually writes to an IDictionary&lt;string, string&gt; instance.
+ /// </summary>
+ /// <param name="dictionary">The dictionary to save the written XML to.</param>
+ /// <returns>The XmlWriter that will save data to the given dictionary.</returns>
+ internal static XmlWriter Create(IDictionary<string, string> dictionary) {
+ return new PseudoXmlWriter(dictionary);
+ }
+
+ /// <summary>
+ /// Writes out a dictionary as if it were XML.
+ /// </summary>
+ private class PseudoXmlWriter : XmlWriter {
+ /// <summary>
+ /// The dictionary to write values to.
+ /// </summary>
+ private IDictionary<string, string> dictionary;
+
+ /// <summary>
+ /// The key being written at the moment.
+ /// </summary>
+ private string key;
+
+ /// <summary>
+ /// The value being written out at the moment.
+ /// </summary>
+ private StringBuilder value = new StringBuilder();
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PseudoXmlWriter"/> class.
+ /// </summary>
+ /// <param name="dictionary">The dictionary that will be written to.</param>
+ internal PseudoXmlWriter(IDictionary<string, string> dictionary) {
+ if (dictionary == null) {
+ throw new ArgumentNullException("dictionary");
+ }
+
+ this.dictionary = dictionary;
+ }
+
+ /// <summary>
+ /// Gets the spoofed state of the <see cref="XmlWriter"/>.
+ /// </summary>
+ public override WriteState WriteState {
+ get { return WriteState.Element; }
+ }
+
+ /// <summary>
+ /// Prepares to write out a new key/value pair with the given key name to the dictionary.
+ /// </summary>
+ /// <param name="prefix">This parameter is ignored.</param>
+ /// <param name="localName">The key to store in the dictionary.</param>
+ /// <param name="ns">This parameter is ignored.</param>
+ public override void WriteStartElement(string prefix, string localName, string ns) {
+ this.key = localName;
+ this.value.Length = 0;
+ }
+
+ /// <summary>
+ /// Appends some text to the value that is to be stored in the dictionary.
+ /// </summary>
+ /// <param name="text">The text to append to the value.</param>
+ public override void WriteString(string text) {
+ if (!string.IsNullOrEmpty(this.key)) {
+ this.value.Append(text);
+ }
+ }
+
+ /// <summary>
+ /// Writes out a completed key/value to the dictionary.
+ /// </summary>
+ public override void WriteEndElement() {
+ if (this.key != null) {
+ this.dictionary[this.key] = this.value.ToString();
+ this.key = null;
+ this.value.Length = 0;
+ }
+ }
+
+ /// <summary>
+ /// Clears the internal key/value building state.
+ /// </summary>
+ /// <param name="prefix">This parameter is ignored.</param>
+ /// <param name="localName">This parameter is ignored.</param>
+ /// <param name="ns">This parameter is ignored.</param>
+ public override void WriteStartAttribute(string prefix, string localName, string ns) {
+ this.key = null;
+ }
+
+ /// <summary>
+ /// This method does not do anything.
+ /// </summary>
+ public override void WriteEndAttribute() { }
+
+ /// <summary>
+ /// This method does not do anything.
+ /// </summary>
+ public override void Close() { }
+
+ #region Unimplemented methods
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void Flush() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="ns">This parameter is ignored.</param>
+ /// <returns>None, since an exception is always thrown.</returns>
+ public override string LookupPrefix(string ns) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="buffer">This parameter is ignored.</param>
+ /// <param name="index">This parameter is ignored.</param>
+ /// <param name="count">This parameter is ignored.</param>
+ public override void WriteBase64(byte[] buffer, int index, int count) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="text">This parameter is ignored.</param>
+ public override void WriteCData(string text) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="ch">This parameter is ignored.</param>
+ public override void WriteCharEntity(char ch) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="buffer">This parameter is ignored.</param>
+ /// <param name="index">This parameter is ignored.</param>
+ /// <param name="count">This parameter is ignored.</param>
+ public override void WriteChars(char[] buffer, int index, int count) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="text">This parameter is ignored.</param>
+ public override void WriteComment(string text) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="name">This parameter is ignored.</param>
+ /// <param name="pubid">This parameter is ignored.</param>
+ /// <param name="sysid">This parameter is ignored.</param>
+ /// <param name="subset">This parameter is ignored.</param>
+ public override void WriteDocType(string name, string pubid, string sysid, string subset) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void WriteEndDocument() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="name">This parameter is ignored.</param>
+ public override void WriteEntityRef(string name) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void WriteFullEndElement() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="name">This parameter is ignored.</param>
+ /// <param name="text">This parameter is ignored.</param>
+ public override void WriteProcessingInstruction(string name, string text) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="data">This parameter is ignored.</param>
+ public override void WriteRaw(string data) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="buffer">This parameter is ignored.</param>
+ /// <param name="index">This parameter is ignored.</param>
+ /// <param name="count">This parameter is ignored.</param>
+ public override void WriteRaw(char[] buffer, int index, int count) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="standalone">This parameter is ignored.</param>
+ public override void WriteStartDocument(bool standalone) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ public override void WriteStartDocument() {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="lowChar">This parameter is ignored.</param>
+ /// <param name="highChar">This parameter is ignored.</param>
+ public override void WriteSurrogateCharEntity(char lowChar, char highChar) {
+ throw new NotImplementedException();
+ }
+
+ /// <summary>
+ /// Throws <see cref="NotImplementedException"/>.
+ /// </summary>
+ /// <param name="ws">This parameter is ignored.</param>
+ public override void WriteWhitespace(string ws) {
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs
new file mode 100644
index 0000000..3654367
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs
@@ -0,0 +1,137 @@
+//-----------------------------------------------------------------------
+// <copyright file="HttpRequestInfo.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Specialized;
+ using System.IO;
+ using System.Net;
+ using System.Web;
+
+ /// <summary>
+ /// A property store of details of an incoming HTTP request.
+ /// </summary>
+ /// <remarks>
+ /// This serves a very similar purpose to <see cref="HttpRequest"/>, except that
+ /// ASP.NET does not let us fully initialize that class, so we have to write one
+ /// of our one.
+ /// </remarks>
+ internal class HttpRequestInfo {
+ /// <summary>
+ /// The key/value pairs found in the entity of a POST request.
+ /// </summary>
+ private NameValueCollection form;
+
+ /// <summary>
+ /// The key/value pairs found in the querystring of the incoming request.
+ /// </summary>
+ private NameValueCollection queryString;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
+ /// </summary>
+ internal HttpRequestInfo() {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
+ /// </summary>
+ /// <param name="request">The ASP.NET structure to copy from.</param>
+ internal HttpRequestInfo(HttpRequest request) {
+ this.HttpMethod = request.HttpMethod;
+ this.Url = request.Url;
+ this.Headers = GetHeaderCollection(request.Headers);
+ this.InputStream = request.InputStream;
+
+ // These values would normally be calculated, but we'll reuse them from
+ // HttpRequest since they're already calculated, and there's a chance (<g>)
+ // that ASP.NET does a better job of being comprehensive about gathering
+ // these as well.
+ this.form = request.Form;
+ this.queryString = request.QueryString;
+ }
+
+ /// <summary>
+ /// Gets or sets the verb in the request (i.e. GET, POST, etc.)
+ /// </summary>
+ internal string HttpMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the entire URL of the request.
+ /// </summary>
+ internal Uri Url { get; set; }
+
+ /// <summary>
+ /// Gets the query part of the URL (The ? and everything after it).
+ /// </summary>
+ internal string Query {
+ get { return this.Url != null ? this.Url.Query : null; }
+ }
+
+ /// <summary>
+ /// Gets or sets the collection of headers that came in with the request.
+ /// </summary>
+ internal WebHeaderCollection Headers { get; set; }
+
+ /// <summary>
+ /// Gets or sets the entity, or body of the request, if any.
+ /// </summary>
+ internal Stream InputStream { get; set; }
+
+ /// <summary>
+ /// Gets the key/value pairs found in the entity of a POST request.
+ /// </summary>
+ internal NameValueCollection Form {
+ get {
+ if (this.form == null) {
+ if (this.HttpMethod == "POST" && this.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded") {
+ StreamReader reader = new StreamReader(this.InputStream);
+ long originalPosition = this.InputStream.Position;
+ this.form = HttpUtility.ParseQueryString(reader.ReadToEnd());
+ if (this.InputStream.CanSeek) {
+ this.InputStream.Seek(originalPosition, SeekOrigin.Begin);
+ }
+ } else {
+ this.form = new NameValueCollection();
+ }
+ }
+
+ return this.form;
+ }
+ }
+
+ /// <summary>
+ /// Gets the key/value pairs found in the querystring of the incoming request.
+ /// </summary>
+ internal NameValueCollection QueryString {
+ get {
+ if (this.queryString == null) {
+ this.queryString = HttpUtility.ParseQueryString(this.Query);
+ }
+
+ return this.queryString;
+ }
+ }
+
+ /// <summary>
+ /// Converts a NameValueCollection to a WebHeaderCollection.
+ /// </summary>
+ /// <param name="pairs">The collection a HTTP headers.</param>
+ /// <returns>A new collection of the given headers.</returns>
+ private static WebHeaderCollection GetHeaderCollection(NameValueCollection pairs) {
+ if (pairs == null) {
+ throw new ArgumentNullException("pairs");
+ }
+
+ WebHeaderCollection headers = new WebHeaderCollection();
+ foreach (string key in pairs) {
+ headers.Add(key, pairs[key]);
+ }
+
+ return headers;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs b/src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs
new file mode 100644
index 0000000..bebd303
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/IDirectedProtocolMessage.cs
@@ -0,0 +1,20 @@
+//-----------------------------------------------------------------------
+// <copyright file="IDirectedProtocolMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+
+ /// <summary>
+ /// Implemented by messages that have explicit recipients
+ /// (direct requests and all indirect messages).
+ /// </summary>
+ internal interface IDirectedProtocolMessage : IProtocolMessage {
+ /// <summary>
+ /// Gets the URL of the intended receiver of this message.
+ /// </summary>
+ Uri Recipient { get; }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/IMessageTypeProvider.cs b/src/DotNetOAuth/Messaging/IMessageTypeProvider.cs
new file mode 100644
index 0000000..17affcb
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/IMessageTypeProvider.cs
@@ -0,0 +1,41 @@
+//-----------------------------------------------------------------------
+// <copyright file="IMessageTypeProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+
+ /// <summary>
+ /// A tool to analyze an incoming message to figure out what concrete class
+ /// is designed to deserialize it.
+ /// </summary>
+ internal interface IMessageTypeProvider {
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ Type GetRequestMessageType(IDictionary<string, string> fields);
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="request">
+ /// The message that was sent as a request that resulted in the response.
+ /// </param>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields);
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/IProtocolMessage.cs b/src/DotNetOAuth/Messaging/IProtocolMessage.cs
new file mode 100644
index 0000000..3d9f82e
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/IProtocolMessage.cs
@@ -0,0 +1,42 @@
+//-----------------------------------------------------------------------
+// <copyright file="IProtocolMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+
+ /// <summary>
+ /// The interface that classes must implement to be serialized/deserialized
+ /// as OAuth messages.
+ /// </summary>
+ internal interface IProtocolMessage {
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ Protocol Protocol { get; }
+
+ /// <summary>
+ /// Gets whether this is a direct or indirect message.
+ /// </summary>
+ [Obsolete("Are we using this anywhere?")]
+ MessageTransport Transport { get; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ /// <remarks>
+ /// <para>Some messages have required fields, or combinations of fields that must relate to each other
+ /// in specialized ways. After deserializing a message, this method checks the state of the
+ /// message to see if it conforms to the protocol.</para>
+ /// <para>Note that this property should <i>not</i> check signatures or perform any state checks
+ /// outside this scope of this particular message.</para>
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if the message is invalid.</exception>
+ void EnsureValidMessage();
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessageScheme.cs b/src/DotNetOAuth/Messaging/MessageScheme.cs
new file mode 100644
index 0000000..ee91740
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessageScheme.cs
@@ -0,0 +1,35 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageScheme.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ /// <summary>
+ /// The methods available for the Consumer to send direct messages to the Service Provider.
+ /// </summary>
+ /// <remarks>
+ /// See 1.0 spec section 5.2.
+ /// </remarks>
+ internal enum MessageScheme {
+ /// <summary>
+ /// In the HTTP Authorization header as defined in OAuth HTTP Authorization Scheme (OAuth HTTP Authorization Scheme).
+ /// </summary>
+ AuthorizationHeaderRequest,
+
+ /// <summary>
+ /// As the HTTP POST request body with a content-type of application/x-www-form-urlencoded.
+ /// </summary>
+ PostRequest,
+
+ /// <summary>
+ /// Added to the URLs in the query part (as defined by [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) section 3).
+ /// </summary>
+ GetRequest,
+
+ /// <summary>
+ /// Response parameters are sent by the Service Provider to return Tokens and other information to the Consumer in the HTTP response body. The parameter names and values are first encoded as per Parameter Encoding (Parameter Encoding), and concatenated with the ‘&amp;’ character (ASCII code 38) as defined in [RFC3986] (Berners-Lee, T., “Uniform Resource Identifiers (URI): Generic Syntax,” .) Section 2.1.
+ /// </summary>
+ QueryStyleResponse,
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessageSerializer.cs b/src/DotNetOAuth/Messaging/MessageSerializer.cs
new file mode 100644
index 0000000..ac40232
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessageSerializer.cs
@@ -0,0 +1,167 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageSerializer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Linq;
+ using System.Runtime.Serialization;
+ using System.Xml;
+ using System.Xml.Linq;
+
+ /// <summary>
+ /// Serializes/deserializes OAuth messages for/from transit.
+ /// </summary>
+ internal class MessageSerializer {
+ /// <summary>
+ /// The serializer that will be used as a reflection engine to extract
+ /// the OAuth message properties out of their containing <see cref="IProtocolMessage"/>
+ /// objects.
+ /// </summary>
+ private readonly DataContractSerializer serializer;
+
+ /// <summary>
+ /// The specific <see cref="IProtocolMessage"/>-derived type
+ /// that will be serialized and deserialized using this class.
+ /// </summary>
+ private readonly Type messageType;
+
+ /// <summary>
+ /// An AppDomain-wide cache of shared serializers for optimization purposes.
+ /// </summary>
+ private static Dictionary<Type, MessageSerializer> prebuiltSerializers = new Dictionary<Type, MessageSerializer>();
+
+ /// <summary>
+ /// Backing field for the <see cref="RootElement"/> property
+ /// </summary>
+ private XName rootElement;
+
+ /// <summary>
+ /// Initializes a new instance of the MessageSerializer class.
+ /// </summary>
+ /// <param name="messageType">The specific <see cref="IProtocolMessage"/>-derived type
+ /// that will be serialized and deserialized using this class.</param>
+ private MessageSerializer(Type messageType) {
+ if (messageType == null) {
+ throw new ArgumentNullException("messageType");
+ }
+ if (!typeof(IProtocolMessage).IsAssignableFrom(messageType)) {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.UnexpectedType,
+ typeof(IProtocolMessage).FullName,
+ messageType.FullName),
+ "messageType");
+ }
+
+ this.messageType = messageType;
+ this.serializer = new DataContractSerializer(
+ messageType, this.RootElement.LocalName, this.RootElement.NamespaceName);
+ }
+
+ /// <summary>
+ /// Gets the XML element that is used to surround all the XML values from the dictionary.
+ /// </summary>
+ private XName RootElement {
+ get {
+ if (this.rootElement == null) {
+ DataContractAttribute attribute = this.messageType.GetCustomAttributes(typeof(DataContractAttribute), false).OfType<DataContractAttribute>().Single();
+ this.rootElement = XName.Get("root", attribute.Namespace);
+ }
+
+ return this.rootElement;
+ }
+ }
+
+ /// <summary>
+ /// Returns a message serializer from a reusable collection of serializers.
+ /// </summary>
+ /// <param name="messageType">The type of message that will be serialized/deserialized.</param>
+ /// <returns>A previously created serializer if one exists, or a newly created one.</returns>
+ internal static MessageSerializer Get(Type messageType) {
+ if (messageType == null) {
+ throw new ArgumentNullException("messageType");
+ }
+
+ // We do this as efficiently as possible by first trying to fetch the
+ // serializer out of the dictionary without taking a lock.
+ MessageSerializer serializer;
+ if (prebuiltSerializers.TryGetValue(messageType, out serializer)) {
+ return serializer;
+ }
+
+ // Since it wasn't there, we'll be trying to write to the dictionary so
+ // we take a lock and try reading again first, then creating the serializer
+ // and storing it when we're sure it absolutely necessary.
+ lock (prebuiltSerializers) {
+ if (prebuiltSerializers.TryGetValue(messageType, out serializer)) {
+ return serializer;
+ }
+ serializer = new MessageSerializer(messageType);
+ prebuiltSerializers.Add(messageType, serializer);
+ }
+ return serializer;
+ }
+
+ /// <summary>
+ /// Reads the data from a message instance and returns a series of name=value pairs for the fields that must be included in the message.
+ /// </summary>
+ /// <param name="message">The message to be serialized.</param>
+ /// <returns>The dictionary of values to send for the message.</returns>
+ internal IDictionary<string, string> Serialize(IProtocolMessage message) {
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ var fields = new Dictionary<string, string>(StringComparer.Ordinal);
+ this.Serialize(fields, message);
+ return fields;
+ }
+
+ /// <summary>
+ /// Saves the [DataMember] properties of a message to an existing dictionary.
+ /// </summary>
+ /// <param name="fields">The dictionary to save values to.</param>
+ /// <param name="message">The message to pull values from.</param>
+ internal void Serialize(IDictionary<string, string> fields, IProtocolMessage message) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+ if (message == null) {
+ throw new ArgumentNullException("message");
+ }
+
+ message.EnsureValidMessage();
+ using (XmlWriter writer = DictionaryXmlWriter.Create(fields)) {
+ this.serializer.WriteObjectContent(writer, message);
+ }
+ }
+
+ /// <summary>
+ /// Reads name=value pairs into an OAuth message.
+ /// </summary>
+ /// <param name="fields">The name=value pairs that were read in from the transport.</param>
+ /// <returns>The instantiated and initialized <see cref="IProtocolMessage"/> instance.</returns>
+ internal IProtocolMessage Deserialize(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ var reader = DictionaryXmlReader.Create(this.RootElement, fields);
+ IProtocolMessage result;
+ try {
+ result = (IProtocolMessage)this.serializer.ReadObject(reader, false);
+ } catch (SerializationException ex) {
+ // Missing required fields is one cause of this exception.
+ throw new ProtocolException(Strings.InvalidIncomingMessage, ex);
+ }
+ result.EnsureValidMessage();
+ return result;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessageTransport.cs b/src/DotNetOAuth/Messaging/MessageTransport.cs
new file mode 100644
index 0000000..40e6897
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessageTransport.cs
@@ -0,0 +1,22 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageTransport.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ /// <summary>
+ /// The type of transport mechanism used for a message: either direct or indirect.
+ /// </summary>
+ public enum MessageTransport {
+ /// <summary>
+ /// A message that is sent directly from the Consumer to the Service Provider, or vice versa.
+ /// </summary>
+ Direct,
+
+ /// <summary>
+ /// A message that is sent from one party to another via a redirect in the user agent.
+ /// </summary>
+ Indirect,
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs
new file mode 100644
index 0000000..98e8cc6
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs
@@ -0,0 +1,108 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:2.0.50727.3053
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "2.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class MessagingStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal MessagingStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOAuth.Messaging.MessagingStrings", typeof(MessagingStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An instance of type {0} was expected, but received unexpected derived type {1}..
+ /// </summary>
+ internal static string DerivedTypeNotExpected {
+ get {
+ return ResourceManager.GetString("DerivedTypeNotExpected", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The directed message&apos;s Recipient property must not be null..
+ /// </summary>
+ internal static string DirectedMessageMissingRecipient {
+ get {
+ return ResourceManager.GetString("DirectedMessageMissingRecipient", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Error occurred while sending a direct message or gettings the response..
+ /// </summary>
+ internal static string ErrorInRequestReplyMessage {
+ get {
+ return ResourceManager.GetString("ErrorInRequestReplyMessage", 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 {
+ get {
+ return ResourceManager.GetString("QueuedMessageResponseAlreadyExists", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The type {0} or a derived type was expected, but {1} was given..
+ /// </summary>
+ internal static string UnexpectedType {
+ get {
+ return ResourceManager.GetString("UnexpectedType", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.resx b/src/DotNetOAuth/Messaging/MessagingStrings.resx
new file mode 100644
index 0000000..2125e15
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessagingStrings.resx
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="DerivedTypeNotExpected" xml:space="preserve">
+ <value>An instance of type {0} was expected, but received unexpected derived type {1}.</value>
+ </data>
+ <data name="DirectedMessageMissingRecipient" xml:space="preserve">
+ <value>The directed message's Recipient property must not be null.</value>
+ </data>
+ <data name="ErrorInRequestReplyMessage" xml:space="preserve">
+ <value>Error occurred while sending a direct message or gettings the response.</value>
+ </data>
+ <data name="QueuedMessageResponseAlreadyExists" xml:space="preserve">
+ <value>A message response is already queued for sending in the response stream.</value>
+ </data>
+ <data name="UnexpectedType" xml:space="preserve">
+ <value>The type {0} or a derived type was expected, but {1} was given.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOAuth/Messaging/MessagingUtilities.cs b/src/DotNetOAuth/Messaging/MessagingUtilities.cs
new file mode 100644
index 0000000..e8069f6
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/MessagingUtilities.cs
@@ -0,0 +1,119 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessagingUtilities.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+
+ /// <summary>
+ /// A grab-bag of utility methods useful for the channel stack of the protocol.
+ /// </summary>
+ internal static class MessagingUtilities {
+ /// <summary>
+ /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance,
+ /// taking care to set some headers to the appropriate properties of
+ /// <see cref="HttpResponse" />
+ /// </summary>
+ /// <param name="headers">The headers to add.</param>
+ /// <param name="response">The <see cref="HttpResponse"/> instance to set the appropriate values to.</param>
+ internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponse response) {
+ if (headers == null) {
+ throw new ArgumentNullException("headers");
+ }
+ if (response == null) {
+ throw new ArgumentNullException("response");
+ }
+ foreach (string headerName in headers) {
+ switch (headerName) {
+ case "Content-Type":
+ response.ContentType = headers[HttpResponseHeader.ContentType];
+ break;
+
+ // Add more special cases here as necessary.
+ default:
+ response.AddHeader(headerName, headers[headerName]);
+ break;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Concatenates a list of name-value pairs as key=value&amp;key=value,
+ /// taking care to properly encode each key and value for URL
+ /// transmission. No ? is prefixed to the string.
+ /// </summary>
+ /// <param name="args">The dictionary of key/values to read from.</param>
+ /// <returns>The formulated querystring style string.</returns>
+ internal static string CreateQueryString(IDictionary<string, string> args) {
+ if (args == null) {
+ throw new ArgumentNullException("args");
+ }
+ if (args.Count == 0) {
+ return string.Empty;
+ }
+ StringBuilder sb = new StringBuilder(args.Count * 10);
+
+ foreach (var p in args) {
+ sb.Append(HttpUtility.UrlEncode(p.Key));
+ sb.Append('=');
+ sb.Append(HttpUtility.UrlEncode(p.Value));
+ sb.Append('&');
+ }
+ sb.Length--; // remove trailing &
+
+ return sb.ToString();
+ }
+
+ /// <summary>
+ /// Adds a set of name-value pairs to the end of a given URL
+ /// as part of the querystring piece. Prefixes a ? or &amp; before
+ /// first element as necessary.
+ /// </summary>
+ /// <param name="builder">The UriBuilder to add arguments to.</param>
+ /// <param name="args">
+ /// The arguments to add to the query.
+ /// If null, <paramref name="builder"/> is not changed.
+ /// </param>
+ internal static void AppendQueryArgs(UriBuilder builder, IDictionary<string, string> args) {
+ if (builder == null) {
+ throw new ArgumentNullException("builder");
+ }
+
+ if (args != null && args.Count > 0) {
+ StringBuilder sb = new StringBuilder(50 + (args.Count * 10));
+ if (!string.IsNullOrEmpty(builder.Query)) {
+ sb.Append(builder.Query.Substring(1));
+ sb.Append('&');
+ }
+ sb.Append(CreateQueryString(args));
+
+ builder.Query = sb.ToString();
+ }
+ }
+
+ /// <summary>
+ /// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
+ /// </summary>
+ /// <param name="nvc">The NameValueCollection to convert. May be null.</param>
+ /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns>
+ internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc) {
+ if (nvc == null) {
+ return null;
+ }
+
+ var dictionary = new Dictionary<string, string>();
+ foreach (string key in nvc) {
+ dictionary.Add(key, nvc[key]);
+ }
+
+ return dictionary;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/Messaging/Response.cs b/src/DotNetOAuth/Messaging/Response.cs
new file mode 100644
index 0000000..68950ff
--- /dev/null
+++ b/src/DotNetOAuth/Messaging/Response.cs
@@ -0,0 +1,73 @@
+//-----------------------------------------------------------------------
+// <copyright file="Response.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Messaging {
+ using System;
+ using System.Net;
+ using System.Web;
+
+ /// <summary>
+ /// A protocol message (request or response) that passes between Consumer and Service Provider
+ /// via the user agent using a redirect or form POST submission,
+ /// OR a direct message response.
+ /// </summary>
+ /// <remarks>
+ /// <para>An instance of this type describes the HTTP response that must be sent
+ /// in response to the current HTTP request.</para>
+ /// <para>It is important that this response make up the entire HTTP response.
+ /// A hosting ASPX page should not be allowed to render its normal HTML output
+ /// after this response is sent. The normal rendered output of an ASPX page
+ /// can be canceled by calling <see cref="HttpResponse.End"/> after this message
+ /// is sent on the response stream.</para>
+ /// </remarks>
+ public class Response {
+ /// <summary>
+ /// Gets the headers that must be included in the response to the user agent.
+ /// </summary>
+ /// <remarks>
+ /// The headers in this collection are not meant to be a comprehensive list
+ /// of exactly what should be sent, but are meant to augment whatever headers
+ /// are generally included in a typical response.
+ /// </remarks>
+ public WebHeaderCollection Headers { get; internal set; }
+
+ /// <summary>
+ /// Gets the body of the HTTP response.
+ /// </summary>
+ public byte[] Body { get; internal set; }
+
+ /// <summary>
+ /// Gets the HTTP status code to use in the HTTP response.
+ /// </summary>
+ public HttpStatusCode Status { get; internal set; }
+
+ /// <summary>
+ /// Gets or sets a reference to the actual protocol message that
+ /// is being sent via the user agent.
+ /// </summary>
+ internal IProtocolMessage OriginalMessage { get; set; }
+
+ /// <summary>
+ /// Automatically sends the appropriate response to the user agent.
+ /// Requires a current HttpContext.
+ /// </summary>
+ public void Send() {
+ if (HttpContext.Current == null) {
+ throw new InvalidOperationException(Strings.CurrentHttpContextRequired);
+ }
+
+ HttpContext.Current.Response.Clear();
+ HttpContext.Current.Response.StatusCode = (int)this.Status;
+ MessagingUtilities.ApplyHeadersToResponse(this.Headers, HttpContext.Current.Response);
+ if (this.Body != null && this.Body.Length > 0) {
+ HttpContext.Current.Response.OutputStream.Write(this.Body, 0, this.Body.Length);
+ HttpContext.Current.Response.OutputStream.Flush();
+ }
+ HttpContext.Current.Response.OutputStream.Close();
+ HttpContext.Current.Response.End();
+ }
+ }
+}