2007-03-18 Igor Zelmanovich <igorz@mainsoft.com>
[mono.git] / mcs / class / System.Web / System.Web / VirtualPathUtility.cs
1 //\r
2 // System.Web.VirtualPathUtility.cs\r
3 //\r
4 // Author:\r
5 //      Chris Toshok (toshok@ximian.com)\r
6 //      Gonzalo Paniagua Javier (gonzalo@ximian.com)\r
7 //\r
8 \r
9 //\r
10 // Copyright (C) 2005 Novell, Inc (http://www.novell.com)\r
11 //\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
19 // \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
22 // \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
30 //\r
31 \r
32 \r
33 using System.Web.Util;\r
34 using System.Text;\r
35 \r
36 namespace System.Web {\r
37 \r
38 #if NET_2_0\r
39         public\r
40 #endif\r
41         static class VirtualPathUtility\r
42         {\r
43                 public static string AppendTrailingSlash (string virtualPath)\r
44                 {\r
45                         if (virtualPath == null)\r
46                                 return virtualPath;\r
47 \r
48                         int length = virtualPath.Length;\r
49                         if (length == 0 || virtualPath [length - 1] == '/')\r
50                                 return virtualPath;\r
51 \r
52                         return virtualPath + "/";\r
53                 }\r
54 \r
55                 public static string Combine (string basePath, string relativePath)\r
56                 {\r
57                         basePath = Normalize (basePath);\r
58 \r
59                         if (IsRooted (relativePath))\r
60                                 return Normalize (relativePath);\r
61 \r
62                         if (basePath [basePath.Length - 1] != '/') {\r
63                                 if (basePath.Length > 1) {\r
64                                         int lastSlash = basePath.LastIndexOf ('/');\r
65                                         if (lastSlash >= 0)\r
66                                                 basePath = basePath.Substring (0, lastSlash + 1);\r
67                                 }\r
68                                 else { // "~" only\r
69                                         basePath += "/";\r
70                                 }\r
71                         }\r
72 \r
73                         return Normalize (basePath + relativePath);\r
74                 }\r
75 \r
76                 public static string GetDirectory (string virtualPath)\r
77                 {\r
78                         virtualPath = Normalize (virtualPath);\r
79 \r
80                         if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/"\r
81                                 virtualPath = ToAbsolute (virtualPath);\r
82                         }\r
83                         \r
84                         if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/"\r
85                                 return null;\r
86                         }\r
87 \r
88                         int last = virtualPath.LastIndexOf ('/', virtualPath.Length - 2, virtualPath.Length - 2);\r
89                         if (last > 0)\r
90                                 return virtualPath.Substring (0, last + 1);\r
91                         else\r
92                                 return "/";\r
93                 }\r
94 \r
95                 public static string GetExtension (string virtualPath)\r
96                 {\r
97                         if (StrUtils.IsNullOrEmpty (virtualPath))\r
98                                 throw new ArgumentNullException ("virtualPath");\r
99 \r
100                         virtualPath = Canonize (virtualPath);\r
101 \r
102                         int dot = virtualPath.LastIndexOf ('.');\r
103                         if (dot == -1 || dot == virtualPath.Length - 1 || dot < virtualPath.LastIndexOf ('/'))\r
104                                 return String.Empty;\r
105 \r
106                         return virtualPath.Substring (dot);\r
107                 }\r
108 \r
109                 public static string GetFileName (string virtualPath)\r
110                 {\r
111                         virtualPath = Normalize (virtualPath);\r
112                         \r
113                         if (IsAppRelative (virtualPath) && virtualPath.Length < 3) { // "~" or "~/"\r
114                                 virtualPath = ToAbsolute (virtualPath);\r
115                         }\r
116 \r
117                         if (virtualPath.Length == 1 && virtualPath [0] == '/') { // "/"\r
118                                 return String.Empty;\r
119                         }\r
120 \r
121                         virtualPath = RemoveTrailingSlash (virtualPath);\r
122                         int last = virtualPath.LastIndexOf ('/');\r
123                         return virtualPath.Substring (last + 1);\r
124                 }\r
125 \r
126                 internal static bool IsRooted (string virtualPath)\r
127                 {\r
128                         return IsAbsolute (virtualPath) || IsAppRelative (virtualPath);\r
129                 }\r
130 \r
131                 public static bool IsAbsolute (string virtualPath)\r
132                 {\r
133                         if (StrUtils.IsNullOrEmpty (virtualPath))\r
134                                 throw new ArgumentNullException ("virtualPath");\r
135 \r
136                         return (virtualPath [0] == '/' || virtualPath [0] == '\\');\r
137                 }\r
138 \r
139                 public static bool IsAppRelative (string virtualPath)\r
140                 {\r
141                         if (StrUtils.IsNullOrEmpty (virtualPath))\r
142                                 throw new ArgumentNullException ("virtualPath");\r
143 \r
144                         if (virtualPath.Length == 1 && virtualPath [0] == '~')\r
145                                 return true;\r
146 \r
147                         if (virtualPath [0] == '~' && (virtualPath [1] == '/' || virtualPath [1] == '\\'))\r
148                                 return true;\r
149 \r
150                         return false;\r
151                 }\r
152 \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
158                 {\r
159                         if (fromPath == null || toPath == null)\r
160                                 throw new NullReferenceException (); // yeah!\r
161 \r
162                         if (toPath == "")\r
163                                 return toPath;\r
164 \r
165                         toPath = ToAbsoluteInternal (toPath);\r
166                         fromPath = ToAbsoluteInternal (fromPath);\r
167 \r
168                         if (String.CompareOrdinal (fromPath, toPath) == 0 && fromPath [fromPath.Length - 1] == '/')\r
169                                 return "./";\r
170 \r
171                         string [] toPath_parts = toPath.Split ('/');\r
172                         string [] fromPath_parts = fromPath.Split ('/');\r
173                         int dest = 1;\r
174                         while (toPath_parts [dest] == fromPath_parts [dest]) {\r
175                                 if (toPath_parts.Length == (dest + 1) || fromPath_parts.Length == (dest + 1)) {\r
176                                         break;\r
177                                 }\r
178                                 dest++;\r
179                         }\r
180                         StringBuilder res = new StringBuilder();\r
181                         for (int i = 1; i < fromPath_parts.Length - dest; i++) {\r
182                                 res.Append ("../");\r
183                         }\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
187                                         res.Append ('/');\r
188                         }\r
189                         return res.ToString ();\r
190                 }\r
191 \r
192                 private static string ToAbsoluteInternal (string virtualPath)\r
193                 {\r
194                         if (IsAppRelative (virtualPath))\r
195                                 return ToAbsolute (virtualPath, HttpRuntime.AppDomainAppVirtualPath);\r
196                         else if (IsAbsolute (virtualPath))\r
197                                 return Normalize (virtualPath);\r
198 \r
199                         throw new ArgumentOutOfRangeException ("Specified argument was out of the range of valid values.");\r
200                 }\r
201 \r
202                 public static string RemoveTrailingSlash (string virtualPath)\r
203                 {\r
204                         if (virtualPath == null || virtualPath == "")\r
205                                 return null;\r
206 \r
207                         int last = virtualPath.Length - 1;\r
208                         if (last == 0 || virtualPath [last] != '/')\r
209                                 return virtualPath;\r
210 \r
211                         return virtualPath.Substring (0, last);\r
212                 }\r
213 \r
214                 public static string ToAbsolute (string virtualPath)\r
215                 {\r
216                         if(IsAbsolute(virtualPath))\r
217                                 return Normalize (virtualPath);\r
218 \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
222 \r
223                         if (virtualPath.Length == 1 && virtualPath [0] == '~')\r
224                                 return apppath;\r
225 \r
226                         return ToAbsolute (virtualPath,apppath);\r
227                 }\r
228 \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
234                 {\r
235                         if (StrUtils.IsNullOrEmpty (applicationPath))\r
236                                 throw new ArgumentNullException ("applicationPath");\r
237 \r
238                         if (StrUtils.IsNullOrEmpty (virtualPath))\r
239                                 throw new ArgumentNullException ("virtualPath");\r
240 \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
245                         }\r
246 \r
247                         if (virtualPath [0] != '/')\r
248                                 throw new ArgumentException (String.Format ("Relative path not allowed: '{0}'", virtualPath));\r
249 \r
250                         return Normalize (virtualPath);\r
251 \r
252                 }\r
253 \r
254                 public static string ToAppRelative (string virtualPath)\r
255                 {\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
259 \r
260                         return ToAppRelative (virtualPath, apppath);\r
261                 }\r
262 \r
263                 public static string ToAppRelative (string virtualPath, string applicationPath)\r
264                 {\r
265                         virtualPath = Normalize (virtualPath);\r
266                         \r
267                         if (IsAppRelative (virtualPath))\r
268                                 return virtualPath;\r
269 \r
270                         if (!IsAbsolute (applicationPath))\r
271                                 throw new ArgumentException ("appPath is not absolute", "applicationPath");\r
272                         \r
273                         applicationPath = Normalize (applicationPath);\r
274 \r
275                         if (applicationPath.Length == 1)\r
276                                 return "~" + virtualPath;\r
277 \r
278                         int appPath_lenght = applicationPath.Length;\r
279                         if (String.CompareOrdinal (virtualPath, applicationPath) == 0)\r
280                                 return "~/";\r
281                         if (String.CompareOrdinal (virtualPath, 0, applicationPath, 0, appPath_lenght) == 0)\r
282                                 return "~" + virtualPath.Substring (appPath_lenght);\r
283 \r
284                         return virtualPath;\r
285                 }\r
286 \r
287                 static char [] path_sep = { '/' };\r
288 \r
289                 static string Normalize (string path)\r
290                 {\r
291                         if (!IsRooted (path))\r
292                                 throw new ArgumentException (String.Format ("The relative virtual path '{0}' is not allowed here.", path));\r
293 \r
294                         if (path.Length == 1) // '/' or '~'\r
295                                 return path;\r
296 \r
297                         path = Canonize (path);\r
298 \r
299                         if (path.IndexOf ('.') < 0)\r
300                                 return path;\r
301 \r
302                         bool starts_with_tilda = false;\r
303                         bool ends_with_slash = false;\r
304                         string [] apppath_parts= null;\r
305 \r
306                         if (path [0] == '~') {\r
307                                 if (path.Length == 2) // "~/"\r
308                                         return "~/";\r
309                                 starts_with_tilda = true;\r
310                                 path = path.Substring (1);\r
311                         }\r
312                         else if (path.Length == 1) { // "/"\r
313                                 return "/";\r
314                         }\r
315 \r
316                         if (path [path.Length - 1] == '/')\r
317                                 ends_with_slash = true;\r
318 \r
319                         string [] parts = StrUtils.SplitRemoveEmptyEntries (path, path_sep);\r
320                         int end = parts.Length;\r
321 \r
322                         int dest = 0;\r
323 \r
324                         for (int i = 0; i < end; i++) {\r
325                                 string current = parts [i];\r
326                                 if (current == ".")\r
327                                         continue;\r
328 \r
329                                 if (current == "..") {\r
330                                         dest--;\r
331 \r
332                                         if(dest >= 0)\r
333                                                 continue;\r
334 \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
339                                                 }\r
340 \r
341                                                 if ((apppath_parts.Length + dest) >= 0)\r
342                                                         continue;\r
343                                         }\r
344                                         \r
345                                         throw new HttpException ("Cannot use a leading .. to exit above the top directory.");\r
346                                 }\r
347 \r
348                                 if (dest >= 0)\r
349                                         parts [dest] = current;\r
350                                 else\r
351                                         apppath_parts [apppath_parts.Length + dest] = current;\r
352                                 \r
353                                 dest++;\r
354                         }\r
355 \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
360                                 if (dest < 0)\r
361                                         count += dest;\r
362                                 for (int i = 0; i < count; i++) {\r
363                                         str.Append ('/');\r
364                                         str.Append (apppath_parts [i]);\r
365                                 }\r
366                         }\r
367                         else if (starts_with_tilda) {\r
368                                 str.Append ('~');\r
369                         }\r
370 \r
371                         for (int i = 0; i < dest; i++) {\r
372                                 str.Append ('/');\r
373                                 str.Append (parts [i]);\r
374                         }\r
375 \r
376                         if (str.Length > 0) {\r
377                                 if (ends_with_slash)\r
378                                         str.Append ('/');\r
379                         }\r
380                         else {\r
381                                 return "/";\r
382                         }\r
383 \r
384                         return str.ToString ();\r
385                 }\r
386 \r
387                 static string Canonize (string path)\r
388                 {\r
389                         int index = -1;\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
392                                         index = i;\r
393                                         break;\r
394                                 }\r
395                         }\r
396                         if (index < 0)\r
397                                 return path;\r
398 \r
399                         StringBuilder sb = new StringBuilder (path.Length);\r
400                         sb.Append (path, 0, index);\r
401 \r
402                         for (int i = index; i < path.Length; i++) {\r
403                                 if (path [i] == '\\' || path [i] == '/') {\r
404                                         int next = i + 1;\r
405                                         if (next < path.Length && (path [next] == '\\' || path [next] == '/'))\r
406                                                 continue;\r
407                                         sb.Append ('/');\r
408                                 }\r
409                                 else {\r
410                                         sb.Append (path [i]);\r
411                                 }\r
412                         }\r
413 \r
414                         return sb.ToString ();\r
415                 }\r
416 \r
417         }\r
418 }\r