2012-08-13 10:04:31 -01:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
2015-09-07 12:38:46 +02:00
|
|
|
|
using System.IO;
|
2012-08-13 10:04:31 -01:00
|
|
|
|
using System.Linq;
|
2012-11-29 11:56:33 -01:00
|
|
|
|
using Umbraco.Core.Logging;
|
2012-08-13 10:04:31 -01:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.IO
|
|
|
|
|
|
{
|
2013-01-15 01:19:51 +03:00
|
|
|
|
public class PhysicalFileSystem : IFileSystem
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-10 14:10:45 +02:00
|
|
|
|
// the rooted, filesystem path, using directory separator chars, NOT ending with a separator
|
|
|
|
|
|
// eg "c:" or "c:\path\to\site" or "\\server\path"
|
2015-09-07 12:38:46 +02:00
|
|
|
|
private readonly string _rootPath;
|
2015-09-10 14:10:45 +02:00
|
|
|
|
|
|
|
|
|
|
// the ??? url, using url separator chars, NOT ending with a separator
|
|
|
|
|
|
// eg "" (?) or "/Scripts" or ???
|
2012-08-13 10:04:31 -01:00
|
|
|
|
private readonly string _rootUrl;
|
|
|
|
|
|
|
2012-08-13 10:23:45 -01:00
|
|
|
|
public PhysicalFileSystem(string virtualRoot)
|
|
|
|
|
|
{
|
2013-01-15 01:15:08 +03:00
|
|
|
|
if (virtualRoot == null) throw new ArgumentNullException("virtualRoot");
|
2014-12-01 14:14:44 +11:00
|
|
|
|
if (virtualRoot.StartsWith("~/") == false)
|
2013-01-15 01:15:08 +03:00
|
|
|
|
throw new ArgumentException("The virtualRoot argument must be a virtual path and start with '~/'");
|
|
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
_rootPath = IOHelper.MapPath(virtualRoot);
|
|
|
|
|
|
_rootPath = EnsureDirectorySeparatorChar(_rootPath);
|
|
|
|
|
|
_rootPath = _rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
|
|
|
|
|
|
2013-01-14 11:02:12 -01:00
|
|
|
|
_rootUrl = IOHelper.ResolveUrl(virtualRoot);
|
2015-09-10 14:10:45 +02:00
|
|
|
|
_rootUrl = EnsureUrlSeparatorChar(_rootUrl);
|
2015-09-07 12:38:46 +02:00
|
|
|
|
_rootUrl = _rootUrl.TrimEnd('/');
|
2012-08-13 10:23:45 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-13 10:04:31 -01:00
|
|
|
|
public PhysicalFileSystem(string rootPath, string rootUrl)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (string.IsNullOrEmpty(rootPath))
|
|
|
|
|
|
throw new ArgumentException("The argument 'rootPath' cannot be null or empty.");
|
|
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(rootUrl))
|
|
|
|
|
|
throw new ArgumentException("The argument 'rootUrl' cannot be null or empty.");
|
|
|
|
|
|
|
2013-01-15 01:15:08 +03:00
|
|
|
|
if (rootPath.StartsWith("~/"))
|
|
|
|
|
|
throw new ArgumentException("The rootPath argument cannot be a virtual path and cannot start with '~/'");
|
|
|
|
|
|
|
2015-09-03 16:24:43 +02:00
|
|
|
|
// rootPath should be... rooted, as in, it's a root path!
|
|
|
|
|
|
// but the test suite App.config cannot really "root" anything so we'll have to do it here
|
|
|
|
|
|
|
|
|
|
|
|
//var localRoot = AppDomain.CurrentDomain.BaseDirectory;
|
|
|
|
|
|
var localRoot = IOHelper.GetRootDirectorySafe();
|
|
|
|
|
|
if (Path.IsPathRooted(rootPath) == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
rootPath = Path.Combine(localRoot, rootPath);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-09-10 14:10:45 +02:00
|
|
|
|
rootPath = EnsureDirectorySeparatorChar(rootPath);
|
|
|
|
|
|
rootUrl = EnsureUrlSeparatorChar(rootUrl);
|
|
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
_rootPath = rootPath.TrimEnd(Path.DirectorySeparatorChar);
|
|
|
|
|
|
_rootUrl = rootUrl.TrimEnd('/');
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public IEnumerable<string> GetDirectories(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fullPath = GetFullPath(path);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
if (Directory.Exists(fullPath))
|
|
|
|
|
|
return Directory.EnumerateDirectories(fullPath).Select(GetRelativePath);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
catch (UnauthorizedAccessException ex)
|
2012-12-26 06:49:54 -01:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.Error<PhysicalFileSystem>("Not authorized to get directories", ex);
|
|
|
|
|
|
}
|
2012-08-13 10:04:31 -01:00
|
|
|
|
catch (DirectoryNotFoundException ex)
|
2012-12-26 06:49:54 -01:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.Error<PhysicalFileSystem>("Directory not found", ex);
|
|
|
|
|
|
}
|
2012-08-13 10:04:31 -01:00
|
|
|
|
|
|
|
|
|
|
return Enumerable.Empty<string>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public void DeleteDirectory(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
|
|
|
|
|
DeleteDirectory(path, false);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public void DeleteDirectory(string path, bool recursive)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fullPath = GetFullPath(path);
|
|
|
|
|
|
if (Directory.Exists(fullPath) == false)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
Directory.Delete(fullPath, recursive);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
catch (DirectoryNotFoundException ex)
|
2012-12-26 06:49:54 -01:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.Error<PhysicalFileSystem>("Directory not found", ex);
|
|
|
|
|
|
}
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public bool DirectoryExists(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fullPath = GetFullPath(path);
|
|
|
|
|
|
return Directory.Exists(fullPath);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public void AddFile(string path, Stream stream)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
|
|
|
|
|
AddFile(path, stream, true);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public void AddFile(string path, Stream stream, bool overrideIfExists)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fullPath = GetFullPath(path);
|
|
|
|
|
|
var exists = File.Exists(fullPath);
|
|
|
|
|
|
if (exists && overrideIfExists == false)
|
|
|
|
|
|
throw new InvalidOperationException(string.Format("A file at path '{0}' already exists", path));
|
2014-10-22 15:52:32 +10:00
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
Directory.CreateDirectory(Path.GetDirectoryName(fullPath)); // ensure it exists
|
2012-08-13 10:04:31 -01:00
|
|
|
|
|
2012-08-20 10:51:14 -01:00
|
|
|
|
if (stream.CanSeek)
|
|
|
|
|
|
stream.Seek(0, 0);
|
|
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
using (var destination = (Stream)File.Create(fullPath))
|
2012-08-13 10:04:31 -01:00
|
|
|
|
stream.CopyTo(destination);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public IEnumerable<string> GetFiles(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
|
|
|
|
|
return GetFiles(path, "*.*");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public IEnumerable<string> GetFiles(string path, string filter)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fullPath = GetFullPath(path);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2014-10-22 15:52:32 +10:00
|
|
|
|
if (Directory.Exists(fullPath))
|
|
|
|
|
|
return Directory.EnumerateFiles(fullPath, filter).Select(GetRelativePath);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
catch (UnauthorizedAccessException ex)
|
2012-12-26 06:49:54 -01:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.Error<PhysicalFileSystem>("Not authorized to get directories", ex);
|
|
|
|
|
|
}
|
2012-08-13 10:04:31 -01:00
|
|
|
|
catch (DirectoryNotFoundException ex)
|
2012-12-26 06:49:54 -01:00
|
|
|
|
{
|
|
|
|
|
|
LogHelper.Error<PhysicalFileSystem>("Directory not found", ex);
|
|
|
|
|
|
}
|
2012-08-13 10:04:31 -01:00
|
|
|
|
|
|
|
|
|
|
return Enumerable.Empty<string>();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public Stream OpenFile(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2012-11-08 15:55:44 -01:00
|
|
|
|
var fullPath = GetFullPath(path);
|
|
|
|
|
|
return File.OpenRead(fullPath);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public void DeleteFile(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fullPath = GetFullPath(path);
|
|
|
|
|
|
if (File.Exists(fullPath) == false)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
File.Delete(fullPath);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
catch (FileNotFoundException ex)
|
2012-11-29 11:56:33 -01:00
|
|
|
|
{
|
2013-01-18 09:00:18 -01:00
|
|
|
|
LogHelper.Info<PhysicalFileSystem>(string.Format("DeleteFile failed with FileNotFoundException: {0}", ex.InnerException));
|
2012-11-29 11:56:33 -01:00
|
|
|
|
}
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public bool FileExists(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fullpath = GetFullPath(path);
|
|
|
|
|
|
return File.Exists(fullpath);
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-09-10 14:10:45 +02:00
|
|
|
|
// beware, many things depend on how the GetRelative/AbsolutePath methods work!
|
|
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the relative path.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="fullPathOrUrl">The full path or url.</param>
|
|
|
|
|
|
/// <returns>The path, relative to this filesystem's root.</returns>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// <para>The relative path is relative to this filesystem's root, not starting with any
|
2015-09-10 14:10:45 +02:00
|
|
|
|
/// directory separator. If input was recognized as a url (path), then output uses url (path) separator
|
|
|
|
|
|
/// chars.</para>
|
2015-09-07 12:38:46 +02:00
|
|
|
|
/// </remarks>
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public string GetRelativePath(string fullPathOrUrl)
|
2012-08-14 09:11:49 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
// test url
|
2015-09-10 14:10:45 +02:00
|
|
|
|
var path = fullPathOrUrl.Replace('\\', '/'); // ensure url separator char
|
|
|
|
|
|
|
|
|
|
|
|
if (IOHelper.PathStartsWith(path, _rootUrl, '/')) // if it starts with the root url...
|
|
|
|
|
|
return path.Substring(_rootUrl.Length) // strip it
|
|
|
|
|
|
.TrimStart('/'); // it's relative
|
2015-09-07 12:38:46 +02:00
|
|
|
|
|
|
|
|
|
|
// test path
|
|
|
|
|
|
path = EnsureDirectorySeparatorChar(fullPathOrUrl);
|
|
|
|
|
|
|
2015-09-10 14:10:45 +02:00
|
|
|
|
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar)) // if it starts with the root path
|
|
|
|
|
|
return path.Substring(_rootPath.Length) // strip it
|
|
|
|
|
|
.TrimStart(Path.DirectorySeparatorChar); // it's relative
|
2015-09-07 12:38:46 +02:00
|
|
|
|
|
2015-09-10 14:10:45 +02:00
|
|
|
|
// unchanged - including separators
|
|
|
|
|
|
return fullPathOrUrl;
|
2012-08-14 09:11:49 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the full path.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="path">The full or relative path.</param>
|
|
|
|
|
|
/// <returns>The full path.</returns>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// <para>On the physical filesystem, the full path is the rooted (ie non-relative), safe (ie within this
|
|
|
|
|
|
/// filesystem's root) path. All separators are converted to Path.DirectorySeparatorChar.</para>
|
|
|
|
|
|
/// </remarks>
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public string GetFullPath(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
// normalize
|
|
|
|
|
|
var opath = path;
|
|
|
|
|
|
path = EnsureDirectorySeparatorChar(path);
|
|
|
|
|
|
|
|
|
|
|
|
// not sure what we are doing here - so if input starts with a (back) slash,
|
|
|
|
|
|
// we assume it's not a FS relative path and we try to convert it... but it
|
|
|
|
|
|
// really makes little sense?
|
|
|
|
|
|
if (path.StartsWith(Path.DirectorySeparatorChar.ToString()))
|
2014-10-22 15:52:32 +10:00
|
|
|
|
path = GetRelativePath(path);
|
|
|
|
|
|
|
2015-09-03 15:11:49 +02:00
|
|
|
|
// if already a full path, return
|
2015-09-08 17:48:26 +02:00
|
|
|
|
if (IOHelper.PathStartsWith(path, _rootPath, Path.DirectorySeparatorChar))
|
2015-09-03 15:11:49 +02:00
|
|
|
|
return path;
|
|
|
|
|
|
|
|
|
|
|
|
// else combine and sanitize, ie GetFullPath will take care of any relative
|
|
|
|
|
|
// segments in path, eg '../../foo.tmp' - it may throw a SecurityException
|
|
|
|
|
|
// if the combined path reaches illegal parts of the filesystem
|
2015-09-07 12:38:46 +02:00
|
|
|
|
var fpath = Path.Combine(_rootPath, path);
|
2015-09-03 15:11:49 +02:00
|
|
|
|
fpath = Path.GetFullPath(fpath);
|
|
|
|
|
|
|
|
|
|
|
|
// at that point, path is within legal parts of the filesystem, ie we have
|
|
|
|
|
|
// permissions to reach that path, but it may nevertheless be outside of
|
|
|
|
|
|
// our root path, due to relative segments, so better check
|
2015-09-08 17:48:26 +02:00
|
|
|
|
if (IOHelper.PathStartsWith(fpath, _rootPath, Path.DirectorySeparatorChar))
|
2015-09-03 15:11:49 +02:00
|
|
|
|
return fpath;
|
|
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
throw new FileSecurityException("File '" + opath + "' is outside this filesystem's root.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public string GetUrl(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
path = EnsureUrlSeparatorChar(path).Trim('/');
|
|
|
|
|
|
return _rootUrl + "/" + path;
|
2012-08-13 10:04:31 -01:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public DateTimeOffset GetLastModified(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
|
|
|
|
|
return DirectoryExists(path)
|
|
|
|
|
|
? new DirectoryInfo(GetFullPath(path)).LastWriteTimeUtc
|
|
|
|
|
|
: new FileInfo(GetFullPath(path)).LastWriteTimeUtc;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-08-20 10:46:32 -01:00
|
|
|
|
public DateTimeOffset GetCreated(string path)
|
2012-08-13 10:04:31 -01:00
|
|
|
|
{
|
|
|
|
|
|
return DirectoryExists(path)
|
|
|
|
|
|
? Directory.GetCreationTimeUtc(GetFullPath(path))
|
|
|
|
|
|
: File.GetCreationTimeUtc(GetFullPath(path));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#region Helper Methods
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void EnsureDirectory(string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
path = GetFullPath(path);
|
|
|
|
|
|
Directory.CreateDirectory(path);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected string EnsureTrailingSeparator(string path)
|
|
|
|
|
|
{
|
2015-09-07 12:38:46 +02:00
|
|
|
|
return path.EnsureEndsWith(Path.DirectorySeparatorChar);
|
|
|
|
|
|
}
|
2012-08-13 10:04:31 -01:00
|
|
|
|
|
2015-09-07 12:38:46 +02:00
|
|
|
|
protected string EnsureDirectorySeparatorChar(string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
path = path.Replace('/', Path.DirectorySeparatorChar);
|
|
|
|
|
|
path = path.Replace('\\', Path.DirectorySeparatorChar);
|
|
|
|
|
|
return path;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected string EnsureUrlSeparatorChar(string path)
|
|
|
|
|
|
{
|
|
|
|
|
|
path = path.Replace('\\', '/');
|
2012-08-13 10:04:31 -01:00
|
|
|
|
return path;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#endregion
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|