FxCop fixes.
[dotnetoauth.git] / src / DotNetOpenAuth / Messaging / MessagingUtilities.cs
blobc774e995fcedb09b8f91d83ea0b21abf471cfb08
1 //-----------------------------------------------------------------------
2 // <copyright file="MessagingUtilities.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
4 // </copyright>
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth.Messaging {
8 using System;
9 using System.Collections.Generic;
10 using System.Collections.Specialized;
11 using System.Diagnostics.CodeAnalysis;
12 using System.Globalization;
13 using System.IO;
14 using System.Linq;
15 using System.Net;
16 using System.Security.Cryptography;
17 using System.Text;
18 using System.Web;
19 using DotNetOpenAuth.Messaging.Reflection;
21 /// <summary>
22 /// A grab-bag of utility methods useful for the channel stack of the protocol.
23 /// </summary>
24 public static class MessagingUtilities {
25 /// <summary>
26 /// The cryptographically strong random data generator used for creating secrets.
27 /// </summary>
28 /// <remarks>The random number generator is thread-safe.</remarks>
29 internal static readonly RandomNumberGenerator CryptoRandomDataGenerator = new RNGCryptoServiceProvider();
31 /// <summary>
32 /// A set of escaping mappings that help secure a string from javscript execution.
33 /// </summary>
34 /// <remarks>
35 /// The characters to escape here are inspired by
36 /// http://code.google.com/p/doctype/wiki/ArticleXSSInJavaScript
37 /// </remarks>
38 private static readonly Dictionary<string, string> javascriptStaticStringEscaping = new Dictionary<string, string> {
39 { "\\", @"\\" }, // this WAS just above the & substitution but we moved it here to prevent double-escaping
40 { "\t", @"\t" },
41 { "\n", @"\n" },
42 { "\r", @"\r" },
43 { "\u0085", @"\u0085" },
44 { "\u2028", @"\u2028" },
45 { "\u2029", @"\u2029" },
46 { "'", @"\x27" },
47 { "\"", @"\x22" },
48 { "&", @"\x26" },
49 { "<", @"\x3c" },
50 { ">", @"\x3e" },
51 { "=", @"\x3d" },
54 /// <summary>
55 /// HTTP headers that must be copied using their proper properties instead of directly.
56 /// </summary>
57 private static readonly string[] HeadersToNotCopy = new string[] { "Host", "Connection" };
59 /// <summary>
60 /// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any.
61 /// Cookieless session directory (if applicable) is also included.
62 /// </summary>
63 /// <returns>The URL in the user agent's Location bar.</returns>
64 [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "The Uri merging requires use of a string value.")]
65 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call should not be a property.")]
66 public static Uri GetRequestUrlFromContext() {
67 ErrorUtilities.VerifyHttpContext();
68 HttpContext context = HttpContext.Current;
70 // We use Request.Url for the full path to the server, and modify it
71 // with Request.RawUrl to capture both the cookieless session "directory" if it exists
72 // and the original path in case URL rewriting is going on. We don't want to be
73 // fooled by URL rewriting because we're comparing the actual URL with what's in
74 // the return_to parameter in some cases.
75 // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
76 // session, but not the URL rewriting problem.
77 return new Uri(context.Request.Url, context.Request.RawUrl);
80 /// <summary>
81 /// Gets the query data from the original request (before any URL rewriting has occurred.)
82 /// </summary>
83 /// <returns>A <see cref="NameValueCollection"/> containing all the parameters in the query string.</returns>
84 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call")]
85 public static NameValueCollection GetQueryFromContext() {
86 ErrorUtilities.VerifyHttpContext();
88 HttpRequest request = HttpContext.Current.Request;
90 // This request URL may have been rewritten by the host site.
91 // For openid protocol purposes, we really need to look at
92 // the original query parameters before any rewriting took place.
93 if (request.Url.PathAndQuery == request.RawUrl) {
94 // No rewriting has taken place.
95 return request.QueryString;
96 } else {
97 // Rewriting detected! Recover the original request URI.
98 return HttpUtility.ParseQueryString(GetRequestUrlFromContext().Query);
102 /// <summary>
103 /// Gets the query or form data from the original request (before any URL rewriting has occurred.)
104 /// </summary>
105 /// <returns>A set of name=value pairs.</returns>
106 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call")]
107 public static NameValueCollection GetQueryOrFormFromContext() {
108 ErrorUtilities.VerifyHttpContext();
109 HttpRequest request = HttpContext.Current.Request;
110 NameValueCollection query;
111 if (request.RequestType == "GET") {
112 query = GetQueryFromContext();
113 } else {
114 query = request.Form;
116 return query;
119 /// <summary>
120 /// Strips any and all URI query parameters that start with some prefix.
121 /// </summary>
122 /// <param name="uri">The URI that may have a query with parameters to remove.</param>
123 /// <param name="prefix">The prefix for parameters to remove.</param>
124 /// <returns>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.</returns>
125 public static Uri StripQueryArgumentsWithPrefix(this Uri uri, string prefix) {
126 ErrorUtilities.VerifyArgumentNotNull(uri, "uri");
128 NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query);
129 var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
130 if (matchingKeys.Count > 0) {
131 UriBuilder builder = new UriBuilder(uri);
132 foreach (string key in matchingKeys) {
133 queryArgs.Remove(key);
135 builder.Query = CreateQueryString(queryArgs.ToDictionary());
136 return builder.Uri;
137 } else {
138 return uri;
142 /// <summary>
143 /// Gets a cryptographically strong random sequence of values.
144 /// </summary>
145 /// <param name="length">The length of the sequence to generate.</param>
146 /// <returns>The generated values, which may contain zeros.</returns>
147 internal static byte[] GetCryptoRandomData(int length) {
148 byte[] buffer = new byte[length];
149 CryptoRandomDataGenerator.GetBytes(buffer);
150 return buffer;
153 /// <summary>
154 /// Gets a cryptographically strong random sequence of values.
155 /// </summary>
156 /// <param name="binaryLength">The length of the byte sequence to generate.</param>
157 /// <returns>A base64 encoding of the generated random data,
158 /// whose length in characters will likely be greater than <paramref name="binaryLength"/>.</returns>
159 internal static string GetCryptoRandomDataAsBase64(int binaryLength) {
160 byte[] uniq_bytes = GetCryptoRandomData(binaryLength);
161 string uniq = Convert.ToBase64String(uniq_bytes);
162 return uniq;
165 /// <summary>
166 /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance,
167 /// taking care to set some headers to the appropriate properties of
168 /// <see cref="HttpResponse" />
169 /// </summary>
170 /// <param name="headers">The headers to add.</param>
171 /// <param name="response">The <see cref="HttpResponse"/> instance to set the appropriate values to.</param>
172 internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponse response) {
173 ErrorUtilities.VerifyArgumentNotNull(headers, "headers");
174 ErrorUtilities.VerifyArgumentNotNull(response, "response");
176 foreach (string headerName in headers) {
177 switch (headerName) {
178 case "Content-Type":
179 response.ContentType = headers[HttpResponseHeader.ContentType];
180 break;
182 // Add more special cases here as necessary.
183 default:
184 response.AddHeader(headerName, headers[headerName]);
185 break;
190 /// <summary>
191 /// Copies the contents of one stream to another.
192 /// </summary>
193 /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param>
194 /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param>
195 /// <returns>The total number of bytes copied.</returns>
196 /// <remarks>
197 /// Copying begins at the streams' current positions.
198 /// The positions are NOT reset after copying is complete.
199 /// </remarks>
200 internal static int CopyTo(this Stream copyFrom, Stream copyTo) {
201 return CopyTo(copyFrom, copyTo, int.MaxValue);
204 /// <summary>
205 /// Copies the contents of one stream to another.
206 /// </summary>
207 /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param>
208 /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param>
209 /// <param name="maximumBytesToCopy">The maximum bytes to copy.</param>
210 /// <returns>The total number of bytes copied.</returns>
211 /// <remarks>
212 /// Copying begins at the streams' current positions.
213 /// The positions are NOT reset after copying is complete.
214 /// </remarks>
215 internal static int CopyTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy) {
216 ErrorUtilities.VerifyArgumentNotNull(copyFrom, "copyFrom");
217 ErrorUtilities.VerifyArgumentNotNull(copyTo, "copyTo");
218 ErrorUtilities.VerifyArgument(copyFrom.CanRead, MessagingStrings.StreamUnreadable);
219 ErrorUtilities.VerifyArgument(copyTo.CanWrite, MessagingStrings.StreamUnwritable, "copyTo");
221 byte[] buffer = new byte[1024];
222 int readBytes;
223 int totalCopiedBytes = 0;
224 while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(1024, maximumBytesToCopy))) > 0) {
225 int writeBytes = Math.Min(maximumBytesToCopy, readBytes);
226 copyTo.Write(buffer, 0, writeBytes);
227 totalCopiedBytes += writeBytes;
228 maximumBytesToCopy -= writeBytes;
231 return totalCopiedBytes;
234 /// <summary>
235 /// Creates a snapshot of some stream so it is seekable, and the original can be closed.
236 /// </summary>
237 /// <param name="copyFrom">The stream to copy bytes from.</param>
238 /// <returns>A seekable stream with the same contents as the original.</returns>
239 internal static Stream CreateSnapshot(this Stream copyFrom) {
240 ErrorUtilities.VerifyArgumentNotNull(copyFrom, "copyFrom");
242 MemoryStream copyTo = new MemoryStream(copyFrom.CanSeek ? (int)copyFrom.Length : 4 * 1024);
243 copyFrom.CopyTo(copyTo);
244 copyTo.Position = 0;
245 return copyTo;
248 /// <summary>
249 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
250 /// </summary>
251 /// <param name="request">The request to clone.</param>
252 /// <returns>The newly created instance.</returns>
253 internal static HttpWebRequest Clone(this HttpWebRequest request) {
254 ErrorUtilities.VerifyArgumentNotNull(request, "request");
255 return Clone(request, request.RequestUri);
258 /// <summary>
259 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
260 /// </summary>
261 /// <param name="request">The request to clone.</param>
262 /// <param name="newRequestUri">The new recipient of the request.</param>
263 /// <returns>The newly created instance.</returns>
264 internal static HttpWebRequest Clone(this HttpWebRequest request, Uri newRequestUri) {
265 ErrorUtilities.VerifyArgumentNotNull(request, "request");
266 ErrorUtilities.VerifyArgumentNotNull(newRequestUri, "newRequestUri");
268 var newRequest = (HttpWebRequest)WebRequest.Create(newRequestUri);
269 newRequest.Accept = request.Accept;
270 newRequest.AllowAutoRedirect = request.AllowAutoRedirect;
271 newRequest.AllowWriteStreamBuffering = request.AllowWriteStreamBuffering;
272 newRequest.AuthenticationLevel = request.AuthenticationLevel;
273 newRequest.AutomaticDecompression = request.AutomaticDecompression;
274 newRequest.CachePolicy = request.CachePolicy;
275 newRequest.ClientCertificates = request.ClientCertificates;
276 newRequest.ConnectionGroupName = request.ConnectionGroupName;
277 if (request.ContentLength >= 0) {
278 newRequest.ContentLength = request.ContentLength;
280 newRequest.ContentType = request.ContentType;
281 newRequest.ContinueDelegate = request.ContinueDelegate;
282 newRequest.CookieContainer = request.CookieContainer;
283 newRequest.Credentials = request.Credentials;
284 newRequest.Expect = request.Expect;
285 newRequest.IfModifiedSince = request.IfModifiedSince;
286 newRequest.ImpersonationLevel = request.ImpersonationLevel;
287 newRequest.KeepAlive = request.KeepAlive;
288 newRequest.MaximumAutomaticRedirections = request.MaximumAutomaticRedirections;
289 newRequest.MaximumResponseHeadersLength = request.MaximumResponseHeadersLength;
290 newRequest.MediaType = request.MediaType;
291 newRequest.Method = request.Method;
292 newRequest.Pipelined = request.Pipelined;
293 newRequest.PreAuthenticate = request.PreAuthenticate;
294 newRequest.ProtocolVersion = request.ProtocolVersion;
295 newRequest.Proxy = request.Proxy;
296 newRequest.ReadWriteTimeout = request.ReadWriteTimeout;
297 newRequest.Referer = request.Referer;
298 newRequest.SendChunked = request.SendChunked;
299 newRequest.Timeout = request.Timeout;
300 newRequest.TransferEncoding = request.TransferEncoding;
301 newRequest.UnsafeAuthenticatedConnectionSharing = request.UnsafeAuthenticatedConnectionSharing;
302 newRequest.UseDefaultCredentials = request.UseDefaultCredentials;
303 newRequest.UserAgent = request.UserAgent;
305 // We copy headers last, and only those that do not yet exist as a result
306 // of setting these properties, so as to avoid exceptions thrown because
307 // there are properties .NET wants us to use rather than direct headers.
308 foreach (string header in request.Headers) {
309 if (!HeadersToNotCopy.Contains(header) && string.IsNullOrEmpty(newRequest.Headers[header])) {
310 newRequest.Headers.Add(header, request.Headers[header]);
314 return newRequest;
317 /// <summary>
318 /// Tests whether two arrays are equal in contents and ordering.
319 /// </summary>
320 /// <typeparam name="T">The type of elements in the arrays.</typeparam>
321 /// <param name="first">The first array in the comparison. May not be null.</param>
322 /// <param name="second">The second array in the comparison. May not be null.</param>
323 /// <returns>True if the arrays equal; false otherwise.</returns>
324 internal static bool AreEquivalent<T>(T[] first, T[] second) {
325 ErrorUtilities.VerifyArgumentNotNull(first, "first");
326 ErrorUtilities.VerifyArgumentNotNull(second, "second");
327 if (first.Length != second.Length) {
328 return false;
330 for (int i = 0; i < first.Length; i++) {
331 if (!first[i].Equals(second[i])) {
332 return false;
335 return true;
338 /// <summary>
339 /// Tests two sequences for same contents and ordering.
340 /// </summary>
341 /// <typeparam name="T">The type of elements in the arrays.</typeparam>
342 /// <param name="sequence1">The first sequence in the comparison. May not be null.</param>
343 /// <param name="sequence2">The second sequence in the comparison. May not be null.</param>
344 /// <returns>True if the arrays equal; false otherwise.</returns>
345 internal static bool AreEquivalent<T>(IEnumerable<T> sequence1, IEnumerable<T> sequence2) {
346 if (sequence1 == null && sequence2 == null) {
347 return true;
349 if ((sequence1 == null) ^ (sequence2 == null)) {
350 return false;
353 IEnumerator<T> iterator1 = sequence1.GetEnumerator();
354 IEnumerator<T> iterator2 = sequence2.GetEnumerator();
355 bool movenext1, movenext2;
356 while (true) {
357 movenext1 = iterator1.MoveNext();
358 movenext2 = iterator2.MoveNext();
359 if (!movenext1 || !movenext2) { // if we've reached the end of at least one sequence
360 break;
362 object obj1 = iterator1.Current;
363 object obj2 = iterator2.Current;
364 if (obj1 == null && obj2 == null) {
365 continue; // both null is ok
367 if (obj1 == null ^ obj2 == null) {
368 return false; // exactly one null is different
370 if (!obj1.Equals(obj2)) {
371 return false; // if they're not equal to each other
375 return movenext1 == movenext2; // did they both reach the end together?
378 /// <summary>
379 /// Tests two unordered collections for same contents.
380 /// </summary>
381 /// <typeparam name="T">The type of elements in the collections.</typeparam>
382 /// <param name="first">The first collection in the comparison. May not be null.</param>
383 /// <param name="second">The second collection in the comparison. May not be null.</param>
384 /// <returns>True if the collections have the same contents; false otherwise.</returns>
385 internal static bool AreEquivalentUnordered<T>(ICollection<T> first, ICollection<T> second) {
386 if (first == null && second == null) {
387 return true;
389 if ((first == null) ^ (second == null)) {
390 return false;
393 if (first.Count != second.Count) {
394 return false;
397 foreach (T value in first) {
398 if (!second.Contains(value)) {
399 return false;
403 return true;
406 /// <summary>
407 /// Tests whether two dictionaries are equal in length and contents.
408 /// </summary>
409 /// <typeparam name="TKey">The type of keys in the dictionaries.</typeparam>
410 /// <typeparam name="TValue">The type of values in the dictionaries.</typeparam>
411 /// <param name="first">The first dictionary in the comparison. May not be null.</param>
412 /// <param name="second">The second dictionary in the comparison. May not be null.</param>
413 /// <returns>True if the arrays equal; false otherwise.</returns>
414 internal static bool AreEquivalent<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second) {
415 return AreEquivalent(first.ToArray(), second.ToArray());
418 /// <summary>
419 /// Concatenates a list of name-value pairs as key=value&amp;key=value,
420 /// taking care to properly encode each key and value for URL
421 /// transmission. No ? is prefixed to the string.
422 /// </summary>
423 /// <param name="args">The dictionary of key/values to read from.</param>
424 /// <returns>The formulated querystring style string.</returns>
425 internal static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args) {
426 ErrorUtilities.VerifyArgumentNotNull(args, "args");
427 if (args.Count() == 0) {
428 return string.Empty;
430 StringBuilder sb = new StringBuilder(args.Count() * 10);
432 foreach (var p in args) {
433 ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(p.Key), MessagingStrings.UnexpectedNullOrEmptyKey);
434 ErrorUtilities.VerifyArgument(p.Value != null, MessagingStrings.UnexpectedNullValue, p.Key);
435 sb.Append(HttpUtility.UrlEncode(p.Key));
436 sb.Append('=');
437 sb.Append(HttpUtility.UrlEncode(p.Value));
438 sb.Append('&');
440 sb.Length--; // remove trailing &
442 return sb.ToString();
445 /// <summary>
446 /// Adds a set of name-value pairs to the end of a given URL
447 /// as part of the querystring piece. Prefixes a ? or &amp; before
448 /// first element as necessary.
449 /// </summary>
450 /// <param name="builder">The UriBuilder to add arguments to.</param>
451 /// <param name="args">
452 /// The arguments to add to the query.
453 /// If null, <paramref name="builder"/> is not changed.
454 /// </param>
455 internal static void AppendQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) {
456 if (builder == null) {
457 throw new ArgumentNullException("builder");
460 if (args != null && args.Count() > 0) {
461 StringBuilder sb = new StringBuilder(50 + (args.Count() * 10));
462 if (!string.IsNullOrEmpty(builder.Query)) {
463 sb.Append(builder.Query.Substring(1));
464 sb.Append('&');
466 sb.Append(CreateQueryString(args));
468 builder.Query = sb.ToString();
472 /// <summary>
473 /// Extracts the recipient from an HttpRequestInfo.
474 /// </summary>
475 /// <param name="request">The request to get recipient information from.</param>
476 /// <returns>The recipient.</returns>
477 internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) {
478 return new MessageReceivingEndpoint(request.Url, request.HttpMethod == "GET" ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest);
481 /// <summary>
482 /// Copies some extra parameters into a message.
483 /// </summary>
484 /// <param name="message">The message to copy the extra data into.</param>
485 /// <param name="extraParameters">The extra data to copy into the message. May be null to do nothing.</param>
486 internal static void AddExtraParameters(this IMessage message, IDictionary<string, string> extraParameters) {
487 ErrorUtilities.VerifyArgumentNotNull(message, "message");
489 if (extraParameters != null) {
490 MessageDictionary messageDictionary = new MessageDictionary(message);
491 foreach (var pair in extraParameters) {
492 messageDictionary.Add(pair);
497 /// <summary>
498 /// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
499 /// </summary>
500 /// <param name="nvc">The NameValueCollection to convert. May be null.</param>
501 /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns>
502 /// <remarks>
503 /// If a <c>null</c> key is encountered, its value is ignored since
504 /// <c>Dictionary&lt;string, string&gt;</c> does not allow null keys.
505 /// </remarks>
506 internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc) {
507 return ToDictionary(nvc, false);
510 /// <summary>
511 /// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
512 /// </summary>
513 /// <param name="nvc">The NameValueCollection to convert. May be null.</param>
514 /// <param name="throwOnNullKey">
515 /// A value indicating whether a null key in the <see cref="NameValueCollection"/> should be silently skipped since it is not a valid key in a Dictionary.
516 /// Use <c>true</c> to throw an exception if a null key is encountered.
517 /// Use <c>false</c> to silently continue converting the valid keys.
518 /// </param>
519 /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns>
520 /// <exception cref="ArgumentException">Thrown if <paramref name="throwOnNullKey"/> is <c>true</c> and a null key is encountered.</exception>
521 internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc, bool throwOnNullKey) {
522 if (nvc == null) {
523 return null;
526 var dictionary = new Dictionary<string, string>();
527 foreach (string key in nvc) {
528 // NameValueCollection supports a null key, but Dictionary<K,V> does not.
529 if (key == null) {
530 if (throwOnNullKey) {
531 throw new ArgumentException(MessagingStrings.UnexpectedNullKey);
532 } else {
533 Logger.WarnFormat("Null key with value {0} encountered while translating NameValueCollection to Dictionary.", nvc[key]);
535 } else {
536 dictionary.Add(key, nvc[key]);
540 return dictionary;
543 /// <summary>
544 /// Sorts the elements of a sequence in ascending order by using a specified comparer.
545 /// </summary>
546 /// <typeparam name="TSource">The type of the elements of source.</typeparam>
547 /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam>
548 /// <param name="source">A sequence of values to order.</param>
549 /// <param name="keySelector">A function to extract a key from an element.</param>
550 /// <param name="comparer">A comparison function to compare keys.</param>
551 /// <returns>An System.Linq.IOrderedEnumerable&lt;TElement&gt; whose elements are sorted according to a key.</returns>
552 internal static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Comparison<TKey> comparer) {
553 return System.Linq.Enumerable.OrderBy<TSource, TKey>(source, keySelector, new ComparisonHelper<TKey>(comparer));
556 /// <summary>
557 /// Determines whether the specified message is a request (indirect message or direct request).
558 /// </summary>
559 /// <param name="message">The message in question.</param>
560 /// <returns>
561 /// <c>true</c> if the specified message is a request; otherwise, <c>false</c>.
562 /// </returns>
563 /// <remarks>
564 /// Although an <see cref="IProtocolMessage"/> may implement the <see cref="IDirectedProtocolMessage"/>
565 /// interface, it may only be doing that for its derived classes. These objects are only requests
566 /// if their <see cref="IDirectedProtocolMessage.Recipient"/> property is non-null.
567 /// </remarks>
568 internal static bool IsRequest(this IDirectedProtocolMessage message) {
569 ErrorUtilities.VerifyArgumentNotNull(message, "message");
570 return message.Recipient != null;
573 /// <summary>
574 /// Determines whether the specified message is a direct response.
575 /// </summary>
576 /// <param name="message">The message in question.</param>
577 /// <returns>
578 /// <c>true</c> if the specified message is a direct response; otherwise, <c>false</c>.
579 /// </returns>
580 /// <remarks>
581 /// Although an <see cref="IProtocolMessage"/> may implement the
582 /// <see cref="IDirectResponseProtocolMessage"/> interface, it may only be doing
583 /// that for its derived classes. These objects are only requests if their
584 /// <see cref="IDirectResponseProtocolMessage.OriginatingRequest"/> property is non-null.
585 /// </remarks>
586 internal static bool IsDirectResponse(this IDirectResponseProtocolMessage message) {
587 ErrorUtilities.VerifyArgumentNotNull(message, "message");
588 return message.OriginatingRequest != null;
591 /// <summary>
592 /// Constructs a Javascript expression that will create an object
593 /// on the user agent when assigned to a variable.
594 /// </summary>
595 /// <param name="namesAndValues">The untrusted names and untrusted values to inject into the JSON object.</param>
596 /// <returns>The Javascript JSON object as a string.</returns>
597 internal static string CreateJsonObject(IEnumerable<KeyValuePair<string, string>> namesAndValues) {
598 StringBuilder builder = new StringBuilder();
599 builder.Append("{ ");
601 foreach (var pair in namesAndValues) {
602 builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Key));
603 builder.Append(": ");
604 builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Value));
605 builder.Append(",");
608 if (builder[builder.Length - 1] == ',') {
609 builder.Length -= 1;
611 builder.Append("}");
612 return builder.ToString();
615 /// <summary>
616 /// Prepares what SHOULD be simply a string value for safe injection into Javascript
617 /// by using appropriate character escaping.
618 /// </summary>
619 /// <param name="value">The untrusted string value to be escaped to protected against XSS attacks. May be null.</param>
620 /// <returns>The escaped string.</returns>
621 internal static string GetSafeJavascriptValue(string value) {
622 if (value == null) {
623 return "null";
626 // We use a StringBuilder because we have potentially many replacements to do,
627 // and we don't want to create a new string for every intermediate replacement step.
628 StringBuilder builder = new StringBuilder(value);
629 foreach (var pair in javascriptStaticStringEscaping) {
630 builder.Replace(pair.Key, pair.Value);
632 builder.Insert(0, '\'');
633 builder.Append('\'');
634 return builder.ToString();
637 /// <summary>
638 /// A class to convert a <see cref="Comparison&lt;T&gt;"/> into an <see cref="IComparer&lt;T&gt;"/>.
639 /// </summary>
640 /// <typeparam name="T">The type of objects being compared.</typeparam>
641 private class ComparisonHelper<T> : IComparer<T> {
642 /// <summary>
643 /// The comparison method to use.
644 /// </summary>
645 private Comparison<T> comparison;
647 /// <summary>
648 /// Initializes a new instance of the ComparisonHelper class.
649 /// </summary>
650 /// <param name="comparison">The comparison method to use.</param>
651 internal ComparisonHelper(Comparison<T> comparison) {
652 if (comparison == null) {
653 throw new ArgumentNullException("comparison");
656 this.comparison = comparison;
659 #region IComparer<T> Members
661 /// <summary>
662 /// Compares two instances of <typeparamref name="T"/>.
663 /// </summary>
664 /// <param name="x">The first object to compare.</param>
665 /// <param name="y">The second object to compare.</param>
666 /// <returns>Any of -1, 0, or 1 according to standard comparison rules.</returns>
667 public int Compare(T x, T y) {
668 return this.comparison(x, y);
671 #endregion