//-----------------------------------------------------------------------
//
// Copyright (c) Andrew Arnott. All rights reserved.
//
//-----------------------------------------------------------------------
namespace DotNetOAuth.Messaging {
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using DotNetOAuth.ChannelElements;
using DotNetOAuth.Messaging.Reflection;
///
/// A grab-bag of utility methods useful for the channel stack of the protocol.
///
internal static class MessagingUtilities {
///
/// Adds a set of HTTP headers to an instance,
/// taking care to set some headers to the appropriate properties of
///
///
/// The headers to add.
/// The instance to set the appropriate values to.
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;
}
}
}
///
/// Copies the contents of one stream to another.
///
/// The stream to copy from, at the position where copying should begin.
/// The stream to copy to, at the position where bytes should be written.
///
/// Copying begins at the streams' current positions.
/// The positions are NOT reset after copying is complete.
///
internal static void CopyTo(this Stream copyFrom, Stream copyTo) {
if (copyFrom == null) {
throw new ArgumentNullException("copyFrom");
}
if (copyTo == null) {
throw new ArgumentNullException("copyTo");
}
if (!copyFrom.CanRead) {
throw new ArgumentException(MessagingStrings.StreamUnreadable, "copyFrom");
}
if (!copyTo.CanWrite) {
throw new ArgumentException(MessagingStrings.StreamUnwritable, "copyTo");
}
byte[] buffer = new byte[1024];
int readBytes;
while ((readBytes = copyFrom.Read(buffer, 0, 1024)) > 0) {
copyTo.Write(buffer, 0, readBytes);
}
}
///
/// Concatenates a list of name-value pairs as key=value&key=value,
/// taking care to properly encode each key and value for URL
/// transmission. No ? is prefixed to the string.
///
/// The dictionary of key/values to read from.
/// The formulated querystring style string.
internal static string CreateQueryString(IDictionary 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();
}
///
/// Adds a set of name-value pairs to the end of a given URL
/// as part of the querystring piece. Prefixes a ? or & before
/// first element as necessary.
///
/// The UriBuilder to add arguments to.
///
/// The arguments to add to the query.
/// If null, is not changed.
///
internal static void AppendQueryArgs(UriBuilder builder, IDictionary 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();
}
}
///
/// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any.
/// Cookieless session directory (if applicable) is also included.
///
/// The URL in the user agent's Location bar.
internal static Uri GetRequestUrlFromContext() {
HttpContext context = HttpContext.Current;
if (context == null) {
throw new InvalidOperationException(MessagingStrings.CurrentHttpContextRequired);
}
// We use Request.Url for the full path to the server, and modify it
// with Request.RawUrl to capture both the cookieless session "directory" if it exists
// and the original path in case URL rewriting is going on. We don't want to be
// fooled by URL rewriting because we're comparing the actual URL with what's in
// the return_to parameter in some cases.
// Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
// session, but not the URL rewriting problem.
return new Uri(context.Request.Url, context.Request.RawUrl);
}
///
/// Strips any and all URI query parameters that start with some prefix.
///
/// The URI that may have a query with parameters to remove.
/// The prefix for parameters to remove.
/// Either a new Uri with the parameters removed if there were any to remove, or the same Uri instance if no parameters needed to be removed.
internal static Uri StripQueryArgumentsWithPrefix(this Uri uri, string prefix) {
NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query);
var matchingKeys = queryArgs.Keys.OfType().Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
if (matchingKeys.Count > 0) {
UriBuilder builder = new UriBuilder(uri);
foreach (string key in matchingKeys) {
queryArgs.Remove(key);
}
builder.Query = CreateQueryString(queryArgs.ToDictionary());
return builder.Uri;
} else {
return uri;
}
}
///
/// Extracts the recipient from an HttpRequestInfo.
///
/// The request to get recipient information from.
/// The recipient.
internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) {
return new MessageReceivingEndpoint(request.Url, request.HttpMethod == "GET" ? HttpDeliveryMethod.GetRequest : HttpDeliveryMethod.PostRequest);
}
///
/// Copies some extra parameters into a message.
///
/// The message to copy the extra data into.
/// The extra data to copy into the message. May be null to do nothing.
internal static void AddNonOAuthParameters(this IProtocolMessage message, IDictionary extraParameters) {
if (message == null) {
throw new ArgumentNullException("message");
}
if (extraParameters != null) {
MessageDictionary messageDictionary = new MessageDictionary(message);
foreach (var pair in extraParameters) {
messageDictionary.Add(pair);
}
}
}
///
/// Converts a to an IDictionary<string, string>.
///
/// The NameValueCollection to convert. May be null.
/// The generated dictionary, or null if is null.
internal static Dictionary ToDictionary(this NameValueCollection nvc) {
if (nvc == null) {
return null;
}
var dictionary = new Dictionary();
foreach (string key in nvc) {
dictionary.Add(key, nvc[key]);
}
return dictionary;
}
///
/// Sorts the elements of a sequence in ascending order by using a specified comparer.
///
/// The type of the elements of source.
/// The type of the key returned by keySelector.
/// A sequence of values to order.
/// A function to extract a key from an element.
/// A comparison function to compare keys.
/// An System.Linq.IOrderedEnumerable<TElement> whose elements are sorted according to a key.
internal static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, Comparison comparer) {
return System.Linq.Enumerable.OrderBy(source, keySelector, new ComparisonHelper(comparer));
}
///
/// A class to convert a into an .
///
/// The type of objects being compared.
private class ComparisonHelper : IComparer {
///
/// The comparison method to use.
///
private Comparison comparison;
///
/// Initializes a new instance of the ComparisonHelper class.
///
/// The comparison method to use.
internal ComparisonHelper(Comparison comparison) {
if (comparison == null) {
throw new ArgumentNullException("comparison");
}
this.comparison = comparison;
}
#region IComparer Members
///
/// Compares two instances of .
///
/// The first object to compare.
/// The second object to compare.
/// Any of -1, 0, or 1 according to standard comparison rules.
public int Compare(T x, T y) {
return this.comparison(x, y);
}
#endregion
}
}
}