2009-07-28 Gonzalo Paniagua Javier <gonzalo@novell.com>
[mono.git] / mcs / class / System.Web / System.Web / HttpUtility.cs
index 76179ce22efb493bb54fa09858cfdc5565a5fbae..fe54758eac0e2d5631dca95d962b402b2083d411 100644 (file)
@@ -3,7 +3,7 @@
 //
 // Authors:
 //   Patrik Torstensson (Patrik.Torstensson@labs2.com)
-//   Wictor Wilén (decode/encode functions) (wictor@ibizkit.se)
+//   Wictor Wilén (decode/encode functions) (wictor@ibizkit.se)
 //   Tim Coleman (tim@timcoleman.com)
 //   Gonzalo Paniagua Javier (gonzalo@ximian.com)
 //
@@ -37,12 +37,15 @@ using System.Security.Permissions;
 using System.Text;
 using System.Web.Util;
 
+#if NET_2_0
+using System.Collections.Generic;
+#endif
+
 namespace System.Web {
 
        // CAS - no InheritanceDemand here as the class is sealed
        [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
        public sealed class HttpUtility {
-
                #region Fields
        
                static Hashtable entities;
@@ -340,20 +343,32 @@ namespace System.Web {
                        if (null == s) 
                                return null;
        
-                       if (s.IndexOf ('&') == -1 && s.IndexOf ('"') == -1)
+                       bool needEncode = false;
+                       for (int i = 0; i < s.Length; i++) {
+                               if (s [i] == '&' || s [i] == '"' || s [i] == '<') {
+                                       needEncode = true;
+                                       break;
+                               }
+                       }
+
+                       if (!needEncode)
                                return s;
 
                        StringBuilder output = new StringBuilder ();
-                       foreach (char c in s) 
-                               switch (c) {
+                       int len = s.Length;
+                       for (int i = 0; i < len; i++)
+                               switch (s [i]) {
                                case '&' : 
                                        output.Append ("&amp;");
                                        break;
                                case '"' :
                                        output.Append ("&quot;");
                                        break;
+                               case '<':
+                                       output.Append ("&lt;");
+                                       break;
                                default:
-                                       output.Append (c);
+                                       output.Append (s [i]);
                                        break;
                                }
        
@@ -365,11 +380,20 @@ namespace System.Web {
                        return UrlDecode(str, Encoding.UTF8);
                }
        
-               private static char [] GetChars (MemoryStream b, Encoding e)
+               static char [] GetChars (MemoryStream b, Encoding e)
                {
                        return e.GetChars (b.GetBuffer (), 0, (int) b.Length);
                }
 
+               static void WriteCharBytes (IList buf, char ch, Encoding e)
+               {
+                       if (ch > 255) {
+                               foreach (byte b in e.GetBytes (new char[] { ch }))
+                                       buf.Add (b);
+                       } else
+                               buf.Add ((byte)ch);
+               }
+               
                public static string UrlDecode (string s, Encoding e)
                {
                        if (null == s) 
@@ -377,57 +401,53 @@ namespace System.Web {
 
                        if (s.IndexOf ('%') == -1 && s.IndexOf ('+') == -1)
                                return s;
-
+                       
                        if (e == null)
                                e = Encoding.UTF8;
-       
-                       StringBuilder output = new StringBuilder ();
+
                        long len = s.Length;
-                       MemoryStream bytes = new MemoryStream ();
+#if NET_2_0
+                       var bytes = new List <byte> ();
+#else
+                       ArrayList bytes = new ArrayList ();
+#endif
                        int xchar;
-       
+                       char ch;
+                       
                        for (int i = 0; i < len; i++) {
-                               if (s [i] == '%' && i + 2 < len && s [i + 1] != '%') {
+                               ch = s [i];
+                               if (ch == '%' && i + 2 < len && s [i + 1] != '%') {
                                        if (s [i + 1] == 'u' && i + 5 < len) {
-                                               if (bytes.Length > 0) {
-                                                       output.Append (GetChars (bytes, e));
-                                                       bytes.SetLength (0);
-                                               }
-
+                                               // unicode hex sequence
                                                xchar = GetChar (s, i + 2, 4);
                                                if (xchar != -1) {
-                                                       output.Append ((char) xchar);
+                                                       WriteCharBytes (bytes, (char)xchar, e);
                                                        i += 5;
-                                               } else {
-                                                       output.Append ('%');
-                                               }
+                                               } else
+                                                       WriteCharBytes (bytes, '%', e);
                                        } else if ((xchar = GetChar (s, i + 1, 2)) != -1) {
-                                               bytes.WriteByte ((byte) xchar);
+                                               WriteCharBytes (bytes, (char)xchar, e);
                                                i += 2;
                                        } else {
-                                               output.Append ('%');
+                                               WriteCharBytes (bytes, '%', e);
                                        }
                                        continue;
                                }
 
-                               if (bytes.Length > 0) {
-                                       output.Append (GetChars (bytes, e));
-                                       bytes.SetLength (0);
-                               }
-
-                               if (s [i] == '+') {
-                                       output.Append (' ');
-                               } else {
-                                       output.Append (s [i]);
-                               }
-                       }
-       
-                       if (bytes.Length > 0) {
-                               output.Append (GetChars (bytes, e));
+                               if (ch == '+')
+                                       WriteCharBytes (bytes, ' ', e);
+                               else
+                                       WriteCharBytes (bytes, ch, e);
                        }
-
+                       
+#if NET_2_0
+                       byte[] buf = bytes.ToArray ();
+#else
+                       byte[] buf = (byte[])bytes.ToArray (typeof (byte));
+#endif
                        bytes = null;
-                       return output.ToString ();
+                       return e.GetString (buf);
+                       
                }
        
                public static string UrlDecode (byte [] bytes, Encoding e)
@@ -438,7 +458,7 @@ namespace System.Web {
                        return UrlDecode (bytes, 0, bytes.Length, e);
                }
 
-               private static int GetInt (byte b)
+               static int GetInt (byte b)
                {
                        char c = (char) b;
                        if (c >= '0' && c <= '9')
@@ -453,7 +473,7 @@ namespace System.Web {
                        return -1;
                }
 
-               private static int GetChar (byte [] bytes, int offset, int length)
+               static int GetChar (byte [] bytes, int offset, int length)
                {
                        int value = 0;
                        int end = length + offset;
@@ -467,7 +487,7 @@ namespace System.Web {
                        return value;
                }
 
-               private static int GetChar (string str, int offset, int length)
+               static int GetChar (string str, int offset, int length)
                {
                        int val = 0;
                        int end = length + offset;
@@ -616,8 +636,26 @@ namespace System.Web {
                        if (s == "")
                                return "";
 
-                       byte [] bytes = Enc.GetBytes (s);
-                       return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, 0, bytes.Length));
+                       bool needEncode = false;
+                       int len = s.Length;
+                       for (int i = 0; i < len; i++) {
+                               char c = s [i];
+                               if ((c < '0') || (c < 'A' && c > '9') || (c > 'Z' && c < 'a') || (c > 'z')) {
+                                       if (NotEncoded (c))
+                                               continue;
+
+                                       needEncode = true;
+                                       break;
+                               }
+                       }
+
+                       if (!needEncode)
+                               return s;
+
+                       // avoided GetByteCount call
+                       byte [] bytes = new byte[Enc.GetMaxByteCount(s.Length)];
+                       int realLen = Enc.GetBytes (s, 0, s.Length, bytes, 0);
+                       return Encoding.ASCII.GetString (UrlEncodeToBytes (bytes, 0, realLen));
                }
          
                public static string UrlEncode (byte [] bytes)
@@ -671,7 +709,11 @@ namespace System.Web {
                }
 
                static char [] hexChars = "0123456789abcdef".ToCharArray ();
-               const string notEncoded = "!'()*-._";
+
+               static bool NotEncoded (char c)
+               {
+                       return (c == '!' || c == '\'' || c == '(' || c == ')' || c == '*' || c == '-' || c == '.' || c == '_');
+               }
 
                static void UrlEncodeChar (char c, Stream result, bool isUnicode) {
                        if (c > 255) {
@@ -694,7 +736,7 @@ namespace System.Web {
                                return;
                        }
                        
-                       if (c>' ' && notEncoded.IndexOf (c)!=-1) {
+                       if (c > ' ' && NotEncoded (c)) {
                                result.WriteByte ((byte)c);
                                return;
                        }
@@ -900,10 +942,23 @@ namespace System.Web {
                        if (s == null)
                                return null;
 
+                       bool needEncode = false;
+                       for (int i = 0; i < s.Length; i++) {
+                               char c = s [i];
+                               if (c == '&' || c == '"' || c == '<' || c == '>' || c > 159) {
+                                       needEncode = true;
+                                       break;
+                               }
+                       }
+
+                       if (!needEncode)
+                               return s;
+
                        StringBuilder output = new StringBuilder ();
                        
-                       foreach (char c in s) 
-                               switch (c) {
+                       int len = s.Length;
+                       for (int i = 0; i < len; i++) 
+                               switch (s [i]) {
                                case '&' :
                                        output.Append ("&amp;");
                                        break;
@@ -920,12 +975,16 @@ namespace System.Web {
                                        // MS starts encoding with &# from 160 and stops at 255.
                                        // We don't do that. One reason is the 65308/65310 unicode
                                        // characters that look like '<' and '>'.
-                                       if (c > 159) {
+#if TARGET_JVM
+                                       if (s [i] > 159 && s [i] < 256) {
+#else
+                                       if (s [i] > 159) {
+#endif
                                                output.Append ("&#");
-                                               output.Append (((int) c).ToString (CultureInfo.InvariantCulture));
+                                               output.Append (((int) s [i]).ToString (CultureInfo.InvariantCulture));
                                                output.Append (";");
                                        } else {
-                                               output.Append (c);
+                                               output.Append (s [i]);
                                        }
                                        break;
                                }
@@ -946,23 +1005,60 @@ namespace System.Web {
 #if NET_1_1
                public static string UrlPathEncode (string s)
                {
-                       if (s == null)
-                               return null;
+                       if (s == null || s.Length == 0)
+                               return s;
 
-                       int idx = s.IndexOf ("?");
-                       string s2 = null;
-                       if (idx != -1) {
-                               s2 = s.Substring (0, idx-1);
-                               s2 = UrlEncode (s2) + s.Substring (idx);
-                       } else {
-                               s2 = UrlEncode (s);
+                       MemoryStream result = new MemoryStream ();
+                       int length = s.Length;
+            for (int i = 0; i < length; i++) {
+                               UrlPathEncodeChar (s [i], result);
                        }
-
-                       return s2;
+                       return Encoding.ASCII.GetString (result.ToArray ());
+               }
+               
+               static void UrlPathEncodeChar (char c, Stream result) {
+#if NET_2_0
+                       if (c < 33 || c > 126) {
+#else
+                       if (c > 127) {
+#endif
+                               byte [] bIn = Encoding.UTF8.GetBytes (c.ToString ());
+                               for (int i = 0; i < bIn.Length; i++) {
+                                       result.WriteByte ((byte) '%');
+                                       int idx = ((int) bIn [i]) >> 4;
+                                       result.WriteByte ((byte) hexChars [idx]);
+                                       idx = ((int) bIn [i]) & 0x0F;
+                                       result.WriteByte ((byte) hexChars [idx]);
+                               }
+                       }
+                       else if (c == ' ') {
+                               result.WriteByte ((byte) '%');
+                               result.WriteByte ((byte) '2');
+                               result.WriteByte ((byte) '0');
+                       }
+                       else
+                               result.WriteByte ((byte) c);
                }
 #endif
 
 #if NET_2_0
+               class HttpQSCollection : NameValueCollection {
+                       public override string ToString ()
+                       {
+                               int count = Count;
+                               if (count == 0)
+                                       return "";
+                               StringBuilder sb = new StringBuilder ();
+                               string [] keys = AllKeys;
+                               for (int i = 0; i < count; i++) {
+                                       sb.AppendFormat ("{0}={1}&", keys [i], this [keys [i]]);
+                               }
+                               if (sb.Length > 0)
+                                       sb.Length--;
+                               return sb.ToString ();
+                       }
+               }
+
                public static NameValueCollection ParseQueryString (string query)
                {
                        return ParseQueryString (query, Encoding.UTF8);
@@ -979,7 +1075,7 @@ namespace System.Web {
                        if (query[0] == '?')
                                query = query.Substring (1);
                                
-                       NameValueCollection result = new NameValueCollection ();
+                       NameValueCollection result = new HttpQSCollection ();
                        ParseQueryString (query, encoding, result);
                        return result;
                }                               
@@ -990,35 +1086,45 @@ namespace System.Web {
                        if (query.Length == 0)
                                return;
 
+                       string decoded = HtmlDecode (query);
+                       int decodedLength = decoded.Length;
                        int namePos = 0;
-                       while (namePos <= query.Length) {
+                       bool first = true;
+                       while (namePos <= decodedLength) {
                                int valuePos = -1, valueEnd = -1;
-                               for (int q = namePos; q < query.Length; q++) {
-                                       if (valuePos == -1 && query[q] == '=') {
+                               for (int q = namePos; q < decodedLength; q++) {
+                                       if (valuePos == -1 && decoded [q] == '=') {
                                                valuePos = q + 1;
-                                       } else if (query[q] == '&') {
+                                       } else if (decoded [q] == '&') {
                                                valueEnd = q;
                                                break;
                                        }
                                }
 
+                               if (first) {
+                                       first = false;
+                                       if (decoded [namePos] == '?')
+                                               namePos++;
+                               }
+                               
                                string name, value;
                                if (valuePos == -1) {
                                        name = null;
                                        valuePos = namePos;
                                } else {
-                                       name = UrlDecode (query.Substring (namePos, valuePos - namePos - 1), encoding);
+                                       name = UrlDecode (decoded.Substring (namePos, valuePos - namePos - 1), encoding);
                                }
                                if (valueEnd < 0) {
                                        namePos = -1;
-                                       valueEnd = query.Length;
+                                       valueEnd = decoded.Length;
                                } else {
                                        namePos = valueEnd + 1;
                                }
-                               value = UrlDecode (query.Substring (valuePos, valueEnd - valuePos), encoding);
+                               value = UrlDecode (decoded.Substring (valuePos, valueEnd - valuePos), encoding);
 
                                result.Add (name, value);
-                               if (namePos == -1) break;
+                               if (namePos == -1)
+                                       break;
                        }
                }
                #endregion // Methods