1
//-----------------------------------------------------------------------
2 // <copyright file="MessagingUtilities.cs" company="Andrew Arnott">
3 // Copyright (c) Andrew Arnott. All rights reserved.
5 //-----------------------------------------------------------------------
7 namespace DotNetOpenAuth
.Messaging
{
9 using System
.Collections
.Generic
;
10 using System
.Collections
.Specialized
;
11 using System
.Diagnostics
.CodeAnalysis
;
12 using System
.Globalization
;
16 using System
.Security
.Cryptography
;
19 using DotNetOpenAuth
.Messaging
.Reflection
;
22 /// A grab-bag of utility methods useful for the channel stack of the protocol.
24 public static class MessagingUtilities
{
26 /// The cryptographically strong random data generator used for creating secrets.
28 /// <remarks>The random number generator is thread-safe.</remarks>
29 internal static readonly RandomNumberGenerator CryptoRandomDataGenerator
= new RNGCryptoServiceProvider();
32 /// A set of escaping mappings that help secure a string from javscript execution.
35 /// The characters to escape here are inspired by
36 /// http://code.google.com/p/doctype/wiki/ArticleXSSInJavaScript
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
43 { "\u0085", @"\u0085" }
,
44 { "\u2028", @"\u2028" }
,
45 { "\u2029", @"\u2029" }
,
55 /// HTTP headers that must be copied using their proper properties instead of directly.
57 private static readonly string[] HeadersToNotCopy
= new string[] { "Host", "Connection" }
;
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.
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
);
81 /// Gets the query data from the original request (before any URL rewriting has occurred.)
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
;
97 // Rewriting detected! Recover the original request URI.
98 return HttpUtility
.ParseQueryString(GetRequestUrlFromContext().Query
);
103 /// Gets the query or form data from the original request (before any URL rewriting has occurred.)
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();
114 query
= request
.Form
;
120 /// Strips any and all URI query parameters that start with some prefix.
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());
143 /// Gets a cryptographically strong random sequence of values.
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
);
154 /// Gets a cryptographically strong random sequence of values.
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
);
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" />
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
) {
179 response
.ContentType
= headers
[HttpResponseHeader
.ContentType
];
182 // Add more special cases here as necessary.
184 response
.AddHeader(headerName
, headers
[headerName
]);
191 /// Copies the contents of one stream to another.
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>
197 /// Copying begins at the streams' current positions.
198 /// The positions are NOT reset after copying is complete.
200 internal static int CopyTo(this Stream copyFrom
, Stream copyTo
) {
201 return CopyTo(copyFrom
, copyTo
, int.MaxValue
);
205 /// Copies the contents of one stream to another.
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>
212 /// Copying begins at the streams' current positions.
213 /// The positions are NOT reset after copying is complete.
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];
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
;
235 /// Creates a snapshot of some stream so it is seekable, and the original can be closed.
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
);
249 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
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
);
259 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
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
]);
318 /// Tests whether two arrays are equal in contents and ordering.
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
) {
330 for (int i
= 0; i
< first
.Length
; i
++) {
331 if (!first
[i
].Equals(second
[i
])) {
339 /// Tests two sequences for same contents and ordering.
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) {
349 if ((sequence1
== null) ^
(sequence2
== null)) {
353 IEnumerator
<T
> iterator1
= sequence1
.GetEnumerator();
354 IEnumerator
<T
> iterator2
= sequence2
.GetEnumerator();
355 bool movenext1
, movenext2
;
357 movenext1
= iterator1
.MoveNext();
358 movenext2
= iterator2
.MoveNext();
359 if (!movenext1
|| !movenext2
) { // if we've reached the end of at least one sequence
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?
379 /// Tests two unordered collections for same contents.
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) {
389 if ((first
== null) ^
(second
== null)) {
393 if (first
.Count
!= second
.Count
) {
397 foreach (T
value in first
) {
398 if (!second
.Contains(value)) {
407 /// Tests whether two dictionaries are equal in length and contents.
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());
419 /// Concatenates a list of name-value pairs as key=value&key=value,
420 /// taking care to properly encode each key and value for URL
421 /// transmission. No ? is prefixed to the string.
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) {
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
));
437 sb
.Append(HttpUtility
.UrlEncode(p
.Value
));
440 sb
.Length
--; // remove trailing &
442 return sb
.ToString();
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 & before
448 /// first element as necessary.
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.
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));
466 sb
.Append(CreateQueryString(args
));
468 builder
.Query
= sb
.ToString();
473 /// Extracts the recipient from an HttpRequestInfo.
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
);
482 /// Copies some extra parameters into a message.
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
);
498 /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>.
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>
503 /// If a <c>null</c> key is encountered, its value is ignored since
504 /// <c>Dictionary<string, string></c> does not allow null keys.
506 internal static Dictionary
<string, string> ToDictionary(this NameValueCollection nvc
) {
507 return ToDictionary(nvc
, false);
511 /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>.
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.
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
) {
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.
530 if (throwOnNullKey
) {
531 throw new ArgumentException(MessagingStrings
.UnexpectedNullKey
);
533 Logger
.WarnFormat("Null key with value {0} encountered while translating NameValueCollection to Dictionary.", nvc
[key
]);
536 dictionary
.Add(key
, nvc
[key
]);
544 /// Sorts the elements of a sequence in ascending order by using a specified comparer.
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<TElement> 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
));
557 /// Determines whether the specified message is a request (indirect message or direct request).
559 /// <param name="message">The message in question.</param>
561 /// <c>true</c> if the specified message is a request; otherwise, <c>false</c>.
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.
568 internal static bool IsRequest(this IDirectedProtocolMessage message
) {
569 ErrorUtilities
.VerifyArgumentNotNull(message
, "message");
570 return message
.Recipient
!= null;
574 /// Determines whether the specified message is a direct response.
576 /// <param name="message">The message in question.</param>
578 /// <c>true</c> if the specified message is a direct response; otherwise, <c>false</c>.
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.
586 internal static bool IsDirectResponse(this IDirectResponseProtocolMessage message
) {
587 ErrorUtilities
.VerifyArgumentNotNull(message
, "message");
588 return message
.OriginatingRequest
!= null;
592 /// Constructs a Javascript expression that will create an object
593 /// on the user agent when assigned to a variable.
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
));
608 if (builder
[builder
.Length
- 1] == ',') {
612 return builder
.ToString();
616 /// Prepares what SHOULD be simply a string value for safe injection into Javascript
617 /// by using appropriate character escaping.
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) {
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();
638 /// A class to convert a <see cref="Comparison<T>"/> into an <see cref="IComparer<T>"/>.
640 /// <typeparam name="T">The type of objects being compared.</typeparam>
641 private class ComparisonHelper
<T
> : IComparer
<T
> {
643 /// The comparison method to use.
645 private Comparison
<T
> comparison
;
648 /// Initializes a new instance of the ComparisonHelper class.
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
662 /// Compares two instances of <typeparamref name="T"/>.
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
);