2 // System.Web.VirtualPathUtility.cs
5 // Chris Toshok (toshok@ximian.com)
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
10 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
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:
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
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.
32 using System.Collections.Specialized;
33 using System.Web.Configuration;
34 using System.Web.Util;
36 using Microsoft.Win32;
38 namespace System.Web {
40 public static class VirtualPathUtility
42 static bool monoSettingsVerifyCompatibility;
43 static bool runningOnWindows;
45 static VirtualPathUtility ()
48 runningOnWindows = RuntimeHelpers.RunningOnWindows;
49 var monoSettings = WebConfigurationManager.GetWebApplicationSection ("system.web/monoSettings") as MonoSettingsSection;
50 if (monoSettings != null)
51 monoSettingsVerifyCompatibility = monoSettings.VerificationCompatibility != 1;
57 public static string AppendTrailingSlash (string virtualPath)
59 if (virtualPath == null)
62 int length = virtualPath.Length;
63 if (length == 0 || virtualPath [length - 1] == '/')
66 return virtualPath + "/";
69 public static string Combine (string basePath, string relativePath)
71 basePath = Normalize (basePath);
73 if (IsRooted (relativePath))
74 return Normalize (relativePath);
76 int basePathLen = basePath.Length;
77 if (basePath [basePathLen - 1] != '/') {
78 if (basePathLen > 1) {
79 int lastSlash = basePath.LastIndexOf ('/');
81 basePath = basePath.Substring (0, lastSlash + 1);
87 return Normalize (basePath + relativePath);
90 public static string GetDirectory (string virtualPath)
92 return GetDirectory (virtualPath, true);
95 internal static string GetDirectory (string virtualPath, bool normalize)
98 virtualPath = Normalize (virtualPath);
100 int vpLen = virtualPath.Length;
101 if (IsAppRelative (virtualPath) && vpLen < 3) { // "~" or "~/"
102 virtualPath = ToAbsolute (virtualPath);
103 vpLen = virtualPath.Length;
106 if (vpLen == 1 && virtualPath [0] == '/') // "/"
109 int last = virtualPath.LastIndexOf ('/', vpLen - 2, vpLen - 2);
111 return virtualPath.Substring (0, last + 1);
116 public static string GetExtension (string virtualPath)
118 if (StrUtils.IsNullOrEmpty (virtualPath))
119 throw new ArgumentNullException ("virtualPath");
121 virtualPath = Canonize (virtualPath);
123 int dot = virtualPath.LastIndexOf ('.');
124 if (dot == -1 || dot == virtualPath.Length - 1 || dot < virtualPath.LastIndexOf ('/'))
127 return virtualPath.Substring (dot);
130 public static string GetFileName (string virtualPath)
132 virtualPath = Normalize (virtualPath);
134 if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/"
135 virtualPath = ToAbsolute (virtualPath);
138 if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/"
142 virtualPath = RemoveTrailingSlash (virtualPath);
143 int last = virtualPath.LastIndexOf ('/');
144 return virtualPath.Substring (last + 1);
147 internal static bool IsRooted (string virtualPath)
149 return IsAbsolute (virtualPath) || IsAppRelative (virtualPath);
152 public static bool IsAbsolute (string virtualPath)
154 if (StrUtils.IsNullOrEmpty (virtualPath))
155 throw new ArgumentNullException ("virtualPath");
157 return (virtualPath [0] == '/' || virtualPath [0] == '\\');
160 public static bool IsAppRelative (string virtualPath)
162 if (StrUtils.IsNullOrEmpty (virtualPath))
163 throw new ArgumentNullException ("virtualPath");
165 if (virtualPath.Length == 1 && virtualPath [0] == '~')
168 if (virtualPath [0] == '~' && (virtualPath [1] == '/' || virtualPath [1] == '\\'))
174 // MSDN: If the fromPath and toPath parameters are not rooted; that is,
175 // they do not equal the root operator (the tilde [~]), do not start with a tilde (~),
176 // such as a tilde and a slash mark (~/) or a tilde and a double backslash (~//),
177 // or do not start with a slash mark (/), an ArgumentException exception is thrown.
178 public static string MakeRelative (string fromPath, string toPath)
180 if (fromPath == null || toPath == null)
181 throw new NullReferenceException (); // yeah!
186 toPath = ToAbsoluteInternal (toPath);
187 fromPath = ToAbsoluteInternal (fromPath);
189 if (String.CompareOrdinal (fromPath, toPath) == 0 && fromPath [fromPath.Length - 1] == '/')
192 string [] toPath_parts = toPath.Split ('/');
193 string [] fromPath_parts = fromPath.Split ('/');
195 while (toPath_parts [dest] == fromPath_parts [dest]) {
196 if (toPath_parts.Length == (dest + 1) || fromPath_parts.Length == (dest + 1)) {
201 StringBuilder res = new StringBuilder();
202 for (int i = 1; i < fromPath_parts.Length - dest; i++) {
205 for (int i = dest; i < toPath_parts.Length; i++) {
206 res.Append (toPath_parts [i]);
207 if (i < toPath_parts.Length - 1)
210 return res.ToString ();
213 static string ToAbsoluteInternal (string virtualPath)
215 if (IsAppRelative (virtualPath))
216 return ToAbsolute (virtualPath, HttpRuntime.AppDomainAppVirtualPath);
217 else if (IsAbsolute (virtualPath))
218 return Normalize (virtualPath);
220 throw new ArgumentOutOfRangeException ("Specified argument was out of the range of valid values.");
223 public static string RemoveTrailingSlash (string virtualPath)
225 if (virtualPath == null || virtualPath == "")
228 int last = virtualPath.Length - 1;
229 if (last == 0 || virtualPath [last] != '/')
232 return virtualPath.Substring (0, last);
235 public static string ToAbsolute (string virtualPath)
237 return ToAbsolute (virtualPath, true);
240 internal static string ToAbsolute (string virtualPath, bool normalize)
242 if (IsAbsolute (virtualPath)) {
244 return Normalize (virtualPath);
249 string apppath = HttpRuntime.AppDomainAppVirtualPath;
251 throw new HttpException ("The path to the application is not known");
253 if (virtualPath.Length == 1 && virtualPath [0] == '~')
256 return ToAbsolute (virtualPath,apppath);
259 // If virtualPath is:
260 // Absolute, the ToAbsolute method returns the virtual path with no changes.
261 // Application relative, the ToAbsolute method adds applicationPath to the beginning of the virtual path.
262 // Not rooted, the ToAbsolute method raises an ArgumentOutOfRangeException exception.
263 public static string ToAbsolute (string virtualPath, string applicationPath)
265 return ToAbsolute (virtualPath, applicationPath, true);
268 internal static string ToAbsolute (string virtualPath, string applicationPath, bool normalize)
270 if (StrUtils.IsNullOrEmpty (applicationPath))
271 throw new ArgumentNullException ("applicationPath");
273 if (StrUtils.IsNullOrEmpty (virtualPath))
274 throw new ArgumentNullException ("virtualPath");
276 if (IsAppRelative(virtualPath)) {
277 if (applicationPath [0] != '/')
278 throw new ArgumentException ("appPath is not rooted", "applicationPath");
280 string path = applicationPath + (virtualPath.Length == 1 ? "/" : virtualPath.Substring (1));
282 return Normalize (path);
287 if (virtualPath [0] != '/')
288 throw new ArgumentException (String.Format ("Relative path not allowed: '{0}'", virtualPath));
291 return Normalize (virtualPath);
297 public static string ToAppRelative (string virtualPath)
299 string apppath = HttpRuntime.AppDomainAppVirtualPath;
301 throw new HttpException ("The path to the application is not known");
303 return ToAppRelative (virtualPath, apppath);
306 public static string ToAppRelative (string virtualPath, string applicationPath)
308 virtualPath = Normalize (virtualPath);
309 if (IsAppRelative (virtualPath))
312 if (!IsAbsolute (applicationPath))
313 throw new ArgumentException ("appPath is not absolute", "applicationPath");
315 applicationPath = Normalize (applicationPath);
317 if (applicationPath.Length == 1)
318 return "~" + virtualPath;
320 int appPath_lenght = applicationPath.Length;
321 if (String.CompareOrdinal (virtualPath, applicationPath) == 0)
323 if (String.CompareOrdinal (virtualPath, 0, applicationPath, 0, appPath_lenght) == 0)
324 return "~" + virtualPath.Substring (appPath_lenght);
329 static char [] path_sep = { '/' };
331 internal static string Normalize (string path)
333 if (!IsRooted (path))
334 throw new ArgumentException (String.Format ("The relative virtual path '{0}' is not allowed here.", path));
336 if (path.Length == 1) // '/' or '~'
339 path = Canonize (path);
341 int dotPos = path.IndexOf ('.');
342 while (dotPos >= 0) {
343 if (++dotPos == path.Length)
346 char nextChar = path [dotPos];
348 if ((nextChar == '/') || (nextChar == '.'))
351 dotPos = path.IndexOf ('.', dotPos);
357 bool starts_with_tilda = false;
358 bool ends_with_slash = false;
359 string [] apppath_parts= null;
361 if (path [0] == '~') {
362 if (path.Length == 2) // "~/"
364 starts_with_tilda = true;
365 path = path.Substring (1);
367 else if (path.Length == 1) { // "/"
371 if (path [path.Length - 1] == '/')
372 ends_with_slash = true;
374 string [] parts = StrUtils.SplitRemoveEmptyEntries (path, path_sep);
375 int end = parts.Length;
379 for (int i = 0; i < end; i++) {
380 string current = parts [i];
384 if (current == "..") {
390 if (starts_with_tilda) {
391 if (apppath_parts == null) {
392 string apppath = HttpRuntime.AppDomainAppVirtualPath;
393 apppath_parts = StrUtils.SplitRemoveEmptyEntries (apppath, path_sep);
396 if ((apppath_parts.Length + dest) >= 0)
400 throw new HttpException ("Cannot use a leading .. to exit above the top directory.");
404 parts [dest] = current;
406 apppath_parts [apppath_parts.Length + dest] = current;
411 StringBuilder str = new StringBuilder();
412 if (apppath_parts != null) {
413 starts_with_tilda = false;
414 int count = apppath_parts.Length;
417 for (int i = 0; i < count; i++) {
419 str.Append (apppath_parts [i]);
422 else if (starts_with_tilda) {
426 for (int i = 0; i < dest; i++) {
428 str.Append (parts [i]);
431 if (str.Length > 0) {
439 return str.ToString ();
442 internal static string Canonize (string path)
445 for (int i=0; i < path.Length; i++) {
446 if ((path [i] == '\\') || (path [i] == '/' && (i + 1) < path.Length && (path [i + 1] == '/' || path [i + 1] == '\\'))) {
454 StringBuilder sb = new StringBuilder (path.Length);
455 sb.Append (path, 0, index);
457 for (int i = index; i < path.Length; i++) {
458 if (path [i] == '\\' || path [i] == '/') {
460 if (next < path.Length && (path [next] == '\\' || path [next] == '/'))
465 sb.Append (path [i]);
469 return sb.ToString ();
472 // See: http://support.microsoft.com/kb/932552
473 // See: https://bugzilla.novell.com/show_bug.cgi?id=509163
474 static readonly char[] invalidVirtualPathChars = {':', '*'};
475 static readonly string aspNetVerificationKey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\ASP.NET";
476 internal static bool IsValidVirtualPath (string path)
481 bool doValidate = true;
482 if (runningOnWindows) {
484 object v = Registry.GetValue (aspNetVerificationKey, "VerificationCompatibility", null);
485 if (v != null && v is int)
486 doValidate = (int)v != 1;
493 doValidate = monoSettingsVerifyCompatibility;
498 return path.IndexOfAny (invalidVirtualPathChars) == -1;