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 /// 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.
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
);
76 /// Gets the query data from the original request (before any URL rewriting has occurred.)
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
;
91 // Rewriting detected! Recover the original request URI.
92 return HttpUtility
.ParseQueryString(GetRequestUrlFromContext().Query
);
97 /// Gets the query or form data from the original request (before any URL rewriting has occurred.)
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();
107 query
= request
.Form
;
113 /// Strips any and all URI query parameters that start with some prefix.
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());
136 /// Gets a cryptographically strong random sequence of values.
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
);
147 /// Gets a cryptographically strong random sequence of values.
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
);
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" />
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
) {
172 response
.ContentType
= headers
[HttpResponseHeader
.ContentType
];
175 // Add more special cases here as necessary.
177 response
.AddHeader(headerName
, headers
[headerName
]);
184 /// Copies the contents of one stream to another.
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>
190 /// Copying begins at the streams' current positions.
191 /// The positions are NOT reset after copying is complete.
193 internal static int CopyTo(this Stream copyFrom
, Stream copyTo
) {
194 return CopyTo(copyFrom
, copyTo
, int.MaxValue
);
198 /// Copies the contents of one stream to another.
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>
205 /// Copying begins at the streams' current positions.
206 /// The positions are NOT reset after copying is complete.
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];
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
;
228 /// Creates a snapshot of some stream so it is seekable, and the original can be closed.
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
);
242 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
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
);
252 /// Clones an <see cref="HttpWebRequest"/> in order to send it again.
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
]);
311 /// Tests whether two arrays are equal in contents and ordering.
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
) {
323 for (int i
= 0; i
< first
.Length
; i
++) {
324 if (!first
[i
].Equals(second
[i
])) {
332 /// Tests two sequences for same contents and ordering.
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) {
342 if ((sequence1
== null) ^
(sequence2
== null)) {
346 IEnumerator
<T
> iterator1
= sequence1
.GetEnumerator();
347 IEnumerator
<T
> iterator2
= sequence2
.GetEnumerator();
348 bool movenext1
, movenext2
;
350 movenext1
= iterator1
.MoveNext();
351 movenext2
= iterator2
.MoveNext();
352 if (!movenext1
|| !movenext2
) { // if we've reached the end of at least one sequence
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?
372 /// Tests two unordered collections for same contents.
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) {
382 if ((first
== null) ^
(second
== null)) {
386 if (first
.Count
!= second
.Count
) {
390 foreach (T
value in first
) {
391 if (!second
.Contains(value)) {
400 /// Tests whether two dictionaries are equal in length and contents.
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());
412 /// Concatenates a list of name-value pairs as key=value&key=value,
413 /// taking care to properly encode each key and value for URL
414 /// transmission. No ? is prefixed to the string.
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) {
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
));
430 sb
.Append(HttpUtility
.UrlEncode(p
.Value
));
433 sb
.Length
--; // remove trailing &
435 return sb
.ToString();
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 & before
441 /// first element as necessary.
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.
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));
459 sb
.Append(CreateQueryString(args
));
461 builder
.Query
= sb
.ToString();
466 /// Extracts the recipient from an HttpRequestInfo.
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
);
475 /// Copies some extra parameters into a message.
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
);
491 /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>.
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>
496 /// If a <c>null</c> key is encountered, its value is ignored since
497 /// <c>Dictionary<string, string></c> does not allow null keys.
499 internal static Dictionary
<string, string> ToDictionary(this NameValueCollection nvc
) {
500 return ToDictionary(nvc
, false);
504 /// Converts a <see cref="NameValueCollection"/> to an IDictionary<string, string>.
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.
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
) {
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.
523 if (throwOnNullKey
) {
524 throw new ArgumentException(MessagingStrings
.UnexpectedNullKey
);
526 Logger
.WarnFormat("Null key with value {0} encountered while translating NameValueCollection to Dictionary.", nvc
[key
]);
529 dictionary
.Add(key
, nvc
[key
]);
537 /// Sorts the elements of a sequence in ascending order by using a specified comparer.
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<TElement> 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
));
550 /// Determines whether the specified message is a request (indirect message or direct request).
552 /// <param name="message">The message in question.</param>
554 /// <c>true</c> if the specified message is a request; otherwise, <c>false</c>.
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.
561 internal static bool IsRequest(this IDirectedProtocolMessage message
) {
562 ErrorUtilities
.VerifyArgumentNotNull(message
, "message");
563 return message
.Recipient
!= null;
567 /// Determines whether the specified message is a direct response.
569 /// <param name="message">The message in question.</param>
571 /// <c>true</c> if the specified message is a direct response; otherwise, <c>false</c>.
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.
579 internal static bool IsDirectResponse(this IDirectResponseProtocolMessage message
) {
580 ErrorUtilities
.VerifyArgumentNotNull(message
, "message");
581 return message
.OriginatingRequest
!= null;
585 /// Constructs a Javascript expression that will create an object
586 /// on the user agent when assigned to a variable.
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
));
601 if (builder
[builder
.Length
- 1] == ',') {
605 return builder
.ToString();
609 /// Prepares what SHOULD be simply a string value for safe injection into Javascript
610 /// by using appropriate character escaping.
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) {
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();
631 /// A class to convert a <see cref="Comparison<T>"/> into an <see cref="IComparer<T>"/>.
633 /// <typeparam name="T">The type of objects being compared.</typeparam>
634 private class ComparisonHelper
<T
> : IComparer
<T
> {
636 /// The comparison method to use.
638 private Comparison
<T
> comparison
;
641 /// Initializes a new instance of the ComparisonHelper class.
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
655 /// Compares two instances of <typeparamref name="T"/>.
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
);