2012-07-28 00:13:06 +06:00
using System ;
using System.Collections.Generic ;
2013-05-22 07:48:42 -02:00
using System.Globalization ;
2012-07-28 00:13:06 +06:00
using System.Reflection ;
using System.IO ;
using System.Configuration ;
2017-02-08 14:45:07 +01:00
using System.Linq ;
2012-07-28 00:13:06 +06:00
using System.Web ;
using System.Text.RegularExpressions ;
2015-07-16 15:29:46 +02:00
using System.Web.Hosting ;
2012-07-28 00:13:06 +06:00
using Umbraco.Core.Configuration ;
namespace Umbraco.Core.IO
{
2012-10-10 20:44:29 +05:00
public static class IOHelper
2012-07-28 00:13:06 +06:00
{
private static string _rootDir = "" ;
2012-09-29 11:11:48 +07:00
2012-07-28 00:13:06 +06:00
// static compiled regex for faster performance
private readonly static Regex ResolveUrlPattern = new Regex ( "(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?" , RegexOptions . Compiled | RegexOptions . IgnoreCase | RegexOptions . IgnorePatternWhitespace ) ;
public static char DirSepChar
{
get
{
return Path . DirectorySeparatorChar ;
}
}
//helper to try and match the old path to a new virtual one
public static string FindFile ( string virtualPath )
{
string retval = virtualPath ;
if ( virtualPath . StartsWith ( "~" ) )
retval = virtualPath . Replace ( "~" , SystemDirectories . Root ) ;
2013-05-22 07:48:42 -02:00
if ( virtualPath . StartsWith ( "/" ) & & virtualPath . StartsWith ( SystemDirectories . Root ) = = false )
2012-07-28 00:13:06 +06:00
retval = SystemDirectories . Root + "/" + virtualPath . TrimStart ( '/' ) ;
return retval ;
}
//Replaces tildes with the root dir
public static string ResolveUrl ( string virtualPath )
{
2013-11-07 12:36:35 +02:00
if ( virtualPath . StartsWith ( "~" ) )
2012-07-28 00:13:06 +06:00
return virtualPath . Replace ( "~" , SystemDirectories . Root ) . Replace ( "//" , "/" ) ;
2013-06-27 00:04:37 +01:00
else if ( Uri . IsWellFormedUriString ( virtualPath , UriKind . Absolute ) )
return virtualPath ;
2012-07-28 00:13:06 +06:00
else
return VirtualPathUtility . ToAbsolute ( virtualPath , SystemDirectories . Root ) ;
}
2015-07-16 10:40:43 +02:00
public static Attempt < string > TryResolveUrl ( string virtualPath )
{
try
{
if ( virtualPath . StartsWith ( "~" ) )
return Attempt . Succeed ( virtualPath . Replace ( "~" , SystemDirectories . Root ) . Replace ( "//" , "/" ) ) ;
if ( Uri . IsWellFormedUriString ( virtualPath , UriKind . Absolute ) )
return Attempt . Succeed ( virtualPath ) ;
return Attempt . Succeed ( VirtualPathUtility . ToAbsolute ( virtualPath , SystemDirectories . Root ) ) ;
}
catch ( Exception ex )
{
return Attempt . Fail ( virtualPath , ex ) ;
}
}
[Obsolete("Use Umbraco.Web.Templates.TemplateUtilities.ResolveUrlsFromTextString instead, this method on this class will be removed in future versions")]
2012-10-10 20:44:29 +05:00
internal static string ResolveUrlsFromTextString ( string text )
2012-07-28 00:13:06 +06:00
{
2013-09-25 19:23:41 +10:00
if ( UmbracoConfig . For . UmbracoSettings ( ) . Content . ResolveUrlsFromTextString )
2012-09-29 11:11:48 +07:00
{
2015-01-16 12:07:29 +11:00
using ( DisposableTimer . DebugDuration ( typeof ( IOHelper ) , "ResolveUrlsFromTextString starting" , "ResolveUrlsFromTextString complete" ) )
2012-09-29 11:11:48 +07:00
{
// find all relative urls (ie. urls that contain ~)
var tags = ResolveUrlPattern . Matches ( text ) ;
2015-01-16 12:07:29 +11:00
2012-09-29 11:11:48 +07:00
foreach ( Match tag in tags )
{
string url = "" ;
if ( tag . Groups [ 1 ] . Success )
url = tag . Groups [ 1 ] . Value ;
2015-07-16 15:29:46 +02:00
if ( String . IsNullOrEmpty ( url ) = = false )
2012-09-29 11:11:48 +07:00
{
string resolvedUrl = ( url . Substring ( 0 , 1 ) = = "/" ) ? ResolveUrl ( url . Substring ( 1 ) ) : ResolveUrl ( url ) ;
text = text . Replace ( url , resolvedUrl ) ;
}
}
}
2012-07-28 00:13:06 +06:00
}
return text ;
}
public static string MapPath ( string path , bool useHttpContext )
{
2017-06-19 23:02:06 +10:00
if ( path = = null ) throw new ArgumentNullException ( "path" ) ;
2012-07-28 00:13:06 +06:00
// Check if the path is already mapped
2012-10-25 08:14:10 -02:00
if ( ( path . Length > = 2 & & path [ 1 ] = = Path . VolumeSeparatorChar )
| | path . StartsWith ( @"\\" ) ) //UNC Paths start with "\\". If the site is running off a network drive mapped paths will look like "\\Whatever\Boo\Bar"
{
2012-07-28 00:13:06 +06:00
return path ;
2012-10-25 08:14:10 -02:00
}
2012-07-28 00:13:06 +06:00
// Check that we even have an HttpContext! otherwise things will fail anyways
// http://umbraco.codeplex.com/workitem/30946
if ( useHttpContext & & HttpContext . Current ! = null )
{
//string retval;
2015-07-16 15:29:46 +02:00
if ( String . IsNullOrEmpty ( path ) = = false & & ( path . StartsWith ( "~" ) | | path . StartsWith ( SystemDirectories . Root ) ) )
return HostingEnvironment . MapPath ( path ) ;
2012-07-28 00:13:06 +06:00
else
2015-07-16 15:29:46 +02:00
return HostingEnvironment . MapPath ( "~/" + path . TrimStart ( '/' ) ) ;
2012-07-28 00:13:06 +06:00
}
var root = GetRootDirectorySafe ( ) ;
var newPath = path . TrimStart ( '~' , '/' ) . Replace ( '/' , IOHelper . DirSepChar ) ;
2013-05-22 07:48:42 -02:00
var retval = root + IOHelper . DirSepChar . ToString ( CultureInfo . InvariantCulture ) + newPath ;
2012-07-28 00:13:06 +06:00
return retval ;
}
public static string MapPath ( string path )
{
return MapPath ( path , true ) ;
}
//use a tilde character instead of the complete path
2012-10-10 20:44:29 +05:00
internal static string ReturnPath ( string settingsKey , string standardPath , bool useTilde )
2012-07-28 00:13:06 +06:00
{
string retval = ConfigurationManager . AppSettings [ settingsKey ] ;
2015-07-16 15:29:46 +02:00
if ( String . IsNullOrEmpty ( retval ) )
2012-07-28 00:13:06 +06:00
retval = standardPath ;
return retval . TrimEnd ( '/' ) ;
}
2012-10-10 20:44:29 +05:00
internal static string ReturnPath ( string settingsKey , string standardPath )
2012-07-28 00:13:06 +06:00
{
return ReturnPath ( settingsKey , standardPath , false ) ;
}
/// <summary>
2013-02-06 13:25:27 -01:00
/// Verifies that the current filepath matches a directory where the user is allowed to edit a file.
2012-07-28 00:13:06 +06:00
/// </summary>
2013-02-06 13:25:27 -01:00
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validDir">The valid directory.</param>
/// <returns>A value indicating whether the filepath is valid.</returns>
internal static bool VerifyEditPath ( string filePath , string validDir )
2012-07-28 00:13:06 +06:00
{
2015-09-03 15:11:49 +02:00
return VerifyEditPath ( filePath , new [ ] { validDir } ) ;
2013-02-06 13:25:27 -01:00
}
2012-07-28 00:13:06 +06:00
2013-02-06 13:25:27 -01:00
/// <summary>
/// Validates that the current filepath matches a directory where the user is allowed to edit a file.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validDir">The valid directory.</param>
/// <returns>True, if the filepath is valid, else an exception is thrown.</returns>
/// <exception cref="FileSecurityException">The filepath is invalid.</exception>
internal static bool ValidateEditPath ( string filePath , string validDir )
{
2013-05-22 07:48:42 -02:00
if ( VerifyEditPath ( filePath , validDir ) = = false )
2013-02-06 13:25:27 -01:00
throw new FileSecurityException ( String . Format ( "The filepath '{0}' is not within an allowed directory for this type of files" , filePath . Replace ( MapPath ( SystemDirectories . Root ) , "" ) ) ) ;
2012-07-28 00:13:06 +06:00
return true ;
}
2013-02-06 13:25:27 -01:00
/// <summary>
/// Verifies that the current filepath matches one of several directories where the user is allowed to edit a file.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validDirs">The valid directories.</param>
/// <returns>A value indicating whether the filepath is valid.</returns>
internal static bool VerifyEditPath ( string filePath , IEnumerable < string > validDirs )
2012-09-27 06:30:59 -02:00
{
2015-09-08 17:48:26 +02:00
// this is called from ScriptRepository, PartialViewRepository, etc.
// filePath is the fullPath (rooted, filesystem path, can be trusted)
// validDirs are virtual paths (eg ~/Views)
//
// except that for templates, filePath actually is a virtual path
//TODO
// what's below is dirty, there are too many ways to get the root dir, etc.
// not going to fix everything today
2015-09-03 15:11:49 +02:00
var mappedRoot = MapPath ( SystemDirectories . Root ) ;
if ( filePath . StartsWith ( mappedRoot ) = = false )
filePath = MapPath ( filePath ) ;
2015-09-08 17:48:26 +02:00
// yes we can (see above)
//// don't trust what we get, it may contain relative segments
//filePath = Path.GetFullPath(filePath);
2015-09-03 15:11:49 +02:00
2012-09-27 06:30:59 -02:00
foreach ( var dir in validDirs )
{
var validDir = dir ;
2015-09-03 15:11:49 +02:00
if ( validDir . StartsWith ( mappedRoot ) = = false )
2012-09-27 06:30:59 -02:00
validDir = MapPath ( validDir ) ;
2015-09-08 17:48:26 +02:00
if ( PathStartsWith ( filePath , validDir , Path . DirectorySeparatorChar ) )
2012-09-27 06:30:59 -02:00
return true ;
}
2013-02-06 13:25:27 -01:00
return false ;
}
/// <summary>
/// Validates that the current filepath matches one of several directories where the user is allowed to edit a file.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validDirs">The valid directories.</param>
/// <returns>True, if the filepath is valid, else an exception is thrown.</returns>
/// <exception cref="FileSecurityException">The filepath is invalid.</exception>
internal static bool ValidateEditPath ( string filePath , IEnumerable < string > validDirs )
{
2013-05-22 07:48:42 -02:00
if ( VerifyEditPath ( filePath , validDirs ) = = false )
2012-09-27 06:30:59 -02:00
throw new FileSecurityException ( String . Format ( "The filepath '{0}' is not within an allowed directory for this type of files" , filePath . Replace ( MapPath ( SystemDirectories . Root ) , "" ) ) ) ;
2013-02-06 13:25:27 -01:00
return true ;
2012-09-27 06:30:59 -02:00
}
2013-02-06 13:25:27 -01:00
/// <summary>
/// Verifies that the current filepath has one of several authorized extensions.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validFileExtensions">The valid extensions.</param>
/// <returns>A value indicating whether the filepath is valid.</returns>
internal static bool VerifyFileExtension ( string filePath , List < string > validFileExtensions )
2012-07-28 00:13:06 +06:00
{
2015-09-03 15:11:49 +02:00
var ext = Path . GetExtension ( filePath ) ;
return ext ! = null & & validFileExtensions . Contains ( ext . TrimStart ( '.' ) ) ;
2013-02-06 13:25:27 -01:00
}
2012-07-28 00:13:06 +06:00
2013-02-06 13:25:27 -01:00
/// <summary>
/// Validates that the current filepath has one of several authorized extensions.
/// </summary>
/// <param name="filePath">The filepath to validate.</param>
/// <param name="validFileExtensions">The valid extensions.</param>
/// <returns>True, if the filepath is valid, else an exception is thrown.</returns>
/// <exception cref="FileSecurityException">The filepath is invalid.</exception>
internal static bool ValidateFileExtension ( string filePath , List < string > validFileExtensions )
{
2013-05-22 07:48:42 -02:00
if ( VerifyFileExtension ( filePath , validFileExtensions ) = = false )
2012-07-28 00:13:06 +06:00
throw new FileSecurityException ( String . Format ( "The extension for the current file '{0}' is not of an allowed type for this editor. This is typically controlled from either the installed MacroEngines or based on configuration in /config/umbracoSettings.config" , filePath . Replace ( MapPath ( SystemDirectories . Root ) , "" ) ) ) ;
return true ;
}
2015-09-08 17:48:26 +02:00
public static bool PathStartsWith ( string path , string root , char separator )
{
// either it is identical to root,
// or it is root + separator + anything
if ( path . StartsWith ( root , StringComparison . OrdinalIgnoreCase ) = = false ) return false ;
if ( path . Length = = root . Length ) return true ;
if ( path . Length < root . Length ) return false ;
return path [ root . Length ] = = separator ;
}
2012-07-28 00:13:06 +06:00
/// <summary>
/// Returns the path to the root of the application, by getting the path to where the assembly where this
/// method is included is present, then traversing until it's past the /bin directory. Ie. this makes it work
/// even if the assembly is in a /bin/debug or /bin/release folder
/// </summary>
/// <returns></returns>
internal static string GetRootDirectorySafe ( )
{
2015-07-16 15:29:46 +02:00
if ( String . IsNullOrEmpty ( _rootDir ) = = false )
2012-07-28 00:13:06 +06:00
{
return _rootDir ;
}
var codeBase = Assembly . GetExecutingAssembly ( ) . CodeBase ;
var uri = new Uri ( codeBase ) ;
var path = uri . LocalPath ;
var baseDirectory = Path . GetDirectoryName ( path ) ;
2015-07-16 15:29:46 +02:00
if ( String . IsNullOrEmpty ( baseDirectory ) )
2013-05-22 07:12:58 -02:00
throw new Exception ( "No root directory could be resolved. Please ensure that your Umbraco solution is correctly configured." ) ;
_rootDir = baseDirectory . Contains ( "bin" )
? baseDirectory . Substring ( 0 , baseDirectory . LastIndexOf ( "bin" , StringComparison . OrdinalIgnoreCase ) - 1 )
: baseDirectory ;
2012-07-28 00:13:06 +06:00
return _rootDir ;
}
2013-06-07 05:19:25 -02:00
internal static string GetRootDirectoryBinFolder ( )
{
2015-07-16 15:29:46 +02:00
string binFolder = String . Empty ;
if ( String . IsNullOrEmpty ( _rootDir ) )
2013-06-07 05:19:25 -02:00
{
binFolder = Assembly . GetExecutingAssembly ( ) . GetAssemblyFile ( ) . Directory . FullName ;
return binFolder ;
}
binFolder = Path . Combine ( GetRootDirectorySafe ( ) , "bin" ) ;
#if DEBUG
var debugFolder = Path . Combine ( binFolder , "debug" ) ;
if ( Directory . Exists ( debugFolder ) )
return debugFolder ;
#endif
var releaseFolder = Path . Combine ( binFolder , "release" ) ;
if ( Directory . Exists ( releaseFolder ) )
return releaseFolder ;
if ( Directory . Exists ( binFolder ) )
return binFolder ;
return _rootDir ;
}
2013-05-22 07:48:42 -02:00
/// <summary>
/// Allows you to overwrite RootDirectory, which would otherwise be resolved
/// automatically upon application start.
/// </summary>
/// <remarks>The supplied path should be the absolute path to the root of the umbraco site.</remarks>
/// <param name="rootPath"></param>
internal static void SetRootDirectory ( string rootPath )
{
_rootDir = rootPath ;
}
2012-09-19 12:45:11 -02:00
/// <summary>
/// Check to see if filename passed has any special chars in it and strips them to create a safe filename. Used to overcome an issue when Umbraco is used in IE in an intranet environment.
/// </summary>
/// <param name="filePath">The filename passed to the file handler from the upload field.</param>
/// <returns>A safe filename without any path specific chars.</returns>
internal static string SafeFileName ( string filePath )
{
2013-03-11 14:58:07 -01:00
// use string extensions
return filePath . ToSafeFileName ( ) ;
2012-09-19 12:45:11 -02:00
}
2015-07-16 15:29:46 +02:00
public static void EnsurePathExists ( string path )
{
var absolutePath = IOHelper . MapPath ( path ) ;
if ( Directory . Exists ( absolutePath ) = = false )
Directory . CreateDirectory ( absolutePath ) ;
}
public static void EnsureFileExists ( string path , string contents )
{
var absolutePath = IOHelper . MapPath ( path ) ;
if ( File . Exists ( absolutePath ) = = false )
{
using ( var writer = File . CreateText ( absolutePath ) )
{
writer . Write ( contents ) ;
}
}
2017-02-08 14:45:07 +01:00
}
/// <summary>
/// Checks if a given path is a full path including drive letter
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
// From: http://stackoverflow.com/a/35046453/5018
internal static bool IsFullPath ( this string path )
{
return string . IsNullOrWhiteSpace ( path ) = = false
& & path . IndexOfAny ( Path . GetInvalidPathChars ( ) . ToArray ( ) ) = = - 1
& & Path . IsPathRooted ( path )
& & Path . GetPathRoot ( path ) . Equals ( Path . DirectorySeparatorChar . ToString ( ) , StringComparison . Ordinal ) = = false ;
}
2017-02-08 16:19:47 +01:00
/// <summary>
/// Get properly formatted relative path from an existing absolute or relative path
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal static string GetRelativePath ( this string path )
{
if ( path . IsFullPath ( ) )
{
var rootDirectory = GetRootDirectorySafe ( ) ;
var relativePath = path . ToLowerInvariant ( ) . Replace ( rootDirectory . ToLowerInvariant ( ) , string . Empty ) ;
path = relativePath ;
}
2017-02-10 09:25:16 +01:00
return path . EnsurePathIsApplicationRootPrefixed ( ) ;
2017-02-08 16:19:47 +01:00
}
/// <summary>
/// Ensures that a path has `~/` as prefix
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
2017-02-10 09:25:16 +01:00
internal static string EnsurePathIsApplicationRootPrefixed ( this string path )
2017-02-08 16:19:47 +01:00
{
2017-02-10 09:25:16 +01:00
if ( path . StartsWith ( "~/" ) )
return path ;
2017-02-08 16:19:47 +01:00
if ( path . StartsWith ( "/" ) = = false & & path . StartsWith ( "\\" ) = = false )
path = string . Format ( "/{0}" , path ) ;
if ( path . StartsWith ( "~" ) = = false )
path = string . Format ( "~{0}" , path ) ;
return path ;
}
2012-07-28 00:13:06 +06:00
}
}