1 //------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //------------------------------------------------------------
4 
5 namespace System.Runtime
6 {
7     using System.Collections;
8     using System.Collections.Specialized;
9     using System.Runtime.Serialization;
10     using System.Text;
11 
12     //copied from System.Web.HttpUtility code (renamed here) to remove dependency on System.Web.dll
13     static class UrlUtility
14     {
15         //  Query string parsing support
ParseQueryString(string query)16         public static NameValueCollection ParseQueryString(string query)
17         {
18             return ParseQueryString(query, Encoding.UTF8);
19         }
20 
ParseQueryString(string query, Encoding encoding)21         public static NameValueCollection ParseQueryString(string query, Encoding encoding)
22         {
23             if (query == null)
24             {
25                 throw Fx.Exception.ArgumentNull("query");
26             }
27 
28             if (encoding == null)
29             {
30                 throw Fx.Exception.ArgumentNull("encoding");
31             }
32 
33             if (query.Length > 0 && query[0] == '?')
34             {
35                 query = query.Substring(1);
36             }
37 
38             return new HttpValueCollection(query, encoding);
39         }
40 
UrlEncode(string str)41         public static string UrlEncode(string str)
42         {
43             if (str == null)
44             {
45                 return null;
46             }
47             return UrlEncode(str, Encoding.UTF8);
48         }
49 
50         // URL encodes a path portion of a URL string and returns the encoded string.
UrlPathEncode(string str)51         public static string UrlPathEncode(string str)
52         {
53             if (str == null)
54             {
55                 return null;
56             }
57 
58             // recurse in case there is a query string
59             int i = str.IndexOf('?');
60             if (i >= 0)
61             {
62                 return UrlPathEncode(str.Substring(0, i)) + str.Substring(i);
63             }
64 
65             // encode DBCS characters and spaces only
66             return UrlEncodeSpaces(UrlEncodeNonAscii(str, Encoding.UTF8));
67         }
68 
UrlEncode(string str, Encoding encoding)69         public static string UrlEncode(string str, Encoding encoding)
70         {
71             if (str == null)
72             {
73                 return null;
74             }
75             return Encoding.ASCII.GetString(UrlEncodeToBytes(str, encoding));
76         }
77 
UrlEncodeUnicode(string str)78         public static string UrlEncodeUnicode(string str)
79         {
80             if (str == null)
81                 return null;
82             return UrlEncodeUnicodeStringToStringInternal(str, false);
83 
84         }
85 
UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii)86         private static string UrlEncodeUnicodeStringToStringInternal(string s, bool ignoreAscii)
87         {
88             int l = s.Length;
89             StringBuilder sb = new StringBuilder(l);
90 
91             for (int i = 0; i < l; i++)
92             {
93                 char ch = s[i];
94 
95                 if ((ch & 0xff80) == 0)
96                 {  // 7 bit?
97                     if (ignoreAscii || IsSafe(ch))
98                     {
99                         sb.Append(ch);
100                     }
101                     else if (ch == ' ')
102                     {
103                         sb.Append('+');
104                     }
105                     else
106                     {
107                         sb.Append('%');
108                         sb.Append(IntToHex((ch >> 4) & 0xf));
109                         sb.Append(IntToHex((ch) & 0xf));
110                     }
111                 }
112                 else
113                 { // arbitrary Unicode?
114                     sb.Append("%u");
115                     sb.Append(IntToHex((ch >> 12) & 0xf));
116                     sb.Append(IntToHex((ch >> 8) & 0xf));
117                     sb.Append(IntToHex((ch >> 4) & 0xf));
118                     sb.Append(IntToHex((ch) & 0xf));
119                 }
120             }
121 
122             return sb.ToString();
123         }
124 
125         //  Helper to encode the non-ASCII url characters only
UrlEncodeNonAscii(string str, Encoding e)126         static string UrlEncodeNonAscii(string str, Encoding e)
127         {
128             if (string.IsNullOrEmpty(str))
129             {
130                 return str;
131             }
132             if (e == null)
133             {
134                 e = Encoding.UTF8;
135             }
136             byte[] bytes = e.GetBytes(str);
137             bytes = UrlEncodeBytesToBytesInternalNonAscii(bytes, 0, bytes.Length, false);
138             return Encoding.ASCII.GetString(bytes);
139         }
140 
141         //  Helper to encode spaces only
UrlEncodeSpaces(string str)142         static string UrlEncodeSpaces(string str)
143         {
144             if (str != null && str.IndexOf(' ') >= 0)
145             {
146                 str = str.Replace(" ", "%20");
147             }
148             return str;
149         }
150 
UrlEncodeToBytes(string str, Encoding e)151         public static byte[] UrlEncodeToBytes(string str, Encoding e)
152         {
153             if (str == null)
154             {
155                 return null;
156             }
157             byte[] bytes = e.GetBytes(str);
158             return UrlEncodeBytesToBytesInternal(bytes, 0, bytes.Length, false);
159         }
160 
161         //public static string UrlDecode(string str)
162         //{
163         //    if (str == null)
164         //        return null;
165         //    return UrlDecode(str, Encoding.UTF8);
166         //}
167 
UrlDecode(string str, Encoding e)168         public static string UrlDecode(string str, Encoding e)
169         {
170             if (str == null)
171             {
172                 return null;
173             }
174             return UrlDecodeStringFromStringInternal(str, e);
175         }
176 
177         //  Implementation for encoding
UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)178         static byte[] UrlEncodeBytesToBytesInternal(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)
179         {
180             int cSpaces = 0;
181             int cUnsafe = 0;
182 
183             // count them first
184             for (int i = 0; i < count; i++)
185             {
186                 char ch = (char)bytes[offset + i];
187 
188                 if (ch == ' ')
189                 {
190                     cSpaces++;
191                 }
192                 else if (!IsSafe(ch))
193                 {
194                     cUnsafe++;
195                 }
196             }
197 
198             // nothing to expand?
199             if (!alwaysCreateReturnValue && cSpaces == 0 && cUnsafe == 0)
200             {
201                 return bytes;
202             }
203 
204             // expand not 'safe' characters into %XX, spaces to +s
205             byte[] expandedBytes = new byte[count + cUnsafe * 2];
206             int pos = 0;
207 
208             for (int i = 0; i < count; i++)
209             {
210                 byte b = bytes[offset + i];
211                 char ch = (char)b;
212 
213                 if (IsSafe(ch))
214                 {
215                     expandedBytes[pos++] = b;
216                 }
217                 else if (ch == ' ')
218                 {
219                     expandedBytes[pos++] = (byte)'+';
220                 }
221                 else
222                 {
223                     expandedBytes[pos++] = (byte)'%';
224                     expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf);
225                     expandedBytes[pos++] = (byte)IntToHex(b & 0x0f);
226                 }
227             }
228 
229             return expandedBytes;
230         }
231 
232 
IsNonAsciiByte(byte b)233         static bool IsNonAsciiByte(byte b)
234         {
235             return (b >= 0x7F || b < 0x20);
236         }
237 
UrlEncodeBytesToBytesInternalNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)238         static byte[] UrlEncodeBytesToBytesInternalNonAscii(byte[] bytes, int offset, int count, bool alwaysCreateReturnValue)
239         {
240             int cNonAscii = 0;
241 
242             // count them first
243             for (int i = 0; i < count; i++)
244             {
245                 if (IsNonAsciiByte(bytes[offset + i]))
246                 {
247                     cNonAscii++;
248                 }
249             }
250 
251             // nothing to expand?
252             if (!alwaysCreateReturnValue && cNonAscii == 0)
253             {
254                 return bytes;
255             }
256 
257             // expand not 'safe' characters into %XX, spaces to +s
258             byte[] expandedBytes = new byte[count + cNonAscii * 2];
259             int pos = 0;
260 
261             for (int i = 0; i < count; i++)
262             {
263                 byte b = bytes[offset + i];
264 
265                 if (IsNonAsciiByte(b))
266                 {
267                     expandedBytes[pos++] = (byte)'%';
268                     expandedBytes[pos++] = (byte)IntToHex((b >> 4) & 0xf);
269                     expandedBytes[pos++] = (byte)IntToHex(b & 0x0f);
270                 }
271                 else
272                 {
273                     expandedBytes[pos++] = b;
274                 }
275             }
276 
277             return expandedBytes;
278         }
279 
UrlDecodeStringFromStringInternal(string s, Encoding e)280         static string UrlDecodeStringFromStringInternal(string s, Encoding e)
281         {
282             int count = s.Length;
283             UrlDecoder helper = new UrlDecoder(count, e);
284 
285             // go through the string's chars collapsing %XX and %uXXXX and
286             // appending each char as char, with exception of %XX constructs
287             // that are appended as bytes
288 
289             for (int pos = 0; pos < count; pos++)
290             {
291                 char ch = s[pos];
292 
293                 if (ch == '+')
294                 {
295                     ch = ' ';
296                 }
297                 else if (ch == '%' && pos < count - 2)
298                 {
299                     if (s[pos + 1] == 'u' && pos < count - 5)
300                     {
301                         int h1 = HexToInt(s[pos + 2]);
302                         int h2 = HexToInt(s[pos + 3]);
303                         int h3 = HexToInt(s[pos + 4]);
304                         int h4 = HexToInt(s[pos + 5]);
305 
306                         if (h1 >= 0 && h2 >= 0 && h3 >= 0 && h4 >= 0)
307                         {   // valid 4 hex chars
308                             ch = (char)((h1 << 12) | (h2 << 8) | (h3 << 4) | h4);
309                             pos += 5;
310 
311                             // only add as char
312                             helper.AddChar(ch);
313                             continue;
314                         }
315                     }
316                     else
317                     {
318                         int h1 = HexToInt(s[pos + 1]);
319                         int h2 = HexToInt(s[pos + 2]);
320 
321                         if (h1 >= 0 && h2 >= 0)
322                         {     // valid 2 hex chars
323                             byte b = (byte)((h1 << 4) | h2);
324                             pos += 2;
325 
326                             // don't add as char
327                             helper.AddByte(b);
328                             continue;
329                         }
330                     }
331                 }
332 
333                 if ((ch & 0xFF80) == 0)
334                 {
335                     helper.AddByte((byte)ch); // 7 bit have to go as bytes because of Unicode
336                 }
337                 else
338                 {
339                     helper.AddChar(ch);
340                 }
341             }
342 
343             return helper.GetString();
344         }
345 
346         // Private helpers for URL encoding/decoding
HexToInt(char h)347         static int HexToInt(char h)
348         {
349             return (h >= '0' && h <= '9') ? h - '0' :
350             (h >= 'a' && h <= 'f') ? h - 'a' + 10 :
351             (h >= 'A' && h <= 'F') ? h - 'A' + 10 :
352             -1;
353         }
354 
IntToHex(int n)355         static char IntToHex(int n)
356         {
357             //WCF CHANGE: CHANGED FROM Debug.Assert() to Fx.Assert()
358             Fx.Assert(n < 0x10, "n < 0x10");
359 
360             if (n <= 9)
361             {
362                 return (char)(n + (int)'0');
363             }
364             else
365             {
366                 return (char)(n - 10 + (int)'a');
367             }
368         }
369 
370         // Set of safe chars, from RFC 1738.4 minus '+'
IsSafe(char ch)371         internal static bool IsSafe(char ch)
372         {
373             if (ch >= 'a' && ch <= 'z' || ch >= 'A' && ch <= 'Z' || ch >= '0' && ch <= '9')
374             {
375                 return true;
376             }
377 
378             switch (ch)
379             {
380                 case '-':
381                 case '_':
382                 case '.':
383                 case '!':
384                 case '*':
385                 case '\'':
386                 case '(':
387                 case ')':
388                     return true;
389             }
390 
391             return false;
392         }
393 
394         // Internal class to facilitate URL decoding -- keeps char buffer and byte buffer, allows appending of either chars or bytes
395         class UrlDecoder
396         {
397             int _bufferSize;
398 
399             // Accumulate characters in a special array
400             int _numChars;
401             char[] _charBuffer;
402 
403             // Accumulate bytes for decoding into characters in a special array
404             int _numBytes;
405             byte[] _byteBuffer;
406 
407             // Encoding to convert chars to bytes
408             Encoding _encoding;
409 
FlushBytes()410             void FlushBytes()
411             {
412                 if (_numBytes > 0)
413                 {
414                     _numChars += _encoding.GetChars(_byteBuffer, 0, _numBytes, _charBuffer, _numChars);
415                     _numBytes = 0;
416                 }
417             }
418 
UrlDecoder(int bufferSize, Encoding encoding)419             internal UrlDecoder(int bufferSize, Encoding encoding)
420             {
421                 _bufferSize = bufferSize;
422                 _encoding = encoding;
423 
424                 _charBuffer = new char[bufferSize];
425                 // byte buffer created on demand
426             }
427 
AddChar(char ch)428             internal void AddChar(char ch)
429             {
430                 if (_numBytes > 0)
431                 {
432                     FlushBytes();
433                 }
434 
435                 _charBuffer[_numChars++] = ch;
436             }
437 
AddByte(byte b)438             internal void AddByte(byte b)
439             {
440                 // if there are no pending bytes treat 7 bit bytes as characters
441                 // this optimization is temp disable as it doesn't work for some encodings
442 
443                 //if (_numBytes == 0 && ((b & 0x80) == 0)) {
444                 //    AddChar((char)b);
445                 //}
446                 //else
447 
448                 {
449                     if (_byteBuffer == null)
450                     {
451                         _byteBuffer = new byte[_bufferSize];
452                     }
453 
454                     _byteBuffer[_numBytes++] = b;
455                 }
456             }
457 
GetString()458             internal string GetString()
459             {
460                 if (_numBytes > 0)
461                 {
462                     FlushBytes();
463                 }
464 
465                 if (_numChars > 0)
466                 {
467                     return new String(_charBuffer, 0, _numChars);
468                 }
469                 else
470                 {
471                     return string.Empty;
472                 }
473             }
474         }
475 
476         [Serializable]
477         class HttpValueCollection : NameValueCollection
478         {
HttpValueCollection(string str, Encoding encoding)479             internal HttpValueCollection(string str, Encoding encoding)
480                 : base(StringComparer.OrdinalIgnoreCase)
481             {
482                 if (!string.IsNullOrEmpty(str))
483                 {
484                     FillFromString(str, true, encoding);
485                 }
486 
487                 IsReadOnly = false;
488             }
489 
HttpValueCollection(SerializationInfo info, StreamingContext context)490             protected HttpValueCollection(SerializationInfo info, StreamingContext context)
491                 : base(info, context)
492             {
493             }
494 
FillFromString(string s, bool urlencoded, Encoding encoding)495             internal void FillFromString(string s, bool urlencoded, Encoding encoding)
496             {
497                 int l = (s != null) ? s.Length : 0;
498                 int i = 0;
499 
500                 while (i < l)
501                 {
502                     // find next & while noting first = on the way (and if there are more)
503 
504                     int si = i;
505                     int ti = -1;
506 
507                     while (i < l)
508                     {
509                         char ch = s[i];
510 
511                         if (ch == '=')
512                         {
513                             if (ti < 0)
514                                 ti = i;
515                         }
516                         else if (ch == '&')
517                         {
518                             break;
519                         }
520 
521                         i++;
522                     }
523 
524                     // extract the name / value pair
525 
526                     string name = null;
527                     string value = null;
528 
529                     if (ti >= 0)
530                     {
531                         name = s.Substring(si, ti - si);
532                         value = s.Substring(ti + 1, i - ti - 1);
533                     }
534                     else
535                     {
536                         value = s.Substring(si, i - si);
537                     }
538 
539                     // add name / value pair to the collection
540 
541                     if (urlencoded)
542                     {
543                         base.Add(
544                            UrlUtility.UrlDecode(name, encoding),
545                            UrlUtility.UrlDecode(value, encoding));
546                     }
547                     else
548                     {
549                         base.Add(name, value);
550                     }
551 
552                     // trailing '&'
553 
554                     if (i == l - 1 && s[i] == '&')
555                     {
556                         base.Add(null, string.Empty);
557                     }
558 
559                     i++;
560                 }
561             }
562 
ToString()563             public override string ToString()
564             {
565                 return ToString(true, null);
566             }
567 
ToString(bool urlencoded, IDictionary excludeKeys)568             string ToString(bool urlencoded, IDictionary excludeKeys)
569             {
570                 int n = Count;
571                 if (n == 0)
572                     return string.Empty;
573 
574                 StringBuilder s = new StringBuilder();
575                 string key, keyPrefix, item;
576 
577                 for (int i = 0; i < n; i++)
578                 {
579                     key = GetKey(i);
580 
581                     if (excludeKeys != null && key != null && excludeKeys[key] != null)
582                     {
583                         continue;
584                     }
585                     if (urlencoded)
586                     {
587                         key = UrlUtility.UrlEncodeUnicode(key);
588                     }
589                     keyPrefix = (!string.IsNullOrEmpty(key)) ? (key + "=") : string.Empty;
590 
591                     ArrayList values = (ArrayList)BaseGet(i);
592                     int numValues = (values != null) ? values.Count : 0;
593 
594                     if (s.Length > 0)
595                     {
596                         s.Append('&');
597                     }
598 
599                     if (numValues == 1)
600                     {
601                         s.Append(keyPrefix);
602                         item = (string)values[0];
603                         if (urlencoded)
604                             item = UrlUtility.UrlEncodeUnicode(item);
605                         s.Append(item);
606                     }
607                     else if (numValues == 0)
608                     {
609                         s.Append(keyPrefix);
610                     }
611                     else
612                     {
613                         for (int j = 0; j < numValues; j++)
614                         {
615                             if (j > 0)
616                             {
617                                 s.Append('&');
618                             }
619                             s.Append(keyPrefix);
620                             item = (string)values[j];
621                             if (urlencoded)
622                             {
623                                 item = UrlUtility.UrlEncodeUnicode(item);
624                             }
625                             s.Append(item);
626                         }
627                     }
628                 }
629 
630                 return s.ToString();
631             }
632         }
633     }
634 }
635