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-05-12 14:49:44 +02:00
using System.Linq ;
2012-07-28 00:13:06 +06:00
using System.Web ;
2015-07-16 15:29:46 +02:00
using System.Web.Hosting ;
2017-10-16 18:26:03 +02:00
using System.IO.Compression ;
2012-07-28 00:13:06 +06:00
namespace Umbraco.Core.IO
{
2017-07-20 11:21:28 +02:00
public static class IOHelper
2012-07-28 00:13:06 +06:00
{
2018-03-20 18:21:37 +01:00
/// <summary>
/// Gets or sets a value forcing Umbraco to consider it is non-hosted.
/// </summary>
/// <remarks>This should always be false, unless unit testing.</remarks>
public static bool ForceNotHosted { get ; set ; }
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
2016-11-03 10:31:44 +01:00
//private static readonly Regex ResolveUrlPattern = new Regex("(=[\"\']?)(\\W?\\~(?:.(?![\"\']?\\s+(?:\\S+)=|[>\"\']))+.)[\"\']?", RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.IgnorePatternWhitespace);
2012-07-28 00:13:06 +06:00
2018-03-20 18:21:37 +01:00
/// <summary>
/// Gets a value indicating whether Umbraco is hosted.
/// </summary>
public static bool IsHosted = > ! ForceNotHosted & & ( HttpContext . Current ! = null | | HostingEnvironment . IsHosted ) ;
2016-11-03 10:31:44 +01:00
public static char DirSepChar = > Path . DirectorySeparatorChar ;
2012-07-28 00:13:06 +06:00
2016-11-03 10:31:44 +01:00
internal static void UnZip ( string zipFilePath , string unPackDirectory , bool deleteZipFile )
2017-07-20 11:21:28 +02:00
{
// Unzip
2017-10-16 18:26:03 +02:00
var tempDir = unPackDirectory ;
2017-07-20 11:21:28 +02:00
Directory . CreateDirectory ( tempDir ) ;
2017-10-16 18:26:03 +02:00
ZipFile . ExtractToDirectory ( zipFilePath , unPackDirectory ) ;
if ( deleteZipFile )
File . Delete ( zipFilePath ) ;
2017-07-20 11:21:28 +02:00
}
2015-12-22 19:25:10 +01:00
2017-07-20 11:21:28 +02:00
//helper to try and match the old path to a new virtual one
2012-07-28 00:13:06 +06:00
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 ;
}
2018-01-19 19:08:12 +01:00
public static string ResolveVirtualUrl ( string path )
{
if ( string . IsNullOrWhiteSpace ( path ) ) return path ;
return path . StartsWith ( "~/" ) ? ResolveUrl ( path ) : path ;
}
2012-07-28 00:13:06 +06:00
//Replaces tildes with the root dir
public static string ResolveUrl ( string virtualPath )
{
2017-10-16 18:26:03 +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 ) ;
}
}
2012-07-28 00:13:06 +06:00
public static string MapPath ( string path , bool useHttpContext )
{
2017-08-14 18:21:48 +02:00
if ( path = = null ) throw new ArgumentNullException ( "path" ) ;
2018-03-20 18:21:37 +01:00
useHttpContext = useHttpContext & & IsHosted ;
2017-08-14 18:21:48 +02:00
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
}
2017-07-20 11:21:28 +02:00
// Check that we even have an HttpContext! otherwise things will fail anyways
// http://umbraco.codeplex.com/workitem/30946
2012-07-28 00:13:06 +06:00
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
}
2017-07-20 11:21:28 +02:00
var root = GetRootDirectorySafe ( ) ;
var newPath = path . TrimStart ( '~' , '/' ) . Replace ( '/' , IOHelper . DirSepChar ) ;
var retval = root + IOHelper . DirSepChar . ToString ( CultureInfo . InvariantCulture ) + newPath ;
2012-07-28 00:13:06 +06:00
2017-07-20 11:21:28 +02:00
return retval ;
2012-07-28 00:13:06 +06:00
}
public static string MapPath ( string path )
{
return MapPath ( path , true ) ;
}
2018-01-20 12:09:15 +01:00
public static string MapPathIfVirtual ( string path )
{
return path . StartsWith ( "~/" ) ? MapPath ( path ) : path ;
}
2012-07-28 00:13:06 +06:00
//use a tilde character instead of the complete path
2017-07-20 11:21:28 +02:00
internal static string ReturnPath ( string settingsKey , string standardPath , bool useTilde )
2012-07-28 00:13:06 +06:00
{
2018-04-05 12:50:00 +02:00
var retval = ConfigurationManager . AppSettings [ settingsKey ] ;
2012-07-28 00:13:06 +06:00
2018-04-05 12:50:00 +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>
/// 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 ;
}
2017-07-20 11:21:28 +02:00
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 ;
2016-05-18 10:55:19 +02:00
#endif
2013-06-07 05:19:25 -02:00
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 )
2017-07-20 11:21:28 +02:00
{
2013-05-22 07:48:42 -02:00
_rootDir = rootPath ;
2017-07-20 11:21:28 +02:00
}
2013-05-22 07:48:42 -02:00
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
2017-07-20 11:21:28 +02:00
public static void EnsurePathExists ( string path )
{
var absolutePath = 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 ) ) return ;
using ( var writer = File . CreateText ( absolutePath ) )
{
writer . Write ( contents ) ;
}
}
2017-05-12 14:49:44 +02: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 ;
}
/// <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 ;
}
return path . EnsurePathIsApplicationRootPrefixed ( ) ;
}
/// <summary>
/// Ensures that a path has `~/` as prefix
/// </summary>
/// <param name="path"></param>
/// <returns></returns>
internal static string EnsurePathIsApplicationRootPrefixed ( this string path )
{
if ( path . StartsWith ( "~/" ) )
return path ;
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
}
}