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 ;
2016-05-18 10:55:19 +02:00
using System.Linq ;
2012-07-28 00:13:06 +06:00
using System.Web ;
using System.Text.RegularExpressions ;
2016-05-18 10:55:19 +02:00
using System.Threading.Tasks ;
2015-07-16 15:29:46 +02:00
using System.Web.Hosting ;
2015-12-22 19:25:10 +01:00
using ICSharpCode.SharpZipLib.Zip ;
2012-07-28 00:13:06 +06:00
using Umbraco.Core.Configuration ;
2012-09-29 11:11:48 +07:00
using Umbraco.Core.Logging ;
2012-07-28 00:13:06 +06:00
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 ;
}
}
2015-12-22 19:25:10 +01:00
internal static void UnZip ( string zipFilePath , string unPackDirectory , bool deleteZipFile )
{
// Unzip
string tempDir = unPackDirectory ;
Directory . CreateDirectory ( tempDir ) ;
2016-05-31 19:24:33 +02:00
//TODO: Get rid of SharpZipLib library
2015-12-22 19:25:10 +01:00
using ( ZipInputStream s = new ZipInputStream ( File . OpenRead ( zipFilePath ) ) )
{
ZipEntry theEntry ;
while ( ( theEntry = s . GetNextEntry ( ) ) ! = null )
{
string directoryName = Path . GetDirectoryName ( theEntry . Name ) ;
string fileName = Path . GetFileName ( theEntry . Name ) ;
if ( fileName ! = String . Empty )
{
FileStream streamWriter = File . Create ( tempDir + Path . DirectorySeparatorChar + fileName ) ;
int size = 2048 ;
byte [ ] data = new byte [ 2048 ] ;
while ( true )
{
size = s . Read ( data , 0 , data . Length ) ;
if ( size > 0 )
{
streamWriter . Write ( data , 0 , size ) ;
}
else
{
break ;
}
}
streamWriter . Close ( ) ;
}
}
// Clean up
s . Close ( ) ;
if ( deleteZipFile )
File . Delete ( zipFilePath ) ;
}
}
//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 ;
}
//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 ) ;
}
}
2012-07-28 00:13:06 +06:00
public static string MapPath ( string path , bool useHttpContext )
{
// 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>
/// 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 ;
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 )
{
_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 )
{
2016-05-18 10:55:19 +02:00
var absolutePath = MapPath ( path ) ;
2015-07-16 15:29:46 +02:00
if ( Directory . Exists ( absolutePath ) = = false )
Directory . CreateDirectory ( absolutePath ) ;
}
public static void EnsureFileExists ( string path , string contents )
{
var absolutePath = IOHelper . MapPath ( path ) ;
2016-05-18 10:55:19 +02:00
if ( File . Exists ( absolutePath ) ) return ;
using ( var writer = File . CreateText ( absolutePath ) )
2015-07-16 15:29:46 +02:00
{
2016-05-18 10:55:19 +02:00
writer . Write ( contents ) ;
2015-07-16 15:29:46 +02:00
}
}
2016-05-18 10:55:19 +02:00
/// <summary>
/// Deletes all files passed in.
/// </summary>
/// <param name="files"></param>
/// <param name="onError"></param>
/// <returns></returns>
internal static bool DeleteFiles ( IEnumerable < string > files , Action < string , Exception > onError = null )
{
//ensure duplicates are removed
files = files . Distinct ( ) ;
var allsuccess = true ;
var fs = FileSystemProviderManager . Current . GetFileSystemProvider < MediaFileSystem > ( ) ;
Parallel . ForEach ( files , file = >
{
try
{
if ( file . IsNullOrWhiteSpace ( ) ) return ;
var relativeFilePath = fs . GetRelativePath ( file ) ;
if ( fs . FileExists ( relativeFilePath ) = = false ) return ;
var parentDirectory = Path . GetDirectoryName ( relativeFilePath ) ;
// don't want to delete the media folder if not using directories.
if ( UmbracoConfig . For . UmbracoSettings ( ) . Content . UploadAllowDirectories & & parentDirectory ! = fs . GetRelativePath ( "/" ) )
{
//issue U4-771: if there is a parent directory the recursive parameter should be true
fs . DeleteDirectory ( parentDirectory , String . IsNullOrEmpty ( parentDirectory ) = = false ) ;
}
else
{
fs . DeleteFile ( file , true ) ;
}
}
catch ( Exception e )
{
onError ? . Invoke ( file , e ) ;
allsuccess = false ;
}
} ) ;
return allsuccess ;
}
2012-07-28 00:13:06 +06:00
}
}