Added sreg client javascript support for responses.
[dotnetoauth.git] / src / DotNetOpenAuth / Messaging / MessagingUtilities.cs
blob48922f6fe77ca0288eff284c8cf2c0df4efd5ae4
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 /// Gets the original request URL, as seen from the browser before any URL rewrites on the server if any.
56 /// Cookieless session directory (if applicable) is also included.
57 /// </summary>
58 /// <returns>The URL in the user agent's Location bar.</returns>
59 [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "The Uri merging requires use of a string value.")]
60 [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Expensive call should not be a property.")]
61 public static Uri GetRequestUrlFromContext() {
62 ErrorUtilities.VerifyHttpContext();
63 HttpContext context = HttpContext.Current;
65 // We use Request.Url for the full path to the server, and modify it
66 // with Request.RawUrl to capture both the cookieless session "directory" if it exists
67 // and the original path in case URL rewriting is going on. We don't want to be
68 // fooled by URL rewriting because we're comparing the actual URL with what's in
69 // the return_to parameter in some cases.
70 // Response.ApplyAppPathModifier(builder.Path) would have worked for the cookieless
71 // session, but not the URL rewriting problem.
72 return new Uri(context.Request.Url, context.Request.RawUrl);
75 /// <summary>
76 /// Gets the query data from the original request (before any URL rewriting has occurred.)
77 /// </summary>
78 /// <returns>A <see cref="NameValueCollection"/> containing all the parameters in the query string.</returns>
79 public static NameValueCollection GetQueryFromContextNVC() {
80 ErrorUtilities.VerifyHttpContext();
82 HttpRequest request = HttpContext.Current.Request;
84 // This request URL may have been rewritten by the host site.
85 // For openid protocol purposes, we really need to look at
86 // the original query parameters before any rewriting took place.
87 if (request.Url.PathAndQuery == request.RawUrl) {
88 // No rewriting has taken place.
89 return request.QueryString;
90 } else {
91 // Rewriting detected! Recover the original request URI.
92 return HttpUtility.ParseQueryString(GetRequestUrlFromContext().Query);
96 /// <summary>
97 /// Gets the query or form data from the original request (before any URL rewriting has occurred.)
98 /// </summary>
99 /// <returns>A set of name=value pairs.</returns>
100 public static NameValueCollection GetQueryOrFormFromContext() {
101 ErrorUtilities.VerifyHttpContext();
102 HttpRequest request = HttpContext.Current.Request;
103 NameValueCollection query;
104 if (request.RequestType == "GET") {
105 query = GetQueryFromContextNVC();
106 } else {
107 query = request.Form;
109 return query;
112 /// <summary>
113 /// Strips any and all URI query parameters that start with some prefix.
114 /// </summary>
115 /// <param name="uri">The URI that may have a query with parameters to remove.</param>
116 /// <param name="prefix">The prefix for parameters to remove.</param>
117 /// <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>
118 public static Uri StripQueryArgumentsWithPrefix(this Uri uri, string prefix) {
119 ErrorUtilities.VerifyArgumentNotNull(uri, "uri");
121 NameValueCollection queryArgs = HttpUtility.ParseQueryString(uri.Query);
122 var matchingKeys = queryArgs.Keys.OfType<string>().Where(key => key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)).ToList();
123 if (matchingKeys.Count > 0) {
124 UriBuilder builder = new UriBuilder(uri);
125 foreach (string key in matchingKeys) {
126 queryArgs.Remove(key);
128 builder.Query = CreateQueryString(queryArgs.ToDictionary());
129 return builder.Uri;
130 } else {
131 return uri;
135 /// <summary>
136 /// Gets a cryptographically strong random sequence of values.
137 /// </summary>
138 /// <param name="length">The length of the sequence to generate.</param>
139 /// <returns>The generated values, which may contain zeros.</returns>
140 internal static byte[] GetCryptoRandomData(int length) {
141 byte[] buffer = new byte[length];
142 CryptoRandomDataGenerator.GetBytes(buffer);
143 return buffer;
146 /// <summary>
147 /// Gets a cryptographically strong random sequence of values.
148 /// </summary>
149 /// <param name="binaryLength">The length of the byte sequence to generate.</param>
150 /// <returns>A base64 encoding of the generated random data,
151 /// whose length in characters will likely be greater than <paramref name="binaryLength"/>.</returns>
152 internal static string GetCryptoRandomDataAsBase64(int binaryLength) {
153 byte[] uniq_bytes = GetCryptoRandomData(binaryLength);
154 string uniq = Convert.ToBase64String(uniq_bytes);
155 return uniq;
158 /// <summary>
159 /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance,
160 /// taking care to set some headers to the appropriate properties of
161 /// <see cref="HttpResponse" />
162 /// </summary>
163 /// <param name="headers">The headers to add.</param>
164 /// <param name="response">The <see cref="HttpResponse"/> instance to set the appropriate values to.</param>
165 internal static void ApplyHeadersToResponse(WebHeaderCollection headers, HttpResponse response) {
166 ErrorUtilities.VerifyArgumentNotNull(headers, "headers");
167 ErrorUtilities.VerifyArgumentNotNull(response, "response");
169 foreach (string headerName in headers) {
170 switch (headerName) {
171 case "Content-Type":
172 response.ContentType = headers[HttpResponseHeader.ContentType];
173 break;
175 // Add more special cases here as necessary.
176 default:
177 response.AddHeader(headerName, headers[headerName]);
178 break;
183 /// <summary>
184 /// Copies the contents of one stream to another.
185 /// </summary>
186 /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param>
187 /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param>
188 /// <returns>The total number of bytes copied.</returns>
189 /// <remarks>
190 /// Copying begins at the streams' current positions.
191 /// The positions are NOT reset after copying is complete.
192 /// </remarks>
193 internal static int CopyTo(this Stream copyFrom, Stream copyTo) {
194 return CopyTo(copyFrom, copyTo, int.MaxValue);
197 /// <summary>
198 /// Copies the contents of one stream to another.
199 /// </summary>
200 /// <param name="copyFrom">The stream to copy from, at the position where copying should begin.</param>
201 /// <param name="copyTo">The stream to copy to, at the position where bytes should be written.</param>
202 /// <param name="maximumBytesToCopy">The maximum bytes to copy.</param>
203 /// <returns>The total number of bytes copied.</returns>
204 /// <remarks>
205 /// Copying begins at the streams' current positions.
206 /// The positions are NOT reset after copying is complete.
207 /// </remarks>
208 internal static int CopyTo(this Stream copyFrom, Stream copyTo, int maximumBytesToCopy) {
209 ErrorUtilities.VerifyArgumentNotNull(copyFrom, "copyFrom");
210 ErrorUtilities.VerifyArgumentNotNull(copyTo, "copyTo");
211 ErrorUtilities.VerifyArgument(copyFrom.CanRead, MessagingStrings.StreamUnreadable);
212 ErrorUtilities.VerifyArgument(copyTo.CanWrite, MessagingStrings.StreamUnwritable, "copyTo");
214 byte[] buffer = new byte[1024];
215 int readBytes;
216 int totalCopiedBytes = 0;
217 while ((readBytes = copyFrom.Read(buffer, 0, Math.Min(1024, maximumBytesToCopy))) > 0) {
218 int writeBytes = Math.Min(maximumBytesToCopy, readBytes);
219 copyTo.Write(buffer, 0, writeBytes);
220 totalCopiedBytes += writeBytes;
221 maximumBytesToCopy -= writeBytes;
224 return totalCopiedBytes;
227 /// <summary>
228 /// Creates a snapshot of some stream so it is seekable, and the original can be closed.
229 /// </summary>
230 /// <param name="copyFrom">The stream to copy bytes from.</param>
231 /// <returns>A seekable stream with the same contents as the original.</returns>
232 internal static Stream CreateSnapshot(this Stream copyFrom) {
233 ErrorUtilities.VerifyArgumentNotNull(copyFrom, "copyFrom");
235 MemoryStream copyTo = new MemoryStream(copyFrom.CanSeek ? (int)copyFrom.Length : 4 * 1024);
236 copyFrom.CopyTo(copyTo);
237 copyTo.Position = 0;
238 return copyTo;
241 /// <summary>
242 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
243 /// </summary>
244 /// <param name="request">The request to clone.</param>
245 /// <returns>The newly created instance.</returns>
246 internal static HttpWebRequest Clone(this HttpWebRequest request) {
247 ErrorUtilities.VerifyArgumentNotNull(request, "request");
248 return Clone(request, request.RequestUri);
251 /// <summary>
252 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
253 /// </summary>
254 /// <param name="request">The request to clone.</param>
255 /// <param name="newRequestUri">The new recipient of the request.</param>
256 /// <returns>The newly created instance.</returns>
257 internal static HttpWebRequest Clone(this HttpWebRequest request, Uri newRequestUri) {
258 ErrorUtilities.VerifyArgumentNotNull(request, "request");
259 ErrorUtilities.VerifyArgumentNotNull(newRequestUri, "newRequestUri");
261 var newRequest = (HttpWebRequest)WebRequest.Create(newRequestUri);
262 newRequest.Accept = request.Accept;
263 newRequest.AllowAutoRedirect = request.AllowAutoRedirect;
264 newRequest.AllowWriteStreamBuffering = request.AllowWriteStreamBuffering;
265 newRequest.AuthenticationLevel = request.AuthenticationLevel;
266 newRequest.AutomaticDecompression = request.AutomaticDecompression;
267 newRequest.CachePolicy = request.CachePolicy;
268 newRequest.ClientCertificates = request.ClientCertificates;
269 newRequest.ConnectionGroupName = request.ConnectionGroupName;
270 if (request.ContentLength >= 0) {
271 newRequest.ContentLength = request.ContentLength;
273 newRequest.ContentType = request.ContentType;
274 newRequest.ContinueDelegate = request.ContinueDelegate;
275 newRequest.CookieContainer = request.CookieContainer;
276 newRequest.Credentials = request.Credentials;
277 newRequest.Expect = request.Expect;
278 newRequest.IfModifiedSince = request.IfModifiedSince;
279 newRequest.ImpersonationLevel = request.ImpersonationLevel;
280 newRequest.KeepAlive = request.KeepAlive;
281 newRequest.MaximumAutomaticRedirections = request.MaximumAutomaticRedirections;
282 newRequest.MaximumResponseHeadersLength = request.MaximumResponseHeadersLength;
283 newRequest.MediaType = request.MediaType;
284 newRequest.Method = request.Method;
285 newRequest.Pipelined = request.Pipelined;
286 newRequest.PreAuthenticate = request.PreAuthenticate;
287 newRequest.ProtocolVersion = request.ProtocolVersion;
288 newRequest.Proxy = request.Proxy;
289 newRequest.ReadWriteTimeout = request.ReadWriteTimeout;
290 newRequest.Referer = request.Referer;
291 newRequest.SendChunked = request.SendChunked;
292 newRequest.Timeout = request.Timeout;
293 newRequest.TransferEncoding = request.TransferEncoding;
294 newRequest.UnsafeAuthenticatedConnectionSharing = request.UnsafeAuthenticatedConnectionSharing;
295 newRequest.UseDefaultCredentials = request.UseDefaultCredentials;
296 newRequest.UserAgent = request.UserAgent;
298 // We copy headers last, and only those that do not yet exist as a result
299 // of setting these properties, so as to avoid exceptions thrown because
300 // there are properties .NET wants us to use rather than direct headers.
301 foreach (string header in request.Headers) {
302 if (string.IsNullOrEmpty(newRequest.Headers[header])) {
303 newRequest.Headers.Add(header, request.Headers[header]);
307 return newRequest;
310 /// <summary>
311 /// Tests whether two arrays are equal in contents and ordering.
312 /// </summary>
313 /// <typeparam name="T">The type of elements in the arrays.</typeparam>
314 /// <param name="first">The first array in the comparison. May not be null.</param>
315 /// <param name="second">The second array in the comparison. May not be null.</param>
316 /// <returns>True if the arrays equal; false otherwise.</returns>
317 internal static bool AreEquivalent<T>(T[] first, T[] second) {
318 ErrorUtilities.VerifyArgumentNotNull(first, "first");
319 ErrorUtilities.VerifyArgumentNotNull(second, "second");
320 if (first.Length != second.Length) {
321 return false;
323 for (int i = 0; i < first.Length; i++) {
324 if (!first[i].Equals(second[i])) {
325 return false;
328 return true;
331 /// <summary>
332 /// Tests two sequences for same contents and ordering.
333 /// </summary>
334 /// <typeparam name="T">The type of elements in the arrays.</typeparam>
335 /// <param name="sequence1">The first sequence in the comparison. May not be null.</param>
336 /// <param name="sequence2">The second sequence in the comparison. May not be null.</param>
337 /// <returns>True if the arrays equal; false otherwise.</returns>
338 internal static bool AreEquivalent<T>(IEnumerable<T> sequence1, IEnumerable<T> sequence2) {
339 if (sequence1 == null && sequence2 == null) {
340 return true;
342 if ((sequence1 == null) ^ (sequence2 == null)) {
343 return false;
346 IEnumerator<T> iterator1 = sequence1.GetEnumerator();
347 IEnumerator<T> iterator2 = sequence2.GetEnumerator();
348 bool movenext1, movenext2;
349 while (true) {
350 movenext1 = iterator1.MoveNext();
351 movenext2 = iterator2.MoveNext();
352 if (!movenext1 || !movenext2) { // if we've reached the end of at least one sequence
353 break;
355 object obj1 = iterator1.Current;
356 object obj2 = iterator2.Current;
357 if (obj1 == null && obj2 == null) {
358 continue; // both null is ok
360 if (obj1 == null ^ obj2 == null) {
361 return false; // exactly one null is different
363 if (!obj1.Equals(obj2)) {
364 return false; // if they're not equal to each other
368 return movenext1 == movenext2; // did they both reach the end together?
371 /// <summary>
372 /// Tests two unordered collections for same contents.
373 /// </summary>
374 /// <typeparam name="T">The type of elements in the collections.</typeparam>
375 /// <param name="first">The first collection in the comparison. May not be null.</param>
376 /// <param name="second">The second collection in the comparison. May not be null.</param>
377 /// <returns>True if the collections have the same contents; false otherwise.</returns>
378 internal static bool AreEquivalentUnordered<T>(ICollection<T> first, ICollection<T> second) {
379 if (first == null && second == null) {
380 return true;
382 if ((first == null) ^ (second == null)) {
383 return false;
386 if (first.Count != second.Count) {
387 return false;
390 foreach (T value in first) {
391 if (!second.Contains(value)) {
392 return false;
396 return true;
399 /// <summary>
400 /// Tests whether two dictionaries are equal in length and contents.
401 /// </summary>
402 /// <typeparam name="TKey">The type of keys in the dictionaries.</typeparam>
403 /// <typeparam name="TValue">The type of values in the dictionaries.</typeparam>
404 /// <param name="first">The first dictionary in the comparison. May not be null.</param>
405 /// <param name="second">The second dictionary in the comparison. May not be null.</param>
406 /// <returns>True if the arrays equal; false otherwise.</returns>
407 internal static bool AreEquivalent<TKey, TValue>(IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second) {
408 return AreEquivalent(first.ToArray(), second.ToArray());
411 /// <summary>
412 /// Concatenates a list of name-value pairs as key=value&amp;key=value,
413 /// taking care to properly encode each key and value for URL
414 /// transmission. No ? is prefixed to the string.
415 /// </summary>
416 /// <param name="args">The dictionary of key/values to read from.</param>
417 /// <returns>The formulated querystring style string.</returns>
418 internal static string CreateQueryString(IEnumerable<KeyValuePair<string, string>> args) {
419 ErrorUtilities.VerifyArgumentNotNull(args, "args");
420 if (args.Count() == 0) {
421 return string.Empty;
423 StringBuilder sb = new StringBuilder(args.Count() * 10);
425 foreach (var p in args) {
426 ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(p.Key), MessagingStrings.UnexpectedNullOrEmptyKey);
427 ErrorUtilities.VerifyArgument(p.Value != null, MessagingStrings.UnexpectedNullValue, p.Key);
428 sb.Append(HttpUtility.UrlEncode(p.Key));
429 sb.Append('=');
430 sb.Append(HttpUtility.UrlEncode(p.Value));
431 sb.Append('&');
433 sb.Length--; // remove trailing &
435 return sb.ToString();
438 /// <summary>
439 /// Adds a set of name-value pairs to the end of a given URL
440 /// as part of the querystring piece. Prefixes a ? or &amp; before
441 /// first element as necessary.
442 /// </summary>
443 /// <param name="builder">The UriBuilder to add arguments to.</param>
444 /// <param name="args">
445 /// The arguments to add to the query.
446 /// If null, <paramref name="builder"/> is not changed.
447 /// </param>
448 internal static void AppendQueryArgs(this UriBuilder builder, IEnumerable<KeyValuePair<string, string>> args) {
449 if (builder == null) {
450 throw new ArgumentNullException("builder");
453 if (args != null && args.Count() > 0) {
454 StringBuilder sb = new StringBuilder(50 + (args.Count() * 10));
455 if (!string.IsNullOrEmpty(builder.Query)) {
456 sb.Append(builder.Query.Substring(1));
457 sb.Append('&');
459 sb.Append(CreateQueryString(args));
461 builder.Query = sb.ToString();
465 /// <summary>
466 /// Extracts the recipient from an HttpRequestInfo.
467 /// </summary>
468 /// <param name="request">The request to get recipient information from.</param>
469 /// <returns>The recipient.</returns>
470 internal static MessageReceivingEndpoint GetRecipient(this HttpRequestInfo request) {
471 return new MessageReceivingEndpoint(request.Url, request.HttpMethod == "GET" ? HttpDeliveryMethods.GetRequest : HttpDeliveryMethods.PostRequest);
474 /// <summary>
475 /// Copies some extra parameters into a message.
476 /// </summary>
477 /// <param name="message">The message to copy the extra data into.</param>
478 /// <param name="extraParameters">The extra data to copy into the message. May be null to do nothing.</param>
479 internal static void AddExtraParameters(this IMessage message, IDictionary<string, string> extraParameters) {
480 ErrorUtilities.VerifyArgumentNotNull(message, "message");
482 if (extraParameters != null) {
483 MessageDictionary messageDictionary = new MessageDictionary(message);
484 foreach (var pair in extraParameters) {
485 messageDictionary.Add(pair);
490 /// <summary>
491 /// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
492 /// </summary>
493 /// <param name="nvc">The NameValueCollection to convert. May be null.</param>
494 /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns>
495 /// <remarks>
496 /// If a <c>null</c> key is encountered, its value is ignored since
497 /// <c>Dictionary&lt;string, string&gt;</c> does not allow null keys.
498 /// </remarks>
499 internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc) {
500 return ToDictionary(nvc, false);
503 /// <summary>
504 /// Converts a <see cref="NameValueCollection"/> to an IDictionary&lt;string, string&gt;.
505 /// </summary>
506 /// <param name="nvc">The NameValueCollection to convert. May be null.</param>
507 /// <param name="throwOnNullKey">
508 /// 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.
509 /// Use <c>true</c> to throw an exception if a null key is encountered.
510 /// Use <c>false</c> to silently continue converting the valid keys.
511 /// </param>
512 /// <returns>The generated dictionary, or null if <paramref name="nvc"/> is null.</returns>
513 /// <exception cref="ArgumentException">Thrown if <paramref name="throwOnNullKey"/> is <c>true</c> and a null key is encountered.</exception>
514 internal static Dictionary<string, string> ToDictionary(this NameValueCollection nvc, bool throwOnNullKey) {
515 if (nvc == null) {
516 return null;
519 var dictionary = new Dictionary<string, string>();
520 foreach (string key in nvc) {
521 // NameValueCollection supports a null key, but Dictionary<K,V> does not.
522 if (key == null) {
523 if (throwOnNullKey) {
524 throw new ArgumentException(MessagingStrings.UnexpectedNullKey);
525 } else {
526 Logger.WarnFormat("Null key with value {0} encountered while translating NameValueCollection to Dictionary.", nvc[key]);
528 } else {
529 dictionary.Add(key, nvc[key]);
533 return dictionary;
536 /// <summary>
537 /// Sorts the elements of a sequence in ascending order by using a specified comparer.
538 /// </summary>
539 /// <typeparam name="TSource">The type of the elements of source.</typeparam>
540 /// <typeparam name="TKey">The type of the key returned by keySelector.</typeparam>
541 /// <param name="source">A sequence of values to order.</param>
542 /// <param name="keySelector">A function to extract a key from an element.</param>
543 /// <param name="comparer">A comparison function to compare keys.</param>
544 /// <returns>An System.Linq.IOrderedEnumerable&lt;TElement&gt; whose elements are sorted according to a key.</returns>
545 internal static IOrderedEnumerable<TSource> OrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Comparison<TKey> comparer) {
546 return System.Linq.Enumerable.OrderBy<TSource, TKey>(source, keySelector, new ComparisonHelper<TKey>(comparer));
549 /// <summary>
550 /// Determines whether the specified message is a request (indirect message or direct request).
551 /// </summary>
552 /// <param name="message">The message in question.</param>
553 /// <returns>
554 /// <c>true</c> if the specified message is a request; otherwise, <c>false</c>.
555 /// </returns>
556 /// <remarks>
557 /// Although an <see cref="IProtocolMessage"/> may implement the <see cref="IDirectedProtocolMessage"/>
558 /// interface, it may only be doing that for its derived classes. These objects are only requests
559 /// if their <see cref="IDirectedProtocolMessage.Recipient"/> property is non-null.
560 /// </remarks>
561 internal static bool IsRequest(this IDirectedProtocolMessage message) {
562 ErrorUtilities.VerifyArgumentNotNull(message, "message");
563 return message.Recipient != null;
566 /// <summary>
567 /// Determines whether the specified message is a direct response.
568 /// </summary>
569 /// <param name="message">The message in question.</param>
570 /// <returns>
571 /// <c>true</c> if the specified message is a direct response; otherwise, <c>false</c>.
572 /// </returns>
573 /// <remarks>
574 /// Although an <see cref="IProtocolMessage"/> may implement the
575 /// <see cref="IDirectResponseProtocolMessage"/> interface, it may only be doing
576 /// that for its derived classes. These objects are only requests if their
577 /// <see cref="IDirectResponseProtocolMessage.OriginatingRequest"/> property is non-null.
578 /// </remarks>
579 internal static bool IsDirectResponse(this IDirectResponseProtocolMessage message) {
580 ErrorUtilities.VerifyArgumentNotNull(message, "message");
581 return message.OriginatingRequest != null;
584 /// <summary>
585 /// Constructs a Javascript expression that will create an object
586 /// on the user agent when assigned to a variable.
587 /// </summary>
588 /// <param name="namesAndValues">The untrusted names and untrusted values to inject into the JSON object.</param>
589 /// <returns>The Javascript JSON object as a string.</returns>
590 internal static string CreateJsonObject(IEnumerable<KeyValuePair<string, string>> namesAndValues) {
591 StringBuilder builder = new StringBuilder();
592 builder.Append("{ ");
594 foreach (var pair in namesAndValues) {
595 builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Key));
596 builder.Append(": ");
597 builder.Append(MessagingUtilities.GetSafeJavascriptValue(pair.Value));
598 builder.Append(",");
601 if (builder[builder.Length - 1] == ',') {
602 builder.Length -= 1;
604 builder.Append("}");
605 return builder.ToString();
608 /// <summary>
609 /// Prepares what SHOULD be simply a string value for safe injection into Javascript
610 /// by using appropriate character escaping.
611 /// </summary>
612 /// <param name="value">The untrusted string value to be escaped to protected against XSS attacks. May be null.</param>
613 /// <returns>The escaped string.</returns>
614 internal static string GetSafeJavascriptValue(string value) {
615 if (value == null) {
616 return "null";
619 // We use a StringBuilder because we have potentially many replacements to do,
620 // and we don't want to create a new string for every intermediate replacement step.
621 StringBuilder builder = new StringBuilder(value);
622 foreach (var pair in javascriptStaticStringEscaping) {
623 builder.Replace(pair.Key, pair.Value);
625 builder.Insert(0, '\'');
626 builder.Append('\'');
627 return builder.ToString();
630 /// <summary>
631 /// A class to convert a <see cref="Comparison&lt;T&gt;"/> into an <see cref="IComparer&lt;T&gt;"/>.
632 /// </summary>
633 /// <typeparam name="T">The type of objects being compared.</typeparam>
634 private class ComparisonHelper<T> : IComparer<T> {
635 /// <summary>
636 /// The comparison method to use.
637 /// </summary>
638 private Comparison<T> comparison;
640 /// <summary>
641 /// Initializes a new instance of the ComparisonHelper class.
642 /// </summary>
643 /// <param name="comparison">The comparison method to use.</param>
644 internal ComparisonHelper(Comparison<T> comparison) {
645 if (comparison == null) {
646 throw new ArgumentNullException("comparison");
649 this.comparison = comparison;
652 #region IComparer<T> Members
654 /// <summary>
655 /// Compares two instances of <typeparamref name="T"/>.
656 /// </summary>
657 /// <param name="x">The first object to compare.</param>
658 /// <param name="y">The second object to compare.</param>
659 /// <returns>Any of -1, 0, or 1 according to standard comparison rules.</returns>
660 public int Compare(T x, T y) {
661 return this.comparison(x, y);
664 #endregion