1 //------------------------------------------------------------------------------
2 // <copyright file="VirtualPath.cs" company="Microsoft">
3 //     Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //------------------------------------------------------------------------------
6 
7 namespace System.Web {
8 
9     using System.Globalization;
10     using System.Collections;
11     using System.IO;
12     using System.Web.Util;
13     using System.Web.Hosting;
14     using System.Web.Caching;
15     using System.Security.Permissions;
16     using Microsoft.Win32;
17 
18     [Serializable]
19     internal sealed class VirtualPath : IComparable {
20         private string _appRelativeVirtualPath;
21         private string _virtualPath;
22 
23         // const masks into the BitVector32
24         private const int isWithinAppRootComputed           = 0x00000001;
25         private const int isWithinAppRoot                   = 0x00000002;
26         private const int appRelativeAttempted              = 0x00000004;
27 
28     #pragma warning disable 0649
29         private SimpleBitVector32 flags;
30     #pragma warning restore 0649
31 
32 #if DBG
33         private static char[] s_illegalVirtualPathChars           = new char[] { '\0' };
34 
35         // Debug only method to check that the object is in a consistent state
ValidateState()36         private void ValidateState() {
37 
38             Debug.Assert(_virtualPath != null || _appRelativeVirtualPath != null);
39 
40             if (_virtualPath != null) {
41                 CheckValidVirtualPath(_virtualPath);
42             }
43 
44             if (_appRelativeVirtualPath != null) {
45                 Debug.Assert(UrlPath.IsAppRelativePath(_appRelativeVirtualPath));
46                 CheckValidVirtualPath(_appRelativeVirtualPath);
47             }
48         }
49 
CheckValidVirtualPath(string virtualPath)50         private static void CheckValidVirtualPath(string virtualPath) {
51             Debug.Assert(virtualPath.IndexOfAny(s_illegalVirtualPathChars) < 0);
52             Debug.Assert(virtualPath.IndexOf('\\') < 0);
53         }
54 #endif
55 
56         internal static VirtualPath RootVirtualPath = VirtualPath.Create("/");
57 
VirtualPath()58         private VirtualPath() { }
59 
60         // This is called to set the appropriate virtual path field when we already know
61         // that the path is generally well formed.
VirtualPath(string virtualPath)62         private VirtualPath(string virtualPath) {
63             if (UrlPath.IsAppRelativePath(virtualPath)) {
64                 _appRelativeVirtualPath = virtualPath;
65             }
66             else {
67                 _virtualPath = virtualPath;
68             }
69         }
70 
IComparable.CompareTo(object obj)71         int IComparable.CompareTo(object obj) {
72 
73             VirtualPath virtualPath = obj as VirtualPath;
74 
75             // Make sure we're compared to another VirtualPath
76             if (virtualPath == null)
77                 throw new ArgumentException();
78 
79             // Check if it's the same object
80             if (virtualPath == this)
81                 return 0;
82 
83             return StringComparer.InvariantCultureIgnoreCase.Compare(
84                 this.VirtualPathString, virtualPath.VirtualPathString);
85         }
86 
87         public string VirtualPathString {
88             get {
89                 if (_virtualPath == null) {
90                     Debug.Assert(_appRelativeVirtualPath != null);
91 
92                     // This is not valid if we don't know the app path
93                     if (HttpRuntime.AppDomainAppVirtualPathObject == null) {
94                         throw new HttpException(SR.GetString(SR.VirtualPath_CantMakeAppAbsolute,
95                             _appRelativeVirtualPath));
96                     }
97 
98                     if (_appRelativeVirtualPath.Length == 1) {
99                         _virtualPath = HttpRuntime.AppDomainAppVirtualPath;
100                     }
101                     else {
102                         _virtualPath = HttpRuntime.AppDomainAppVirtualPathString +
103                             _appRelativeVirtualPath.Substring(2);
104                     }
105                 }
106 
107                 return _virtualPath;
108             }
109         }
110 
111         internal string VirtualPathStringNoTrailingSlash {
112             get {
113                 return UrlPath.RemoveSlashFromPathIfNeeded(VirtualPathString);
114             }
115         }
116 
117         // Return the virtual path string if we have it, otherwise null
118         internal string VirtualPathStringIfAvailable {
119             get {
120                 return _virtualPath;
121             }
122         }
123 
124         internal string AppRelativeVirtualPathStringOrNull {
125             get {
126                 if (_appRelativeVirtualPath == null) {
127                     Debug.Assert(_virtualPath != null);
128 
129                     // If we already tried to get it and couldn't, return null
130                     if (flags[appRelativeAttempted])
131                         return null;
132 
133                     // This is not valid if we don't know the app path
134                     if (HttpRuntime.AppDomainAppVirtualPathObject == null) {
135                         throw new HttpException(SR.GetString(SR.VirtualPath_CantMakeAppRelative, _virtualPath));
136                     }
137 
138                     _appRelativeVirtualPath = UrlPath.MakeVirtualPathAppRelativeOrNull(_virtualPath);
139 
140                     // Remember that we've attempted it
141                     flags[appRelativeAttempted] = true;
142 
143                     // It could be null if it's not under the app root
144                     if (_appRelativeVirtualPath == null)
145                         return null;
146 #if DBG
147                     ValidateState();
148 #endif
149                 }
150 
151                 return _appRelativeVirtualPath;
152             }
153         }
154 
155         // Return the app relative path if possible. Otherwise, settle for the absolute.
156         public string AppRelativeVirtualPathString {
157             get {
158                 string appRelativeVirtualPath = AppRelativeVirtualPathStringOrNull;
159                 return (appRelativeVirtualPath != null) ? appRelativeVirtualPath : _virtualPath;
160             }
161         }
162 
163         // Return the app relative virtual path string if we have it, otherwise null
164         internal string AppRelativeVirtualPathStringIfAvailable {
165             get {
166                 return _appRelativeVirtualPath;
167             }
168         }
169         // Return the virtual string that's either app relative or not, depending on which
170         // one we already have internally.  If we have both, we return absolute
171         internal string VirtualPathStringWhicheverAvailable {
172             get {
173                 return _virtualPath != null ? _virtualPath : _appRelativeVirtualPath;
174             }
175         }
176 
177         public string Extension {
178             get {
179                 return UrlPath.GetExtension(VirtualPathString);
180             }
181         }
182 
183         public string FileName {
184             get {
185                 return UrlPath.GetFileName(VirtualPathStringNoTrailingSlash);
186             }
187         }
188 
189         // If it's relative, combine it with the app root
CombineWithAppRoot()190         public VirtualPath CombineWithAppRoot() {
191             return HttpRuntime.AppDomainAppVirtualPathObject.Combine(this);
192         }
193 
Combine(VirtualPath relativePath)194         public VirtualPath Combine(VirtualPath relativePath) {
195             if (relativePath == null)
196                 throw new ArgumentNullException("relativePath");
197 
198             // If it's not relative, return it unchanged
199             if (!relativePath.IsRelative)
200                 return relativePath;
201 
202             // The base of the combine should never be relative
203             FailIfRelativePath();
204 
205             // Get either _appRelativeVirtualPath or _virtualPath
206             string virtualPath = VirtualPathStringWhicheverAvailable;
207 
208             // Combine it with the relative
209             virtualPath = UrlPath.Combine(virtualPath, relativePath.VirtualPathString);
210 
211             // Set the appropriate virtual path in the new object
212             return new VirtualPath(virtualPath);
213         }
214 
215         // This simple version of combine should only be used when the relative
216         // path is known to be relative.  It's more efficient, but doesn't do any
217         // sanity checks.
SimpleCombine(string relativePath)218         internal VirtualPath SimpleCombine(string relativePath) {
219             return SimpleCombine(relativePath, false /*addTrailingSlash*/);
220         }
221 
SimpleCombineWithDir(string directoryName)222         internal VirtualPath SimpleCombineWithDir(string directoryName) {
223             return SimpleCombine(directoryName, true /*addTrailingSlash*/);
224         }
225 
SimpleCombine(string filename, bool addTrailingSlash)226         private VirtualPath SimpleCombine(string filename, bool addTrailingSlash) {
227 
228             // The left part should always be a directory
229             Debug.Assert(HasTrailingSlash);
230 
231             // The right part should not start or end with a slash
232             Debug.Assert(filename[0] != '/' && !UrlPath.HasTrailingSlash(filename));
233 
234             // Use either _appRelativeVirtualPath or _virtualPath
235             string virtualPath = VirtualPathStringWhicheverAvailable + filename;
236             if (addTrailingSlash)
237                 virtualPath += "/";
238 
239             // Set the appropriate virtual path in the new object
240             VirtualPath combinedVirtualPath = new VirtualPath(virtualPath);
241 
242             // Copy some flags over to avoid having to recalculate them
243             combinedVirtualPath.CopyFlagsFrom(this, isWithinAppRootComputed | isWithinAppRoot | appRelativeAttempted);
244 #if DBG
245             combinedVirtualPath.ValidateState();
246 #endif
247             return combinedVirtualPath;
248         }
249 
MakeRelative(VirtualPath toVirtualPath)250         public VirtualPath MakeRelative(VirtualPath toVirtualPath) {
251             VirtualPath resultVirtualPath = new VirtualPath();
252 
253             // Neither path can be relative
254             FailIfRelativePath();
255             toVirtualPath.FailIfRelativePath();
256 
257             // Set it directly since we know the slashes are already ok
258             resultVirtualPath._virtualPath = UrlPath.MakeRelative(this.VirtualPathString, toVirtualPath.VirtualPathString);
259 #if DBG
260             resultVirtualPath.ValidateState();
261 #endif
262             return resultVirtualPath;
263         }
264 
MapPath()265         public string MapPath() {
266             return HostingEnvironment.MapPath(this);
267         }
268 
MapPathInternal()269         internal string MapPathInternal() {
270             return HostingEnvironment.MapPathInternal(this);
271         }
272 
MapPathInternal(bool permitNull)273         internal string MapPathInternal(bool permitNull) {
274             return HostingEnvironment.MapPathInternal(this, permitNull);
275         }
276 
MapPathInternal(VirtualPath baseVirtualDir, bool allowCrossAppMapping)277         internal string MapPathInternal(VirtualPath baseVirtualDir, bool allowCrossAppMapping) {
278             return HostingEnvironment.MapPathInternal(this, baseVirtualDir, allowCrossAppMapping);
279         }
280 
281         ///////////// VirtualPathProvider wrapper methods /////////////
282 
GetFileHash(IEnumerable virtualPathDependencies)283         public string GetFileHash(IEnumerable virtualPathDependencies) {
284             return HostingEnvironment.VirtualPathProvider.GetFileHash(this, virtualPathDependencies);
285         }
286 
GetCacheDependency(IEnumerable virtualPathDependencies, DateTime utcStart)287         public CacheDependency GetCacheDependency(IEnumerable virtualPathDependencies, DateTime utcStart) {
288             return HostingEnvironment.VirtualPathProvider.GetCacheDependency(
289                 this, virtualPathDependencies, utcStart);
290         }
291 
FileExists()292         public bool FileExists() {
293             return HostingEnvironment.VirtualPathProvider.FileExists(this);
294         }
295 
DirectoryExists()296         public bool DirectoryExists() {
297             return HostingEnvironment.VirtualPathProvider.DirectoryExists(this);
298         }
299 
GetFile()300         public VirtualFile GetFile() {
301             return HostingEnvironment.VirtualPathProvider.GetFile(this);
302         }
303 
GetDirectory()304         public VirtualDirectory GetDirectory() {
305             Debug.Assert(this.HasTrailingSlash);
306             return HostingEnvironment.VirtualPathProvider.GetDirectory(this);
307         }
308 
GetCacheKey()309         public string GetCacheKey() {
310             return HostingEnvironment.VirtualPathProvider.GetCacheKey(this);
311         }
312 
OpenFile()313         public Stream OpenFile() {
314             return VirtualPathProvider.OpenFile(this);
315         }
316 
317         ///////////// end of VirtualPathProvider methods /////////////
318 
319 
320         internal bool HasTrailingSlash {
321             get {
322                 if (_virtualPath != null) {
323                     return UrlPath.HasTrailingSlash(_virtualPath);
324                 }
325                 else {
326                     return UrlPath.HasTrailingSlash(_appRelativeVirtualPath);
327                 }
328             }
329         }
330 
331         public bool IsWithinAppRoot {
332             get {
333                 // If we don't already know it, compute it and cache it
334                 if (!flags[isWithinAppRootComputed]) {
335                     if (HttpRuntime.AppDomainIdInternal == null) {
336                         Debug.Assert(false);
337                         return true;    // app domain not initialized
338                     }
339 
340                     if (flags[appRelativeAttempted]) {
341                         // If we already tried to get the app relative path, we can tell whether
342                         // it's in the app root by checking whether it's not null
343                         flags[isWithinAppRoot] = (_appRelativeVirtualPath != null);
344                     }
345                     else {
346                         flags[isWithinAppRoot] = UrlPath.IsEqualOrSubpath(HttpRuntime.AppDomainAppVirtualPathString,
347                             VirtualPathString);
348                     }
349 
350                     flags[isWithinAppRootComputed] = true;
351                 }
352 
353                 return flags[isWithinAppRoot];
354             }
355         }
356 
FailIfNotWithinAppRoot()357         internal void FailIfNotWithinAppRoot() {
358             if (!this.IsWithinAppRoot) {
359                 throw new ArgumentException(SR.GetString(SR.Cross_app_not_allowed, this.VirtualPathString));
360             }
361         }
362 
FailIfRelativePath()363         internal void FailIfRelativePath() {
364             if (this.IsRelative) {
365                 throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowRelativePath, _virtualPath));
366             }
367         }
368 
369         public bool IsRelative {
370             get {
371                 // Note that we don't need to check for "~/", since _virtualPath never contains
372                 // app relative paths (_appRelativeVirtualPath does)
373                 return _virtualPath != null && _virtualPath[0] != '/';
374             }
375         }
376 
377         public bool IsRoot {
378             get {
379                 return _virtualPath == "/";
380             }
381         }
382 
383         public VirtualPath Parent {
384             get {
385                 // Getting the parent doesn't make much sense on relative paths
386                 FailIfRelativePath();
387 
388                 // "/" doesn't have a parent, so return null
389                 if (IsRoot)
390                     return null;
391 
392                 // Get either _appRelativeVirtualPath or _virtualPath
393                 string virtualPath = VirtualPathStringWhicheverAvailable;
394 
395                 // Get rid of the ending slash, otherwise we end up with Parent("/app/sub/") == "/app/sub/"
396                 virtualPath = UrlPath.RemoveSlashFromPathIfNeeded(virtualPath);
397 
398                 // But if it's just "~", use the absolute path instead to get the parent
399                 if (virtualPath == "~")
400                     virtualPath = VirtualPathStringNoTrailingSlash;
401 
402                 int index = virtualPath.LastIndexOf('/');
403                 Debug.Assert(index >= 0);
404 
405                 // e.g. the parent of "/blah" is "/"
406                 if (index == 0)
407                     return RootVirtualPath;
408 
409                 //
410 
411                 // Get the parent
412                 virtualPath = virtualPath.Substring(0, index + 1);
413 
414                 // Set the appropriate virtual path in the new object
415                 return new VirtualPath(virtualPath);
416             }
417         }
418 
Combine(VirtualPath v1, VirtualPath v2)419         internal static VirtualPath Combine(VirtualPath v1, VirtualPath v2) {
420 
421             // If the first is null, use the app root instead
422             if (v1 == null) {
423                 v1 = HttpRuntime.AppDomainAppVirtualPathObject;
424             }
425 
426             // If the first is still null, return the second, unless it's relative
427             if (v1 == null) {
428                 v2.FailIfRelativePath();
429                 return v2;
430             }
431 
432             return v1.Combine(v2);
433         }
434 
operator ==(VirtualPath v1, VirtualPath v2)435         public static bool operator == (VirtualPath v1, VirtualPath v2) {
436             return VirtualPath.Equals(v1, v2);
437         }
438 
operator !=(VirtualPath v1, VirtualPath v2)439         public static bool operator != (VirtualPath v1, VirtualPath v2) {
440             return !VirtualPath.Equals(v1, v2);
441         }
442 
Equals(VirtualPath v1, VirtualPath v2)443         public static bool Equals(VirtualPath v1, VirtualPath v2) {
444 
445             // Check if it's the same object
446             if ((Object)v1 == (Object)v2) {
447                 return true;
448             }
449 
450             if ((Object)v1 == null || (Object)v2 == null) {
451                 return false;
452             }
453 
454             return EqualsHelper(v1, v2);
455         }
456 
Equals(object value)457         public override bool Equals(object value) {
458 
459             if (value == null)
460                 return false;
461 
462             VirtualPath virtualPath = value as VirtualPath;
463             if ((object)virtualPath == null) {
464                 Debug.Assert(false);
465                 return false;
466             }
467 
468             return EqualsHelper(virtualPath, this);
469         }
470 
EqualsHelper(VirtualPath v1, VirtualPath v2)471         private static bool EqualsHelper(VirtualPath v1, VirtualPath v2) {
472             return StringComparer.InvariantCultureIgnoreCase.Compare(
473                 v1.VirtualPathString, v2.VirtualPathString) == 0;
474         }
475 
GetHashCode()476         public override int GetHashCode() {
477             return StringComparer.InvariantCultureIgnoreCase.GetHashCode(VirtualPathString);
478         }
479 
ToString()480         public override String ToString() {
481             // If we only have the app relative path, and we don't know the app root, return
482             // the app relative path instead of accessing VirtualPathString, which would throw
483             if (_virtualPath == null && HttpRuntime.AppDomainAppVirtualPathObject == null) {
484                 Debug.Assert(_appRelativeVirtualPath != null);
485                 return _appRelativeVirtualPath;
486             }
487 
488             return VirtualPathString;
489         }
490 
491         // Copy a set of flags from another VirtualPath object
CopyFlagsFrom(VirtualPath virtualPath, int mask)492         private void CopyFlagsFrom(VirtualPath virtualPath, int mask) {
493             flags.IntegerValue |= virtualPath.flags.IntegerValue & mask;
494         }
495 
GetVirtualPathString(VirtualPath virtualPath)496         internal static string GetVirtualPathString(VirtualPath virtualPath) {
497             return virtualPath == null ? null : virtualPath.VirtualPathString;
498         }
499 
GetVirtualPathStringNoTrailingSlash(VirtualPath virtualPath)500         internal static string GetVirtualPathStringNoTrailingSlash(VirtualPath virtualPath) {
501             return virtualPath == null ? null : virtualPath.VirtualPathStringNoTrailingSlash;
502         }
503 
GetAppRelativeVirtualPathString(VirtualPath virtualPath)504         internal static string GetAppRelativeVirtualPathString(VirtualPath virtualPath) {
505             return virtualPath == null ? null : virtualPath.AppRelativeVirtualPathString;
506         }
507 
508         // Same as GetAppRelativeVirtualPathString, but returns "" instead of null
GetAppRelativeVirtualPathStringOrEmpty(VirtualPath virtualPath)509         internal static string GetAppRelativeVirtualPathStringOrEmpty(VirtualPath virtualPath) {
510             return virtualPath == null ? String.Empty : virtualPath.AppRelativeVirtualPathString;
511         }
512 
513         // Default Create method
Create(string virtualPath)514         public static VirtualPath Create(string virtualPath) {
515             return Create(virtualPath, VirtualPathOptions.AllowAllPath);
516         }
517 
CreateTrailingSlash(string virtualPath)518         public static VirtualPath CreateTrailingSlash(string virtualPath) {
519             return Create(virtualPath, VirtualPathOptions.AllowAllPath | VirtualPathOptions.EnsureTrailingSlash);
520         }
521 
CreateAllowNull(string virtualPath)522         public static VirtualPath CreateAllowNull(string virtualPath) {
523             return Create(virtualPath, VirtualPathOptions.AllowAllPath | VirtualPathOptions.AllowNull);
524         }
525 
CreateAbsolute(string virtualPath)526         public static VirtualPath CreateAbsolute(string virtualPath) {
527             return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath);
528         }
529 
CreateNonRelative(string virtualPath)530         public static VirtualPath CreateNonRelative(string virtualPath) {
531             return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath);
532         }
533 
CreateAbsoluteTrailingSlash(string virtualPath)534         public static VirtualPath CreateAbsoluteTrailingSlash(string virtualPath) {
535             return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.EnsureTrailingSlash);
536         }
537 
CreateNonRelativeTrailingSlash(string virtualPath)538         public static VirtualPath CreateNonRelativeTrailingSlash(string virtualPath) {
539             return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath |
540                 VirtualPathOptions.EnsureTrailingSlash);
541         }
542 
CreateAbsoluteAllowNull(string virtualPath)543         public static VirtualPath CreateAbsoluteAllowNull(string virtualPath) {
544             return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowNull);
545         }
546 
CreateNonRelativeAllowNull(string virtualPath)547         public static VirtualPath CreateNonRelativeAllowNull(string virtualPath) {
548             return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath | VirtualPathOptions.AllowNull);
549         }
550 
CreateNonRelativeTrailingSlashAllowNull(string virtualPath)551         public static VirtualPath CreateNonRelativeTrailingSlashAllowNull(string virtualPath) {
552             return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath |
553                 VirtualPathOptions.AllowNull | VirtualPathOptions.EnsureTrailingSlash);
554         }
555 
Create(string virtualPath, VirtualPathOptions options)556         public static VirtualPath Create(string virtualPath, VirtualPathOptions options) {
557 
558             // Trim it first, so that blank strings (e.g. "  ") get treated as empty
559             if (virtualPath != null)
560                 virtualPath = virtualPath.Trim();
561 
562             // If it's empty, check whether we allow it
563             if (String.IsNullOrEmpty(virtualPath)) {
564                 if ((options & VirtualPathOptions.AllowNull) != 0)
565                     return null;
566 
567                 throw new ArgumentNullException("virtualPath");
568             }
569 
570             // Dev10 767308: optimize for normal paths, and scan once for
571             //     i) invalid chars
572             //    ii) slashes
573             //   iii) '.'
574 
575             bool slashes = false;
576             bool dot = false;
577             int len = virtualPath.Length;
578             unsafe {
579                 fixed (char * p = virtualPath) {
580                     for (int i = 0; i < len; i++) {
581                         switch (p[i]) {
582                             // need to fix slashes ?
583                             case '/':
584                                 if (i > 0 && p[i-1] == '/')
585                                     slashes = true;
586                                 break;
587                             case '\\':
588                                 slashes = true;
589                                 break;
590                             // contains "." or ".."
591                             case '.':
592                                 dot = true;
593                                 break;
594                             // invalid chars
595                             case '\0':
596                                 throw new HttpException(SR.GetString(SR.Invalid_vpath, virtualPath));
597                             default:
598                                 break;
599                         }
600                     }
601                 }
602             }
603 
604             if (slashes) {
605                 // If we're supposed to fail on malformed path, then throw
606                 if ((options & VirtualPathOptions.FailIfMalformed) != 0) {
607                     throw new HttpException(SR.GetString(SR.Invalid_vpath, virtualPath));
608                 }
609                 // Flip ----lashes, and remove duplicate slashes
610                 virtualPath = UrlPath.FixVirtualPathSlashes(virtualPath);
611             }
612 
613             // Make sure it ends with a trailing slash if requested
614             if ((options & VirtualPathOptions.EnsureTrailingSlash) != 0)
615                 virtualPath = UrlPath.AppendSlashToPathIfNeeded(virtualPath);
616 
617             VirtualPath virtualPathObject = new VirtualPath();
618 
619             if (UrlPath.IsAppRelativePath(virtualPath)) {
620 
621                 if (dot)
622                     virtualPath = UrlPath.ReduceVirtualPath(virtualPath);
623 
624                 if (virtualPath[0] == UrlPath.appRelativeCharacter) {
625                     if ((options & VirtualPathOptions.AllowAppRelativePath) == 0) {
626                         throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowAppRelativePath, virtualPath));
627                     }
628 
629                     virtualPathObject._appRelativeVirtualPath = virtualPath;
630                 }
631                 else {
632                     // It's possible for the path to become absolute after calling Reduce,
633                     // even though it started with "~/".  e.g. if the app is "/app" and the path is
634                     // "~/../hello.aspx", it becomes "/hello.aspx", which is absolute
635 
636                     if ((options & VirtualPathOptions.AllowAbsolutePath) == 0) {
637                         throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowAbsolutePath, virtualPath));
638                     }
639 
640                     virtualPathObject._virtualPath = virtualPath;
641                 }
642             }
643             else {
644                 if (virtualPath[0] != '/') {
645                     if ((options & VirtualPathOptions.AllowRelativePath) == 0) {
646                         throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowRelativePath, virtualPath));
647                     }
648 
649                     // Don't Reduce relative paths, since the Reduce method is broken (e.g. "../foo.aspx" --> "/foo.aspx!")
650                     //
651                     virtualPathObject._virtualPath = virtualPath;
652                 }
653                 else {
654                     if ((options & VirtualPathOptions.AllowAbsolutePath) == 0) {
655                         throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowAbsolutePath, virtualPath));
656                     }
657 
658                     if (dot)
659                         virtualPath = UrlPath.ReduceVirtualPath(virtualPath);
660 
661                     virtualPathObject._virtualPath = virtualPath;
662                 }
663             }
664 #if DBG
665             virtualPathObject.ValidateState();
666 #endif
667             return virtualPathObject;
668         }
669     }
670 
671     [Flags]
672     internal enum VirtualPathOptions {
673         AllowNull               = 0x00000001,
674         EnsureTrailingSlash     = 0x00000002,
675         AllowAbsolutePath       = 0x00000004,
676         AllowAppRelativePath    = 0x00000008,
677         AllowRelativePath       = 0x00000010,
678         FailIfMalformed         = 0x00000020,
679 
680         AllowAllPath = AllowAbsolutePath | AllowAppRelativePath | AllowRelativePath,
681     }
682 }
683