WebDavSharp/WebDavSharp_MVC/WebDAVSharp.Server/Stores/DiskStore/WebDavDiskStoreCollection.cs

441 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.AccessControl;
using System.Security.Principal;
using WebDAVSharp.Server.Exceptions;
namespace WebDAVSharp.Server.Stores.DiskStore {
/// <summary>
/// This class implements a disk-based <see cref="IWebDavStore" /> that maps to a folder on disk.
/// </summary>
[DebuggerDisplay("Directory ({Name})")]
public sealed class WebDavDiskStoreCollection : WebDavDiskStoreItem, IWebDavStoreCollection {
private readonly Dictionary<string, WeakReference> _items = new Dictionary<string, WeakReference>();
/// <summary>
/// Initializes a new instance of the <see cref="WebDavDiskStoreCollection" /> class.
/// </summary>
/// <param name="parentCollection">The parent <see cref="WebDavDiskStoreCollection" /> that contains this <see cref="WebDavDiskStoreCollection" />.</param>
/// <param name="path">The path to the folder on this that this <see cref="WebDavDiskStoreCollection" /> maps to.</param>
public WebDavDiskStoreCollection(WebDavDiskStoreCollection parentCollection, string path)
: base(parentCollection, path) {
}
#region IWebDAVStoreCollection Members
/// <summary>
/// Gets a collection of all the items in this <see cref="IWebDavStoreCollection" />.
/// </summary>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">If the user is unauthorized or doesn't have access</exception>
public IEnumerable<IWebDavStoreItem> Items {
get {
HashSet<WeakReference> toDelete = new HashSet<WeakReference>(_items.Values);
List<IWebDavStoreItem> items = new List<IWebDavStoreItem>();
// TODO: Refactor to get rid of duplicate loop code
List<string> directories = new List<string>();
try {
// Impersonate the current user and get all the directories
using (Identity.Impersonate()) {
foreach (string dirName in Directory.GetDirectories(ItemPath)) {
try {
bool canread = CanReadDirectory(Path.Combine(ItemPath, dirName));
if (canread) {
directories.Add(dirName);
}
}
catch (Exception) {
// do nothing
}
}
}
}
catch {
throw new WebDavUnauthorizedException();
}
foreach (string subDirectoryPath in directories) {
string name = Path.GetFileName(subDirectoryPath);
WebDavDiskStoreCollection collection = null;
WeakReference wr;
if (_items.TryGetValue(name, out wr)) {
collection = wr.Target as WebDavDiskStoreCollection;
if (collection == null)
toDelete.Remove(wr);
}
if (collection == null) {
collection = new WebDavDiskStoreCollection(this, subDirectoryPath);
_items[name] = new WeakReference(collection);
}
items.Add(collection);
}
List<string> files = new List<string>();
try {
// Impersonate the current user and get all the files
using (Identity.Impersonate()) {
files.AddRange(Directory.GetFiles(ItemPath).Where(fileName => CanReadFile(Path.Combine(ItemPath, fileName))));
}
}
catch {
throw new WebDavUnauthorizedException();
}
foreach (string filePath in files) {
string name = Path.GetFileName(filePath);
WebDavDiskStoreDocument document = null;
WeakReference wr;
if (_items.TryGetValue(name, out wr)) {
document = wr.Target as WebDavDiskStoreDocument;
if (document == null)
toDelete.Remove(wr);
}
if (document == null) {
document = new WebDavDiskStoreDocument(this, filePath);
_items[name] = new WeakReference(document);
}
items.Add(document);
}
return items.ToArray();
}
}
/// <summary>
/// Checks if the user has access to the path
/// </summary>
/// <param name="path">The path.</param>
/// <returns>
/// True if access, false if not
/// </returns>
private bool CanRead(string path) {
if (File.Exists(path))
return CanReadFile(path);
return Directory.Exists(path) && CanReadDirectory(path);
}
/// <summary>
/// Checks if access to the file is granted.
/// </summary>
/// <param name="path">The path to the file as a <see cref="string" /></param>
/// <returns>
/// The <see cref="bool" /> true if the user has access, else false
/// </returns>
/// <remarks>
/// Source: <see href="http://stackoverflow.com/questions/17318585/check-if-file-can-be-read" />
/// </remarks>
private static bool CanReadFile(string path) {
try {
File.Open(path, FileMode.Open, FileAccess.Read).Dispose();
return true;
}
catch (Exception) {
return false;
}
}
/// <summary>
/// Checks if access to the directory is granted.
/// </summary>
/// <param name="path">The path to the director as a <see cref="string" /></param>
/// <returns>
/// The <see cref="bool" /> true if the user has access, else false
/// </returns>
/// <remarks>
/// Source: <see href="http://stackoverflow.com/questions/11709862/check-if-directory-is-accessible-in-c" />
/// </remarks>
private static bool CanReadDirectory(string path) {
bool readAllow = false;
bool readDeny = false;
DirectorySecurity accessControlList = Directory.GetAccessControl(path);
if (accessControlList == null)
return false;
AuthorizationRuleCollection accessRules = accessControlList.GetAccessRules(true, true, typeof(SecurityIdentifier));
foreach (FileSystemAccessRule rule in accessRules.Cast<FileSystemAccessRule>().Where(rule => (FileSystemRights.Read & rule.FileSystemRights) == FileSystemRights.Read)) {
switch (rule.AccessControlType) {
case AccessControlType.Allow:
readAllow = true;
break;
case AccessControlType.Deny:
readDeny = true;
break;
}
}
return readAllow && !readDeny;
}
/// <summary>
/// Retrieves a store item by its name.
/// </summary>
/// <param name="name">The name of the store item to retrieve.</param>
/// <returns>
/// The store item that has the specified
/// <paramref name="name" />;
/// or
/// <c>null</c> if there is no store item with that name.
/// </returns>
public IWebDavStoreItem GetItemByName(string name) {
string path = Path.Combine(ItemPath, name);
using (Identity.Impersonate()) {
if (File.Exists(path) && CanReadFile(path))
return new WebDavDiskStoreDocument(this, path);
if (Directory.Exists(path) && CanReadDirectory(path))
return new WebDavDiskStoreCollection(this, path);
}
return null;
}
/// <summary>
/// Creates a new collection with the specified name.
/// </summary>
/// <param name="name">The name of the new collection.</param>
/// <returns>
/// The created <see cref="IWebDavStoreCollection" /> instance.
/// </returns>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">When the user is unauthorized or has no access</exception>
public IWebDavStoreCollection CreateCollection(string name) {
string path = Path.Combine(ItemPath, name);
try {
// Impersonate the current user and make file changes
WindowsImpersonationContext wic = Identity.Impersonate();
Directory.CreateDirectory(path);
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
return GetItemByName(name) as IWebDavStoreCollection;
}
/// <summary>
/// Deletes a store item by its name.
/// </summary>
/// <param name="item">The name of the store item to delete.</param>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavNotFoundException">If the item was not found.</exception>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">If the user is unauthorized or has no access.</exception>
public void Delete(IWebDavStoreItem item) {
WebDavDiskStoreItem diskItem = (WebDavDiskStoreItem)item;
string itemPath = diskItem.ItemPath;
if (item is WebDavDiskStoreDocument) {
if (!File.Exists(itemPath))
throw new WebDavNotFoundException();
try {
// Impersonate the current user and delete the file
WindowsImpersonationContext wic = Identity.Impersonate();
File.Delete(itemPath);
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
}
else {
if (!Directory.Exists(itemPath))
throw new WebDavNotFoundException();
try {
// Impersonate the current user and delete the directory
WindowsImpersonationContext wic = Identity.Impersonate();
Directory.Delete(diskItem.ItemPath);
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
}
}
/// <summary>
/// Creates a new document with the specified name.
/// </summary>
/// <param name="name">The name of the new document.</param>
/// <returns>
/// The created <see cref="IWebDavStoreDocument" /> instance.
/// </returns>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavConflictException">If the item already exists</exception>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">If the user is unauthorized or has no access</exception>
public IWebDavStoreDocument CreateDocument(string name) {
string itemPath = Path.Combine(ItemPath, name);
if (File.Exists(itemPath) || Directory.Exists(itemPath))
throw new WebDavConflictException();
try {
// Impersonate the current user and delete the directory
WindowsImpersonationContext wic = Identity.Impersonate();
File.Create(itemPath).Dispose();
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
WebDavDiskStoreDocument document = new WebDavDiskStoreDocument(this, itemPath);
_items.Add(name, new WeakReference(document));
return document;
}
/// <summary>
/// Copies an existing store item into this collection, overwriting any existing items.
/// </summary>
/// <param name="source">The store item to copy from.</param>
/// <param name="destinationName">The name of the copy to create of <paramref name="source" />.</param>
/// <param name="includeContent">The boolean for copying the containing files/folders or not.</param>
/// <returns>
/// The created <see cref="IWebDavStoreItem" /> instance.
/// </returns>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">If the user is unauthorized or has no access</exception>
public IWebDavStoreItem CopyItemHere(IWebDavStoreItem source, string destinationName, bool includeContent) {
// We get the path of
string destinationItemPath = Path.Combine(ItemPath, destinationName);
// the moving of the item
if (source.IsCollection) {
try {
// Impersonate the current user and move the directory
WindowsImpersonationContext wic = Identity.Impersonate();
DirectoryCopy(source.ItemPath, destinationItemPath, true);
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
// We return the moved file as a WebDAVDiskStoreDocument
WebDavDiskStoreCollection collection = new WebDavDiskStoreCollection(this, destinationItemPath);
_items.Add(destinationName, new WeakReference(collection));
return collection;
}
// We copy the file with an override.
try {
// Impersonate the current user and copy the file
WindowsImpersonationContext wic = Identity.Impersonate();
File.Copy(source.ItemPath, destinationItemPath, true);
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
// We return the moved file as a WebDAVDiskStoreDocument
WebDavDiskStoreDocument document = new WebDavDiskStoreDocument(this, destinationItemPath);
_items.Add(destinationName, new WeakReference(document));
return document;
}
/// <summary>
/// Directories the copy.
/// </summary>
/// <param name="sourceDirName">Name of the source dir.</param>
/// <param name="destDirName">Name of the dest dir.</param>
/// <param name="copySubDirs">if set to <c>true</c> [copy sub dirs].</param>
/// <exception cref="System.IO.DirectoryNotFoundException">Source directory does not exist or could not be found:
/// + sourceDirName</exception>
private static void DirectoryCopy(string sourceDirName, string destDirName, bool copySubDirs) {
// Get the subdirectories for the specified directory.
DirectoryInfo dir = new DirectoryInfo(sourceDirName);
DirectoryInfo[] dirs = dir.GetDirectories();
if (!dir.Exists) {
throw new DirectoryNotFoundException(
"Source directory does not exist or could not be found: "
+ sourceDirName);
}
// If the destination directory doesn't exist, create it.
if (!Directory.Exists(destDirName)) {
Directory.CreateDirectory(destDirName);
}
// If copying subdirectories, copy them and their contents to new location.
if (!copySubDirs) return;
// Get the files in the directory and copy them to the new location.
FileInfo[] files = dir.GetFiles();
foreach (FileInfo file in files)
file.CopyTo(Path.Combine(destDirName, file.Name), false);
foreach (DirectoryInfo subdir in dirs)
DirectoryCopy(subdir.FullName, Path.Combine(destDirName, subdir.Name), copySubDirs);
}
/// <summary>
/// Moves an existing store item into this collection, overwriting any existing items.
/// </summary>
/// <param name="source">The store item to move.</param>
/// <param name="destinationName">The
/// <see cref="IWebDavStoreItem" /> that refers to the item that was moved,
/// in its new location.</param>
/// <returns>
/// The moved <see cref="IWebDavStoreItem" /> instance.
/// </returns>
/// <exception cref="System.Exception">Path to the source item not defined.</exception>
/// <exception cref="WebDAVSharp.Server.Exceptions.WebDavUnauthorizedException">If the user is unauthorized or has no access</exception>
/// <remarks>
/// Note that the method should fail without creating or overwriting content in the
/// target collection if the move cannot go through.
/// </remarks>
public IWebDavStoreItem MoveItemHere(IWebDavStoreItem source, string destinationName) {
// try to get the path of the source item
string sourceItemPath = "";
WebDavDiskStoreItem sourceItem = (WebDavDiskStoreItem)source;
sourceItemPath = sourceItem.ItemPath;
if (sourceItemPath.Equals("")) {
throw new Exception("Path to the source item not defined.");
}
// We get the path of
string destinationItemPath = Path.Combine(ItemPath, destinationName);
// the moving of the item
if (source.IsCollection) {
try {
// Impersonate the current user and move the directory
WindowsImpersonationContext wic = Identity.Impersonate();
Directory.Move(sourceItemPath, destinationItemPath);
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
// We return the moved file as a WebDAVDiskStoreDocument
var collection = new WebDavDiskStoreCollection(this, destinationItemPath);
_items.Add(destinationName, new WeakReference(collection));
return collection;
}
try {
// Impersonate the current user and move the file
WindowsImpersonationContext wic = Identity.Impersonate();
File.Move(sourceItemPath, destinationItemPath);
wic.Undo();
}
catch {
throw new WebDavUnauthorizedException();
}
// We return the moved file as a WebDAVDiskStoreDocument
WebDavDiskStoreDocument document = new WebDavDiskStoreDocument(this, destinationItemPath);
_items.Add(destinationName, new WeakReference(document));
return document;
}
#endregion
}
}