Merge pull request #1860 from saper/tz-fix
[mono.git] / mcs / class / System.Web / System.Web / HttpUtility.cs
1 // 
2 // System.Web.HttpUtility
3 //
4 // Authors:
5 //   Patrik Torstensson (Patrik.Torstensson@labs2.com)
6 //   Wictor WilĂ©n (decode/encode functions) (wictor@ibizkit.se)
7 //   Tim Coleman (tim@timcoleman.com)
8 //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
9 //
10 // Copyright (C) 2005-2010 Novell, Inc (http://www.novell.com)
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Collections.Specialized;
35 using System.Globalization;
36 using System.IO;
37 using System.Security.Permissions;
38 using System.Text;
39 using System.Web.Util;
40
41 namespace System.Web {
42
43 #if !MOBILE
44         // CAS - no InheritanceDemand here as the class is sealed
45         [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
46 #endif
47         public sealed class HttpUtility
48         {
49                 sealed class HttpQSCollection : NameValueCollection
50                 {
51                         public override string ToString ()
52                         {
53                                 int count = Count;
54                                 if (count == 0)
55                                         return "";
56                                 StringBuilder sb = new StringBuilder ();
57                                 string [] keys = AllKeys;
58                                 for (int i = 0; i < count; i++) {
59                                         sb.AppendFormat ("{0}={1}&", keys [i], UrlEncode (this [keys [i]]));
60                                 }
61                                 if (sb.Length > 0)
62                                         sb.Length--;
63                                 return sb.ToString ();
64                         }
65                 }
66                 
67                 #region Constructors
68
69                 public HttpUtility () 
70                 {
71                 }
72         
73                 #endregion // Constructors
74         
75                 #region Methods
76         
77                 public static void HtmlAttributeEncode (string s, TextWriter output) 
78                 {
79                         if (output == null) {
80                                 throw new ArgumentNullException ("output");
81                         }
82                         HttpEncoder.Current.HtmlAttributeEncode (s, output);
83                 }
84         
85                 public static string HtmlAttributeEncode (string s) 
86                 {
87                         if (s == null)
88                                 return null;
89                         
90                         using (var sw = new StringWriter ()) {
91                                 HttpEncoder.Current.HtmlAttributeEncode (s, sw);
92                                 return sw.ToString ();
93                         }
94                 }
95         
96                 public static string UrlDecode (string str) 
97                 {
98                         return UrlDecode(str, Encoding.UTF8);
99                 }
100         
101                 static char [] GetChars (MemoryStream b, Encoding e)
102                 {
103                         return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
104                 }
105
106                 static void WriteCharBytes (IList buf, char ch, Encoding e)
107                 {
108                         if (ch > 255) {
109                                 foreach (byte b in e.GetBytes (new char[] { ch }))
110                                         buf.Add (b);
111                         } else
112                                 buf.Add ((byte)ch);
113                 }
114                 
115                 public static string UrlDecode (string s, Encoding e)
116                 {
117                         if (null == s) 
118                                 return null;
119
120                         if (s.IndexOf ('%') == -1 && s.IndexOf ('+') == -1)
121                                 return s;
122                         
123                         if (e == null)
124                                 e = Encoding.UTF8;
125
126                         long len = s.Length;
127                         var bytes = new List <byte> ();
128                         int xchar;
129                         char ch;
130                         
131                         for (int i = 0; i < len; i++) {
132                                 ch = s [i];
133                                 if (ch == '%' && i + 2 < len && s [i + 1] != '%') {
134                                         if (s [i + 1] == 'u' && i + 5 < len) {
135                                                 // unicode hex sequence
136                                                 xchar = GetChar (s, i + 2, 4);
137                                                 if (xchar != -1) {
138                                                         WriteCharBytes (bytes, (char)xchar, e);
139                                                         i += 5;
140                                                 } else
141                                                         WriteCharBytes (bytes, '%', e);
142                                         } else if ((xchar = GetChar (s, i + 1, 2)) != -1) {
143                                                 WriteCharBytes (bytes, (char)xchar, e);
144                                                 i += 2;
145                                         } else {
146                                                 WriteCharBytes (bytes, '%', e);
147                                         }
148                                         continue;
149                                 }
150
151                                 if (ch == '+')
152                                         WriteCharBytes (bytes, ' ', e);
153                                 else
154                                         WriteCharBytes (bytes, ch, e);
155                         }
156                         
157                         byte[] buf = bytes.ToArray ();
158                         bytes = null;
159                         return e.GetString (buf);
160                         
161                 }
162         
163                 public static string UrlDecode (byte [] bytes, Encoding e)
164                 {
165                         if (bytes == null)
166                                 return null;
167
168                         return UrlDecode (bytes, 0, bytes.Length, e);
169                 }
170
171                 static int GetInt (byte b)
172                 {
173                         char c = (char) b;
174                         if (c >= '0' && c <= '9')
175                                 return c - '0';
176
177                         if (c >= 'a' && c <= 'f')
178                                 return c - 'a' + 10;
179
180                         if (c >= 'A' && c <= 'F')
181                                 return c - 'A' + 10;
182
183                         return -1;
184                 }
185
186                 static int GetChar (byte [] bytes, int offset, int length)
187                 {
188                         int value = 0;
189                         int end = length + offset;
190                         for (int i = offset; i < end; i++) {
191                                 int current = GetInt (bytes [i]);
192                                 if (current == -1)
193                                         return -1;
194                                 value = (value << 4) + current;
195                         }
196
197                         return value;
198                 }
199
200                 static int GetChar (string str, int offset, int length)
201                 {
202                         int val = 0;
203                         int end = length + offset;
204                         for (int i = offset; i < end; i++) {
205                                 char c = str [i];
206                                 if (c > 127)
207                                         return -1;
208
209                                 int current = GetInt ((byte) c);
210                                 if (current == -1)
211                                         return -1;
212                                 val = (val << 4) + current;
213                         }
214
215                         return val;
216                 }
217                 
218                 public static string UrlDecode (byte [] bytes, int offset, int count, Encoding e)
219                 {
220                         if (bytes == null)
221                                 return null;
222                         if (count == 0)
223                                 return String.Empty;
224
225                         if (bytes == null)
226                                 throw new ArgumentNullException ("bytes");
227
228                         if (offset < 0 || offset > bytes.Length)
229                                 throw new ArgumentOutOfRangeException ("offset");
230
231                         if (count < 0 || offset + count > bytes.Length)
232                                 throw new ArgumentOutOfRangeException ("count");
233
234                         StringBuilder output = new StringBuilder ();
235                         MemoryStream acc = new MemoryStream ();
236
237                         int end = count + offset;
238                         int xchar;
239                         for (int i = offset; i < end; i++) {
240                                 if (bytes [i] == '%' && i + 2 < count && bytes [i + 1] != '%') {
241                                         if (bytes [i + 1] == (byte) 'u' && i + 5 < end) {
242                                                 if (acc.Length > 0) {
243                                                         output.Append (GetChars (acc, e));
244                                                         acc.SetLength (0);
245                                                 }
246                                                 xchar = GetChar (bytes, i + 2, 4);
247                                                 if (xchar != -1) {
248                                                         output.Append ((char) xchar);
249                                                         i += 5;
250                                                         continue;
251                                                 }
252                                         } else if ((xchar = GetChar (bytes, i + 1, 2)) != -1) {
253                                                 acc.WriteByte ((byte) xchar);
254                                                 i += 2;
255                                                 continue;
256                                         }
257                                 }
258
259                                 if (acc.Length > 0) {
260                                         output.Append (GetChars (acc, e));
261                                         acc.SetLength (0);
262                                 }
263
264                                 if (bytes [i] == '+') {
265                                         output.Append (' ');
266                                 } else {
267                                         output.Append ((char) bytes [i]);
268                                 }
269                         }
270
271                         if (acc.Length > 0) {
272                                 output.Append (GetChars (acc, e));
273                         }
274                         
275                         acc = null;
276                         return output.ToString ();
277                 }
278         
279                 public static byte [] UrlDecodeToBytes (byte [] bytes)
280                 {
281                         if (bytes == null)
282                                 return null;
283
284                         return UrlDecodeToBytes (bytes, 0, bytes.Length);
285                 }
286
287                 public static byte [] UrlDecodeToBytes (string str)
288                 {
289                         return UrlDecodeToBytes (str, Encoding.UTF8);
290                 }
291
292                 public static byte [] UrlDecodeToBytes (string str, Encoding e)
293                 {
294                         if (str == null)
295                                 return null;
296
297                         if (e == null)
298                                 throw new ArgumentNullException ("e");
299
300                         return UrlDecodeToBytes (e.GetBytes (str));
301                 }
302
303                 public static byte [] UrlDecodeToBytes (byte [] bytes, int offset, int count)
304                 {
305                         if (bytes == null)
306                                 return null;
307                         if (count == 0)
308                                 return new byte [0];
309
310                         int len = bytes.Length;
311                         if (offset < 0 || offset >= len)
312                                 throw new ArgumentOutOfRangeException("offset");
313
314                         if (count < 0 || offset > len - count)
315                                 throw new ArgumentOutOfRangeException("count");
316
317                         MemoryStream result = new MemoryStream ();
318                         int end = offset + count;
319                         for (int i = offset; i < end; i++){
320                                 char c = (char) bytes [i];
321                                 if (c == '+') {
322                                         c = ' ';
323                                 } else if (c == '%' && i < end - 2) {
324                                         int xchar = GetChar (bytes, i + 1, 2);
325                                         if (xchar != -1) {
326                                                 c = (char) xchar;
327                                                 i += 2;
328                                         }
329                                 }
330                                 result.WriteByte ((byte) c);
331                         }
332
333                         return result.ToArray ();
334                 }
335
336                 public static string UrlEncode(string str) 
337                 {
338                         return UrlEncode(str, Encoding.UTF8);
339                 }
340         
341                 public static string UrlEncode (string s, Encoding Enc) 
342                 {
343                         if (s == null)
344                                 return null;
345
346                         if (s == String.Empty)
347                                 return String.Empty;
348
349                         bool needEncode = false;
350                         int len = s.Length;
351                         for (int i = 0; i < len; i++) {
352                                 char c = s [i];
353                                 if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z')) {
354                                         if (HttpEncoder.NotEncoded (c))
355                                                 continue;
356
357                                         needEncode = true;
358                                         break;
359                                 }
360                         }
361
362                         if (!needEncode)
363                                 return s;
364
365                         // avoided GetByteCount call
366                         byte [] bytes = new byte[Enc.GetMaxByteCount(s.Length)];
367                         int realLen = Enc.GetBytes (s, 0, s.Length, bytes, 0);
368                         return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, 0, realLen));
369                 }
370           
371                 public static string UrlEncode (byte [] bytes)
372                 {
373                         if (bytes == null)
374                                 return null;
375
376                         if (bytes.Length == 0)
377                                 return String.Empty;
378
379                         return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, 0, bytes.Length));
380                 }
381
382                 public static string UrlEncode (byte [] bytes, int offset, int count)
383                 {
384                         if (bytes == null)
385                                 return null;
386
387                         if (bytes.Length == 0)
388                                 return String.Empty;
389
390                         return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, offset, count));
391                 }
392
393                 public static byte [] UrlEncodeToBytes (string str)
394                 {
395                         return UrlEncodeToBytes (str, Encoding.UTF8);
396                 }
397
398                 public static byte [] UrlEncodeToBytes (string str, Encoding e)
399                 {
400                         if (str == null)
401                                 return null;
402
403                         if (str.Length == 0)
404                                 return new byte [0];
405
406                         byte [] bytes = e.GetBytes (str);
407                         return UrlEncodeToBytes (bytes, 0, bytes.Length);
408                 }
409
410                 public static byte [] UrlEncodeToBytes (byte [] bytes)
411                 {
412                         if (bytes == null)
413                                 return null;
414
415                         if (bytes.Length == 0)
416                                 return new byte [0];
417
418                         return UrlEncodeToBytes (bytes, 0, bytes.Length);
419                 }
420
421                 public static byte [] UrlEncodeToBytes (byte [] bytes, int offset, int count)
422                 {
423                         if (bytes == null)
424                                 return null;
425                         return HttpEncoder.Current.UrlEncode (bytes, offset, count);
426                 }
427
428                 public static string UrlEncodeUnicode (string str)
429                 {
430                         if (str == null)
431                                 return null;
432
433                         return Encoding.ASCII.GetString (UrlEncodeUnicodeToBytes (str));
434                 }
435
436                 public static byte [] UrlEncodeUnicodeToBytes (string str)
437                 {
438                         if (str == null)
439                                 return null;
440
441                         if (str.Length == 0)
442                                 return new byte [0];
443
444                         MemoryStream result = new MemoryStream (str.Length);
445                         foreach (char c in str){
446                                 HttpEncoder.UrlEncodeChar (c, result, true);
447                         }
448                         return result.ToArray ();
449                 }
450
451                 /// <summary>
452                 /// Decodes an HTML-encoded string and returns the decoded string.
453                 /// </summary>
454                 /// <param name="s">The HTML string to decode. </param>
455                 /// <returns>The decoded text.</returns>
456                 public static string HtmlDecode (string s) 
457                 {
458                         if (s == null)
459                                 return null;
460                         
461                         using (var sw = new StringWriter ()) {
462                                 HttpEncoder.Current.HtmlDecode (s, sw);
463                                 return sw.ToString ();
464                         }
465                 }
466         
467                 /// <summary>
468                 /// Decodes an HTML-encoded string and sends the resulting output to a TextWriter output stream.
469                 /// </summary>
470                 /// <param name="s">The HTML string to decode</param>
471                 /// <param name="output">The TextWriter output stream containing the decoded string. </param>
472                 public static void HtmlDecode(string s, TextWriter output) 
473                 {
474                         if (output == null) {
475                                 throw new ArgumentNullException ("output");
476                         }
477                                 
478                         if (!String.IsNullOrEmpty (s)) {
479                                 HttpEncoder.Current.HtmlDecode (s, output);
480                         }
481                 }
482
483                 public static string HtmlEncode (string s)
484                 {
485                         if (s == null)
486                                 return null;
487                         
488                         using (var sw = new StringWriter ()) {
489                                 HttpEncoder.Current.HtmlEncode (s, sw);
490                                 return sw.ToString ();
491                         }
492                 }
493                 
494                 /// <summary>
495                 /// HTML-encodes a string and sends the resulting output to a TextWriter output stream.
496                 /// </summary>
497                 /// <param name="s">The string to encode. </param>
498                 /// <param name="output">The TextWriter output stream containing the encoded string. </param>
499                 public static void HtmlEncode(string s, TextWriter output) 
500                 {
501                         if (output == null) {
502                                 throw new ArgumentNullException ("output");
503                         }
504                                 
505                         if (!String.IsNullOrEmpty (s)) {
506                                 HttpEncoder.Current.HtmlEncode (s, output);
507                         }
508                 }
509                 public static string HtmlEncode (object value)
510                 {
511                         if (value == null)
512                                 return null;
513
514 #if !(MOBILE || NO_SYSTEM_WEB_DEPENDENCY)
515                         IHtmlString htmlString = value as IHtmlString;
516                         if (htmlString != null)
517                                 return htmlString.ToHtmlString ();
518 #endif
519
520                         return HtmlEncode (value.ToString ());
521                 }
522
523                 public static string JavaScriptStringEncode (string value)
524                 {
525                         return JavaScriptStringEncode (value, false);
526                 }
527
528                 public static string JavaScriptStringEncode (string value, bool addDoubleQuotes)
529                 {
530                         if (String.IsNullOrEmpty (value))
531                                 return addDoubleQuotes ? "\"\"" : String.Empty;
532
533                         int len = value.Length;
534                         bool needEncode = false;
535                         char c;
536                         for (int i = 0; i < len; i++) {
537                                 c = value [i];
538
539                                 if (c >= 0 && c <= 31 || c == 34 || c == 39 || c == 60 || c == 62 || c == 92) {
540                                         needEncode = true;
541                                         break;
542                                 }
543                         }
544
545                         if (!needEncode)
546                                 return addDoubleQuotes ? "\"" + value + "\"" : value;
547
548                         var sb = new StringBuilder ();
549                         if (addDoubleQuotes)
550                                 sb.Append ('"');
551
552                         for (int i = 0; i < len; i++) {
553                                 c = value [i];
554                                 if (c >= 0 && c <= 7 || c == 11 || c >= 14 && c <= 31 || c == 39 || c == 60 || c == 62)
555                                         sb.AppendFormat ("\\u{0:x4}", (int)c);
556                                 else switch ((int)c) {
557                                                 case 8:
558                                                         sb.Append ("\\b");
559                                                         break;
560
561                                                 case 9:
562                                                         sb.Append ("\\t");
563                                                         break;
564
565                                                 case 10:
566                                                         sb.Append ("\\n");
567                                                         break;
568
569                                                 case 12:
570                                                         sb.Append ("\\f");
571                                                         break;
572
573                                                 case 13:
574                                                         sb.Append ("\\r");
575                                                         break;
576
577                                                 case 34:
578                                                         sb.Append ("\\\"");
579                                                         break;
580
581                                                 case 92:
582                                                         sb.Append ("\\\\");
583                                                         break;
584
585                                                 default:
586                                                         sb.Append (c);
587                                                         break;
588                                         }
589                         }
590
591                         if (addDoubleQuotes)
592                                 sb.Append ('"');
593
594                         return sb.ToString ();
595                 }
596                 public static string UrlPathEncode (string s)
597                 {
598                         return HttpEncoder.Current.UrlPathEncode (s);
599                 }
600
601                 public static NameValueCollection ParseQueryString (string query)
602                 {
603                         return ParseQueryString (query, Encoding.UTF8);
604                 }
605
606                 public static NameValueCollection ParseQueryString (string query, Encoding encoding)
607                 {
608                         if (query == null)
609                                 throw new ArgumentNullException ("query");
610                         if (encoding == null)
611                                 throw new ArgumentNullException ("encoding");
612                         if (query.Length == 0 || (query.Length == 1 && query[0] == '?'))
613                                 return new HttpQSCollection ();
614                         if (query[0] == '?')
615                                 query = query.Substring (1);
616                                 
617                         NameValueCollection result = new HttpQSCollection ();
618                         ParseQueryString (query, encoding, result);
619                         return result;
620                 }
621
622                 internal static void ParseQueryString (string query, Encoding encoding, NameValueCollection result)
623                 {
624                         if (query.Length == 0)
625                                 return;
626
627                         string decoded = HtmlDecode (query);
628                         int decodedLength = decoded.Length;
629                         int namePos = 0;
630                         bool first = true;
631                         while (namePos <= decodedLength) {
632                                 int valuePos = -1, valueEnd = -1;
633                                 for (int q = namePos; q < decodedLength; q++) {
634                                         if (valuePos == -1 && decoded [q] == '=') {
635                                                 valuePos = q + 1;
636                                         } else if (decoded [q] == '&') {
637                                                 valueEnd = q;
638                                                 break;
639                                         }
640                                 }
641
642                                 if (first) {
643                                         first = false;
644                                         if (decoded [namePos] == '?')
645                                                 namePos++;
646                                 }
647                                 
648                                 string name, value;
649                                 if (valuePos == -1) {
650                                         name = null;
651                                         valuePos = namePos;
652                                 } else {
653                                         name = UrlDecode (decoded.Substring (namePos, valuePos - namePos - 1), encoding);
654                                 }
655                                 if (valueEnd < 0) {
656                                         namePos = -1;
657                                         valueEnd = decoded.Length;
658                                 } else {
659                                         namePos = valueEnd + 1;
660                                 }
661                                 value = UrlDecode (decoded.Substring (valuePos, valueEnd - valuePos), encoding);
662
663                                 result.Add (name, value);
664                                 if (namePos == -1)
665                                         break;
666                         }
667                 }
668                 #endregion // Methods
669         }
670 }
671