//----------------------------------------------------------------------- // // 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); } /// /// 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 } } }