X-Git-Url: http://wien.tomnetworks.com/gitweb/?a=blobdiff_plain;f=mcs%2Fclass%2FSystem.Web%2FSystem.Web%2FVirtualPathUtility.cs;h=12bcf8a7e699e9e80666a8cde443b724ff0223d7;hb=e2a31b612868ee44b269f517ee37d12c06ed9151;hp=f31a9600903a743db833d244b64ea766c12ed3eb;hpb=987f8c63e214937c50dcb308149f7558a2cbba41;p=mono.git diff --git a/mcs/class/System.Web/System.Web/VirtualPathUtility.cs b/mcs/class/System.Web/System.Web/VirtualPathUtility.cs index f31a9600903..12bcf8a7e69 100644 --- a/mcs/class/System.Web/System.Web/VirtualPathUtility.cs +++ b/mcs/class/System.Web/System.Web/VirtualPathUtility.cs @@ -29,14 +29,31 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // -#if NET_2_0 - +using System.Collections.Specialized; +using System.Web.Configuration; using System.Web.Util; +using System.Text; +using Microsoft.Win32; namespace System.Web { public static class VirtualPathUtility { + static bool monoSettingsVerifyCompatibility; + static bool runningOnWindows; + + static VirtualPathUtility () + { + try { + runningOnWindows = RuntimeHelpers.RunningOnWindows; + var monoSettings = WebConfigurationManager.GetWebApplicationSection ("system.web/monoSettings") as MonoSettingsSection; + if (monoSettings != null) + monoSettingsVerifyCompatibility = monoSettings.VerificationCompatibility != 1; + } catch { + // ignore + } + } + public static string AppendTrailingSlash (string virtualPath) { if (virtualPath == null) @@ -51,71 +68,113 @@ namespace System.Web { public static string Combine (string basePath, string relativePath) { - if (basePath == null || basePath == "") - throw new ArgumentNullException ("basePath"); - - if (relativePath == null || relativePath == "") - throw new ArgumentNullException ("relativePath"); - - if (basePath [0] != '/') - throw new ArgumentException ("basePath is not an absolute path", "basePath"); - - if (relativePath [0] != '/') - return "/" + relativePath; - - return UrlUtils.Combine (basePath, relativePath); + basePath = Normalize (basePath); + + if (IsRooted (relativePath)) + return Normalize (relativePath); + + int basePathLen = basePath.Length; + if (basePath [basePathLen - 1] != '/') { + if (basePathLen > 1) { + int lastSlash = basePath.LastIndexOf ('/'); + if (lastSlash >= 0) + basePath = basePath.Substring (0, lastSlash + 1); + } else { // "~" only + basePath += "/"; + } + } + + return Normalize (basePath + relativePath); } public static string GetDirectory (string virtualPath) { - if (virtualPath == null || virtualPath == "") // Yes, "" throws an ArgumentNullException - throw new ArgumentNullException ("virtualPath"); + return GetDirectory (virtualPath, true); + } - if (virtualPath [0] != '/') - throw new ArgumentException ("The virtual path is not rooted", "virtualPath"); + internal static string GetDirectory (string virtualPath, bool normalize) + { + if (normalize) + virtualPath = Normalize (virtualPath); + + int vpLen = virtualPath.Length; + if (IsAppRelative (virtualPath) && vpLen < 3) { // "~" or "~/" + virtualPath = ToAbsolute (virtualPath); + vpLen = virtualPath.Length; + } + + if (vpLen == 1 && virtualPath [0] == '/') // "/" + return null; - string result = UrlUtils.GetDirectory (virtualPath); - return AppendTrailingSlash (result); + int last = virtualPath.LastIndexOf ('/', vpLen - 2, vpLen - 2); + if (last > 0) + return virtualPath.Substring (0, last + 1); + else + return "/"; } public static string GetExtension (string virtualPath) { - string filename = GetFileName (virtualPath); - int dot = filename.LastIndexOf ('.'); - if (dot == -1 || dot == filename.Length + 1) - return ""; + if (StrUtils.IsNullOrEmpty (virtualPath)) + throw new ArgumentNullException ("virtualPath"); + + virtualPath = Canonize (virtualPath); + + int dot = virtualPath.LastIndexOf ('.'); + if (dot == -1 || dot == virtualPath.Length - 1 || dot < virtualPath.LastIndexOf ('/')) + return String.Empty; - return filename.Substring (dot); + return virtualPath.Substring (dot); } public static string GetFileName (string virtualPath) { - if (virtualPath == null || virtualPath == "") // Yes, "" throws an ArgumentNullException - throw new ArgumentNullException ("virtualPath"); + virtualPath = Normalize (virtualPath); - return UrlUtils.GetFile (RemoveTrailingSlash (virtualPath)); + if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/" + virtualPath = ToAbsolute (virtualPath); + } + + if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/" + return String.Empty; + } + + virtualPath = RemoveTrailingSlash (virtualPath); + int last = virtualPath.LastIndexOf ('/'); + return virtualPath.Substring (last + 1); + } + + internal static bool IsRooted (string virtualPath) + { + return IsAbsolute (virtualPath) || IsAppRelative (virtualPath); } public static bool IsAbsolute (string virtualPath) { - if (virtualPath == "" || virtualPath == null) + if (StrUtils.IsNullOrEmpty (virtualPath)) throw new ArgumentNullException ("virtualPath"); - return (virtualPath [0] == '/'); + return (virtualPath [0] == '/' || virtualPath [0] == '\\'); } public static bool IsAppRelative (string virtualPath) { - if (virtualPath == null || virtualPath == "") + if (StrUtils.IsNullOrEmpty (virtualPath)) throw new ArgumentNullException ("virtualPath"); - string vpath = HttpRuntime.AppDomainAppVirtualPath; - if (vpath == null) - return false; + if (virtualPath.Length == 1 && virtualPath [0] == '~') + return true; - return virtualPath.StartsWith (AppendTrailingSlash (vpath)); + if (virtualPath [0] == '~' && (virtualPath [1] == '/' || virtualPath [1] == '\\')) + return true; + + return false; } + // MSDN: If the fromPath and toPath parameters are not rooted; that is, + // they do not equal the root operator (the tilde [~]), do not start with a tilde (~), + // such as a tilde and a slash mark (~/) or a tilde and a double backslash (~//), + // or do not start with a slash mark (/), an ArgumentException exception is thrown. public static string MakeRelative (string fromPath, string toPath) { if (fromPath == null || toPath == null) @@ -124,14 +183,41 @@ namespace System.Web { if (toPath == "") return toPath; - if (toPath [0] != '/') - throw new ArgumentOutOfRangeException (); // This is what MS does. + toPath = ToAbsoluteInternal (toPath); + fromPath = ToAbsoluteInternal (fromPath); + + if (String.CompareOrdinal (fromPath, toPath) == 0 && fromPath [fromPath.Length - 1] == '/') + return "./"; + + string [] toPath_parts = toPath.Split ('/'); + string [] fromPath_parts = fromPath.Split ('/'); + int dest = 1; + while (toPath_parts [dest] == fromPath_parts [dest]) { + if (toPath_parts.Length == (dest + 1) || fromPath_parts.Length == (dest + 1)) { + break; + } + dest++; + } + StringBuilder res = new StringBuilder(); + for (int i = 1; i < fromPath_parts.Length - dest; i++) { + res.Append ("../"); + } + for (int i = dest; i < toPath_parts.Length; i++) { + res.Append (toPath_parts [i]); + if (i < toPath_parts.Length - 1) + res.Append ('/'); + } + return res.ToString (); + } - if (fromPath.Length > 0 && fromPath [0] != '/') - throw new ArgumentOutOfRangeException (); // This is what MS does. + static string ToAbsoluteInternal (string virtualPath) + { + if (IsAppRelative (virtualPath)) + return ToAbsolute (virtualPath, HttpRuntime.AppDomainAppVirtualPath); + else if (IsAbsolute (virtualPath)) + return Normalize (virtualPath); - Uri from = new Uri ("http://nothing" + fromPath); - return from.MakeRelativeUri (new Uri ("http://nothing" + toPath)).AbsolutePath; + throw new ArgumentOutOfRangeException ("Specified argument was out of the range of valid values."); } public static string RemoveTrailingSlash (string virtualPath) @@ -148,28 +234,63 @@ namespace System.Web { public static string ToAbsolute (string virtualPath) { + return ToAbsolute (virtualPath, true); + } + + internal static string ToAbsolute (string virtualPath, bool normalize) + { + if (IsAbsolute (virtualPath)) { + if (normalize) + return Normalize (virtualPath); + else + return virtualPath; + } + string apppath = HttpRuntime.AppDomainAppVirtualPath; if (apppath == null) throw new HttpException ("The path to the application is not known"); - return ToAbsolute (virtualPath,apppath); + if (virtualPath.Length == 1 && virtualPath [0] == '~') + return apppath; + + return ToAbsolute (virtualPath, apppath, normalize); } + // If virtualPath is: + // Absolute, the ToAbsolute method returns the virtual path with no changes. + // Application relative, the ToAbsolute method adds applicationPath to the beginning of the virtual path. + // Not rooted, the ToAbsolute method raises an ArgumentOutOfRangeException exception. public static string ToAbsolute (string virtualPath, string applicationPath) { - if (applicationPath == null || applicationPath == "") + return ToAbsolute (virtualPath, applicationPath, true); + } + + internal static string ToAbsolute (string virtualPath, string applicationPath, bool normalize) + { + if (StrUtils.IsNullOrEmpty (applicationPath)) throw new ArgumentNullException ("applicationPath"); - if (virtualPath == null || virtualPath == "") + if (StrUtils.IsNullOrEmpty (virtualPath)) throw new ArgumentNullException ("virtualPath"); - if (virtualPath.StartsWith ("..")) - throw new ArgumentException (String.Format ("Relative path not allowed: '{0}'", virtualPath)); + if (IsAppRelative(virtualPath)) { + if (applicationPath [0] != '/') + throw new ArgumentException ("appPath is not rooted", "applicationPath"); + + string path = applicationPath + (virtualPath.Length == 1 ? "/" : virtualPath.Substring (1)); + if (normalize) + return Normalize (path); + else + return path; + } - if (applicationPath [0] != '/') - throw new ArgumentOutOfRangeException ("appPath is not rooted", "applicationPath"); + if (virtualPath [0] != '/') + throw new ArgumentException (String.Format ("Relative path not allowed: '{0}'", virtualPath)); - return UrlUtils.Combine (applicationPath, virtualPath); + if (normalize) + return Normalize (virtualPath); + else + return virtualPath; } @@ -179,27 +300,202 @@ namespace System.Web { if (apppath == null) throw new HttpException ("The path to the application is not known"); - return ToAppRelative (apppath, virtualPath); + return ToAppRelative (virtualPath, apppath); } public static string ToAppRelative (string virtualPath, string applicationPath) { - if (applicationPath == null || applicationPath == "") - throw new ArgumentNullException ("applicationPath"); + virtualPath = Normalize (virtualPath); + if (IsAppRelative (virtualPath)) + return virtualPath; - if (virtualPath == null || virtualPath == "") - throw new ArgumentNullException ("virtualPath"); + if (!IsAbsolute (applicationPath)) + throw new ArgumentException ("appPath is not absolute", "applicationPath"); + + applicationPath = Normalize (applicationPath); - if (virtualPath.StartsWith ("..")) - throw new ArgumentException (String.Format ("Relative path not allowed: '{0}'", virtualPath)); + if (applicationPath.Length == 1) + return "~" + virtualPath; - if (applicationPath [0] != '/') - throw new ArgumentOutOfRangeException ("appPath is not rooted", "applicationPath"); + int appPath_lenght = applicationPath.Length; + if (String.CompareOrdinal (virtualPath, applicationPath) == 0) + return "~/"; + if (String.CompareOrdinal (virtualPath, 0, applicationPath, 0, appPath_lenght) == 0) + return "~" + virtualPath.Substring (appPath_lenght); - return MakeRelative (applicationPath, virtualPath); + return virtualPath; } - } -} -#endif + static char [] path_sep = { '/' }; + internal static string Normalize (string path) + { + if (!IsRooted (path)) + throw new ArgumentException (String.Format ("The relative virtual path '{0}' is not allowed here.", path)); + + if (path.Length == 1) // '/' or '~' + return path; + + path = Canonize (path); + + int dotPos = path.IndexOf ('.'); + while (dotPos >= 0) { + if (++dotPos == path.Length) + break; + + char nextChar = path [dotPos]; + + if ((nextChar == '/') || (nextChar == '.')) + break; + + dotPos = path.IndexOf ('.', dotPos); + } + + if (dotPos < 0) + return path; + + bool starts_with_tilda = false; + bool ends_with_slash = false; + string [] apppath_parts= null; + + if (path [0] == '~') { + if (path.Length == 2) // "~/" + return "~/"; + starts_with_tilda = true; + path = path.Substring (1); + } + else if (path.Length == 1) { // "/" + return "/"; + } + + if (path [path.Length - 1] == '/') + ends_with_slash = true; + + string [] parts = StrUtils.SplitRemoveEmptyEntries (path, path_sep); + int end = parts.Length; + + int dest = 0; + + for (int i = 0; i < end; i++) { + string current = parts [i]; + if (current == ".") + continue; + + if (current == "..") { + dest--; + + if(dest >= 0) + continue; + + if (starts_with_tilda) { + if (apppath_parts == null) { + string apppath = HttpRuntime.AppDomainAppVirtualPath; + apppath_parts = StrUtils.SplitRemoveEmptyEntries (apppath, path_sep); + } + + if ((apppath_parts.Length + dest) >= 0) + continue; + } + + throw new HttpException ("Cannot use a leading .. to exit above the top directory."); + } + + if (dest >= 0) + parts [dest] = current; + else + apppath_parts [apppath_parts.Length + dest] = current; + + dest++; + } + + StringBuilder str = new StringBuilder(); + if (apppath_parts != null) { + starts_with_tilda = false; + int count = apppath_parts.Length; + if (dest < 0) + count += dest; + for (int i = 0; i < count; i++) { + str.Append ('/'); + str.Append (apppath_parts [i]); + } + } + else if (starts_with_tilda) { + str.Append ('~'); + } + + for (int i = 0; i < dest; i++) { + str.Append ('/'); + str.Append (parts [i]); + } + + if (str.Length > 0) { + if (ends_with_slash) + str.Append ('/'); + } + else { + return "/"; + } + + return str.ToString (); + } + + internal static string Canonize (string path) + { + int index = -1; + for (int i=0; i < path.Length; i++) { + if ((path [i] == '\\') || (path [i] == '/' && (i + 1) < path.Length && (path [i + 1] == '/' || path [i + 1] == '\\'))) { + index = i; + break; + } + } + if (index < 0) + return path; + + StringBuilder sb = new StringBuilder (path.Length); + sb.Append (path, 0, index); + + for (int i = index; i < path.Length; i++) { + if (path [i] == '\\' || path [i] == '/') { + int next = i + 1; + if (next < path.Length && (path [next] == '\\' || path [next] == '/')) + continue; + sb.Append ('/'); + } + else { + sb.Append (path [i]); + } + } + + return sb.ToString (); + } + + // See: http://support.microsoft.com/kb/932552 + // See: https://bugzilla.novell.com/show_bug.cgi?id=509163 + static readonly char[] invalidVirtualPathChars = {':', '*'}; + static readonly string aspNetVerificationKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\ASP.NET"; + internal static bool IsValidVirtualPath (string path) + { + if (path == null) + return false; + + bool doValidate = true; + if (runningOnWindows) { + try { + object v = Registry.GetValue (aspNetVerificationKey, "VerificationCompatibility", null); + if (v != null && v is int) + doValidate = (int)v != 1; + } catch { + // ignore + } + } + + if (doValidate) + doValidate = monoSettingsVerifyCompatibility; + + if (!doValidate) + return true; + + return path.IndexOfAny (invalidVirtualPathChars) == -1; + } + } +}