//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOAuth {
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Text;
using System.Web;
using DotNetOAuth.Messaging;
///
/// An OAuth-specific implementation of the class.
///
internal class OAuthChannel : Channel {
///
/// Initializes a new instance of the class.
///
internal OAuthChannel()
: base(new OAuthMessageTypeProvider()) {
}
///
/// Searches an incoming HTTP request for data that could be used to assemble
/// a protocol request message.
///
/// The HTTP request to search.
/// A dictionary of data in the request. Should never be null, but may be empty.
protected internal override IProtocolMessage ReadFromRequest(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();
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.ReadFromRequest(request);
}
///
/// Gets the protocol message that may be in the given HTTP response stream.
///
/// The response that is anticipated to contain an OAuth message.
/// The deserialized message, if one is found. Null otherwise.
protected internal override IProtocolMessage ReadFromResponse(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);
}
}
///
/// Sends a direct message to a remote party and waits for the response.
///
/// The message to send.
/// The remote party's response.
protected internal override IProtocolMessage Request(IDirectedProtocolMessage request) {
if (request == null) {
throw new ArgumentNullException("request");
}
HttpWebRequest httpRequest;
MessageScheme transmissionMethod = MessageScheme.AuthorizationHeaderRequest;
switch (transmissionMethod) {
case MessageScheme.AuthorizationHeaderRequest:
httpRequest = this.InitializeRequestAsAuthHeader(request);
break;
case MessageScheme.PostRequest:
httpRequest = this.InitializeRequestAsPost(request);
break;
case MessageScheme.GetRequest:
httpRequest = this.InitializeRequestAsGet(request);
break;
default:
throw new NotSupportedException();
}
// Submit the request and await the reply.
Dictionary responseFields;
try {
using (HttpWebResponse response = (HttpWebResponse)httpRequest.GetResponse()) {
using (StreamReader reader = new StreamReader(response.GetResponseStream())) {
string queryString = reader.ReadToEnd();
responseFields = HttpUtility.ParseQueryString(queryString).ToDictionary();
}
}
} catch (WebException ex) {
throw new ProtocolException(MessagingStrings.ErrorInRequestReplyMessage, ex);
}
Type messageType = this.MessageTypeProvider.GetResponseMessageType(request, responseFields);
var responseSerialize = MessageSerializer.Get(messageType);
var responseMessage = responseSerialize.Deserialize(responseFields);
return responseMessage;
}
///
/// Queues a message for sending in the response stream where the fields
/// are sent in the response stream in querystring style.
///
/// The message to send as a response.
///
/// This method implements spec V1.0 section 5.3.
///
protected override void SendDirectMessageResponse(IProtocolMessage response) {
MessageSerializer serializer = MessageSerializer.Get(response.GetType());
var fields = serializer.Serialize(response);
string responseBody = MessagingUtilities.CreateQueryString(fields);
Response encodedResponse = new Response {
Body = Encoding.UTF8.GetBytes(responseBody),
OriginalMessage = response,
Status = System.Net.HttpStatusCode.OK,
Headers = new System.Net.WebHeaderCollection(),
};
this.QueueIndirectOrResponseMessage(encodedResponse);
}
///
/// Reports an error to the user via the user agent.
///
/// The error information.
protected override void ReportErrorToUser(ProtocolException exception) {
throw new NotImplementedException();
}
///
/// Sends an error result directly to the calling remote party according to the
/// rules of the protocol.
///
/// The error information.
protected override void ReportErrorAsDirectResponse(ProtocolException exception) {
throw new NotImplementedException();
}
///
/// Prepares to send a request to the Service Provider via the Authorization header.
///
/// The message to be transmitted to the ServiceProvider.
/// The web request ready to send.
///
/// This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).
///
private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) {
var serializer = MessageSerializer.Get(requestMessage.GetType());
var fields = serializer.Serialize(requestMessage);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
StringBuilder authorization = new StringBuilder();
authorization.Append(requestMessage.Protocol.AuthorizationHeaderScheme);
authorization.Append(" ");
foreach (var pair in fields) {
string key = Uri.EscapeDataString(pair.Key);
string value = Uri.EscapeDataString(pair.Value);
authorization.Append(key);
authorization.Append("=\"");
authorization.Append(value);
authorization.Append("\",");
}
authorization.Length--; // remove trailing comma
httpRequest.Headers.Add(HttpRequestHeader.Authorization, authorization.ToString());
return httpRequest;
}
///
/// Prepares to send a request to the Service Provider as the payload of a POST request.
///
/// The message to be transmitted to the ServiceProvider.
/// The web request ready to send.
///
/// This method implements OAuth 1.0 section 5.2, item #2.
///
private HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) {
var serializer = MessageSerializer.Get(requestMessage.GetType());
var fields = serializer.Serialize(requestMessage);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
httpRequest.Method = "POST";
httpRequest.ContentType = "application/x-www-form-urlencoded";
string requestBody = MessagingUtilities.CreateQueryString(fields);
httpRequest.ContentLength = requestBody.Length;
using (StreamWriter writer = new StreamWriter(httpRequest.GetRequestStream())) {
writer.Write(requestBody);
}
return httpRequest;
}
///
/// Prepares to send a request to the Service Provider as the query string in a GET request.
///
/// The message to be transmitted to the ServiceProvider.
/// The web request ready to send.
///
/// This method implements OAuth 1.0 section 5.2, item #3.
///
private HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) {
var serializer = MessageSerializer.Get(requestMessage.GetType());
var fields = serializer.Serialize(requestMessage);
UriBuilder builder = new UriBuilder(requestMessage.Recipient);
MessagingUtilities.AppendQueryArgs(builder, fields);
HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
return httpRequest;
}
}
}