2 // System.Web.VirtualPathUtility.cs
\r
5 // Chris Toshok (toshok@ximian.com)
\r
6 // Gonzalo Paniagua Javier (gonzalo@ximian.com)
\r
10 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
\r
12 // Permission is hereby granted, free of charge, to any person obtaining
\r
13 // a copy of this software and associated documentation files (the
\r
14 // "Software"), to deal in the Software without restriction, including
\r
15 // without limitation the rights to use, copy, modify, merge, publish,
\r
16 // distribute, sublicense, and/or sell copies of the Software, and to
\r
17 // permit persons to whom the Software is furnished to do so, subject to
\r
18 // the following conditions:
\r
20 // The above copyright notice and this permission notice shall be
\r
21 // included in all copies or substantial portions of the Software.
\r
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
\r
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
\r
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
\r
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
\r
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\r
33 using System.Web.Util;
\r
36 namespace System.Web {
\r
41 static class VirtualPathUtility
\r
43 public static string AppendTrailingSlash (string virtualPath)
\r
45 if (virtualPath == null)
\r
48 int length = virtualPath.Length;
\r
49 if (length == 0 || virtualPath [length - 1] == '/')
\r
52 return virtualPath + "/";
\r
55 public static string Combine (string basePath, string relativePath)
\r
57 basePath = Normalize (basePath);
\r
59 if (IsRooted (relativePath))
\r
60 return Normalize (relativePath);
\r
62 if (basePath [basePath.Length - 1] != '/') {
\r
63 if (basePath.Length > 1) {
\r
64 int lastSlash = basePath.LastIndexOf ('/');
\r
66 basePath = basePath.Substring (0, lastSlash + 1);
\r
73 return Normalize (basePath + relativePath);
\r
76 public static string GetDirectory (string virtualPath)
\r
78 virtualPath = Normalize (virtualPath);
\r
80 if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/"
\r
81 virtualPath = ToAbsolute (virtualPath);
\r
84 if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/"
\r
88 int last = virtualPath.LastIndexOf ('/', virtualPath.Length - 2, virtualPath.Length - 2);
\r
90 return virtualPath.Substring (0, last + 1);
\r
95 public static string GetExtension (string virtualPath)
\r
97 if (StrUtils.IsNullOrEmpty (virtualPath))
\r
98 throw new ArgumentNullException ("virtualPath");
\r
100 virtualPath = Canonize (virtualPath);
\r
102 int dot = virtualPath.LastIndexOf ('.');
\r
103 if (dot == -1 || dot == virtualPath.Length - 1 || dot < virtualPath.LastIndexOf ('/'))
\r
104 return String.Empty;
\r
106 return virtualPath.Substring (dot);
\r
109 public static string GetFileName (string virtualPath)
\r
111 virtualPath = Normalize (virtualPath);
\r
113 if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/"
\r
114 virtualPath = ToAbsolute (virtualPath);
\r
117 if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/"
\r
118 return String.Empty;
\r
121 virtualPath = RemoveTrailingSlash (virtualPath);
\r
122 int last = virtualPath.LastIndexOf ('/');
\r
123 return virtualPath.Substring (last + 1);
\r
126 internal static bool IsRooted (string virtualPath)
\r
128 return IsAbsolute (virtualPath) || IsAppRelative (virtualPath);
\r
131 public static bool IsAbsolute (string virtualPath)
\r
133 if (StrUtils.IsNullOrEmpty (virtualPath))
\r
134 throw new ArgumentNullException ("virtualPath");
\r
136 return (virtualPath [0] == '/' || virtualPath [0] == '\\');
\r
139 public static bool IsAppRelative (string virtualPath)
\r
141 if (StrUtils.IsNullOrEmpty (virtualPath))
\r
142 throw new ArgumentNullException ("virtualPath");
\r
144 if (virtualPath.Length == 1 && virtualPath [0] == '~')
\r
147 if (virtualPath [0] == '~' && (virtualPath [1] == '/' || virtualPath [1] == '\\'))
\r
153 // MSDN: If the fromPath and toPath parameters are not rooted; that is,
\r
154 // they do not equal the root operator (the tilde [~]), do not start with a tilde (~),
\r
155 // such as a tilde and a slash mark (~/) or a tilde and a double backslash (~//),
\r
156 // or do not start with a slash mark (/), an ArgumentException exception is thrown.
\r
157 public static string MakeRelative (string fromPath, string toPath)
\r
159 if (fromPath == null || toPath == null)
\r
160 throw new NullReferenceException (); // yeah!
\r
165 toPath = ToAbsoluteInternal (toPath);
\r
166 fromPath = ToAbsoluteInternal (fromPath);
\r
168 if (String.CompareOrdinal (fromPath, toPath) == 0 && fromPath [fromPath.Length - 1] == '/')
\r
171 string [] toPath_parts = toPath.Split ('/');
\r
172 string [] fromPath_parts = fromPath.Split ('/');
\r
174 while (toPath_parts [dest] == fromPath_parts [dest]) {
\r
175 if (toPath_parts.Length == (dest + 1) || fromPath_parts.Length == (dest + 1)) {
\r
180 StringBuilder res = new StringBuilder();
\r
181 for (int i = 1; i < fromPath_parts.Length - dest; i++) {
\r
182 res.Append ("../");
\r
184 for (int i = dest; i < toPath_parts.Length; i++) {
\r
185 res.Append (toPath_parts [i]);
\r
186 if (i < toPath_parts.Length - 1)
\r
189 return res.ToString ();
\r
192 private static string ToAbsoluteInternal (string virtualPath)
\r
194 if (IsAppRelative (virtualPath))
\r
195 return ToAbsolute (virtualPath, HttpRuntime.AppDomainAppVirtualPath);
\r
196 else if (IsAbsolute (virtualPath))
\r
197 return Normalize (virtualPath);
\r
199 throw new ArgumentOutOfRangeException ("Specified argument was out of the range of valid values.");
\r
202 public static string RemoveTrailingSlash (string virtualPath)
\r
204 if (virtualPath == null || virtualPath == "")
\r
207 int last = virtualPath.Length - 1;
\r
208 if (last == 0 || virtualPath [last] != '/')
\r
209 return virtualPath;
\r
211 return virtualPath.Substring (0, last);
\r
214 public static string ToAbsolute (string virtualPath)
\r
216 if(IsAbsolute(virtualPath))
\r
217 return Normalize (virtualPath);
\r
219 string apppath = HttpRuntime.AppDomainAppVirtualPath;
\r
220 if (apppath == null)
\r
221 throw new HttpException ("The path to the application is not known");
\r
223 if (virtualPath.Length == 1 && virtualPath [0] == '~')
\r
226 return ToAbsolute (virtualPath,apppath);
\r
229 // If virtualPath is:
\r
230 // Absolute, the ToAbsolute method returns the virtual path with no changes.
\r
231 // Application relative, the ToAbsolute method adds applicationPath to the beginning of the virtual path.
\r
232 // Not rooted, the ToAbsolute method raises an ArgumentOutOfRangeException exception.
\r
233 public static string ToAbsolute (string virtualPath, string applicationPath)
\r
235 if (StrUtils.IsNullOrEmpty (applicationPath))
\r
236 throw new ArgumentNullException ("applicationPath");
\r
238 if (StrUtils.IsNullOrEmpty (virtualPath))
\r
239 throw new ArgumentNullException ("virtualPath");
\r
241 if (IsAppRelative(virtualPath)) {
\r
242 if (applicationPath [0] != '/')
\r
243 throw new ArgumentException ("appPath is not rooted", "applicationPath");
\r
244 return Normalize ((applicationPath + (virtualPath.Length == 1 ? "/" : virtualPath.Substring (1))));
\r
247 if (virtualPath [0] != '/')
\r
248 throw new ArgumentException (String.Format ("Relative path not allowed: '{0}'", virtualPath));
\r
250 return Normalize (virtualPath);
\r
254 public static string ToAppRelative (string virtualPath)
\r
256 string apppath = HttpRuntime.AppDomainAppVirtualPath;
\r
257 if (apppath == null)
\r
258 throw new HttpException ("The path to the application is not known");
\r
260 return ToAppRelative (virtualPath, apppath);
\r
263 public static string ToAppRelative (string virtualPath, string applicationPath)
\r
265 virtualPath = Normalize (virtualPath);
\r
267 if (IsAppRelative (virtualPath))
\r
268 return virtualPath;
\r
270 if (!IsAbsolute (applicationPath))
\r
271 throw new ArgumentException ("appPath is not absolute", "applicationPath");
\r
273 applicationPath = Normalize (applicationPath);
\r
275 if (applicationPath.Length == 1)
\r
276 return "~" + virtualPath;
\r
278 int appPath_lenght = applicationPath.Length;
\r
279 if (String.CompareOrdinal (virtualPath, applicationPath) == 0)
\r
281 if (String.CompareOrdinal (virtualPath, 0, applicationPath, 0, appPath_lenght) == 0)
\r
282 return "~" + virtualPath.Substring (appPath_lenght);
\r
284 return virtualPath;
\r
287 static char [] path_sep = { '/' };
\r
289 static string Normalize (string path)
\r
291 if (!IsRooted (path))
\r
292 throw new ArgumentException (String.Format ("The relative virtual path '{0}' is not allowed here.", path));
\r
294 if (path.Length == 1) // '/' or '~'
\r
297 path = Canonize (path);
\r
299 if (path.IndexOf ('.') < 0)
\r
302 bool starts_with_tilda = false;
\r
303 bool ends_with_slash = false;
\r
304 string [] apppath_parts= null;
\r
306 if (path [0] == '~') {
\r
307 if (path.Length == 2) // "~/"
\r
309 starts_with_tilda = true;
\r
310 path = path.Substring (1);
\r
312 else if (path.Length == 1) { // "/"
\r
316 if (path [path.Length - 1] == '/')
\r
317 ends_with_slash = true;
\r
319 string [] parts = StrUtils.SplitRemoveEmptyEntries (path, path_sep);
\r
320 int end = parts.Length;
\r
324 for (int i = 0; i < end; i++) {
\r
325 string current = parts [i];
\r
326 if (current == ".")
\r
329 if (current == "..") {
\r
335 if (starts_with_tilda) {
\r
336 if (apppath_parts == null) {
\r
337 string apppath = HttpRuntime.AppDomainAppVirtualPath;
\r
338 apppath_parts = StrUtils.SplitRemoveEmptyEntries (apppath, path_sep);
\r
341 if ((apppath_parts.Length + dest) >= 0)
\r
345 throw new HttpException ("Cannot use a leading .. to exit above the top directory.");
\r
349 parts [dest] = current;
\r
351 apppath_parts [apppath_parts.Length + dest] = current;
\r
356 StringBuilder str = new StringBuilder();
\r
357 if (apppath_parts != null) {
\r
358 starts_with_tilda = false;
\r
359 int count = apppath_parts.Length;
\r
362 for (int i = 0; i < count; i++) {
\r
364 str.Append (apppath_parts [i]);
\r
367 else if (starts_with_tilda) {
\r
371 for (int i = 0; i < dest; i++) {
\r
373 str.Append (parts [i]);
\r
376 if (str.Length > 0) {
\r
377 if (ends_with_slash)
\r
384 return str.ToString ();
\r
387 static string Canonize (string path)
\r
390 for (int i=0; i < path.Length; i++) {
\r
391 if ((path [i] == '\\') || (path [i] == '/' && (i + 1) < path.Length && (path [i + 1] == '/' || path [i + 1] == '\\'))) {
\r
399 StringBuilder sb = new StringBuilder (path.Length);
\r
400 sb.Append (path, 0, index);
\r
402 for (int i = index; i < path.Length; i++) {
\r
403 if (path [i] == '\\' || path [i] == '/') {
\r
405 if (next < path.Length && (path [next] == '\\' || path [next] == '/'))
\r
410 sb.Append (path [i]);
\r
414 return sb.ToString ();
\r