summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/DotNetOAuth.Test/Messaging/ChannelTests.cs54
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestChannel.cs4
-rw-r--r--src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs3
-rw-r--r--src/DotNetOAuth.Test/OAuthChannelTests.cs2
-rw-r--r--src/DotNetOAuth/DotNetOAuth.csproj1
-rw-r--r--src/DotNetOAuth/Messaging/Channel.cs54
-rw-r--r--src/DotNetOAuth/Messaging/HttpRequestInfo.cs137
-rw-r--r--src/DotNetOAuth/OAuthChannel.cs98
-rw-r--r--src/DotNetOAuth/OAuthMessageTypeProvider.cs25
9 files changed, 298 insertions, 80 deletions
diff --git a/src/DotNetOAuth.Test/Messaging/ChannelTests.cs b/src/DotNetOAuth.Test/Messaging/ChannelTests.cs
index 5d027fc..3f93cc2 100644
--- a/src/DotNetOAuth.Test/Messaging/ChannelTests.cs
+++ b/src/DotNetOAuth.Test/Messaging/ChannelTests.cs
@@ -6,23 +6,21 @@
namespace DotNetOAuth.Test.Messaging {
using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Text;
- using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using System.IO;
+ using System.Net;
using DotNetOAuth.Messaging;
using DotNetOAuth.Test.Mocks;
- using System.Web;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class ChannelTests : TestBase {
- Channel channel;
+ private Channel channel;
[TestInitialize]
public override void SetUp() {
base.SetUp();
- channel = new TestChannel();
+ this.channel = new TestChannel();
}
[TestMethod]
@@ -32,9 +30,41 @@ namespace DotNetOAuth.Test.Messaging {
[TestMethod]
public void ReceiveFromQueryString() {
- string queryString = "age=15&Name=Andrew&Location=http%3A%2F%2Fhostb%2FpathB";
- var httpRequest = new HttpRequest("filename", "http://localhost/path?" + queryString, queryString);
- IProtocolMessage requestMessage = this.channel.Receive(httpRequest);
+ Uri requestUri = new Uri("http://localhost/path?age=15&Name=Andrew&Location=http%3A%2F%2Fhostb%2FpathB");
+ WebHeaderCollection headers = new WebHeaderCollection();
+ HttpRequestInfo request = new HttpRequestInfo {
+ HttpMethod = "GET",
+ Url = requestUri,
+ Headers = headers,
+ InputStream = new MemoryStream(),
+ };
+ IProtocolMessage requestMessage = this.channel.Receive(request);
+ Assert.IsNotNull(requestMessage);
+ Assert.IsInstanceOfType(requestMessage, typeof(TestMessage));
+ TestMessage testMessage = (TestMessage)requestMessage;
+ Assert.AreEqual(15, testMessage.Age);
+ Assert.AreEqual("Andrew", testMessage.Name);
+ Assert.AreEqual("http://hostb/pathB", testMessage.Location.AbsoluteUri);
+ }
+
+ [TestMethod]
+ public void ReceiveFromForm() {
+ Uri requestUri = new Uri("http://localhost/path");
+ WebHeaderCollection headers = new WebHeaderCollection();
+ headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded");
+ MemoryStream ms = new MemoryStream();
+ StreamWriter sw = new StreamWriter(ms);
+ sw.Write("age=15&Name=Andrew&Location=http%3A%2F%2Fhostb%2FpathB");
+ sw.Flush();
+ ms.Position = 0;
+ HttpRequestInfo request = new HttpRequestInfo {
+ HttpMethod = "POST",
+ Url = requestUri,
+ Headers = headers,
+ InputStream = ms,
+ };
+
+ IProtocolMessage requestMessage = this.channel.Receive(request);
Assert.IsNotNull(requestMessage);
Assert.IsInstanceOfType(requestMessage, typeof(TestMessage));
TestMessage testMessage = (TestMessage)requestMessage;
@@ -42,5 +72,9 @@ namespace DotNetOAuth.Test.Messaging {
Assert.AreEqual("Andrew", testMessage.Name);
Assert.AreEqual("http://hostb/pathB", testMessage.Location.AbsoluteUri);
}
+
+ private static HttpRequestInfo CreateHttpRequest() {
+ throw new NotImplementedException();
+ }
}
}
diff --git a/src/DotNetOAuth.Test/Mocks/TestChannel.cs b/src/DotNetOAuth.Test/Mocks/TestChannel.cs
index c280243..bc2ed1e 100644
--- a/src/DotNetOAuth.Test/Mocks/TestChannel.cs
+++ b/src/DotNetOAuth.Test/Mocks/TestChannel.cs
@@ -20,6 +20,10 @@ namespace DotNetOAuth.Test.Mocks {
throw new NotImplementedException();
}
+ protected internal override IProtocolMessage Receive(System.IO.Stream responseStream) {
+ throw new NotImplementedException();
+ }
+
protected override void SendDirectMessageResponse(IProtocolMessage response) {
throw new NotImplementedException();
}
diff --git a/src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs b/src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs
index 3caf81c..647a09f 100644
--- a/src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs
+++ b/src/DotNetOAuth.Test/Mocks/TestMessageTypeProvider.cs
@@ -12,7 +12,6 @@ namespace DotNetOAuth.Test.Mocks {
using DotNetOAuth.Messaging;
internal class TestMessageTypeProvider : IMessageTypeProvider {
-
#region IMessageTypeProvider Members
public Type GetRequestMessageType(IDictionary<string, string> fields) {
@@ -24,7 +23,7 @@ namespace DotNetOAuth.Test.Mocks {
}
public Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) {
- return GetRequestMessageType(fields);
+ return this.GetRequestMessageType(fields);
}
#endregion
diff --git a/src/DotNetOAuth.Test/OAuthChannelTests.cs b/src/DotNetOAuth.Test/OAuthChannelTests.cs
index 2d55f8c..1e41832 100644
--- a/src/DotNetOAuth.Test/OAuthChannelTests.cs
+++ b/src/DotNetOAuth.Test/OAuthChannelTests.cs
@@ -9,8 +9,8 @@ namespace DotNetOAuth.Test {
using System.Collections.Generic;
using System.Linq;
using System.Text;
- using Microsoft.VisualStudio.TestTools.UnitTesting;
using DotNetOAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
[TestClass]
public class OAuthChannelTests : TestBase {
diff --git a/src/DotNetOAuth/DotNetOAuth.csproj b/src/DotNetOAuth/DotNetOAuth.csproj
index 13bc1ed..4db5eb0 100644
--- a/src/DotNetOAuth/DotNetOAuth.csproj
+++ b/src/DotNetOAuth/DotNetOAuth.csproj
@@ -70,6 +70,7 @@
<Compile Include="Messaging\DictionaryXmlReader.cs" />
<Compile Include="Messaging\DictionaryXmlWriter.cs" />
<Compile Include="Messaging\Channel.cs" />
+ <Compile Include="Messaging\HttpRequestInfo.cs" />
<Compile Include="Messaging\IDirectedProtocolMessage.cs" />
<Compile Include="Messaging\IMessageTypeProvider.cs" />
<Compile Include="Messaging\MessagingStrings.Designer.cs">
diff --git a/src/DotNetOAuth/Messaging/Channel.cs b/src/DotNetOAuth/Messaging/Channel.cs
index 8646cf4..fe03b5f 100644
--- a/src/DotNetOAuth/Messaging/Channel.cs
+++ b/src/DotNetOAuth/Messaging/Channel.cs
@@ -136,22 +136,42 @@ namespace DotNetOAuth.Messaging {
/// Requires an HttpContext.Current context.
/// </remarks>
internal IProtocolMessage Receive() {
- return this.Receive(HttpContext.Current.Request);
+ return this.Receive(new HttpRequestInfo(HttpContext.Current.Request));
}
/// <summary>
- /// Gets the protocol message embedded in the given HTTP request, if present.
+ /// 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>
- internal virtual IProtocolMessage Receive(HttpRequest request) {
+ protected internal virtual IProtocolMessage Receive(HttpRequestInfo request) {
if (request == null) {
throw new ArgumentNullException("request");
}
- // Extract the message data and attempt to determine what kind of message it is.
+ // 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 Receive(Stream responseStream);
+
+ /// <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;
- var fields = this.ExtractDataFromRequest(request);
if (fields != null) {
messageType = this.MessageTypeProvider.GetRequestMessageType(fields);
}
@@ -164,28 +184,8 @@ namespace DotNetOAuth.Messaging {
// We have a message! Assemble it.
var serializer = MessageSerializer.Get(messageType);
IProtocolMessage message = serializer.Deserialize(fields);
-
- return message;
- }
-
- /// <summary>
- /// Searches an incoming HTTP request for data that could be used to assemble
- /// a protocol request message.
- /// </summary>
- /// <param name="request">The HTTP request to search.</param>
- /// <returns>A dictionary of data in the request. Should never be null, but may be empty.</returns>
- protected virtual Dictionary<string, string> ExtractDataFromRequest(HttpRequest 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 fields;
+ return message;
}
/// <summary>
@@ -258,7 +258,7 @@ namespace DotNetOAuth.Messaging {
Body = new byte[0],
OriginalMessage = message
};
-
+
return response;
}
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/OAuthChannel.cs b/src/DotNetOAuth/OAuthChannel.cs
index 52611f6..bfd80da 100644
--- a/src/DotNetOAuth/OAuthChannel.cs
+++ b/src/DotNetOAuth/OAuthChannel.cs
@@ -20,15 +20,69 @@ namespace DotNetOAuth {
/// <summary>
/// Initializes a new instance of the <see cref="OAuthChannel"/> class.
/// </summary>
- /// <param name="messageTypeProvider">
- /// A class prepared to analyze incoming messages and indicate what concrete
- /// message types can deserialize from it.
- /// </param>
internal OAuthChannel()
: base(new OAuthMessageTypeProvider()) {
}
/// <summary>
+ /// Searches an incoming HTTP request for data that could be used to assemble
+ /// a protocol request message.
+ /// </summary>
+ /// <param name="request">The HTTP request to search.</param>
+ /// <returns>A dictionary of data in the request. Should never be null, but may be empty.</returns>
+ protected internal override IProtocolMessage Receive(HttpRequestInfo request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ // First search the Authorization header. Use it exclusively if it's present.
+ string authorization = request.Headers[HttpRequestHeader.Authorization];
+ if (authorization != null) {
+ string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter?
+ string oauthPrefix = Protocol.Default.AuthorizationHeaderScheme + " ";
+
+ // The Authorization header may have multiple uses, and OAuth may be just one of them.
+ // Go through each one looking for an OAuth one.
+ foreach (string auth in authorizationSections) {
+ string trimmedAuth = auth.Trim();
+ if (trimmedAuth.StartsWith(oauthPrefix, StringComparison.Ordinal)) {
+ // We found an Authorization: OAuth header.
+ // Parse it according to the rules in section 5.4.1 of the V1.0 spec.
+ var fields = new Dictionary<string, string>();
+ foreach (string stringPair in trimmedAuth.Substring(oauthPrefix.Length).Split(',')) {
+ string[] keyValueStringPair = stringPair.Trim().Split('=');
+ string key = Uri.UnescapeDataString(keyValueStringPair[0]);
+ string value = Uri.UnescapeDataString(keyValueStringPair[1].Trim('"'));
+ fields.Add(key, value);
+ }
+
+ return this.Receive(fields);
+ }
+ }
+ }
+
+ // We didn't find an OAuth authorization header. Revert to other payload methods.
+ return base.Receive(request);
+ }
+
+ /// <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 override IProtocolMessage Receive(Stream responseStream) {
+ if (responseStream == null) {
+ throw new ArgumentNullException("responseStream");
+ }
+
+ using (StreamReader reader = new StreamReader(responseStream)) {
+ string response = reader.ReadToEnd();
+ var fields = HttpUtility.ParseQueryString(response).ToDictionary();
+ return Receive(fields);
+ }
+ }
+
+ /// <summary>
/// Queues a message for sending in the response stream where the fields
/// are sent in the response stream in querystring style.
/// </summary>
@@ -98,42 +152,6 @@ namespace DotNetOAuth {
}
/// <summary>
- /// Searches an incoming HTTP request for data that could be used to assemble
- /// a protocol request message.
- /// </summary>
- /// <param name="request">The HTTP request to search.</param>
- /// <returns>A dictionary of data in the request. Should never be null, but may be empty.</returns>
- protected override Dictionary<string, string> ExtractDataFromRequest(HttpRequest request) {
- // First search the Authorization header. Use it exclusively if it's present.
- if (request.Headers["Authorization"] != null) {
- string[] authorizationSections = request.Headers["Authorization"].Split(';'); // TODO: is this the right delimiter?
- string oauthPrefix = Protocol.Default.AuthorizationHeaderScheme + " ";
-
- // The Authorization header may have multiple uses, and OAuth may be just one of them.
- // Go through each one looking for an OAuth one.
- foreach (string auth in authorizationSections) {
- string trimmedAuth = auth.Trim();
- if (trimmedAuth.StartsWith(oauthPrefix, StringComparison.Ordinal)) {
- // We found an Authorization: OAuth header.
- // Parse it according to the rules in section 5.4.1 of the V1.0 spec.
- var fields = new Dictionary<string, string>();
- foreach (string stringPair in trimmedAuth.Substring(oauthPrefix.Length).Split(',')) {
- string[] keyValueStringPair = stringPair.Trim().Split('=');
- string key = Uri.UnescapeDataString(keyValueStringPair[0]);
- string value = Uri.UnescapeDataString(keyValueStringPair[1].Trim('"'));
- fields.Add(key, value);
- }
-
- return fields;
- }
- }
- }
-
- // We didn't find an OAuth authorization header. Revert to other payload methods.
- return base.ExtractDataFromRequest(request);
- }
-
- /// <summary>
/// Reports an error to the user via the user agent.
/// </summary>
/// <param name="exception">The error information.</param>
diff --git a/src/DotNetOAuth/OAuthMessageTypeProvider.cs b/src/DotNetOAuth/OAuthMessageTypeProvider.cs
index 63e1e6f..7d4dbcd 100644
--- a/src/DotNetOAuth/OAuthMessageTypeProvider.cs
+++ b/src/DotNetOAuth/OAuthMessageTypeProvider.cs
@@ -11,13 +11,38 @@ namespace DotNetOAuth {
using System.Text;
using DotNetOAuth.Messaging;
+ /// <summary>
+ /// An OAuth-protocol specific implementation of the <see cref="IMessageTypeProvider"/>
+ /// interface.
+ /// </summary>
internal class OAuthMessageTypeProvider : IMessageTypeProvider {
#region IMessageTypeProvider Members
+ /// <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>
public Type GetRequestMessageType(IDictionary<string, string> fields) {
throw new NotImplementedException();
}
+ /// <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>
public Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) {
throw new NotImplementedException();
}