Merge pull request #2377 from joelmartinez/docs-multiassembly-extension-fix
[mono.git] / mcs / class / referencesource / System.Web / Util / UrlPath.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="UrlPath.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6
7 /*
8  * UrlPath class
9  *
10  * Copyright (c) 1999 Microsoft Corporation
11  */
12
13 namespace System.Web.Util {
14 using System.Text;
15 using System.Runtime.Serialization.Formatters;
16 using System.Runtime.InteropServices;
17 using System.Collections;
18 using System.Globalization;
19 using System.IO;
20 using System.Web.Hosting;
21
22 /*
23  * Code to perform Url path combining
24  */
25 internal static class UrlPath {
26
27     internal const char appRelativeCharacter = '~';
28     internal const string appRelativeCharacterString = "~/";
29
30     private static char[] s_slashChars = new char[] { '\\', '/' };
31
32     internal static bool IsRooted(String basepath) {
33         return (String.IsNullOrEmpty(basepath) || basepath[0] == '/' || basepath[0] == '\\');
34     }
35
36     // Checks if virtual path contains a protocol, which is referred to as a scheme in the
37     // URI spec.
38     private static bool HasScheme(string virtualPath) {
39         // URIs have the format <scheme>:<scheme-specific-path>, e.g. mailto:user@ms.com,
40         // http://server/, nettcp://server/, etc.  The <scheme> cannot contain slashes.
41         // The virtualPath passed to this method may be absolute or relative. Although
42         // ':' is only allowed in the <scheme-specific-path> if it is encoded, the 
43         // virtual path that we're receiving here may be decoded, so it is impossible
44         // for us to determine if virtualPath has a scheme.  We will be conservative
45         // and err on the side of assuming it has a scheme when we cannot tell for certain.
46         // To do this, we first check for ':'.  If not found, then it doesn't have a scheme.
47         // If ':' is found, then as long as we find a '/' before the ':', it cannot be
48         // a scheme because schemes don't contain '/'.  Otherwise, we will assume it has a 
49         // scheme.
50         int indexOfColon = virtualPath.IndexOf(':');
51         if (indexOfColon == -1)
52             return false;
53         int indexOfSlash = virtualPath.IndexOf('/');
54         return (indexOfSlash == -1 || indexOfColon < indexOfSlash);
55     }
56
57     // Returns whether the virtual path is relative.  Note that this returns true for
58     // app relative paths (e.g. "~/sub/foo.aspx")
59     internal static bool IsRelativeUrl(string virtualPath) {
60         // If it has a protocol, it's not relative
61         if (HasScheme(virtualPath))
62             return false;
63
64         return !IsRooted(virtualPath);
65     }
66
67
68     internal static bool IsAppRelativePath(string path) {
69
70         if (path == null)
71             return false;
72
73         int len = path.Length;
74
75         // Empty string case
76         if (len == 0) return false;
77
78         // It must start with ~
79         if (path[0] != appRelativeCharacter)
80             return false;
81
82         // Single character case: "~"
83         if (len == 1)
84             return true;
85
86         // If it's longer, checks if it starts with "~/" or "~\"
87         return path[1] == '\\' || path[1] == '/';
88     }
89
90     internal static bool IsValidVirtualPathWithoutProtocol(string path) {
91         if (path == null)
92             return false;
93         return !HasScheme(path);
94     }
95
96     internal static String GetDirectory(String path) {
97         if (String.IsNullOrEmpty(path))
98             throw new ArgumentException(SR.GetString(SR.Empty_path_has_no_directory));
99
100         if (path[0] != '/' && path[0] != appRelativeCharacter)
101             throw new ArgumentException(SR.GetString(SR.Path_must_be_rooted, path));
102
103         // If it's just "~" or "/", return it unchanged
104         if (path.Length == 1)
105             return path;
106
107         int slashIndex = path.LastIndexOf('/');
108
109         // This could happen if the input looks like "~abc"
110         if (slashIndex < 0)
111             throw new ArgumentException(SR.GetString(SR.Path_must_be_rooted, path));
112
113         return path.Substring(0, slashIndex + 1);
114     }
115
116     private static bool IsDirectorySeparatorChar(char ch) {
117         return (ch == '\\' || ch == '/');
118     }
119
120     internal static bool IsAbsolutePhysicalPath(string path) {
121         if (path == null || path.Length < 3)
122             return false;
123
124         // e.g c:\foo
125         if (path[1] == ':' && IsDirectorySeparatorChar(path[2]))
126             return true;
127
128         // e.g \\server\share\foo or //server/share/foo
129         return IsUncSharePath(path);
130     }
131
132     internal static bool IsUncSharePath(string path) {
133         // e.g \\server\share\foo or //server/share/foo
134         if (path.Length > 2 && IsDirectorySeparatorChar(path[0]) && IsDirectorySeparatorChar(path[1]))
135             return true;
136         return false;
137
138     }
139
140     internal static void CheckValidVirtualPath(string path) {
141
142         // Check if it looks like a physical path (UNC shares and C:)
143         if (IsAbsolutePhysicalPath(path)) {
144             throw new HttpException(SR.GetString(SR.Physical_path_not_allowed, path));
145         }
146
147         // Virtual path can't have colons.
148         int iqs = path.IndexOf('?');
149         if (iqs >= 0) {
150             path = path.Substring(0, iqs);
151         }
152         if (HasScheme(path)) {
153             throw new HttpException(SR.GetString(SR.Invalid_vpath, path));
154         }
155     }
156
157     private static String Combine(String appPath, String basepath, String relative) {
158         String path;
159
160         if (String.IsNullOrEmpty(relative))
161             throw new ArgumentNullException("relative");
162         if (String.IsNullOrEmpty(basepath))
163             throw new ArgumentNullException("basepath");
164
165         if (basepath[0] == appRelativeCharacter && basepath.Length == 1) {
166             // If it's "~", change it to "~/"
167             basepath = appRelativeCharacterString;
168         }
169         else {
170             // If the base path includes a file name, get rid of it before combining
171             int lastSlashIndex = basepath.LastIndexOf('/');
172             Debug.Assert(lastSlashIndex >= 0);
173             if (lastSlashIndex < basepath.Length - 1) {
174                 basepath = basepath.Substring(0, lastSlashIndex + 1);
175             }
176         }
177
178         // Make sure it's a virtual path (ASURT 73641)
179         CheckValidVirtualPath(relative);
180
181         if (IsRooted(relative)) {
182             path = relative;
183         }
184         else {
185
186             // If the path is exactly "~", just return the app root path
187             if (relative.Length == 1 && relative[0] == appRelativeCharacter)
188                 return appPath;
189
190             // If the relative path starts with "~/" or "~\", treat it as app root
191             // relative (ASURT 68628)
192             if (IsAppRelativePath(relative)) {
193                 if (appPath.Length > 1)
194                     path = appPath + "/" + relative.Substring(2);
195                 else
196                     path = "/" + relative.Substring(2);
197             } else {
198                 path = SimpleCombine(basepath, relative);
199             }
200         }
201
202         return Reduce(path);
203     }
204
205     internal static String Combine(String basepath, String relative) {
206         return Combine(HttpRuntime.AppDomainAppVirtualPathString, basepath, relative);
207     }
208
209     // This simple version of combine should only be used when the relative
210     // path is known to be relative.  It's more efficient, but doesn't do any
211     // sanity checks.
212     internal static String SimpleCombine(String basepath, String relative) {
213         Debug.Assert(!String.IsNullOrEmpty(basepath));
214         Debug.Assert(!String.IsNullOrEmpty(relative));
215         Debug.Assert(relative[0] != '/');
216
217         if (HasTrailingSlash(basepath))
218             return basepath + relative;
219         else
220             return basepath + "/" + relative;
221     }
222
223     internal static String Reduce(String path) {
224
225         // ignore query string
226         String queryString = null;
227         if (path != null) {
228             int iqs = path.IndexOf('?');
229             if (iqs >= 0) {
230                 queryString = path.Substring(iqs);
231                 path = path.Substring(0, iqs);
232             }
233         }
234
235         // Take care of backslashes and duplicate slashes
236         path = FixVirtualPathSlashes(path);
237
238         path = ReduceVirtualPath(path);
239
240         return (queryString != null) ? (path + queryString) : path;
241     }
242
243     // Same as Reduce, but for a virtual path that is known to be well formed
244     internal static String ReduceVirtualPath(String path) {
245
246         int length = path.Length;
247         int examine;
248
249         // quickly rule out situations in which there are no . or ..
250
251         for (examine = 0; ; examine++) {
252             examine = path.IndexOf('.', examine);
253             if (examine < 0)
254                 return path;
255
256             if ((examine == 0 || path[examine - 1] == '/')
257                 && (examine + 1 == length || path[examine + 1] == '/' ||
258                     (path[examine + 1] == '.' && (examine + 2 == length || path[examine + 2] == '/'))))
259                 break;
260         }
261
262         // OK, we found a . or .. so process it:
263
264         ArrayList list = new ArrayList();
265         StringBuilder sb = new StringBuilder();
266         int start;
267         examine = 0;
268
269         for (; ; ) {
270             start = examine;
271             examine = path.IndexOf('/', start + 1);
272
273             if (examine < 0)
274                 examine = length;
275
276             if (examine - start <= 3 &&
277                 (examine < 1 || path[examine - 1] == '.') &&
278                 (start + 1 >= length || path[start + 1] == '.')) {
279                 if (examine - start == 3) {
280                     if (list.Count == 0)
281                         throw new HttpException(SR.GetString(SR.Cannot_exit_up_top_directory));
282
283                     // We're about to backtrack onto a starting '~', which would yield
284                     // incorrect results.  Instead, make the path App Absolute, and call
285                     // Reduce on that.
286                     if (list.Count == 1 && IsAppRelativePath(path)) {
287                         Debug.Assert(sb.Length == 1);
288                         return ReduceVirtualPath(MakeVirtualPathAppAbsolute(path));
289                     }
290
291                     sb.Length = (int)list[list.Count - 1];
292                     list.RemoveRange(list.Count - 1, 1);
293                 }
294             }
295             else {
296                 list.Add(sb.Length);
297
298                 sb.Append(path, start, examine - start);
299             }
300
301             if (examine == length)
302                 break;
303         }
304
305         string result = sb.ToString();
306
307         // If we end up with en empty string, turn it into either "/" or "." (VSWhidbey 289175)
308         if (result.Length == 0) {
309             if (length > 0 && path[0] == '/')
310                 result = @"/";
311             else
312                 result = ".";
313         }
314
315         return result;
316     }
317
318     // Change backslashes to forward slashes, and remove duplicate slashes
319     internal static String FixVirtualPathSlashes(string virtualPath) {
320
321         // Make sure we don't have any back slashes
322         virtualPath = virtualPath.Replace('\\', '/');
323
324         // Replace any double forward slashes
325         for (;;) {
326             string newPath = virtualPath.Replace("//", "/");
327
328             // If it didn't do anything, we're done
329             if ((object)newPath == (object)virtualPath)
330                 break;
331
332             // We need to loop again to take care of triple (or more) slashes (VSWhidbey 288782)
333             virtualPath = newPath;
334         }
335
336         return virtualPath;
337     }
338
339     // We use file: protocol instead of http:, so that Uri.MakeRelative behaves
340     // in a case insensitive way (VSWhidbey 80078)
341     private const string dummyProtocolAndServer = "file://foo";
342
343     // Return the relative vpath path from one rooted vpath to another
344     internal static string MakeRelative(string from, string to) {
345
346         // If either path is app relative (~/...), make it absolute, since the Uri
347         // class wouldn't know how to deal with it.
348         from = MakeVirtualPathAppAbsolute(from);
349         to = MakeVirtualPathAppAbsolute(to);
350
351         // Make sure both virtual paths are rooted
352         if (!IsRooted(from))
353             throw new ArgumentException(SR.GetString(SR.Path_must_be_rooted, from));
354         if (!IsRooted(to))
355             throw new ArgumentException(SR.GetString(SR.Path_must_be_rooted, to));
356
357         // Remove the query string, so that System.Uri doesn't corrupt it
358         string queryString = null;
359         if (to != null) {
360             int iqs = to.IndexOf('?');
361             if (iqs >= 0) {
362                 queryString = to.Substring(iqs);
363                 to = to.Substring(0, iqs);
364             }
365         }
366
367         // Uri's need full url's so, we use a dummy root
368         Uri fromUri = new Uri(dummyProtocolAndServer + from);
369         Uri toUri = new Uri(dummyProtocolAndServer + to);
370
371         string relativePath;
372
373         // VSWhidbey 144946: If to and from points to identical path (excluding query and fragment), just use them instead
374         // of returning an empty string.
375         if (fromUri.Equals(toUri)) {
376             int iPos = to.LastIndexOfAny(s_slashChars);
377
378             if (iPos >= 0) {
379
380                 // If it's the same directory, simply return "./"
381                 // Browsers should interpret "./" as the original directory.
382                 if (iPos == to.Length - 1) {
383                     relativePath = "./";
384                 }
385                 else {
386                     relativePath = to.Substring(iPos + 1);
387                 }
388             }
389             else {
390                 relativePath = to;
391             }
392         }
393         else {
394 // To avoid deprecation warning.  It says we should use MakeRelativeUri instead (which returns a Uri),
395 // but we wouldn't gain anything from it.  The way we use MakeRelative is hacky anyway (fake protocol, ...),
396 // and I don't want to take the chance of breaking something with this change.
397 #pragma warning disable 0618
398             relativePath = fromUri.MakeRelative(toUri);
399 #pragma warning restore 0618
400         }
401
402         // Note that we need to re-append the query string and fragment (e.g. #anchor)
403         return relativePath + queryString + toUri.Fragment;
404     }
405
406     internal static string GetDirectoryOrRootName(string path) {
407         string dir;
408
409         dir = Path.GetDirectoryName(path);
410         if (dir == null) {
411             dir = Path.GetPathRoot(path);
412         }
413
414         return dir;
415     }
416
417     internal static string GetFileName(string virtualPath) {
418         // Code copied from CLR\BCL\System\IO\Path.cs
419         //    - Check for invalid chars removed
420         //    - Only '/' is used as separator (path.cs also used '\' and ':')
421         if (virtualPath != null) {
422             int length = virtualPath.Length;
423             for (int i = length; --i >= 0;) {
424                 char ch = virtualPath[i];
425                 if (ch == '/')
426                     return virtualPath.Substring(i + 1, length - i - 1);
427
428             }
429         }
430         return virtualPath;
431     }
432
433     internal static string GetFileNameWithoutExtension(string virtualPath) {
434         // Code copied from CLR\BCL\System\IO\Path.cs
435         //    - Check for invalid chars removed
436         virtualPath = GetFileName(virtualPath);
437         if (virtualPath != null) {
438             int i;
439             if ((i=virtualPath.LastIndexOf('.')) == -1)
440                 return virtualPath; // No extension found
441             else
442                 return virtualPath.Substring(0,i);
443         }
444         return null;
445     }
446
447     internal static string GetExtension(string virtualPath) {
448         if (virtualPath == null)
449             return null;
450
451         int length = virtualPath.Length;
452         for (int i = length; --i >= 0;) {
453             char ch = virtualPath[i];
454             if (ch == '.') {
455                 if (i != length - 1)
456                     return virtualPath.Substring(i, length - i);
457                 else
458                     return String.Empty;
459             }
460             if (ch == '/')
461                 break;
462         }
463         return String.Empty;
464     }
465
466     internal static bool HasTrailingSlash(string virtualPath) {
467         return virtualPath[virtualPath.Length - 1] == '/';
468     }
469
470     internal static string AppendSlashToPathIfNeeded(string path) {
471
472         if (path == null) return null;
473
474         int l = path.Length;
475         if (l == 0) return path;
476
477         if (path[l-1] != '/')
478             path += '/';
479
480         return path;
481     }
482
483     //
484     // Remove the trailing forward slash ('/') except in the case of the root ("/").
485     // If the string is null or empty, return null, which represents a machine.config or root web.config.
486     //
487     internal static string RemoveSlashFromPathIfNeeded(string path) {
488         if (string.IsNullOrEmpty(path)) {
489             return null;
490         }
491
492         int l = path.Length;
493         if (l <= 1 || path[l-1] != '/') {
494             return path;
495         }
496
497         return path.Substring(0, l-1);
498     }
499
500     private static bool VirtualPathStartsWithVirtualPath(string virtualPath1, string virtualPath2) {
501         if (virtualPath1 == null) {
502             throw new ArgumentNullException("virtualPath1");
503         }
504
505         if (virtualPath2 == null) {
506             throw new ArgumentNullException("virtualPath2");
507         }
508
509         // if virtualPath1 as a string doesn't start with virtualPath2 as s string, then no for sure
510         if (!StringUtil.StringStartsWithIgnoreCase(virtualPath1, virtualPath2)) {
511             return false;
512         }
513
514         int virtualPath2Length = virtualPath2.Length;
515
516         // same length - same path
517         if (virtualPath1.Length == virtualPath2Length) {
518             return true;
519         }
520
521         // Special case for apps rooted at the root. VSWhidbey 286145
522         if (virtualPath2Length == 1) {
523             Debug.Assert(virtualPath2[0] == '/');
524             return true;
525         }
526
527         // If virtualPath2 ends with a '/', it's definitely a child
528         if (virtualPath2[virtualPath2Length - 1] == '/')
529             return true;
530
531         // If it doesn't, make sure the next char in virtualPath1 is a '/'.
532         // e.g. /app1 vs /app11 (VSWhidbey 285038)
533         if (virtualPath1[virtualPath2Length] != '/') {
534             return false;
535         }
536
537         // passed all checks
538         return true;
539     }
540
541     internal static bool VirtualPathStartsWithAppPath(string virtualPath) {
542         Debug.Assert(HttpRuntime.AppDomainAppVirtualPathObject != null);
543         return VirtualPathStartsWithVirtualPath(virtualPath,
544             HttpRuntime.AppDomainAppVirtualPathString);
545     }
546
547     internal static string MakeVirtualPathAppRelative(string virtualPath) {
548         Debug.Assert(HttpRuntime.AppDomainAppVirtualPathObject != null);
549         return MakeVirtualPathAppRelative(virtualPath,
550             HttpRuntime.AppDomainAppVirtualPathString, false /*nullIfNotInApp*/);
551     }
552
553     // Same as MakeVirtualPathAppRelative, but return null if app relative can't be obtained
554     internal static string MakeVirtualPathAppRelativeOrNull(string virtualPath) {
555         Debug.Assert(HttpRuntime.AppDomainAppVirtualPathObject != null);
556         return MakeVirtualPathAppRelative(virtualPath,
557             HttpRuntime.AppDomainAppVirtualPathString, true /*nullIfNotInApp*/);
558     }
559
560     // If a virtual path starts with the app path, make it start with
561     // ~ instead, so that it becomes application agnostic
562     // E.g. /MyApp/Sub/foo.aspx --> ~/Sub/foo.aspx
563     internal static string MakeVirtualPathAppRelative(string virtualPath,
564         string applicationPath, bool nullIfNotInApp) {
565
566         if (virtualPath == null)
567             throw new ArgumentNullException("virtualPath");
568
569         Debug.Assert(applicationPath[0] == '/');
570         Debug.Assert(HasTrailingSlash(applicationPath));
571
572         int appPathLength = applicationPath.Length;
573         int virtualPathLength = virtualPath.Length;
574
575         // If virtualPath is the same as the app path, but without the ending slash,
576         // treat it as if it were truly the app path (VSWhidbey 495949)
577         if (virtualPathLength == appPathLength - 1) {
578             if (StringUtil.StringStartsWithIgnoreCase(applicationPath, virtualPath))
579                 return appRelativeCharacterString;
580         }
581
582         if (!VirtualPathStartsWithVirtualPath(virtualPath, applicationPath)) {
583             // If it doesn't start with the app path, return either null or the input path
584             if (nullIfNotInApp)
585                 return null;
586             else
587                 return virtualPath;
588         }
589
590         // If they are the same, just return "~/"
591         if (virtualPathLength == appPathLength)
592             return appRelativeCharacterString;
593
594         // Special case for apps rooted at the root:
595         if (appPathLength == 1) {
596             return appRelativeCharacter + virtualPath;
597         }
598
599         return appRelativeCharacter + virtualPath.Substring(appPathLength-1);
600     }
601
602     internal static string MakeVirtualPathAppAbsolute(string virtualPath) {
603         Debug.Assert(HttpRuntime.AppDomainAppVirtualPathObject != null);
604         return MakeVirtualPathAppAbsolute(virtualPath, HttpRuntime.AppDomainAppVirtualPathString);
605     }
606
607     // If a virtual path is app relative (i.e. starts with ~/), change it to
608     // start with the actuall app path.
609     // E.g. ~/Sub/foo.aspx --> /MyApp/Sub/foo.aspx
610     internal static string MakeVirtualPathAppAbsolute(string virtualPath, string applicationPath) {
611
612         // If the path is exactly "~", just return the app root path
613         if (virtualPath.Length == 1 && virtualPath[0] == appRelativeCharacter)
614             return applicationPath;
615
616         // If the virtual path starts with "~/" or "~\", replace with the app path
617         // relative (ASURT 68628)
618         if (virtualPath.Length >=2 && virtualPath[0] == appRelativeCharacter &&
619             (virtualPath[1] == '/' || virtualPath[1] == '\\')) {
620
621             if (applicationPath.Length > 1) {
622                 Debug.Assert(HasTrailingSlash(applicationPath));
623                 return applicationPath + virtualPath.Substring(2);
624             }
625             else
626                 return "/" + virtualPath.Substring(2);
627         }
628
629         // Don't allow relative paths, since they cannot be made App Absolute
630         if (!IsRooted(virtualPath))
631             throw new ArgumentOutOfRangeException("virtualPath");
632
633         // Return it unchanged
634         return virtualPath;
635     }
636
637     // To be called by APIs accepting virtual path that is expectedto be within the app.
638     // returns reduced absolute virtual path or throws
639     internal static string MakeVirtualPathAppAbsoluteReduceAndCheck(string virtualPath) {
640         if (virtualPath == null) {
641             throw new ArgumentNullException("virtualPath");
642         }
643
644         string path = Reduce(MakeVirtualPathAppAbsolute(virtualPath));
645
646         if (!UrlPath.VirtualPathStartsWithAppPath(path)) {
647             throw new ArgumentException(SR.GetString(SR.Invalid_app_VirtualPath, virtualPath));
648         }
649
650         return path;
651     }
652
653     internal static bool PathEndsWithExtraSlash(String path) {
654         if (path == null)
655             return false;
656         int l = path.Length;
657         if (l == 0 || path[l-1] != '\\')
658             return false;
659         if (l == 3 && path[1] == ':')   // c:\ case
660             return false;
661         return true;
662     }
663
664     internal static bool PathIsDriveRoot(string path) {
665         if (path != null) {
666             int l = path.Length;
667             if (l == 3 && path[1] == ':' && path[2] == '\\') {
668                 return true;
669             }
670         }
671
672         return false;
673     }
674
675     //
676     // NOTE: This function is also present in fx\src\configuration\system\configuration\urlpath.cs
677     // Please propagate any changes to that file.
678     //
679     // Determine if subpath is a subpath of path.
680     // For example, /myapp/foo.aspx is a subpath of /myapp
681     // Account for optional trailing slashes.
682     //
683     internal static bool IsEqualOrSubpath(string path, string subpath) {
684         if (String.IsNullOrEmpty(path))
685             return true;
686
687         if (String.IsNullOrEmpty(subpath))
688             return false;
689
690         //
691         // Compare up to but not including trailing slash
692         //
693         int lPath = path.Length;
694         if (path[lPath - 1] == '/') {
695             lPath -= 1;
696         }
697
698         int lSubpath = subpath.Length;
699         if (subpath[lSubpath - 1] == '/') {
700             lSubpath -= 1;
701         }
702
703         if (lSubpath < lPath)
704             return false;
705
706         if (!StringUtil.EqualsIgnoreCase(path, 0, subpath, 0, lPath))
707             return false;
708
709         // Check subpath that character following length of path is a slash
710         if (lSubpath > lPath && subpath[lPath] != '/')
711             return false;
712
713         return true;
714     }
715
716     internal static bool IsPathOnSameServer(string absUriOrLocalPath, Uri currentRequestUri)
717     {
718         // Assuming
719         // (1) currentRequestUri does belong to the THIS host
720         // (2) absUriOrLocalPath is allowed to have different scheme like file:// or https://
721         // (3) absUriOrLocalPath is allowed to point "above" the currentRequestUri path
722         Uri absUri;
723         if (!Uri.TryCreate(absUriOrLocalPath, UriKind.Absolute, out absUri)) {
724             // MSRC 11063
725             // A failure to construct absolute url (by System.Uri) doesn't implictly mean the url is relative (on the same server)
726             // Make sure the url path can't be recognized as absolute
727             return AppSettings.AllowRelaxedRelativeUrl ||
728                    ((IsRooted(absUriOrLocalPath) || IsRelativeUrl(absUriOrLocalPath)) && !absUriOrLocalPath.TrimStart(' ').StartsWith("//", StringComparison.Ordinal));
729         }
730
731         return absUri.IsLoopback || string.Equals(currentRequestUri.Host, absUri.Host, StringComparison.OrdinalIgnoreCase);
732     }
733
734
735 }
736 }