PageRenderTime 70ms CodeModel.GetById 14ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 1ms

/WCFWebApi/src/Microsoft.Server.Common/Microsoft/Server/Common/UrlUtility.cs

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