copying the latest Sys.Web.Services from trunk.
[mono.git] / mcs / class / System.Web / System.Web.Util / UrlUtils.cs
1
2 //
3 // Permission is hereby granted, free of charge, to any person obtaining
4 // a copy of this software and associated documentation files (the
5 // "Software"), to deal in the Software without restriction, including
6 // without limitation the rights to use, copy, modify, merge, publish,
7 // distribute, sublicense, and/or sell copies of the Software, and to
8 // permit persons to whom the Software is furnished to do so, subject to
9 // the following conditions:
10 // 
11 // The above copyright notice and this permission notice shall be
12 // included in all copies or substantial portions of the Software.
13 // 
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 //
22 /**
23  * Namespace: System.Web.UI.Util
24  * Class:     UrlUtils
25  * 
26  * Author:  Gaurav Vaish
27  * Maintainer: gvaish@iitk.ac.in
28  * Status:  ??%
29  * 
30  * (C) Gaurav Vaish (2001)
31  */
32
33 using System;
34 using System.Collections;
35 using System.Text;
36 using System.Web.SessionState;
37
38 namespace System.Web.Util
39 {
40         internal class UrlUtils
41         {
42                 /*
43                  * I could not find these functions in the class System.Uri
44                  * Besides, an instance of Uri will not be formed until and unless the address is of
45                  * the form protocol://[user:pass]host[:port]/[fullpath]
46                  * ie, a protocol, and that too without any blanks before,
47                  * is a must which may not be the case here.
48                  * Important: Escaped URL is assumed here. nothing like .aspx?path=/something
49                  * It should be .aspx?path=%2Fsomething
50                  */
51                 public static string GetProtocol(string url)
52                 {
53                         //Taking code from Java Class java.net.URL
54                         if(url!=null)
55                         {
56                                 if(url.Length>0)
57                                 {
58                                         
59                                         int i, start = 0, limit;
60                                         limit = url.Length;
61                                         char c;
62                                         bool aRef = false;
63                                         while( (limit > 0) && (url[limit-1] <= ' '))
64                                         {
65                                                 limit --;
66                                         }
67                                         while( (start < limit) && (url[start] <= ' '))
68                                         {
69                                                 start++;
70                                         }
71                                         if(RegionMatches(true, url, start, "url:", 0, 4))
72                                         {
73                                                 start += 4;
74                                         }
75                                         if(start < url.Length && url[start]=='#')
76                                         {
77                                                 aRef = true;
78                                         }
79                                         for(i = start; !aRef && (i < limit) && ((c=url[i]) != '/'); i++)
80                                         {
81                                                 if(c==':')
82                                                 {
83                                                         return url.Substring(start, i - start);
84                                                 }
85                                         }
86                                 }
87                         }
88                         return String.Empty;
89                 }
90                 
91                 public static bool IsRelativeUrl(string url)
92                 {
93                         if (url.IndexOf(':') == -1)
94                                 return !IsRooted(url);
95
96                         return false;
97                 }
98
99                 public static bool IsRootUrl(string url)
100                 {
101                         if(url!=null)
102                         {
103                                 if(url.Length>0)
104                                 {
105                                         return IsValidProtocol(GetProtocol(url).ToLower());
106                                 }
107                         }
108                         return true;
109                 }
110                 
111                 public static bool IsRooted(string path)
112                 {
113                         if(path!=null && path.Length > 0)
114                         {
115                                 return (path[0]=='/' || path[0]=='\\');
116                         }
117                         return true;
118                 }
119                 
120                 public static void FailIfPhysicalPath(string path)
121                 {
122                         if(path!= null && path.Length > 1)
123                         {
124                                 if(path[1]==':' || path.StartsWith(@"\\"))
125                                         throw new HttpException(HttpRuntime.FormatResourceString("Physical_path_not_allowed", path));
126                         }
127                 }
128
129                 public static string Combine (string basePath, string relPath)
130                 {
131                         if (relPath == null)
132                                 throw new ArgumentNullException ("relPath");
133
134                         int rlength = relPath.Length;
135                         if (rlength == 0)
136                                 return "";
137
138                         FailIfPhysicalPath (relPath);
139                         relPath = relPath.Replace ("\\", "/");
140                         if (IsRooted (relPath))
141                                 return Reduce (relPath);
142
143                         char first = relPath [0];
144                         if (rlength < 3 || first == '~' || first == '/' || first == '\\') {
145                                 if (basePath == null || (basePath.Length == 1 && basePath [0] == '/'))
146                                         basePath = String.Empty;
147
148                                 string slash = (first == '/') ? "" : "/";
149                                 if (first == '~') {
150                                         if (rlength == 1) {
151                                                 relPath = "";
152                                         } else if (rlength > 1 && relPath [1] == '/') {
153                                                 relPath = relPath.Substring (2);
154                                                 slash = "/";
155                                         }
156
157                                         string appvpath = HttpRuntime.AppDomainAppVirtualPath;
158                                         if (appvpath.EndsWith ("/"))
159                                                 slash = "";
160
161                                         return Reduce (appvpath + slash + relPath);
162                                 }
163
164                                 return Reduce (basePath + slash + relPath);
165                         }
166
167                         if (basePath == null || basePath == "")
168                                 basePath = HttpRuntime.AppDomainAppVirtualPath;
169
170                         if (basePath.Length <= 1)
171                                 basePath = String.Empty;
172
173                         return Reduce (basePath + "/" + relPath);
174                 }
175
176                 public static bool IsValidProtocol(string protocol)
177                 {
178                         if(protocol.Length < 1)
179                                 return false;
180                         char c = protocol[0];
181                         if(!Char.IsLetter(c))
182                         {
183                                 return false;
184                         }
185                         for(int i=1; i < protocol.Length; i++)
186                         {
187                                 c = protocol[i];
188                                 if(!Char.IsLetterOrDigit(c) && c!='.' && c!='+' && c!='-')
189                                 {
190                                         return false;
191                                 }
192                         }
193                         return true;
194                 }
195                 
196                 /*
197                  * MakeRelative("http://www.foo.com/bar1/bar2/file","http://www.foo.com/bar1")
198                  * will return "bar2/file"
199                  * while MakeRelative("http://www.foo.com/bar1/...","http://www.anotherfoo.com")
200                  * return 'null' and so does the call
201                  * MakeRelative("http://www.foo.com/bar1/bar2","http://www.foo.com/bar")
202                  */
203                 public static string MakeRelative(string fullUrl, string relativeTo)
204                 {
205                         if (fullUrl == relativeTo)
206                                 return String.Empty;
207
208                         if (fullUrl.IndexOf (relativeTo) != 0)
209                                 return null;
210
211                         string leftOver = fullUrl.Substring (relativeTo.Length);
212                         if (leftOver.Length > 0 && leftOver [0] == '/')
213                                 leftOver = leftOver.Substring (1);
214
215                         leftOver = Reduce (leftOver);
216                         if (leftOver.Length > 0 && leftOver [0] == '/')
217                                 leftOver = leftOver.Substring (1);
218
219                         return leftOver;
220                 }
221
222                 /*
223                  * Check JavaDocs for java.lang.String#RegionMatches(bool, int, String, int, int)
224                  * Could not find anything similar in the System.String class
225                  */
226                 public static bool RegionMatches(bool ignoreCase, string source, int start, string match, int offset, int len)
227                 {
228                         if(source!=null || match!=null)
229                         {
230                                 if(source.Length>0 && match.Length>0)
231                                 {
232                                         char[] ta = source.ToCharArray();
233                                         char[] pa = match.ToCharArray();
234                                         if((offset < 0) || (start < 0) || (start > (source.Length - len)) || (offset > (match.Length - len)))
235                                         {
236                                                 return false;
237                                         }
238                                         while(len-- > 0)
239                                         {
240                                                 char c1 = ta[start++];
241                                                 char c2 = pa[offset++];
242                                                 if(c1==c2)
243                                                         continue;
244                                                 if(ignoreCase)
245                                                 {
246                                                         if(Char.ToUpper(c1)==Char.ToUpper(c2))
247                                                                 continue;
248                                                         // Check for Gregorian Calendar where the above may not hold good
249                                                         if(Char.ToLower(c1)==Char.ToLower(c2))
250                                                                 continue;
251                                                 }
252                                                 return false;
253                                         }
254                                         return true;
255                                 }
256                         }
257                         return false;
258                 }
259
260                 public static string Reduce (string path)
261                 {
262                         path = path.Replace ('\\','/');
263
264                         string [] parts = path.Split ('/');
265                         ArrayList result = new ArrayList ();
266                         
267                         int end = parts.Length;
268                         for (int i = 0; i < end; i++) {
269                                 string current = parts [i];
270                                 if (current == "." )
271                                         continue;
272
273                                 if (current == "..") {
274                                         if (result.Count == 0) {
275                                                 if (i == 1) // see bug 52599
276                                                         continue;
277
278                                                 throw new HttpException ("Invalid path.");
279                                         }
280
281                                         result.RemoveAt (result.Count - 1);
282                                         continue;
283                                 }
284
285                                 result.Add (current);
286                         }
287
288                         if (result.Count == 0)
289                                 return "/";
290
291                         return String.Join ("/", (string []) result.ToArray (typeof (string)));
292                 }
293                 
294                 public static string GetDirectory(string url)
295                 {
296                         if(url==null)
297                         {
298                                 return null;
299                         }
300                         if(url.Length==0)
301                         {
302                                 return String.Empty;
303                         }
304                         url = url.Replace('\\','/');
305
306                         string baseDir = "";
307                         int last = url.LastIndexOf ('/');
308                         if (last > 0)
309                                 baseDir = url.Substring(0, url.LastIndexOf('/'));
310
311                         if(baseDir.Length==0)
312                         {
313                                 baseDir = "/";
314                         }
315                         return baseDir;
316                 }
317
318                 static string GetFile (string url)
319                 {
320                         if (url == null)
321                                 return null;
322
323                         if (url.Length == 0)
324                                 return String.Empty;
325
326                         url = url.Replace ('\\', '/');
327
328                         int last = url.LastIndexOf ('/') + 1;
329                         if (last != 0) {
330                                 url = url.Substring (last);
331                         }
332
333                         return url;
334                 }
335
336                 // appRoot + SessionID + vpath
337                 public static string InsertSessionId (string id, string path)
338                 {
339                         string dir = GetDirectory (path);
340                         if (!dir.EndsWith ("/"))
341                                 dir += "/";
342
343                         string appvpath = HttpRuntime.AppDomainAppVirtualPath;
344                         if (!appvpath.EndsWith ("/"))
345                                 appvpath += "/";
346
347                         if (path.StartsWith (appvpath))
348                                 path = path.Substring (appvpath.Length);
349
350                         if (path [0] == '/')
351                                 path = path.Length > 1 ? path.Substring (1) : "";
352
353                         return Reduce (appvpath + "(" + id + ")/" + path);
354                 }
355
356                 public static string GetSessionId (string path)
357                 {
358                         string appvpath = HttpRuntime.AppDomainAppVirtualPath;
359                         if (path.Length <= appvpath.Length)
360                                 return null;
361
362                         path = path.Substring (appvpath.Length);
363                         if (path.Length == 0 || path [0] != '/')
364                                 path = '/' + path;
365
366                         int len = path.Length;
367                         if ((len < SessionId.IdLength + 3) || (path [1] != '(') ||
368                             (path [SessionId.IdLength + 2] != ')'))
369                                 return null;
370
371                         return path.Substring (2, SessionId.IdLength);
372                 }
373
374                 public static string RemoveSessionId (string base_path, string file_path)
375                 {
376                         // Caller did a GetSessionId first
377                         int idx = base_path.IndexOf ("/(");
378                         string dir = base_path.Substring (0, idx + 1);
379                         if (!dir.EndsWith ("/"))
380                                 dir += "/";
381
382                         idx = base_path.IndexOf (")/");
383                         if (idx != -1 && base_path.Length > idx + 2) {
384                                 string dir2 = base_path.Substring (idx + 2);
385                                 if (!dir2.EndsWith ("/"))
386                                         dir2 += "/";
387
388                                 dir += dir2;
389                         }
390
391                         return Reduce (dir + GetFile (file_path));
392                 }
393                 
394                 public static string ResolveVirtualPathFromAppAbsolute (string path)
395                 {
396                         if (path [0] != '~') return path;
397                                 
398                         if (path.Length == 1)
399                                 return HttpRuntime.AppDomainAppVirtualPath;
400                         
401                         if (path [1] == '/' || path [1] == '\\') {
402                                 string appPath = HttpRuntime.AppDomainAppVirtualPath;
403                                 if (appPath.Length > 1)
404                                         return appPath + "/" + path.Substring (2);
405                                 return "/" + path.Substring (2);
406                         }
407                         return path;    
408                 }
409                 
410                 public static string ResolvePhysicalPathFromAppAbsolute (string path)
411                 {
412                         if (path [0] != '~') return path;
413                                 
414                         if (path.Length == 1)
415                                 return HttpRuntime.AppDomainAppPath;
416                         
417                         if (path [1] == '/' || path [1] == '\\') {
418                                 string appPath = HttpRuntime.AppDomainAppPath;
419                                 if (appPath.Length > 1)
420                                         return appPath + "/" + path.Substring (2);
421                                 return "/" + path.Substring (2);
422                         }
423                         return path;    
424                 }
425         }
426 }
427