2008-11-18 Marek Habersack <mhabersack@novell.com>
[mono.git] / mcs / class / System.Web / System.Web.Util / UrlUtils.cs
index 955c8d9a17bf8812241c92e9fffbd00cc7ba48ed..b55e245c7c5c9c1479ab933f532e763dcd8b9e9f 100644 (file)
@@ -1,4 +1,13 @@
+//
+// System.Web.UrlUtils.cs 
+//
+// Authors:
+//     Gonzalo Paniagua (gonzalo@ximian.com)
+//      Jackson Harper   (jackson@ximian.com)
+//
 
+//
+// Copyright (C) 2005 Novell, Inc (http://www.novell.com)
 //
 // Permission is hereby granted, free of charge, to any person obtaining
 // a copy of this software and associated documentation files (the
 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
-/**
- * Namespace: System.Web.UI.Util
- * Class:     UrlUtils
- * 
- * Author:  Gaurav Vaish
- * Maintainer: gvaish@iitk.ac.in
- * Status:  ??%
- * 
- * (C) Gaurav Vaish (2001)
- */
-
-using System;
-using System.Collections;
-using System.Text;
+
 using System.Web.SessionState;
+using System.Text;
+namespace System.Web.Util {
+       
+       internal class UrlUtils {
 
-namespace System.Web.Util
-{
-       internal class UrlUtils
-       {
-               /*
-                * I could not find these functions in the class System.Uri
-                * Besides, an instance of Uri will not be formed until and unless the address is of
-                * the form protocol://[user:pass]host[:port]/[fullpath]
-                * ie, a protocol, and that too without any blanks before,
-                * is a must which may not be the case here.
-                * Important: Escaped URL is assumed here. nothing like .aspx?path=/something
-                * It should be .aspx?path=%2Fsomething
-                */
-               public static string GetProtocol(string url)
+               // appRoot + SessionID + vpath
+               internal static string InsertSessionId (string id, string path)
                {
-                       //Taking code from Java Class java.net.URL
-                       if(url!=null)
-                       {
-                               if(url.Length>0)
-                               {
-                                       
-                                       int i, start = 0, limit;
-                                       limit = url.Length;
-                                       char c;
-                                       bool aRef = false;
-                                       while( (limit > 0) && (url[limit-1] <= ' '))
-                                       {
-                                               limit --;
-                                       }
-                                       while( (start < limit) && (url[start] <= ' '))
-                                       {
-                                               start++;
-                                       }
-                                       if(RegionMatches(true, url, start, "url:", 0, 4))
-                                       {
-                                               start += 4;
-                                       }
-                                       if(start < url.Length && url[start]=='#')
-                                       {
-                                               aRef = true;
-                                       }
-                                       for(i = start; !aRef && (i < limit) && ((c=url[i]) != '/'); i++)
-                                       {
-                                               if(c==':')
-                                               {
-                                                       return url.Substring(start, i - start);
-                                               }
-                                       }
-                               }
-                       }
-                       return String.Empty;
-               }
-               
-               public static bool IsRelativeUrl(string url)
-               {
-                       if (url.IndexOf(':') == -1)
-                               return !IsRooted(url);
+                       string dir = GetDirectory (path);
+                       if (!dir.EndsWith ("/"))
+                               dir += "/";
 
-                       return false;
+                       string appvpath = HttpRuntime.AppDomainAppVirtualPath;
+                       if (!appvpath.EndsWith ("/"))
+                               appvpath += "/";
+
+                       if (path.StartsWith (appvpath))
+                               path = path.Substring (appvpath.Length);
+
+                       if (path [0] == '/')
+                               path = path.Length > 1 ? path.Substring (1) : "";
+
+                       return Canonic (appvpath + "(" + id + ")/" + path);
                }
 
-               public static bool IsRootUrl(string url)
+               internal static string GetSessionId (string path)
                {
-                       if(url!=null)
-                       {
-                               if(url.Length>0)
-                               {
-                                       return IsValidProtocol(GetProtocol(url).ToLower());
-                               }
-                       }
-                       return true;
+                       if (path == null)
+                               return null;
+
+                       string appvpath = HttpRuntime.AppDomainAppVirtualPath;
+                       int appvpathlen = appvpath.Length;
+
+                       if (path.Length <= appvpathlen)
+                               return null;
+
+                       path = path.Substring (appvpathlen);
+                       
+                       int len = path.Length;
+                       if (len == 0 || path [0] != '/') {
+                               path = '/' + path;
+                               len++;
+                       }                       
+
+                       if ((len < SessionId.IdLength + 3) || (path [1] != '(') ||
+                           (path [SessionId.IdLength + 2] != ')'))
+                               return null;
+
+                       return path.Substring (2, SessionId.IdLength);
                }
-               
-               public static bool IsRooted(string path)
+
+               internal static bool HasSessionId (string path)
                {
-                       if(path!=null && path.Length > 0)
-                       {
-                               return (path[0]=='/' || path[0]=='\\');
-                       }
-                       return true;
+                       if (path == null || path.Length < 5)
+                               return false;
+
+                       return (StrUtils.StartsWith (path, "/(") && path.IndexOf (")/") > 2);
                }
-               
-               public static void FailIfPhysicalPath(string path)
+
+               internal static string RemoveSessionId (string base_path, string file_path)
                {
-                       if(path!= null && path.Length > 1)
-                       {
-                               if(path[1]==':' || path.StartsWith(@"\\"))
-                                       throw new HttpException(HttpRuntime.FormatResourceString("Physical_path_not_allowed", path));
+                       // Caller did a GetSessionId first
+                       int idx = base_path.IndexOf ("/(");
+                       string dir = base_path.Substring (0, idx + 1);
+                       if (!dir.EndsWith ("/"))
+                               dir += "/";
+
+                       idx = base_path.IndexOf (")/");
+                       if (idx != -1 && base_path.Length > idx + 2) {
+                               string dir2 = base_path.Substring (idx + 2);
+                               if (!dir2.EndsWith ("/"))
+                                       dir2 += "/";
+
+                               dir += dir2;
                        }
+
+                       return Canonic (dir + GetFile (file_path));
                }
 
                public static string Combine (string basePath, string relPath)
@@ -135,10 +118,9 @@ namespace System.Web.Util
                        if (rlength == 0)
                                return "";
 
-                       FailIfPhysicalPath (relPath);
                        relPath = relPath.Replace ("\\", "/");
                        if (IsRooted (relPath))
-                               return Reduce (relPath);
+                               return Canonic (relPath);
 
                        char first = relPath [0];
                        if (rlength < 3 || first == '~' || first == '/' || first == '\\') {
@@ -154,241 +136,187 @@ namespace System.Web.Util
                                                slash = "/";
                                        }
 
-                                       return Reduce (HttpRuntime.AppDomainAppVirtualPath + slash + relPath);
+                                       string appvpath = HttpRuntime.AppDomainAppVirtualPath;
+                                       if (appvpath.EndsWith ("/"))
+                                               slash = "";
+
+                                       return Canonic (appvpath + slash + relPath);
                                }
 
-                               return Reduce (basePath + slash + relPath);
+                               return Canonic (basePath + slash + relPath);
                        }
 
-                       if (basePath == null || basePath == "")
+                       if (basePath == null || basePath.Length == 0 || basePath [0] == '~')
                                basePath = HttpRuntime.AppDomainAppVirtualPath;
 
                        if (basePath.Length <= 1)
                                basePath = String.Empty;
 
-                       return Reduce (basePath + "/" + relPath);
+                       return Canonic (basePath + "/" + relPath);
                }
 
-               public static bool IsValidProtocol(string protocol)
-               {
-                       if(protocol.Length < 1)
-                               return false;
-                       char c = protocol[0];
-                       if(!Char.IsLetter(c))
-                       {
-                               return false;
-                       }
-                       for(int i=1; i < protocol.Length; i++)
-                       {
-                               c = protocol[i];
-                               if(!Char.IsLetterOrDigit(c) && c!='.' && c!='+' && c!='-')
-                               {
-                                       return false;
-                               }
-                       }
-                       return true;
-               }
+               static char [] path_sep = {'\\', '/'};
                
-               /*
-                * MakeRelative("http://www.foo.com/bar1/bar2/file","http://www.foo.com/bar1")
-                * will return "bar2/file"
-                * while MakeRelative("http://www.foo.com/bar1/...","http://www.anotherfoo.com")
-                * return 'null' and so does the call
-                * MakeRelative("http://www.foo.com/bar1/bar2","http://www.foo.com/bar")
-                */
-               public static string MakeRelative(string fullUrl, string relativeTo)
-               {
-                       if (fullUrl == relativeTo)
-                               return String.Empty;
-
-                       if (fullUrl.IndexOf (relativeTo) != 0)
-                               return null;
-
-                       string leftOver = fullUrl.Substring (relativeTo.Length);
-                       if (leftOver.Length > 0 && leftOver [0] == '/')
-                               leftOver = leftOver.Substring (1);
-
-                       leftOver = Reduce (leftOver);
-                       if (leftOver.Length > 0 && leftOver [0] == '/')
-                               leftOver = leftOver.Substring (1);
-
-                       return leftOver;
-               }
-
-               /*
-                * Check JavaDocs for java.lang.String#RegionMatches(bool, int, String, int, int)
-                * Could not find anything similar in the System.String class
-                */
-               public static bool RegionMatches(bool ignoreCase, string source, int start, string match, int offset, int len)
-               {
-                       if(source!=null || match!=null)
-                       {
-                               if(source.Length>0 && match.Length>0)
-                               {
-                                       char[] ta = source.ToCharArray();
-                                       char[] pa = match.ToCharArray();
-                                       if((offset < 0) || (start < 0) || (start > (source.Length - len)) || (offset > (match.Length - len)))
-                                       {
-                                               return false;
-                                       }
-                                       while(len-- > 0)
-                                       {
-                                               char c1 = ta[start++];
-                                               char c2 = pa[offset++];
-                                               if(c1==c2)
-                                                       continue;
-                                               if(ignoreCase)
-                                               {
-                                                       if(Char.ToUpper(c1)==Char.ToUpper(c2))
-                                                               continue;
-                                                       // Check for Gregorian Calendar where the above may not hold good
-                                                       if(Char.ToLower(c1)==Char.ToLower(c2))
-                                                               continue;
-                                               }
-                                               return false;
-                                       }
-                                       return true;
-                               }
-                       }
-                       return false;
-               }
-
-               public static string Reduce (string path)
+               internal static string Canonic (string path)
                {
-                       path = path.Replace ('\\','/');
-
-                       string [] parts = path.Split ('/');
-                       ArrayList result = new ArrayList ();
-                       
+                       bool isRooted = IsRooted(path);
+                       bool endsWithSlash = path.EndsWith("/");
+                       string [] parts = path.Split (path_sep);
                        int end = parts.Length;
+                       
+                       int dest = 0;
+                       
                        for (int i = 0; i < end; i++) {
                                string current = parts [i];
+
+                               if (current.Length == 0)
+                                       continue;
+
                                if (current == "." )
                                        continue;
 
                                if (current == "..") {
-                                       if (result.Count == 0) {
-                                               if (i == 1) // see bug 52599
-                                                       continue;
-
-                                               throw new HttpException ("Invalid path.");
-                                       }
-
-                                       result.RemoveAt (result.Count - 1);
+                                       dest --;
                                        continue;
                                }
+                               if (dest < 0)
+                                       if (!isRooted)
+                                               throw new HttpException ("Invalid path.");
+                                       else
+                                               dest = 0;
 
-                               result.Add (current);
+                               parts [dest++] = current;
                        }
+                       if (dest < 0)
+                               throw new HttpException ("Invalid path.");
 
-                       if (result.Count == 0)
+                       if (dest == 0)
                                return "/";
 
-                       return String.Join ("/", (string []) result.ToArray (typeof (string)));
+                       string str = String.Join ("/", parts, 0, dest);
+#if NET_2_0
+                       str = RemoveDoubleSlashes (str);
+#endif
+                       if (isRooted)
+                               str = "/" + str;
+                       if (endsWithSlash)
+                               str = str + "/";
+
+                       return str;
                }
                
-               public static string GetDirectory(string url)
+               internal static string GetDirectory (string url)
                {
-                       if(url==null)
-                       {
-                               return null;
-                       }
-                       if(url.Length==0)
-                       {
-                               return String.Empty;
-                       }
                        url = url.Replace('\\','/');
-
-                       string baseDir = "";
                        int last = url.LastIndexOf ('/');
-                       if (last > 0)
-                               baseDir = url.Substring(0, url.LastIndexOf('/'));
 
-                       if(baseDir.Length==0)
-                       {
-                               baseDir = "/";
+                       if (last > 0) {
+                               if (last < url.Length)
+                                       last++;
+#if NET_2_0
+                               return RemoveDoubleSlashes (url.Substring (0, last));
+#else
+                               return url.Substring (0, last);
+#endif
                        }
-                       return baseDir;
+
+                       return "/";
                }
 
-               static string GetFile (string url)
+#if NET_2_0
+               internal static string RemoveDoubleSlashes (string input)
                {
-                       if (url == null)
-                               return null;
+                       // MS VirtualPathUtility removes duplicate '/'
 
-                       if (url.Length == 0)
-                               return String.Empty;
+                       int index = -1;
+                       for (int i = 1; i < input.Length; i++)
+                               if (input [i] == '/' && input [i - 1] == '/') {
+                                       index = i - 1;
+                                       break;
+                               }
 
-                       url = url.Replace ('\\', '/');
+                       if (index == -1) // common case optimization
+                               return input;
 
-                       int last = url.LastIndexOf ('/') + 1;
-                       if (last != 0) {
-                               url = url.Substring (last);
+                       StringBuilder sb = new StringBuilder (input.Length);
+                       sb.Append (input, 0, index);
+
+                       for (int i = index; i < input.Length; i++) {
+                               if (input [i] == '/') {
+                                       int next = i + 1;
+                                       if (next < input.Length && input [next] == '/')
+                                               continue;
+                                       sb.Append ('/');
+                               }
+                               else {
+                                       sb.Append (input [i]);
+                               }
                        }
 
-                       return url;
+                       return sb.ToString ();
                }
+#endif
 
-               public static string InsertSessionId (string id, string path)
+               internal static string GetFile (string url)
                {
-                       string dir = GetDirectory (path);
-                       if (!dir.EndsWith ("/"))
-                               dir += "/";
+                       url = url.Replace('\\','/');
+                       int last = url.LastIndexOf ('/');
+                       if (last >= 0) {
+                               if (url.Length == 1) // Empty file name instead of ArgumentOutOfRange
+                                       return "";
+                               return url.Substring (last+1);
+                       }
 
-                       return Reduce (dir + "(" + id + ")/" + GetFile (path));
+                       throw new ArgumentException (String.Format ("GetFile: `{0}' does not contain a /", url));
                }
-
-               public static string GetSessionId (string path)
+               
+               internal static bool IsRooted (string path)
                {
-                       int len = path.Length;
-                       if ((len < SessionId.IdLength + 2) || (path [len - 1] != ')') ||
-                           (path [len - SessionId.IdLength - 2] != '('))
-                               return null;
-
-                       return path.Substring (len - SessionId.IdLength - 1, SessionId.IdLength);
-               }
+                       if (path == null || path.Length == 0)
+                               return true;
 
-               public static string RemoveSessionId (string base_path, string file_path)
-               {
-                       int len = base_path.Length;
-                       string dir = base_path.Substring (0, len - SessionId.IdLength - 2);
-                       if (!dir.EndsWith ("/"))
-                               dir += "/";
+                       char c = path [0];
+                       if (c == '/' || c == '\\')
+                               return true;
 
-                       return Reduce (dir + GetFile (file_path));
-               }
-               
-               public static string ResolveVirtualPathFromAppAbsolute (string path)
-               {
-                       if (path [0] != '~') return path;
-                               
-                       if (path.Length == 1)
-                               return HttpRuntime.AppDomainAppVirtualPath;
-                       
-                       if (path [1] == '/' || path [1] == '\\') {
-                               string appPath = HttpRuntime.AppDomainAppVirtualPath;
-                               if (appPath.Length > 1)
-                                       return appPath + "/" + path.Substring (2);
-                               return "/" + path.Substring (2);
-                       }
-                       return path;    
+                       return false;
                }
-               
-               public static string ResolvePhysicalPathFromAppAbsolute (string path)
+
+               internal static bool IsRelativeUrl (string path)
                {
-                       if (path [0] != '~') return path;
-                               
-                       if (path.Length == 1)
-                               return HttpRuntime.AppDomainAppPath;
-                       
-                       if (path [1] == '/' || path [1] == '\\') {
-                               string appPath = HttpRuntime.AppDomainAppPath;
-                               if (appPath.Length > 1)
-                                       return appPath + "/" + path.Substring (2);
-                               return "/" + path.Substring (2);
-                       }
-                       return path;    
+                       return (path [0] != '/' && path.IndexOf (':') == -1);
                }
+
+                public static string ResolveVirtualPathFromAppAbsolute (string path)
+                {
+                        if (path [0] != '~') return path;
+                                
+                        if (path.Length == 1)
+                                return HttpRuntime.AppDomainAppVirtualPath;
+
+                        if (path [1] == '/' || path [1] == '\\') {
+                                string appPath = HttpRuntime.AppDomainAppVirtualPath;
+                                if (appPath.Length > 1) 
+                                        return appPath + "/" + path.Substring (2);
+                                return "/" + path.Substring (2);
+                        }
+                        return path;    
+                }
+
+                public static string ResolvePhysicalPathFromAppAbsolute (string path) 
+                {
+                        if (path [0] != '~') return path;
+
+                        if (path.Length == 1)
+                                return HttpRuntime.AppDomainAppPath;
+
+                        if (path [1] == '/' || path [1] == '\\') {
+                                string appPath = HttpRuntime.AppDomainAppPath;
+                                if (appPath.Length > 1)
+                                        return appPath + "/" + path.Substring (2);
+                                return "/" + path.Substring (2);
+                        }
+                        return path;
+                }
        }
 }
-