|
//------------------------------------------------------------------------------
// <copyright file="VirtualPath.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
namespace System.Web {
using System.Globalization;
using System.Collections;
using System.IO;
using System.Web.Util;
using System.Web.Hosting;
using System.Web.Caching;
using System.Security.Permissions;
using Microsoft.Win32;
[Serializable]
internal sealed class VirtualPath : IComparable {
private string _appRelativeVirtualPath;
private string _virtualPath;
// const masks into the BitVector32
private const int isWithinAppRootComputed = 0x00000001;
private const int isWithinAppRoot = 0x00000002;
private const int appRelativeAttempted = 0x00000004;
#pragma warning disable 0649
private SimpleBitVector32 flags;
#pragma warning restore 0649
#if DBG
private static char[] s_illegalVirtualPathChars = new char[] { '\0' };
// Debug only method to check that the object is in a consistent state
private void ValidateState() {
Debug.Assert(_virtualPath != null || _appRelativeVirtualPath != null);
if (_virtualPath != null) {
CheckValidVirtualPath(_virtualPath);
}
if (_appRelativeVirtualPath != null) {
Debug.Assert(UrlPath.IsAppRelativePath(_appRelativeVirtualPath));
CheckValidVirtualPath(_appRelativeVirtualPath);
}
}
private static void CheckValidVirtualPath(string virtualPath) {
Debug.Assert(virtualPath.IndexOfAny(s_illegalVirtualPathChars) < 0);
Debug.Assert(virtualPath.IndexOf('\\') < 0);
}
#endif
internal static VirtualPath RootVirtualPath = VirtualPath.Create("/");
private VirtualPath() { }
// This is called to set the appropriate virtual path field when we already know
// that the path is generally well formed.
private VirtualPath(string virtualPath) {
if (UrlPath.IsAppRelativePath(virtualPath)) {
_appRelativeVirtualPath = virtualPath;
}
else {
_virtualPath = virtualPath;
}
}
int IComparable.CompareTo(object obj) {
VirtualPath virtualPath = obj as VirtualPath;
// Make sure we're compared to another VirtualPath
if (virtualPath == null)
throw new ArgumentException();
// Check if it's the same object
if (virtualPath == this)
return 0;
return StringComparer.InvariantCultureIgnoreCase.Compare(
this.VirtualPathString, virtualPath.VirtualPathString);
}
public string VirtualPathString {
get {
if (_virtualPath == null) {
Debug.Assert(_appRelativeVirtualPath != null);
// This is not valid if we don't know the app path
if (HttpRuntime.AppDomainAppVirtualPathObject == null) {
throw new HttpException(SR.GetString(SR.VirtualPath_CantMakeAppAbsolute,
_appRelativeVirtualPath));
}
if (_appRelativeVirtualPath.Length == 1) {
_virtualPath = HttpRuntime.AppDomainAppVirtualPath;
}
else {
_virtualPath = HttpRuntime.AppDomainAppVirtualPathString +
_appRelativeVirtualPath.Substring(2);
}
}
return _virtualPath;
}
}
internal string VirtualPathStringNoTrailingSlash {
get {
return UrlPath.RemoveSlashFromPathIfNeeded(VirtualPathString);
}
}
// Return the virtual path string if we have it, otherwise null
internal string VirtualPathStringIfAvailable {
get {
return _virtualPath;
}
}
internal string AppRelativeVirtualPathStringOrNull {
get {
if (_appRelativeVirtualPath == null) {
Debug.Assert(_virtualPath != null);
// If we already tried to get it and couldn't, return null
if (flags[appRelativeAttempted])
return null;
// This is not valid if we don't know the app path
if (HttpRuntime.AppDomainAppVirtualPathObject == null) {
throw new HttpException(SR.GetString(SR.VirtualPath_CantMakeAppRelative, _virtualPath));
}
_appRelativeVirtualPath = UrlPath.MakeVirtualPathAppRelativeOrNull(_virtualPath);
// Remember that we've attempted it
flags[appRelativeAttempted] = true;
// It could be null if it's not under the app root
if (_appRelativeVirtualPath == null)
return null;
#if DBG
ValidateState();
#endif
}
return _appRelativeVirtualPath;
}
}
// Return the app relative path if possible. Otherwise, settle for the absolute.
public string AppRelativeVirtualPathString {
get {
string appRelativeVirtualPath = AppRelativeVirtualPathStringOrNull;
return (appRelativeVirtualPath != null) ? appRelativeVirtualPath : _virtualPath;
}
}
// Return the app relative virtual path string if we have it, otherwise null
internal string AppRelativeVirtualPathStringIfAvailable {
get {
return _appRelativeVirtualPath;
}
}
// Return the virtual string that's either app relative or not, depending on which
// one we already have internally. If we have both, we return absolute
internal string VirtualPathStringWhicheverAvailable {
get {
return _virtualPath != null ? _virtualPath : _appRelativeVirtualPath;
}
}
public string Extension {
get {
return UrlPath.GetExtension(VirtualPathString);
}
}
public string FileName {
get {
return UrlPath.GetFileName(VirtualPathStringNoTrailingSlash);
}
}
// If it's relative, combine it with the app root
public VirtualPath CombineWithAppRoot() {
return HttpRuntime.AppDomainAppVirtualPathObject.Combine(this);
}
public VirtualPath Combine(VirtualPath relativePath) {
if (relativePath == null)
throw new ArgumentNullException("relativePath");
// If it's not relative, return it unchanged
if (!relativePath.IsRelative)
return relativePath;
// The base of the combine should never be relative
FailIfRelativePath();
// Get either _appRelativeVirtualPath or _virtualPath
string virtualPath = VirtualPathStringWhicheverAvailable;
// Combine it with the relative
virtualPath = UrlPath.Combine(virtualPath, relativePath.VirtualPathString);
// Set the appropriate virtual path in the new object
return new VirtualPath(virtualPath);
}
// This simple version of combine should only be used when the relative
// path is known to be relative. It's more efficient, but doesn't do any
// sanity checks.
internal VirtualPath SimpleCombine(string relativePath) {
return SimpleCombine(relativePath, false /*addTrailingSlash*/);
}
internal VirtualPath SimpleCombineWithDir(string directoryName) {
return SimpleCombine(directoryName, true /*addTrailingSlash*/);
}
private VirtualPath SimpleCombine(string filename, bool addTrailingSlash) {
// The left part should always be a directory
Debug.Assert(HasTrailingSlash);
// The right part should not start or end with a slash
Debug.Assert(filename[0] != '/' && !UrlPath.HasTrailingSlash(filename));
// Use either _appRelativeVirtualPath or _virtualPath
string virtualPath = VirtualPathStringWhicheverAvailable + filename;
if (addTrailingSlash)
virtualPath += "/";
// Set the appropriate virtual path in the new object
VirtualPath combinedVirtualPath = new VirtualPath(virtualPath);
// Copy some flags over to avoid having to recalculate them
combinedVirtualPath.CopyFlagsFrom(this, isWithinAppRootComputed | isWithinAppRoot | appRelativeAttempted);
#if DBG
combinedVirtualPath.ValidateState();
#endif
return combinedVirtualPath;
}
public VirtualPath MakeRelative(VirtualPath toVirtualPath) {
VirtualPath resultVirtualPath = new VirtualPath();
// Neither path can be relative
FailIfRelativePath();
toVirtualPath.FailIfRelativePath();
// Set it directly since we know the slashes are already ok
resultVirtualPath._virtualPath = UrlPath.MakeRelative(this.VirtualPathString, toVirtualPath.VirtualPathString);
#if DBG
resultVirtualPath.ValidateState();
#endif
return resultVirtualPath;
}
public string MapPath() {
return HostingEnvironment.MapPath(this);
}
internal string MapPathInternal() {
return HostingEnvironment.MapPathInternal(this);
}
internal string MapPathInternal(bool permitNull) {
return HostingEnvironment.MapPathInternal(this, permitNull);
}
internal string MapPathInternal(VirtualPath baseVirtualDir, bool allowCrossAppMapping) {
return HostingEnvironment.MapPathInternal(this, baseVirtualDir, allowCrossAppMapping);
}
///////////// VirtualPathProvider wrapper methods /////////////
public string GetFileHash(IEnumerable virtualPathDependencies) {
return HostingEnvironment.VirtualPathProvider.GetFileHash(this, virtualPathDependencies);
}
public CacheDependency GetCacheDependency(IEnumerable virtualPathDependencies, DateTime utcStart) {
return HostingEnvironment.VirtualPathProvider.GetCacheDependency(
this, virtualPathDependencies, utcStart);
}
public bool FileExists() {
return HostingEnvironment.VirtualPathProvider.FileExists(this);
}
public bool DirectoryExists() {
return HostingEnvironment.VirtualPathProvider.DirectoryExists(this);
}
public VirtualFile GetFile() {
return HostingEnvironment.VirtualPathProvider.GetFile(this);
}
public VirtualDirectory GetDirectory() {
Debug.Assert(this.HasTrailingSlash);
return HostingEnvironment.VirtualPathProvider.GetDirectory(this);
}
public string GetCacheKey() {
return HostingEnvironment.VirtualPathProvider.GetCacheKey(this);
}
public Stream OpenFile() {
return VirtualPathProvider.OpenFile(this);
}
///////////// end of VirtualPathProvider methods /////////////
internal bool HasTrailingSlash {
get {
if (_virtualPath != null) {
return UrlPath.HasTrailingSlash(_virtualPath);
}
else {
return UrlPath.HasTrailingSlash(_appRelativeVirtualPath);
}
}
}
public bool IsWithinAppRoot {
get {
// If we don't already know it, compute it and cache it
if (!flags[isWithinAppRootComputed]) {
if (HttpRuntime.AppDomainIdInternal == null) {
Debug.Assert(false);
return true; // app domain not initialized
}
if (flags[appRelativeAttempted]) {
// If we already tried to get the app relative path, we can tell whether
// it's in the app root by checking whether it's not null
flags[isWithinAppRoot] = (_appRelativeVirtualPath != null);
}
else {
flags[isWithinAppRoot] = UrlPath.IsEqualOrSubpath(HttpRuntime.AppDomainAppVirtualPathString,
VirtualPathString);
}
flags[isWithinAppRootComputed] = true;
}
return flags[isWithinAppRoot];
}
}
internal void FailIfNotWithinAppRoot() {
if (!this.IsWithinAppRoot) {
throw new ArgumentException(SR.GetString(SR.Cross_app_not_allowed, this.VirtualPathString));
}
}
internal void FailIfRelativePath() {
if (this.IsRelative) {
throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowRelativePath, _virtualPath));
}
}
public bool IsRelative {
get {
// Note that we don't need to check for "~/", since _virtualPath never contains
// app relative paths (_appRelativeVirtualPath does)
return _virtualPath != null && _virtualPath[0] != '/';
}
}
public bool IsRoot {
get {
return _virtualPath == "/";
}
}
public VirtualPath Parent {
get {
// Getting the parent doesn't make much sense on relative paths
FailIfRelativePath();
// "/" doesn't have a parent, so return null
if (IsRoot)
return null;
// Get either _appRelativeVirtualPath or _virtualPath
string virtualPath = VirtualPathStringWhicheverAvailable;
// Get rid of the ending slash, otherwise we end up with Parent("/app/sub/") == "/app/sub/"
virtualPath = UrlPath.RemoveSlashFromPathIfNeeded(virtualPath);
// But if it's just "~", use the absolute path instead to get the parent
if (virtualPath == "~")
virtualPath = VirtualPathStringNoTrailingSlash;
int index = virtualPath.LastIndexOf('/');
Debug.Assert(index >= 0);
// e.g. the parent of "/blah" is "/"
if (index == 0)
return RootVirtualPath;
//
// Get the parent
virtualPath = virtualPath.Substring(0, index + 1);
// Set the appropriate virtual path in the new object
return new VirtualPath(virtualPath);
}
}
internal static VirtualPath Combine(VirtualPath v1, VirtualPath v2) {
// If the first is null, use the app root instead
if (v1 == null) {
v1 = HttpRuntime.AppDomainAppVirtualPathObject;
}
// If the first is still null, return the second, unless it's relative
if (v1 == null) {
v2.FailIfRelativePath();
return v2;
}
return v1.Combine(v2);
}
public static bool operator == (VirtualPath v1, VirtualPath v2) {
return VirtualPath.Equals(v1, v2);
}
public static bool operator != (VirtualPath v1, VirtualPath v2) {
return !VirtualPath.Equals(v1, v2);
}
public static bool Equals(VirtualPath v1, VirtualPath v2) {
// Check if it's the same object
if ((Object)v1 == (Object)v2) {
return true;
}
if ((Object)v1 == null || (Object)v2 == null) {
return false;
}
return EqualsHelper(v1, v2);
}
public override bool Equals(object value) {
if (value == null)
return false;
VirtualPath virtualPath = value as VirtualPath;
if ((object)virtualPath == null) {
Debug.Assert(false);
return false;
}
return EqualsHelper(virtualPath, this);
}
private static bool EqualsHelper(VirtualPath v1, VirtualPath v2) {
return StringComparer.InvariantCultureIgnoreCase.Compare(
v1.VirtualPathString, v2.VirtualPathString) == 0;
}
public override int GetHashCode() {
return StringComparer.InvariantCultureIgnoreCase.GetHashCode(VirtualPathString);
}
public override String ToString() {
// If we only have the app relative path, and we don't know the app root, return
// the app relative path instead of accessing VirtualPathString, which would throw
if (_virtualPath == null && HttpRuntime.AppDomainAppVirtualPathObject == null) {
Debug.Assert(_appRelativeVirtualPath != null);
return _appRelativeVirtualPath;
}
return VirtualPathString;
}
// Copy a set of flags from another VirtualPath object
private void CopyFlagsFrom(VirtualPath virtualPath, int mask) {
flags.IntegerValue |= virtualPath.flags.IntegerValue & mask;
}
internal static string GetVirtualPathString(VirtualPath virtualPath) {
return virtualPath == null ? null : virtualPath.VirtualPathString;
}
internal static string GetVirtualPathStringNoTrailingSlash(VirtualPath virtualPath) {
return virtualPath == null ? null : virtualPath.VirtualPathStringNoTrailingSlash;
}
internal static string GetAppRelativeVirtualPathString(VirtualPath virtualPath) {
return virtualPath == null ? null : virtualPath.AppRelativeVirtualPathString;
}
// Same as GetAppRelativeVirtualPathString, but returns "" instead of null
internal static string GetAppRelativeVirtualPathStringOrEmpty(VirtualPath virtualPath) {
return virtualPath == null ? String.Empty : virtualPath.AppRelativeVirtualPathString;
}
// Default Create method
public static VirtualPath Create(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAllPath);
}
public static VirtualPath CreateTrailingSlash(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAllPath | VirtualPathOptions.EnsureTrailingSlash);
}
public static VirtualPath CreateAllowNull(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAllPath | VirtualPathOptions.AllowNull);
}
public static VirtualPath CreateAbsolute(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath);
}
public static VirtualPath CreateNonRelative(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath);
}
public static VirtualPath CreateAbsoluteTrailingSlash(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.EnsureTrailingSlash);
}
public static VirtualPath CreateNonRelativeTrailingSlash(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath |
VirtualPathOptions.EnsureTrailingSlash);
}
public static VirtualPath CreateAbsoluteAllowNull(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowNull);
}
public static VirtualPath CreateNonRelativeAllowNull(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath | VirtualPathOptions.AllowNull);
}
public static VirtualPath CreateNonRelativeTrailingSlashAllowNull(string virtualPath) {
return Create(virtualPath, VirtualPathOptions.AllowAbsolutePath | VirtualPathOptions.AllowAppRelativePath |
VirtualPathOptions.AllowNull | VirtualPathOptions.EnsureTrailingSlash);
}
public static VirtualPath Create(string virtualPath, VirtualPathOptions options) {
// Trim it first, so that blank strings (e.g. " ") get treated as empty
if (virtualPath != null)
virtualPath = virtualPath.Trim();
// If it's empty, check whether we allow it
if (String.IsNullOrEmpty(virtualPath)) {
if ((options & VirtualPathOptions.AllowNull) != 0)
return null;
throw new ArgumentNullException("virtualPath");
}
// Dev10 767308: optimize for normal paths, and scan once for
// i) invalid chars
// ii) slashes
// iii) '.'
bool slashes = false;
bool dot = false;
int len = virtualPath.Length;
unsafe {
fixed (char * p = virtualPath) {
for (int i = 0; i < len; i++) {
switch (p[i]) {
// need to fix slashes ?
case '/':
if (i > 0 && p[i-1] == '/')
slashes = true;
break;
case '\\':
slashes = true;
break;
// contains "." or ".."
case '.':
dot = true;
break;
// invalid chars
case '\0':
throw new HttpException(SR.GetString(SR.Invalid_vpath, virtualPath));
default:
break;
}
}
}
}
if (slashes) {
// If we're supposed to fail on malformed path, then throw
if ((options & VirtualPathOptions.FailIfMalformed) != 0) {
throw new HttpException(SR.GetString(SR.Invalid_vpath, virtualPath));
}
// Flip ----lashes, and remove duplicate slashes
virtualPath = UrlPath.FixVirtualPathSlashes(virtualPath);
}
// Make sure it ends with a trailing slash if requested
if ((options & VirtualPathOptions.EnsureTrailingSlash) != 0)
virtualPath = UrlPath.AppendSlashToPathIfNeeded(virtualPath);
VirtualPath virtualPathObject = new VirtualPath();
if (UrlPath.IsAppRelativePath(virtualPath)) {
if (dot)
virtualPath = UrlPath.ReduceVirtualPath(virtualPath);
if (virtualPath[0] == UrlPath.appRelativeCharacter) {
if ((options & VirtualPathOptions.AllowAppRelativePath) == 0) {
throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowAppRelativePath, virtualPath));
}
virtualPathObject._appRelativeVirtualPath = virtualPath;
}
else {
// It's possible for the path to become absolute after calling Reduce,
// even though it started with "~/". e.g. if the app is "/app" and the path is
// "~/../hello.aspx", it becomes "/hello.aspx", which is absolute
if ((options & VirtualPathOptions.AllowAbsolutePath) == 0) {
throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowAbsolutePath, virtualPath));
}
virtualPathObject._virtualPath = virtualPath;
}
}
else {
if (virtualPath[0] != '/') {
if ((options & VirtualPathOptions.AllowRelativePath) == 0) {
throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowRelativePath, virtualPath));
}
// Don't Reduce relative paths, since the Reduce method is broken (e.g. "../foo.aspx" --> "/foo.aspx!")
//
virtualPathObject._virtualPath = virtualPath;
}
else {
if ((options & VirtualPathOptions.AllowAbsolutePath) == 0) {
throw new ArgumentException(SR.GetString(SR.VirtualPath_AllowAbsolutePath, virtualPath));
}
if (dot)
virtualPath = UrlPath.ReduceVirtualPath(virtualPath);
virtualPathObject._virtualPath = virtualPath;
}
}
#if DBG
virtualPathObject.ValidateState();
#endif
return virtualPathObject;
}
}
[Flags]
internal enum VirtualPathOptions {
AllowNull = 0x00000001,
EnsureTrailingSlash = 0x00000002,
AllowAbsolutePath = 0x00000004,
AllowAppRelativePath = 0x00000008,
AllowRelativePath = 0x00000010,
FailIfMalformed = 0x00000020,
AllowAllPath = AllowAbsolutePath | AllowAppRelativePath | AllowRelativePath,
}
}
|