From cc563ba16abaca4d2a60e99ab1e4b44d1d2451b3 Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Mon, 4 Nov 2024 00:01:53 +0100
Subject: [PATCH 1/3] Begin work on migrating NWebDav
---
.../Desktop/SkiaWebDavFileSystem.Linux.cs | 19 ++-----
.../Windows/WindowsWebDavFileSystem.cs | 16 ++----
.../Platforms/Windows/WindowsWebDavVFSRoot.cs | 33 ++++---------
.../AppModels/EncryptingStoreOptions.cs | 9 ++++
.../EncryptingStorage2/EncryptingDiskStore.cs | 20 ++++++--
.../Enums/DavPropertyMode.cs | 13 -----
.../Extensions/BuilderExtensions.cs | 30 ++++++++++++
...SecureFolderFS - Backup.Core.WebDav.csproj | 24 +++++++++
.../SecureFolderFS.Core.WebDav.csproj | 8 +--
.../WebDavFileSystem.cs | 49 ++++++++++++++-----
.../WebDavRootFolder.cs | 26 ++++++----
11 files changed, 154 insertions(+), 93 deletions(-)
create mode 100644 src/SecureFolderFS.Core.WebDav/AppModels/EncryptingStoreOptions.cs
delete mode 100644 src/SecureFolderFS.Core.WebDav/Enums/DavPropertyMode.cs
create mode 100644 src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
create mode 100644 src/SecureFolderFS.Core.WebDav/SecureFolderFS - Backup.Core.WebDav.csproj
diff --git a/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/SkiaWebDavFileSystem.Linux.cs b/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/SkiaWebDavFileSystem.Linux.cs
index b6f0b86b0..b6eb888c1 100644
--- a/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/SkiaWebDavFileSystem.Linux.cs
+++ b/src/Platforms/SecureFolderFS.Uno/Platforms/Desktop/SkiaWebDavFileSystem.Linux.cs
@@ -20,26 +20,15 @@ internal sealed partial class SkiaWebDavFileSystem
#if HAS_UNO_SKIA && !__MACCATALYST__
///
protected override async Task MountAsync(
- int port,
- string domain,
- string protocol,
- HttpListener listener,
- FileSystemOptions options,
- IRequestDispatcher requestDispatcher,
+ WebDavOptions options,
+ IAsyncDisposable webDavInstance,
CancellationToken cancellationToken)
{
- var remotePath = DriveMappingHelpers.GetRemotePath(protocol, "localhost", port, options.VolumeName);
+ var remotePath = DriveMappingHelpers.GetRemotePath(options.Protocol, options.Domain, options.Port, options.VolumeName);
var mountPath = await DriveMappingHelpers.GetMountPathForRemotePathAsync(remotePath);
- var webDavWrapper = new WebDavWrapper(listener, requestDispatcher, mountPath);
- webDavWrapper.StartFileSystem();
-
- // TODO: Remove once the port is displayed in the UI.
- Debug.WriteLine($"WebDAV server started on port {port}.");
- Debug.WriteLine($"MountableDAV\nmountPath: {mountPath}\nremotePath: {remotePath}");
-
// TODO: Currently using MemoryFolder because the check in SystemFolder might sometimes fail
- return new WebDavRootFolder(webDavWrapper, new MemoryFolder(remotePath, options.VolumeName), options);
+ return new WebDavRootFolder(webDavInstance, new MemoryFolder(remotePath, options.VolumeName), options);
}
#endif
}
diff --git a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs
index 618673ba0..b5d9701d9 100644
--- a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs
+++ b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs
@@ -1,9 +1,7 @@
-using System.Diagnostics;
+using System;
using System.IO;
-using System.Net;
using System.Threading;
using System.Threading.Tasks;
-using NWebDav.Server.Dispatching;
using OwlCore.Storage.Memory;
using SecureFolderFS.Core.FileSystem.Helpers;
using SecureFolderFS.Core.WebDav;
@@ -18,9 +16,8 @@ public sealed class WindowsWebDavFileSystem : WebDavFileSystem
{
///
protected override async Task MountAsync(
- HttpListener listener,
WebDavOptions options,
- IRequestDispatcher requestDispatcher,
+ IAsyncDisposable webDavInstance,
CancellationToken cancellationToken)
{
var remotePath = DriveMappingHelpers.GetRemotePath(options.Protocol, "localhost", options.Port, options.VolumeName);
@@ -34,14 +31,7 @@ protected override async Task MountAsync(
await DriveMappingHelpers.MapNetworkDriveAsync(mountPath, remotePath, cancellationToken);
}
- var webDavWrapper = new WebDavWrapper(listener, requestDispatcher, mountPath);
- webDavWrapper.StartFileSystem();
-
- // TODO: Remove once the port is displayed in the UI.
- Debug.WriteLine($"WebDAV server started on port {options.Port}.");
-
- // TODO: Currently using MemoryFolder because the check in SystemFolder might sometimes fail
- return new WindowsWebDavVFSRoot(webDavWrapper, new MemoryFolder(remotePath, options.VolumeName), options);
+ return new WindowsWebDavVFSRoot(webDavInstance, new MemoryFolder(remotePath, options.VolumeName), options);
}
}
}
diff --git a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavVFSRoot.cs b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavVFSRoot.cs
index c702549c9..5a27ea9e0 100644
--- a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavVFSRoot.cs
+++ b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavVFSRoot.cs
@@ -1,8 +1,7 @@
using System.Threading.Tasks;
using OwlCore.Storage;
-using SecureFolderFS.Core.FileSystem;
-using SecureFolderFS.Core.WebDav;
using SecureFolderFS.Storage.VirtualFileSystem;
+using SecureFolderFS.Core.WebDav;
#if WINDOWS
using System;
@@ -15,39 +14,27 @@
namespace SecureFolderFS.Uno.Platforms.Windows
{
///
- internal sealed class WindowsWebDavVFSRoot : VFSRoot
+ internal sealed class WindowsWebDavVFSRoot : WebDavRootFolder
{
- private const uint WM_CLOSE = 0x0010;
-
- private readonly WebDavWrapper _webDavWrapper;
- private bool _disposed;
-
- ///
- public override string FileSystemName { get; } = Core.WebDav.Constants.FileSystem.FS_NAME;
-
- public WindowsWebDavVFSRoot(WebDavWrapper webDavWrapper, IFolder storageRoot, FileSystemOptions options)
- : base(storageRoot, options)
+ public WindowsWebDavVFSRoot(IAsyncDisposable webDavInstance, IFolder storageRoot, FileSystemOptions options)
+ : base(webDavInstance, storageRoot, options)
{
- _webDavWrapper = webDavWrapper;
}
///
- public override async ValueTask DisposeAsync()
+ protected override async ValueTask DisposeInternalAsync()
{
- if (_disposed)
- return;
+ await base.DisposeInternalAsync();
- _disposed = await _webDavWrapper.CloseFileSystemAsync();
- if (_disposed)
- {
- FileSystemManager.Instance.RemoveRoot(this);
- await CloseExplorerShellAsync(Inner.Id);
- }
+ // Close the shell on Windows
+ await CloseExplorerShellAsync(Inner.Id);
}
private static async Task CloseExplorerShellAsync(string path)
{
#if WINDOWS
+ const uint WM_CLOSE = 0x0010;
+
try
{
var formattedPath = PathHelpers.EnsureNoTrailingPathSeparator(path);
diff --git a/src/SecureFolderFS.Core.WebDav/AppModels/EncryptingStoreOptions.cs b/src/SecureFolderFS.Core.WebDav/AppModels/EncryptingStoreOptions.cs
new file mode 100644
index 000000000..4c9a29019
--- /dev/null
+++ b/src/SecureFolderFS.Core.WebDav/AppModels/EncryptingStoreOptions.cs
@@ -0,0 +1,9 @@
+using SecureFolderFS.Core.FileSystem;
+
+namespace SecureFolderFS.Core.WebDav.AppModels
+{
+ internal sealed class EncryptingStoreOptions
+ {
+ public FileSystemSpecifics? Specifics { get; set; }
+ }
+}
diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
index a5b0741ba..4b08da30f 100644
--- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
+++ b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
@@ -1,4 +1,4 @@
-using NWebDav.Server.Http;
+using Microsoft.Extensions.Logging;
using NWebDav.Server.Locking;
using NWebDav.Server.Stores;
using SecureFolderFS.Core.FileSystem;
@@ -9,14 +9,26 @@
namespace SecureFolderFS.Core.WebDav.EncryptingStorage2
{
- internal sealed class EncryptingDiskStore : DiskStore
+ internal sealed class EncryptingDiskStore : DiskStoreBase
{
private readonly FileSystemSpecifics _specifics;
- public EncryptingDiskStore(string directory, FileSystemSpecifics specifics, bool isWritable = true, ILockingManager? lockingManager = null)
- : base(directory, isWritable, lockingManager)
+ ///
+ public override bool IsWritable { get; }
+
+ ///
+ public override string BaseDirectory { get; }
+
+ public EncryptingDiskStore(
+ FileSystemSpecifics specifics,
+ DiskStoreItemPropertyManager itemPropertyManager,
+ DiskStoreCollectionPropertyManager collectionPropertyManager,
+ ILoggerFactory loggerFactory)
+ : base(collectionPropertyManager, itemPropertyManager, loggerFactory)
{
_specifics = specifics;
+ IsWritable = !specifics.FileSystemOptions.IsReadOnly;
+ BaseDirectory = specifics.ContentFolder.Id;
}
public override Task GetItemAsync(Uri uri, IHttpContext context)
diff --git a/src/SecureFolderFS.Core.WebDav/Enums/DavPropertyMode.cs b/src/SecureFolderFS.Core.WebDav/Enums/DavPropertyMode.cs
deleted file mode 100644
index c8cd7ad30..000000000
--- a/src/SecureFolderFS.Core.WebDav/Enums/DavPropertyMode.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System;
-
-namespace SecureFolderFS.Core.WebDav.Enums
-{
- [Flags]
- internal enum DavPropertyMode : uint
- {
- None = 0,
- PropertyNames = 1,
- AllProperties = 2,
- SelectedProperties = 4
- }
-}
diff --git a/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs b/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
new file mode 100644
index 000000000..a2b3c5596
--- /dev/null
+++ b/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
@@ -0,0 +1,30 @@
+using Microsoft.Extensions.DependencyInjection;
+using NWebDav.Server;
+using NWebDav.Server.Stores;
+using SecureFolderFS.Core.WebDav.AppModels;
+using SecureFolderFS.Core.WebDav.EncryptingStorage2;
+using System;
+using Microsoft.Extensions.Options;
+
+namespace SecureFolderFS.Core.WebDav.Extensions
+{
+ internal static class BuilderExtensions
+ {
+ public static IServiceCollection AddEncryptingDiskStore(this IServiceCollection services, Action options)
+ {
+ return services
+ .Configure(options)
+ .AddSingleton()
+ .AddSingleton()
+ .AddScoped(sp =>
+ {
+ var storeOptions = sp.GetService>();
+ if (storeOptions?.Value.Specifics is null)
+ throw new NotSupportedException("Options were not configured.");
+
+ return new(storeOptions.Value.Specifics);
+
+ });
+ }
+ }
+}
diff --git a/src/SecureFolderFS.Core.WebDav/SecureFolderFS - Backup.Core.WebDav.csproj b/src/SecureFolderFS.Core.WebDav/SecureFolderFS - Backup.Core.WebDav.csproj
new file mode 100644
index 000000000..69ab19fea
--- /dev/null
+++ b/src/SecureFolderFS.Core.WebDav/SecureFolderFS - Backup.Core.WebDav.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net8.0
+ disable
+ enable
+ AnyCPU;ARM64;x64;x86
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/SecureFolderFS.Core.WebDav/SecureFolderFS.Core.WebDav.csproj b/src/SecureFolderFS.Core.WebDav/SecureFolderFS.Core.WebDav.csproj
index 22c716f67..9964ec7b8 100644
--- a/src/SecureFolderFS.Core.WebDav/SecureFolderFS.Core.WebDav.csproj
+++ b/src/SecureFolderFS.Core.WebDav/SecureFolderFS.Core.WebDav.csproj
@@ -1,4 +1,4 @@
-
+
net8.0
@@ -8,8 +8,10 @@
-
-
+
+
+
+
diff --git a/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs b/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
index 8ca438d28..93270903c 100644
--- a/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
+++ b/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
@@ -1,12 +1,12 @@
-using NWebDav.Server;
-using NWebDav.Server.Dispatching;
-using NWebDav.Server.Storage;
-using NWebDav.Server.Stores;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using NWebDav.Server;
using OwlCore.Storage;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Core.WebDav.AppModels;
using SecureFolderFS.Core.WebDav.EncryptingStorage2;
+using SecureFolderFS.Core.WebDav.Extensions;
using SecureFolderFS.Core.WebDav.Helpers;
using SecureFolderFS.Shared.ComponentModel;
using SecureFolderFS.Storage.Enums;
@@ -48,31 +48,56 @@ public virtual async Task MountAsync(IFolder folder, IDisposable unloc
if (!PortHelpers.IsPortAvailable(webDavOptions.Port))
webDavOptions.SetPortInternal(PortHelpers.GetNextAvailablePort(webDavOptions.Port));
- var prefix = $"{webDavOptions.Protocol}://{webDavOptions.Domain}:{webDavOptions.Port}/";
+ var url = $"{webDavOptions.Protocol}://{webDavOptions.Domain}:{webDavOptions.Port}/";
+ var builder = WebApplication.CreateBuilder();
+ builder.Services
+ .AddNWebDav()
+ .AddEncryptingDiskStore(x =>
+ {
+ x.Specifics = specifics;
+ });
+
+ var webDavInstance = builder.Build();
+ webDavInstance.UseNWebDav();
+ _ = webDavInstance.RunAsync(url);
+
+
+ return await MountAsync(
+ httpListener,
+ webDavOptions,
+ webDavInstance,
+ cancellationToken);
+
+
+
+
+
+
+
var httpListener = new HttpListener();
httpListener.Prefixes.Add(prefix);
httpListener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
+
+
// TODO: Implement FileSystemSpecifics
var cryptoFolder = (IFolder)null!; // new CryptoFolder(contentFolder, streamsAccess, pathConverter, directoryIdCache);
+
var davFolder = new DavFolder(cryptoFolder);
+ var srv = new ServiceCollection()
+ .AddNWebDav()
+
// TODO: Remove the following line once the new DavStorage is fully implemented.
var encryptingDiskStore = new EncryptingDiskStore(specifics.ContentFolder.Id, specifics, !specifics.FileSystemOptions.IsReadOnly);
var dispatcher = new WebDavDispatcher(new RootDiskStore(specifics.FileSystemOptions.VolumeName, encryptingDiskStore), davFolder, new RequestHandlerProvider(), null);
-
- return await MountAsync(
- httpListener,
- webDavOptions,
- dispatcher,
- cancellationToken);
}
protected abstract Task MountAsync(
HttpListener listener,
WebDavOptions options,
- IRequestDispatcher requestDispatcher,
+ IAsyncDisposable webDavInstance,
CancellationToken cancellationToken);
}
}
diff --git a/src/SecureFolderFS.Core.WebDav/WebDavRootFolder.cs b/src/SecureFolderFS.Core.WebDav/WebDavRootFolder.cs
index 1d565297a..b738a4b82 100644
--- a/src/SecureFolderFS.Core.WebDav/WebDavRootFolder.cs
+++ b/src/SecureFolderFS.Core.WebDav/WebDavRootFolder.cs
@@ -1,34 +1,40 @@
using OwlCore.Storage;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Storage.VirtualFileSystem;
+using System;
using System.Threading.Tasks;
namespace SecureFolderFS.Core.WebDav
{
///
- public sealed class WebDavRootFolder : VFSRoot
+ public class WebDavRootFolder : VFSRoot
{
- private readonly WebDavWrapper _webDavWrapper;
- private bool _disposed;
+ protected readonly IAsyncDisposable webDavInstance;
+ protected bool disposed;
///
public override string FileSystemName { get; } = Constants.FileSystem.FS_NAME;
- public WebDavRootFolder(WebDavWrapper webDavWrapper, IFolder storageRoot, FileSystemOptions options)
+ public WebDavRootFolder(IAsyncDisposable webDavInstance, IFolder storageRoot, FileSystemOptions options)
: base(storageRoot, options)
{
- _webDavWrapper = webDavWrapper;
+ this.webDavInstance = webDavInstance;
}
///
- public override async ValueTask DisposeAsync()
+ public sealed override async ValueTask DisposeAsync()
{
- if (_disposed)
+ if (disposed)
return;
- _disposed = await _webDavWrapper.CloseFileSystemAsync();
- if (_disposed)
- FileSystemManager.Instance.RemoveRoot(this);
+ disposed = true;
+ await DisposeInternalAsync();
+ }
+
+ protected virtual async ValueTask DisposeInternalAsync()
+ {
+ await webDavInstance.DisposeAsync();
+ FileSystemManager.Instance.RemoveRoot(this);
}
}
}
From 3c47c83915d6210982b0c9b66c06c4a501abe0ad Mon Sep 17 00:00:00 2001
From: d2dyno006 <53011783+d2dyno006@users.noreply.github.com>
Date: Mon, 4 Nov 2024 01:08:17 +0100
Subject: [PATCH 2/3] Some more work
---
.../Windows/WindowsWebDavFileSystem.cs | 2 +-
.../Base/DiskStoreBase2.cs | 98 ++++++++
.../EncryptingStorage2/EncryptingDiskStore.cs | 51 ++---
.../EncryptingDiskStoreCollection.cs | 2 +-
.../EncryptingDiskStoreItem.cs | 213 +++++-------------
.../Extensions/BuilderExtensions.cs | 9 +-
.../WebDavFileSystem.cs | 23 --
7 files changed, 181 insertions(+), 217 deletions(-)
create mode 100644 src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs
diff --git a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs
index b5d9701d9..d55c2c68b 100644
--- a/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs
+++ b/src/Platforms/SecureFolderFS.Uno/Platforms/Windows/WindowsWebDavFileSystem.cs
@@ -20,7 +20,7 @@ protected override async Task MountAsync(
IAsyncDisposable webDavInstance,
CancellationToken cancellationToken)
{
- var remotePath = DriveMappingHelpers.GetRemotePath(options.Protocol, "localhost", options.Port, options.VolumeName);
+ var remotePath = DriveMappingHelpers.GetRemotePath(options.Protocol, options.Domain, options.Port, options.VolumeName);
var mountPath = await DriveMappingHelpers.GetMountPathForRemotePathAsync(remotePath);
if (mountPath is null)
{
diff --git a/src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs b/src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs
new file mode 100644
index 000000000..ab54a59ec
--- /dev/null
+++ b/src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs
@@ -0,0 +1,98 @@
+using Microsoft.Extensions.Logging;
+using NWebDav.Server.Stores;
+using System;
+using System.IO;
+using System.Security;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace SecureFolderFS.Core.WebDav.Base
+{
+ internal abstract class DiskStoreBase2 : IStore
+ {
+ private readonly DiskStoreCollectionPropertyManager _diskStoreCollectionPropertyManager;
+ private readonly DiskStoreItemPropertyManager _diskStoreItemPropertyManager;
+ private readonly ILoggerFactory _loggerFactory;
+
+ public abstract bool IsWritable { get; }
+
+ public abstract string BaseDirectory { get; }
+
+ protected DiskStoreBase2(DiskStoreCollectionPropertyManager diskStoreCollectionPropertyManager, DiskStoreItemPropertyManager diskStoreItemPropertyManager, ILoggerFactory loggerFactory)
+ {
+ _diskStoreCollectionPropertyManager = diskStoreCollectionPropertyManager;
+ _diskStoreItemPropertyManager = diskStoreItemPropertyManager;
+ _loggerFactory = loggerFactory;
+ }
+
+ public virtual async Task GetItemAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Get path and item
+ var path = GetPathFromUri(uri);
+ var item = CreateFromPath(path);
+
+ return item;
+ }
+
+ public virtual async Task GetCollectionAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Determine the path from the uri
+ var path = GetPathFromUri(uri);
+ if (!Directory.Exists(path))
+ return null;
+
+ // Return the item
+ return CreateFromPath(path);
+ }
+
+ protected virtual string GetPathFromUri(Uri uri)
+ {
+ // Determine the path
+ var requestedPath = UriHelper.GetDecodedPath(uri)[1..].Replace('/', Path.DirectorySeparatorChar);
+
+ // Determine the full path
+ var fullPath = Path.GetFullPath(Path.Combine(BaseDirectory, requestedPath));
+
+ // Make sure we're still inside the specified directory
+ if (fullPath != BaseDirectory && !fullPath.StartsWith(BaseDirectory + Path.DirectorySeparatorChar))
+ throw new SecurityException($"Uri '{uri}' is outside the '{BaseDirectory}' directory.");
+
+ // Return the combined path
+ return fullPath;
+ }
+
+ public virtual T? CreateFromPath(string path)
+ where T : class, IStoreItem
+ {
+ if (typeof(T).IsAssignableFrom(typeof(IStoreCollection)))
+ {
+ return (T?)(object)CreateCollection(new DirectoryInfo(path));
+ }
+ //else if (typeof(T).IsAssignableFrom(typeof(IStoreFile))) // TODO: Add a StoreFile
+ else
+ {
+ // Check if it's a directory
+ if (Directory.Exists(path))
+ return (T?)(object)CreateCollection(new DirectoryInfo(path));
+
+ // Check if it's a file
+ if (File.Exists(path))
+ return (T?)(object)CreateItem(new FileInfo(path));
+
+ // The item doesn't exist
+ return null;
+ }
+
+ internal DiskStoreCollection CreateCollection(DirectoryInfo directoryInfo) =>
+ new(this, _diskStoreCollectionPropertyManager, directoryInfo, _loggerFactory.CreateLogger());
+
+ internal DiskStoreItem CreateItem(FileInfo file) =>
+ new(this, _diskStoreItemPropertyManager, file, _loggerFactory.CreateLogger());
+ }
+}
diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
index 4b08da30f..c092377a6 100644
--- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
+++ b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
@@ -1,15 +1,14 @@
using Microsoft.Extensions.Logging;
-using NWebDav.Server.Locking;
using NWebDav.Server.Stores;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Core.FileSystem.Helpers.Native;
+using SecureFolderFS.Core.WebDav.Base;
using System;
using System.IO;
-using System.Threading.Tasks;
namespace SecureFolderFS.Core.WebDav.EncryptingStorage2
{
- internal sealed class EncryptingDiskStore : DiskStoreBase
+ internal sealed class EncryptingDiskStore : DiskStoreBase2
{
private readonly FileSystemSpecifics _specifics;
@@ -31,34 +30,30 @@ public EncryptingDiskStore(
BaseDirectory = specifics.ContentFolder.Id;
}
- public override Task GetItemAsync(Uri uri, IHttpContext context)
+ public override T? CreateFromPath(string path)
+ where T : class
{
- // Determine the path from the uri
- var path = GetPathFromUri(uri);
-
- // Check if it's a directory
- if (Directory.Exists(path))
- return Task.FromResult(new EncryptingDiskStoreCollection(LockingManager, new DirectoryInfo(path), IsWritable, _specifics));
-
- // Check if it's a file
- if (File.Exists(path))
- return Task.FromResult(new EncryptingDiskStoreItem(LockingManager, new FileInfo(path), IsWritable, _specifics));
-
- // The item doesn't exist
- return Task.FromResult(null);
- }
-
- public override Task GetCollectionAsync(Uri uri, IHttpContext context)
- {
- // Determine the path from the uri
- var path = GetPathFromUri(uri);
- if (!Directory.Exists(path))
- return Task.FromResult(null);
-
- // Return the item
- return Task.FromResult(new EncryptingDiskStoreCollection(LockingManager, new DirectoryInfo(path), IsWritable, _specifics));
+ if (typeof(T).IsAssignableFrom(typeof(IStoreCollection)))
+ {
+ return (T?)(object)new EncryptingDiskStoreCollection(new DirectoryInfo(path));
+ }
+ //else if (typeof(T).IsAssignableFrom(typeof(IStoreFile))) // TODO: Add a StoreFile
+ else
+ {
+ // Check if it's a directory
+ if (Directory.Exists(path))
+ return (T?)(object)new EncryptingDiskStoreCollection(new DirectoryInfo(path));
+
+ // Check if it's a file
+ if (File.Exists(path))
+ return (T?)(object)new EncryptingDiskStoreItem(new FileInfo(path));
+
+ // The item doesn't exist
+ return null;
+ }
}
+ ///
protected override string GetPathFromUri(Uri uri)
{
var path = base.GetPathFromUri(uri);
diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs
index 75530723f..b41a49f64 100644
--- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs
+++ b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs
@@ -18,7 +18,7 @@
namespace SecureFolderFS.Core.WebDav.EncryptingStorage2
{
- internal sealed class EncryptingDiskStoreCollection : IDiskStoreCollection
+ internal sealed class EncryptingDiskStoreCollection : IStoreCollection
{
private static readonly XElement s_xDavCollection = new XElement(WebDavNamespaces.DavNs + "collection");
private readonly DirectoryInfo _directoryInfo;
diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs
index d3d7ae0d1..487e62917 100644
--- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs
+++ b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs
@@ -1,229 +1,118 @@
-using NWebDav.Server.Helpers;
-using NWebDav.Server.Http;
-using NWebDav.Server.Locking;
+using Microsoft.Extensions.Logging;
+using NWebDav.Server;
+using NWebDav.Server.Helpers;
using NWebDav.Server.Props;
using NWebDav.Server.Stores;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Core.FileSystem.Helpers.Native;
using System;
using System.IO;
-using System.Net;
+using System.Threading;
using System.Threading.Tasks;
namespace SecureFolderFS.Core.WebDav.EncryptingStorage2
{
- internal class EncryptingDiskStoreItem : IDiskStoreItem
+ internal class EncryptingDiskStoreItem : IStoreItem
{
private readonly FileSystemSpecifics _specifics;
- private readonly FileInfo _fileInfo;
+ private readonly ILogger _logger;
- public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo, bool isWritable, FileSystemSpecifics specifics)
- {
- LockingManager = lockingManager;
- IsWritable = isWritable;
- _fileInfo = fileInfo;
- _specifics = specifics;
- }
+ ///
+ public string Name { get; }
- public static PropertyManager DefaultPropertyManager { get; } = new(new DavProperty[]
- {
- // RFC-2518 properties
- new DavCreationDate
- {
- Getter = (context, item) => item._fileInfo.CreationTimeUtc,
- Setter = (context, item, value) =>
- {
- item._fileInfo.CreationTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new DavDisplayName
- {
- Getter = (context, item) => item.Name
- },
- new DavGetContentLength
- {
- Getter = (context, item) => Math.Max(0, item._specifics.Security.ContentCrypt.CalculatePlaintextSize(item._fileInfo.Length - item._specifics.Security.HeaderCrypt.HeaderCiphertextSize))
- },
- new DavGetContentType
- {
- Getter = (context, item) => item.DetermineContentType()
- },
- new DavGetEtag
- {
- Getter = (context, item) => $"{item._fileInfo.Length}-{item._fileInfo.LastWriteTimeUtc.ToFileTime()}"
- },
- new DavGetLastModified
- {
- Getter = (context, item) => item._fileInfo.LastWriteTimeUtc,
- Setter = (context, item, value) =>
- {
- item._fileInfo.LastWriteTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new DavGetResourceType
- {
- Getter = (context, item) => null
- },
+ ///
+ public string UniqueKey { get; }
- // Default locking property handling via the LockingManager
- new DavLockDiscoveryDefault(),
- new DavSupportedLockDefault(),
+ ///
+ public IPropertyManager PropertyManager { get; }
- // Hopmann/Lippert collection properties
- // (although not a collection, the IsHidden property might be valuable)
- new DavExtCollectionIsHidden
- {
- Getter = (context, item) => (item._fileInfo.Attributes & FileAttributes.Hidden) != 0
- },
+ public FileInfo FileInfo { get; } // TODO: Not from interface
+ public bool IsWritable { get; } // TODO: Not from interface
- // Win32 extensions
- new Win32CreationTime
- {
- Getter = (context, item) => item._fileInfo.CreationTimeUtc,
- Setter = (context, item, value) =>
- {
- item._fileInfo.CreationTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new Win32LastAccessTime
- {
- Getter = (context, item) => item._fileInfo.LastAccessTimeUtc,
- Setter = (context, item, value) =>
- {
- item._fileInfo.LastAccessTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new Win32LastModifiedTime
- {
- Getter = (context, item) => item._fileInfo.LastWriteTimeUtc,
- Setter = (context, item, value) =>
- {
- item._fileInfo.LastWriteTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new Win32FileAttributes
- {
- Getter = (context, item) => item._fileInfo.Attributes,
- Setter = (context, item, value) =>
- {
- item._fileInfo.Attributes = value;
- return HttpStatusCode.OK;
- }
- }
- });
+ public EncryptingDiskStoreItem(FileInfo fileInfo, DiskStoreItemPropertyManager propertyManager, FileSystemSpecifics specifics, ILogger logger)
+ {
+ _logger = logger;
+ _specifics = specifics;
+ IsWritable = !specifics.FileSystemOptions.IsReadOnly;
+ UniqueKey = NativePathHelpers.GetPlaintextPath(fileInfo.FullName, specifics) ?? string.Empty;
+ Name = Path.GetFileName(UniqueKey);
+ FileInfo = fileInfo;
+ PropertyManager = propertyManager;
+ }
- public bool IsWritable { get; }
- public string Name => NativePathHelpers.GetPlaintextPath(_fileInfo.FullName, _specifics) ?? string.Empty;
- public string UniqueKey => _fileInfo.FullName;
- public string FullPath => NativePathHelpers.GetPlaintextPath(_fileInfo.FullName, _specifics) ?? string.Empty;
- public Task GetReadableStreamAsync(IHttpContext context) => Task.FromResult(_specifics.StreamsAccess.OpenPlaintextStream(_fileInfo.FullName, _fileInfo.OpenRead()));
- public Task GetWritableStreamAsync(IHttpContext context) => Task.FromResult(_specifics.StreamsAccess.OpenPlaintextStream(_fileInfo.FullName, _fileInfo.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite)));
+ ///
+ public async Task GetReadableStreamAsync(CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ return _specifics.StreamsAccess.OpenPlaintextStream(FileInfo.FullName, FileInfo.OpenRead());
+ }
- public async Task UploadFromStreamAsync(IHttpContext context, Stream inputStream)
+ ///
+ public async Task UploadFromStreamAsync(Stream inputStream, CancellationToken cancellationToken)
{
// Check if the item is writable
if (!IsWritable)
- return HttpStatusCode.Forbidden;
+ return DavStatusCode.Conflict;
- // Copy the stream
try
{
// Copy the information to the destination stream
- using (var outputStream = await GetWritableStreamAsync(context).ConfigureAwait(false))
+ var outputStream = _specifics.StreamsAccess.OpenPlaintextStream(FileInfo.FullName, FileInfo.OpenWrite());
+ await using (outputStream.ConfigureAwait(false))
{
- await inputStream.CopyToAsync(outputStream).ConfigureAwait(false);
+ // Copy the stream
+ await inputStream.CopyToAsync(outputStream, cancellationToken).ConfigureAwait(false);
}
- return HttpStatusCode.OK;
+ return DavStatusCode.Ok;
}
catch (IOException ioException) when (ioException.IsDiskFull())
{
- return HttpStatusCode.InsufficientStorage;
- }
- catch (Exception ex)
- {
- _ = ex;
- throw;
+ return DavStatusCode.InsufficientStorage;
}
}
- public IPropertyManager PropertyManager => DefaultPropertyManager;
- public ILockingManager LockingManager { get; }
-
- public async Task CopyAsync(IStoreCollection destination, string name, bool overwrite, IHttpContext context)
+ ///
+ public async Task CopyAsync(IStoreCollection destination, string name, bool overwrite, CancellationToken cancellationToken)
{
try
{
// If the destination is also a disk-store, then we can use the FileCopy API
// (it's probably a bit more efficient than copying in C#)
- if (destination is DiskStoreCollection diskCollection)
+ if (destination is EncryptingDiskStoreCollection diskCollection)
{
// Check if the collection is writable
if (!diskCollection.IsWritable)
- return new StoreItemResult(HttpStatusCode.Forbidden);
+ return new StoreItemResult(DavStatusCode.PreconditionFailed);
var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(diskCollection.FullPath, name), _specifics);
// Check if the file already exists
var fileExists = File.Exists(destinationPath);
if (fileExists && !overwrite)
- return new StoreItemResult(HttpStatusCode.PreconditionFailed);
+ return new StoreItemResult(DavStatusCode.PreconditionFailed);
// Copy the file
- File.Copy(_fileInfo.FullName, destinationPath, true);
+ File.Copy(FileInfo.FullName, destinationPath, true);
// Return the appropriate status
- return new StoreItemResult(fileExists ? HttpStatusCode.NoContent : HttpStatusCode.Created);
+ return new StoreItemResult(fileExists ? DavStatusCode.NoContent : DavStatusCode.Created);
}
else
{
// Create the item in the destination collection
- var result = await destination.CreateItemAsync(name, overwrite, context).ConfigureAwait(false);
-
- // Check if the item could be created
- if (result.Item != null)
+ var sourceStream = await GetReadableStreamAsync(cancellationToken).ConfigureAwait(false);
+ await using (sourceStream.ConfigureAwait(false))
{
- using (var sourceStream = await GetWritableStreamAsync(context).ConfigureAwait(false))
- {
- var copyResult = await result.Item.UploadFromStreamAsync(context, sourceStream).ConfigureAwait(false);
- if (copyResult != HttpStatusCode.OK)
- return new StoreItemResult(copyResult, result.Item);
- }
+ return await destination.CreateItemAsync(name, sourceStream, overwrite, cancellationToken).ConfigureAwait(false);
}
-
- // Return result
- return new StoreItemResult(result.Result, result.Item);
}
}
catch (Exception exc)
{
- // TODO(wd): Add logging
- //s_log.Log(LogLevel.Error, () => "Unexpected exception while copying data.", exc);
- return new StoreItemResult(HttpStatusCode.InternalServerError);
+ _logger.LogError(exc, "Unexpected exception while copying data.");
+ return new StoreItemResult(DavStatusCode.InternalServerError);
}
}
-
- public override int GetHashCode()
- {
- return _fileInfo.FullName.GetHashCode();
- }
-
- public override bool Equals(object? obj)
- {
- if (obj is not EncryptingDiskStoreItem storeItem)
- return false;
-
- return storeItem._fileInfo.FullName.Equals(_fileInfo.FullName, StringComparison.CurrentCultureIgnoreCase);
- }
-
- private string DetermineContentType()
- {
- return MimeTypeHelper.GetMimeType(Name);
- }
}
}
diff --git a/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs b/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
index a2b3c5596..385ad7231 100644
--- a/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
+++ b/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
@@ -4,6 +4,7 @@
using SecureFolderFS.Core.WebDav.AppModels;
using SecureFolderFS.Core.WebDav.EncryptingStorage2;
using System;
+using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace SecureFolderFS.Core.WebDav.Extensions
@@ -14,15 +15,19 @@ public static IServiceCollection AddEncryptingDiskStore(this IServiceCollection
{
return services
.Configure(options)
- .AddSingleton()
.AddSingleton()
+ .AddSingleton()
.AddScoped(sp =>
{
var storeOptions = sp.GetService>();
if (storeOptions?.Value.Specifics is null)
throw new NotSupportedException("Options were not configured.");
- return new(storeOptions.Value.Specifics);
+ var itemPropertyManager = sp.GetService() ?? throw new ArgumentNullException(nameof(DiskStoreItemPropertyManager));
+ var collectionPropertyManager = sp.GetService() ?? throw new ArgumentNullException(nameof(DiskStoreCollectionPropertyManager));
+ var loggerFactory = sp.GetService() ?? throw new ArgumentNullException(nameof(ILoggerFactory));
+
+ return new(storeOptions.Value.Specifics, itemPropertyManager, collectionPropertyManager, loggerFactory);
});
}
diff --git a/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs b/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
index 93270903c..6034d55fc 100644
--- a/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
+++ b/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
@@ -61,41 +61,18 @@ public virtual async Task MountAsync(IFolder folder, IDisposable unloc
webDavInstance.UseNWebDav();
_ = webDavInstance.RunAsync(url);
-
return await MountAsync(
- httpListener,
webDavOptions,
webDavInstance,
cancellationToken);
-
-
-
-
-
- var httpListener = new HttpListener();
-
- httpListener.Prefixes.Add(prefix);
- httpListener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
-
-
-
- // TODO: Implement FileSystemSpecifics
- var cryptoFolder = (IFolder)null!; // new CryptoFolder(contentFolder, streamsAccess, pathConverter, directoryIdCache);
-
- var davFolder = new DavFolder(cryptoFolder);
-
- var srv = new ServiceCollection()
- .AddNWebDav()
-
// TODO: Remove the following line once the new DavStorage is fully implemented.
var encryptingDiskStore = new EncryptingDiskStore(specifics.ContentFolder.Id, specifics, !specifics.FileSystemOptions.IsReadOnly);
var dispatcher = new WebDavDispatcher(new RootDiskStore(specifics.FileSystemOptions.VolumeName, encryptingDiskStore), davFolder, new RequestHandlerProvider(), null);
}
protected abstract Task MountAsync(
- HttpListener listener,
WebDavOptions options,
IAsyncDisposable webDavInstance,
CancellationToken cancellationToken);
From 7736a2340e38b5aa9b6294ef49e5b6ef783388cf Mon Sep 17 00:00:00 2001
From: d2dyno <53011783+d2dyno1@users.noreply.github.com>
Date: Mon, 4 Nov 2024 09:24:27 +0100
Subject: [PATCH 3/3] Finish DiskStoreCollection
---
.../MacCatalyst/MacOsWebDavFileSystem.cs | 11 +-
...gStoreOptions.cs => CipherStoreOptions.cs} | 2 +-
.../Base/DiskStoreBase2.cs | 98 ----
.../EncryptingDiskStoreCollection.cs | 462 ------------------
.../Extensions/BuilderExtensions.cs | 22 +-
.../CipherStore.cs} | 36 +-
.../Store/CipherStoreBase.cs | 74 +++
.../Store/CipherStoreCollection.cs | 326 ++++++++++++
.../CipherStoreItem.cs} | 16 +-
.../WebDavFileSystem.cs | 20 +-
.../WebDavWrapper.cs | 66 ---
11 files changed, 446 insertions(+), 687 deletions(-)
rename src/SecureFolderFS.Core.WebDav/AppModels/{EncryptingStoreOptions.cs => CipherStoreOptions.cs} (77%)
delete mode 100644 src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs
delete mode 100644 src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs
rename src/SecureFolderFS.Core.WebDav/{EncryptingStorage2/EncryptingDiskStore.cs => Store/CipherStore.cs} (64%)
create mode 100644 src/SecureFolderFS.Core.WebDav/Store/CipherStoreBase.cs
create mode 100644 src/SecureFolderFS.Core.WebDav/Store/CipherStoreCollection.cs
rename src/SecureFolderFS.Core.WebDav/{EncryptingStorage2/EncryptingDiskStoreItem.cs => Store/CipherStoreItem.cs} (89%)
delete mode 100644 src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs
diff --git a/src/Platforms/SecureFolderFS.Uno/Platforms/MacCatalyst/MacOsWebDavFileSystem.cs b/src/Platforms/SecureFolderFS.Uno/Platforms/MacCatalyst/MacOsWebDavFileSystem.cs
index ae1ec38cc..304b93b40 100644
--- a/src/Platforms/SecureFolderFS.Uno/Platforms/MacCatalyst/MacOsWebDavFileSystem.cs
+++ b/src/Platforms/SecureFolderFS.Uno/Platforms/MacCatalyst/MacOsWebDavFileSystem.cs
@@ -3,7 +3,6 @@
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
-using NWebDav.Server.Dispatching;
using SecureFolderFS.Core.WebDav;
using SecureFolderFS.Core.WebDav.AppModels;
using SecureFolderFS.Storage.VirtualFileSystem;
@@ -20,9 +19,8 @@ namespace SecureFolderFS.Uno.Platforms.Desktop
internal sealed partial class MacOsWebDavFileSystem : WebDavFileSystem
{
protected override async Task MountAsync(
- HttpListener listener,
WebDavOptions options,
- IRequestDispatcher requestDispatcher,
+ IAsyncDisposable webDavInstance,
CancellationToken cancellationToken)
{
#if __MACCATALYST__
@@ -32,14 +30,9 @@ protected override async Task MountAsync(
// Mount WebDAV volume via AppleScript
Process.Start("/usr/bin/osascript", ["-e", $"mount volume \"{remoteUri.AbsoluteUri}\""]);
var mountPoint = $"/Volumes/{options.VolumeName}";
-
- // Create wrapper
- var webDavWrapper = new WebDavWrapper(listener, requestDispatcher, mountPoint);
- webDavWrapper.StartFileSystem();
- Debug.WriteLine($"Mounted {remoteUri} on {mountPoint}.");
await Task.CompletedTask;
- return new WebDavRootFolder(webDavWrapper, new MemoryFolder(mountPoint, options.VolumeName), options);
+ return new WebDavRootFolder(webDavInstance, new MemoryFolder(mountPoint, options.VolumeName), options);
#else
throw new PlatformNotSupportedException();
#endif
diff --git a/src/SecureFolderFS.Core.WebDav/AppModels/EncryptingStoreOptions.cs b/src/SecureFolderFS.Core.WebDav/AppModels/CipherStoreOptions.cs
similarity index 77%
rename from src/SecureFolderFS.Core.WebDav/AppModels/EncryptingStoreOptions.cs
rename to src/SecureFolderFS.Core.WebDav/AppModels/CipherStoreOptions.cs
index 4c9a29019..678f41813 100644
--- a/src/SecureFolderFS.Core.WebDav/AppModels/EncryptingStoreOptions.cs
+++ b/src/SecureFolderFS.Core.WebDav/AppModels/CipherStoreOptions.cs
@@ -2,7 +2,7 @@
namespace SecureFolderFS.Core.WebDav.AppModels
{
- internal sealed class EncryptingStoreOptions
+ internal sealed class CipherStoreOptions
{
public FileSystemSpecifics? Specifics { get; set; }
}
diff --git a/src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs b/src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs
deleted file mode 100644
index ab54a59ec..000000000
--- a/src/SecureFolderFS.Core.WebDav/Base/DiskStoreBase2.cs
+++ /dev/null
@@ -1,98 +0,0 @@
-using Microsoft.Extensions.Logging;
-using NWebDav.Server.Stores;
-using System;
-using System.IO;
-using System.Security;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace SecureFolderFS.Core.WebDav.Base
-{
- internal abstract class DiskStoreBase2 : IStore
- {
- private readonly DiskStoreCollectionPropertyManager _diskStoreCollectionPropertyManager;
- private readonly DiskStoreItemPropertyManager _diskStoreItemPropertyManager;
- private readonly ILoggerFactory _loggerFactory;
-
- public abstract bool IsWritable { get; }
-
- public abstract string BaseDirectory { get; }
-
- protected DiskStoreBase2(DiskStoreCollectionPropertyManager diskStoreCollectionPropertyManager, DiskStoreItemPropertyManager diskStoreItemPropertyManager, ILoggerFactory loggerFactory)
- {
- _diskStoreCollectionPropertyManager = diskStoreCollectionPropertyManager;
- _diskStoreItemPropertyManager = diskStoreItemPropertyManager;
- _loggerFactory = loggerFactory;
- }
-
- public virtual async Task GetItemAsync(Uri uri, CancellationToken cancellationToken)
- {
- await Task.CompletedTask;
- cancellationToken.ThrowIfCancellationRequested();
-
- // Get path and item
- var path = GetPathFromUri(uri);
- var item = CreateFromPath(path);
-
- return item;
- }
-
- public virtual async Task GetCollectionAsync(Uri uri, CancellationToken cancellationToken)
- {
- await Task.CompletedTask;
- cancellationToken.ThrowIfCancellationRequested();
-
- // Determine the path from the uri
- var path = GetPathFromUri(uri);
- if (!Directory.Exists(path))
- return null;
-
- // Return the item
- return CreateFromPath(path);
- }
-
- protected virtual string GetPathFromUri(Uri uri)
- {
- // Determine the path
- var requestedPath = UriHelper.GetDecodedPath(uri)[1..].Replace('/', Path.DirectorySeparatorChar);
-
- // Determine the full path
- var fullPath = Path.GetFullPath(Path.Combine(BaseDirectory, requestedPath));
-
- // Make sure we're still inside the specified directory
- if (fullPath != BaseDirectory && !fullPath.StartsWith(BaseDirectory + Path.DirectorySeparatorChar))
- throw new SecurityException($"Uri '{uri}' is outside the '{BaseDirectory}' directory.");
-
- // Return the combined path
- return fullPath;
- }
-
- public virtual T? CreateFromPath(string path)
- where T : class, IStoreItem
- {
- if (typeof(T).IsAssignableFrom(typeof(IStoreCollection)))
- {
- return (T?)(object)CreateCollection(new DirectoryInfo(path));
- }
- //else if (typeof(T).IsAssignableFrom(typeof(IStoreFile))) // TODO: Add a StoreFile
- else
- {
- // Check if it's a directory
- if (Directory.Exists(path))
- return (T?)(object)CreateCollection(new DirectoryInfo(path));
-
- // Check if it's a file
- if (File.Exists(path))
- return (T?)(object)CreateItem(new FileInfo(path));
-
- // The item doesn't exist
- return null;
- }
-
- internal DiskStoreCollection CreateCollection(DirectoryInfo directoryInfo) =>
- new(this, _diskStoreCollectionPropertyManager, directoryInfo, _loggerFactory.CreateLogger());
-
- internal DiskStoreItem CreateItem(FileInfo file) =>
- new(this, _diskStoreItemPropertyManager, file, _loggerFactory.CreateLogger());
- }
-}
diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs
deleted file mode 100644
index b41a49f64..000000000
--- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs
+++ /dev/null
@@ -1,462 +0,0 @@
-using NWebDav.Server;
-using NWebDav.Server.Enums;
-using NWebDav.Server.Http;
-using NWebDav.Server.Locking;
-using NWebDav.Server.Props;
-using NWebDav.Server.Stores;
-using SecureFolderFS.Core.FileSystem;
-using SecureFolderFS.Core.FileSystem.Helpers;
-using SecureFolderFS.Core.FileSystem.Helpers.Native;
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.IO;
-using System.Linq;
-using System.Net;
-using System.Threading.Tasks;
-using System.Xml.Linq;
-
-namespace SecureFolderFS.Core.WebDav.EncryptingStorage2
-{
- internal sealed class EncryptingDiskStoreCollection : IStoreCollection
- {
- private static readonly XElement s_xDavCollection = new XElement(WebDavNamespaces.DavNs + "collection");
- private readonly DirectoryInfo _directoryInfo;
- private readonly FileSystemSpecifics _specifics;
-
- public EncryptingDiskStoreCollection(ILockingManager lockingManager, DirectoryInfo directoryInfo, bool isWritable, FileSystemSpecifics specifics)
- {
- LockingManager = lockingManager;
- _directoryInfo = directoryInfo;
- IsWritable = isWritable;
- _specifics = specifics;
- }
-
- public static PropertyManager DefaultPropertyManager { get; } = new(new DavProperty[]
- {
- // RFC-2518 properties
- new DavCreationDate
- {
- Getter = (context, collection) => collection._directoryInfo.CreationTimeUtc,
- Setter = (context, collection, value) =>
- {
- collection._directoryInfo.CreationTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new DavDisplayName
- {
- Getter = (context, collection) =>
- {
- return collection._directoryInfo.Name == "content"
- // Return the name of the root directory (Name will throw, as the content folder doesn't have a DirectoryID)
- ? context.Request.Url.Segments[1]
- : collection.Name;
- }
- },
- new DavGetLastModified
- {
- Getter = (context, collection) => collection._directoryInfo.LastWriteTimeUtc,
- Setter = (context, collection, value) =>
- {
- collection._directoryInfo.LastWriteTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new DavGetResourceType
- {
- Getter = (context, collection) => new []{s_xDavCollection}
- },
-
- // Default locking property handling via the LockingManager
- new DavLockDiscoveryDefault(),
- new DavSupportedLockDefault(),
-
- // Hopmann/Lippert collection properties
- new DavExtCollectionChildCount
- {
- Getter = (context, collection) => collection._directoryInfo.EnumerateFiles().Count() + collection._directoryInfo.EnumerateDirectories().Count()
- },
- new DavExtCollectionIsFolder
- {
- Getter = (context, collection) => true
- },
- new DavExtCollectionIsHidden
- {
- Getter = (context, collection) => (collection._directoryInfo.Attributes & FileAttributes.Hidden) != 0
- },
- new DavExtCollectionIsStructuredDocument
- {
- Getter = (context, collection) => false
- },
- new DavExtCollectionHasSubs
- {
- Getter = (context, collection) => collection._directoryInfo.EnumerateDirectories().Any()
- },
- new DavExtCollectionNoSubs
- {
- Getter = (context, collection) => false
- },
- new DavExtCollectionObjectCount
- {
- Getter = (context, collection) => collection._directoryInfo.EnumerateFiles().Count()
- },
- new DavExtCollectionReserved
- {
- Getter = (context, collection) => !collection.IsWritable
- },
- new DavExtCollectionVisibleCount
- {
- Getter = (context, collection) =>
- collection._directoryInfo.EnumerateDirectories().Count(di => (di.Attributes & FileAttributes.Hidden) == 0) +
- collection._directoryInfo.EnumerateFiles().Count(fi => (fi.Attributes & FileAttributes.Hidden) == 0)
- },
-
- // Win32 extensions
- new Win32CreationTime
- {
- Getter = (context, collection) => collection._directoryInfo.CreationTimeUtc,
- Setter = (context, collection, value) =>
- {
- collection._directoryInfo.CreationTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new Win32LastAccessTime
- {
- Getter = (context, collection) => collection._directoryInfo.LastAccessTimeUtc,
- Setter = (context, collection, value) =>
- {
- collection._directoryInfo.LastAccessTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new Win32LastModifiedTime
- {
- Getter = (context, collection) => collection._directoryInfo.LastWriteTimeUtc,
- Setter = (context, collection, value) =>
- {
- collection._directoryInfo.LastWriteTimeUtc = value;
- return HttpStatusCode.OK;
- }
- },
- new Win32FileAttributes
- {
- Getter = (context, collection) => collection._directoryInfo.Attributes,
- Setter = (context, collection, value) =>
- {
- collection._directoryInfo.Attributes = value;
- return HttpStatusCode.OK;
- }
- }
- });
-
- public bool IsWritable { get; }
- public string Name => NativePathHelpers.GetPlaintextPath(_directoryInfo.FullName, _specifics) ?? string.Empty;
- public string UniqueKey => _directoryInfo.FullName;
- public string FullPath => NativePathHelpers.GetPlaintextPath(_directoryInfo.FullName, _specifics) ?? string.Empty;
-
- // Disk collections (a.k.a. directories don't have their own data)
- public Task GetReadableStreamAsync(IHttpContext context) => Task.FromResult((Stream)null);
- public Task UploadFromStreamAsync(IHttpContext context, Stream inputStream) => Task.FromResult(HttpStatusCode.Conflict);
-
- public IPropertyManager PropertyManager => DefaultPropertyManager;
- public ILockingManager LockingManager { get; }
-
- public Task GetItemAsync(string name, IHttpContext context)
- {
- // Determine the full path
- var fullPath = NativePathHelpers.GetCiphertextPath(Path.Combine(FullPath, name), _specifics);
-
- // Check if the item is a file
- if (File.Exists(fullPath))
- return Task.FromResult(new EncryptingDiskStoreItem(LockingManager, new FileInfo(fullPath), IsWritable, _specifics));
-
- // Check if the item is a directory
- if (Directory.Exists(fullPath))
- return Task.FromResult(new EncryptingDiskStoreCollection(LockingManager, new DirectoryInfo(fullPath), IsWritable, _specifics));
-
- // Item not found
- return Task.FromResult(null);
- }
-
- public Task> GetItemsAsync(IHttpContext context)
- {
- IEnumerable GetItemsInternal()
- {
- // Add all directories
- foreach (var subDirectory in _directoryInfo.GetDirectories())
- {
- if (PathHelpers.IsCoreFile(subDirectory.Name))
- continue;
-
- yield return new EncryptingDiskStoreCollection(LockingManager, subDirectory, IsWritable, _specifics);
- }
-
- // Add all files
- foreach (var file in _directoryInfo.GetFiles())
- {
- if (PathHelpers.IsCoreFile(file.Name))
- continue;
-
- yield return new EncryptingDiskStoreItem(LockingManager, file, IsWritable, _specifics);
- }
- }
-
- return Task.FromResult(GetItemsInternal());
- }
-
- public Task CreateItemAsync(string name, bool overwrite, IHttpContext context)
- {
- // Return error
- if (!IsWritable)
- return Task.FromResult(new StoreItemResult(HttpStatusCode.Forbidden));
-
- // Determine the destination path
- var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(FullPath, name), _specifics);
-
- // Determine result
- HttpStatusCode result;
-
- // Check if the file can be overwritten
- if (File.Exists(name))
- {
- if (!overwrite)
- return Task.FromResult(new StoreItemResult(HttpStatusCode.PreconditionFailed));
-
- result = HttpStatusCode.NoContent;
- }
- else
- {
- result = HttpStatusCode.Created;
- }
-
- try
- {
- // Create a new file
- File.Create(destinationPath).Dispose();
- }
- catch (Exception exc)
- {
- // Log exception
- // TODO(wd): Add logging
- //s_log.Log(LogLevel.Error, () => $"Unable to create '{destinationPath}' file.", exc);
- return Task.FromResult(new StoreItemResult(HttpStatusCode.InternalServerError));
- }
-
- // Return result
- return Task.FromResult(new StoreItemResult(result, new EncryptingDiskStoreItem(LockingManager, new FileInfo(destinationPath), IsWritable, _specifics)));
- }
-
- public Task CreateCollectionAsync(string name, bool overwrite, IHttpContext context)
- {
- // Return error
- if (!IsWritable)
- return Task.FromResult(new StoreCollectionResult(HttpStatusCode.Forbidden));
-
- // Determine the destination path
- var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(FullPath, name), _specifics);
-
- // Check if the directory can be overwritten
- HttpStatusCode result;
- if (Directory.Exists(destinationPath))
- {
- // Check if overwrite is allowed
- if (!overwrite)
- return Task.FromResult(new StoreCollectionResult(HttpStatusCode.MethodNotAllowed));
-
- // Overwrite existing
- result = HttpStatusCode.NoContent;
- }
- else
- {
- // Created new directory
- result = HttpStatusCode.Created;
- }
-
- try
- {
- // Attempt to create the directory
- Directory.CreateDirectory(destinationPath);
-
- // Create new DirectoryID
- var directoryId = Guid.NewGuid().ToByteArray();
- var directoryIdPath = Path.Combine(destinationPath, FileSystem.Constants.Names.DIRECTORY_ID_FILENAME);
-
- // Initialize directory with DirectoryID
- using var directoryIdStream = File.Open(directoryIdPath, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
- directoryIdStream.Write(directoryId);
-
- // Set DirectoryID to known IDs
- _specifics.DirectoryIdCache.CacheSet(directoryIdPath, new(directoryId));
- }
- catch (Exception exc)
- {
- // Log exception
- // TODO(wd): Add logging
- //s_log.Log(LogLevel.Error, () => $"Unable to create '{destinationPath}' directory.", exc);
- return null;
- }
-
- // Return the collection
- return Task.FromResult(new StoreCollectionResult(result, new EncryptingDiskStoreCollection(LockingManager, new DirectoryInfo(destinationPath), IsWritable, _specifics)));
- }
-
- public async Task CopyAsync(IStoreCollection destinationCollection, string name, bool overwrite, IHttpContext context)
- {
- // Just create the folder itself
- var result = await destinationCollection.CreateCollectionAsync(name, overwrite, context).ConfigureAwait(false);
- return new StoreItemResult(result.Result, result.Collection);
- }
-
- public bool SupportsFastMove(IStoreCollection destination, string destinationName, bool overwrite, IHttpContext context)
- {
- // We can only move disk-store collections
- return destination is EncryptingDiskStoreCollection;
- }
-
- public async Task MoveItemAsync(string sourceName, IStoreCollection destinationCollection, string destinationName, bool overwrite, IHttpContext context)
- {
- // Return error
- if (!IsWritable)
- return new StoreItemResult(HttpStatusCode.Forbidden);
-
- // Determine the object that is being moved
- var item = await GetItemAsync(sourceName, context).ConfigureAwait(false);
- if (item == null)
- return new StoreItemResult(HttpStatusCode.NotFound);
-
- try
- {
- // If the destination collection is a directory too, then we can simply move the file
- if (destinationCollection is EncryptingDiskStoreCollection destinationDiskStoreCollection)
- {
- // Return error
- if (!destinationDiskStoreCollection.IsWritable)
- return new StoreItemResult(HttpStatusCode.Forbidden);
-
- // Determine source and destination paths
- var sourcePath = NativePathHelpers.GetCiphertextPath(Path.Combine(FullPath, sourceName), _specifics);
- var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(destinationDiskStoreCollection.FullPath, destinationName), _specifics);
-
- // Check if the file already exists
- HttpStatusCode result;
- if (File.Exists(destinationPath))
- {
- // Remove the file if it already exists (if allowed)
- if (!overwrite)
- return new StoreItemResult(HttpStatusCode.PreconditionFailed);
-
- // The file will be overwritten
- File.Delete(destinationPath);
- result = HttpStatusCode.NoContent;
- }
- else if (Directory.Exists(destinationPath))
- {
- // Remove the directory if it already exists (if allowed)
- if (!overwrite)
- return new StoreItemResult(HttpStatusCode.PreconditionFailed);
-
- // The file will be overwritten
- Directory.Delete(destinationPath, true);
- result = HttpStatusCode.NoContent;
- }
- else
- {
- // The file will be "created"
- result = HttpStatusCode.Created;
- }
-
- switch (item)
- {
- case EncryptingDiskStoreItem _:
- // Move the file
- File.Move(sourcePath, destinationPath);
- return new StoreItemResult(result, new EncryptingDiskStoreItem(LockingManager, new FileInfo(destinationPath), IsWritable, _specifics));
-
- case EncryptingDiskStoreCollection _:
- // Move the directory
- Directory.Move(sourcePath, destinationPath);
- return new StoreItemResult(result, new EncryptingDiskStoreCollection(LockingManager, new DirectoryInfo(destinationPath), IsWritable, _specifics));
-
- default:
- // Invalid item
- Debug.Fail($"Invalid item {item.GetType()} inside the {nameof(DiskStoreCollection)}.");
- return new StoreItemResult(HttpStatusCode.InternalServerError);
- }
- }
- else
- {
- // Attempt to copy the item to the destination collection
- var result = await item.CopyAsync(destinationCollection, destinationName, overwrite, context).ConfigureAwait(false);
- if (result.Result == HttpStatusCode.Created || result.Result == HttpStatusCode.NoContent)
- await DeleteItemAsync(sourceName, context).ConfigureAwait(false);
-
- // Return the result
- return result;
- }
- }
- catch (UnauthorizedAccessException)
- {
- return new StoreItemResult(HttpStatusCode.Forbidden);
- }
- }
-
- public Task DeleteItemAsync(string name, IHttpContext context)
- {
- // Return error
- if (!IsWritable)
- return Task.FromResult(HttpStatusCode.Forbidden);
-
- // Determine the full path
- var fullPath = NativePathHelpers.GetCiphertextPath(Path.Combine(FullPath, name), _specifics);
- try
- {
- // Check if the file exists
- if (File.Exists(fullPath))
- {
- // Delete the file
- File.Delete(fullPath);
- return Task.FromResult(HttpStatusCode.NoContent);
- }
-
- // Check if the directory exists
- if (Directory.Exists(fullPath))
- {
- // Delete the directory
- Directory.Delete(fullPath, true);
- return Task.FromResult(HttpStatusCode.NoContent);
- }
-
- // Item not found
- return Task.FromResult(HttpStatusCode.NotFound);
- }
- catch (UnauthorizedAccessException)
- {
- return Task.FromResult(HttpStatusCode.Forbidden);
- }
- catch (Exception exc)
- {
- // Log exception
- // TODO(wd): Add logging
- //s_log.Log(LogLevel.Error, () => $"Unable to delete '{fullPath}' directory.", exc);
- return Task.FromResult(HttpStatusCode.InternalServerError);
- }
- }
-
- public EnumerationDepthMode InfiniteDepthMode => EnumerationDepthMode.Rejected;
-
- public override int GetHashCode()
- {
- return _directoryInfo.FullName.GetHashCode();
- }
-
- public override bool Equals(object? obj)
- {
- if (obj is not EncryptingDiskStoreCollection storeCollection)
- return false;
-
- return storeCollection._directoryInfo.FullName.Equals(_directoryInfo.FullName, StringComparison.CurrentCultureIgnoreCase);
- }
- }
-}
diff --git a/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs b/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
index 385ad7231..b9549558d 100644
--- a/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
+++ b/src/SecureFolderFS.Core.WebDav/Extensions/BuilderExtensions.cs
@@ -1,34 +1,32 @@
-using Microsoft.Extensions.DependencyInjection;
-using NWebDav.Server;
-using NWebDav.Server.Stores;
-using SecureFolderFS.Core.WebDav.AppModels;
-using SecureFolderFS.Core.WebDav.EncryptingStorage2;
-using System;
+using System;
+using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
+using NWebDav.Server.Stores;
+using SecureFolderFS.Core.WebDav.AppModels;
+using SecureFolderFS.Core.WebDav.Store;
namespace SecureFolderFS.Core.WebDav.Extensions
{
internal static class BuilderExtensions
{
- public static IServiceCollection AddEncryptingDiskStore(this IServiceCollection services, Action options)
+ public static IServiceCollection AddCipherStore(this IServiceCollection services, Action options)
{
return services
.Configure(options)
.AddSingleton()
.AddSingleton()
- .AddScoped(sp =>
+ .AddScoped(sp =>
{
- var storeOptions = sp.GetService>();
+ var storeOptions = sp.GetService>();
if (storeOptions?.Value.Specifics is null)
throw new NotSupportedException("Options were not configured.");
var itemPropertyManager = sp.GetService() ?? throw new ArgumentNullException(nameof(DiskStoreItemPropertyManager));
var collectionPropertyManager = sp.GetService() ?? throw new ArgumentNullException(nameof(DiskStoreCollectionPropertyManager));
- var loggerFactory = sp.GetService() ?? throw new ArgumentNullException(nameof(ILoggerFactory));
-
- return new(storeOptions.Value.Specifics, itemPropertyManager, collectionPropertyManager, loggerFactory);
+ var logger = sp.GetService() ?? throw new ArgumentNullException(nameof(ILogger));
+ return new(storeOptions.Value.Specifics, itemPropertyManager, collectionPropertyManager, logger);
});
}
}
diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs b/src/SecureFolderFS.Core.WebDav/Store/CipherStore.cs
similarity index 64%
rename from src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
rename to src/SecureFolderFS.Core.WebDav/Store/CipherStore.cs
index c092377a6..ac93321ed 100644
--- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs
+++ b/src/SecureFolderFS.Core.WebDav/Store/CipherStore.cs
@@ -1,14 +1,14 @@
-using Microsoft.Extensions.Logging;
+using System;
+using System.IO;
+using Microsoft.Extensions.Logging;
+using NWebDav.Server.Props;
using NWebDav.Server.Stores;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Core.FileSystem.Helpers.Native;
-using SecureFolderFS.Core.WebDav.Base;
-using System;
-using System.IO;
-namespace SecureFolderFS.Core.WebDav.EncryptingStorage2
+namespace SecureFolderFS.Core.WebDav.Store
{
- internal sealed class EncryptingDiskStore : DiskStoreBase2
+ internal sealed class CipherStore : CipherStoreBase
{
private readonly FileSystemSpecifics _specifics;
@@ -18,12 +18,12 @@ internal sealed class EncryptingDiskStore : DiskStoreBase2
///
public override string BaseDirectory { get; }
- public EncryptingDiskStore(
+ public CipherStore(
FileSystemSpecifics specifics,
- DiskStoreItemPropertyManager itemPropertyManager,
- DiskStoreCollectionPropertyManager collectionPropertyManager,
- ILoggerFactory loggerFactory)
- : base(collectionPropertyManager, itemPropertyManager, loggerFactory)
+ IPropertyManager itemPropertyManager,
+ IPropertyManager collectionPropertyManager,
+ ILogger logger)
+ : base(collectionPropertyManager, itemPropertyManager, logger)
{
_specifics = specifics;
IsWritable = !specifics.FileSystemOptions.IsReadOnly;
@@ -35,18 +35,18 @@ public EncryptingDiskStore(
{
if (typeof(T).IsAssignableFrom(typeof(IStoreCollection)))
{
- return (T?)(object)new EncryptingDiskStoreCollection(new DirectoryInfo(path));
+ return (T?)(object)new CipherStoreCollection(new(path), collectionPropertyManager, _specifics, this, logger);
}
- //else if (typeof(T).IsAssignableFrom(typeof(IStoreFile))) // TODO: Add a StoreFile
+ //else if (typeof(T).IsAssignableFrom(typeof(IStoreFile))) // TODO: Add an IStoreFile
else
{
- // Check if it's a directory
- if (Directory.Exists(path))
- return (T?)(object)new EncryptingDiskStoreCollection(new DirectoryInfo(path));
-
// Check if it's a file
if (File.Exists(path))
- return (T?)(object)new EncryptingDiskStoreItem(new FileInfo(path));
+ return (T?)(object)new CipherStoreItem(new(path), itemPropertyManager, _specifics, this, logger);
+
+ // Check if it's a directory
+ if (Directory.Exists(path))
+ return (T?)(object)new CipherStoreCollection(new(path), collectionPropertyManager, _specifics, this, logger);
// The item doesn't exist
return null;
diff --git a/src/SecureFolderFS.Core.WebDav/Store/CipherStoreBase.cs b/src/SecureFolderFS.Core.WebDav/Store/CipherStoreBase.cs
new file mode 100644
index 000000000..4a77f20ff
--- /dev/null
+++ b/src/SecureFolderFS.Core.WebDav/Store/CipherStoreBase.cs
@@ -0,0 +1,74 @@
+using Microsoft.Extensions.Logging;
+using NWebDav.Server.Stores;
+using System;
+using System.IO;
+using System.Security;
+using System.Threading;
+using System.Threading.Tasks;
+using NWebDav.Server.Helpers;
+using NWebDav.Server.Props;
+
+namespace SecureFolderFS.Core.WebDav.Store
+{
+ internal abstract class CipherStoreBase : IStore
+ {
+ protected readonly IPropertyManager collectionPropertyManager;
+ protected readonly IPropertyManager itemPropertyManager;
+ protected readonly ILogger logger;
+
+ public abstract bool IsWritable { get; }
+
+ public abstract string BaseDirectory { get; }
+
+ protected CipherStoreBase(IPropertyManager collectionPropertyManager, IPropertyManager itemPropertyManager, ILogger logger)
+ {
+ this.collectionPropertyManager = collectionPropertyManager;
+ this.itemPropertyManager = itemPropertyManager;
+ this.logger = logger;
+ }
+
+ public virtual async Task GetItemAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Get path and item
+ var path = GetPathFromUri(uri);
+ var item = CreateFromPath(path);
+
+ return item;
+ }
+
+ public virtual async Task GetCollectionAsync(Uri uri, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Determine the path from the uri
+ var path = GetPathFromUri(uri);
+ if (!Directory.Exists(path))
+ return null;
+
+ // Return the item
+ return CreateFromPath(path);
+ }
+
+ protected virtual string GetPathFromUri(Uri uri)
+ {
+ // Determine the path
+ var requestedPath = UriHelper.GetDecodedPath(uri)[1..].Replace('/', Path.DirectorySeparatorChar);
+
+ // Determine the full path
+ var fullPath = Path.GetFullPath(Path.Combine(BaseDirectory, requestedPath));
+
+ // Make sure we're still inside the specified directory
+ if (fullPath != BaseDirectory && !fullPath.StartsWith(BaseDirectory + Path.DirectorySeparatorChar))
+ throw new SecurityException($"Uri '{uri}' is outside the '{BaseDirectory}' directory.");
+
+ // Return the combined path
+ return fullPath;
+ }
+
+ public abstract T? CreateFromPath(string path) where T : class, IStoreItem;
+ }
+}
diff --git a/src/SecureFolderFS.Core.WebDav/Store/CipherStoreCollection.cs b/src/SecureFolderFS.Core.WebDav/Store/CipherStoreCollection.cs
new file mode 100644
index 000000000..c852aa482
--- /dev/null
+++ b/src/SecureFolderFS.Core.WebDav/Store/CipherStoreCollection.cs
@@ -0,0 +1,326 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Logging;
+using NWebDav.Server;
+using NWebDav.Server.Props;
+using NWebDav.Server.Stores;
+using SecureFolderFS.Core.FileSystem;
+using SecureFolderFS.Core.FileSystem.Helpers;
+using SecureFolderFS.Core.FileSystem.Helpers.Native;
+
+namespace SecureFolderFS.Core.WebDav.Store
+{
+ internal sealed class CipherStoreCollection : IStoreCollection
+ {
+ private readonly FileSystemSpecifics _specifics;
+ private readonly CipherStore _store;
+ private readonly ILogger _logger;
+
+ ///
+ public string Name { get; }
+
+ ///
+ public string UniqueKey { get; }
+
+ ///
+ public InfiniteDepthMode InfiniteDepthMode { get; } = InfiniteDepthMode.Rejected;
+
+ ///
+ public IPropertyManager PropertyManager { get; }
+
+ public DirectoryInfo DirectoryInfo { get; } // TODO: Not from interface
+ public bool IsWritable { get; } // TODO: Not from interface
+
+ public CipherStoreCollection(DirectoryInfo directoryInfo, IPropertyManager propertyManager, FileSystemSpecifics specifics, CipherStore store, ILogger logger)
+ {
+ _specifics = specifics;
+ _store = store;
+ _logger = logger;
+ DirectoryInfo = directoryInfo;
+ IsWritable = specifics.FileSystemOptions.IsReadOnly;
+ UniqueKey = NativePathHelpers.GetPlaintextPath(directoryInfo.FullName, specifics) ?? string.Empty;
+ Name = Path.GetFileName(UniqueKey);
+ DirectoryInfo = directoryInfo;
+ PropertyManager = propertyManager;
+ }
+
+ ///
+ public Task GetReadableStreamAsync(CancellationToken cancellationToken)
+ => Task.FromResult(Stream.Null);
+
+ ///
+ public Task UploadFromStreamAsync(Stream inputStream, CancellationToken cancellationToken)
+ => Task.FromResult(DavStatusCode.Conflict);
+
+ ///
+ public async Task GetItemAsync(string name, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Get the item path
+ var fullPath = NativePathHelpers.GetCiphertextPath(Path.Combine(UniqueKey, name), _specifics);
+
+ // Create a new item instance
+ return _store.CreateFromPath(fullPath);
+ }
+
+ ///
+ public async IAsyncEnumerable GetItemsAsync([EnumeratorCancellation] CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Add all directories
+ foreach (var item in Directory.EnumerateDirectories(DirectoryInfo.FullName))
+ {
+ if (PathHelpers.IsCoreFile(Path.GetFileName(item)))
+ continue;
+
+ var directory = _store.CreateFromPath(item);
+ if (directory is null)
+ continue;
+
+ yield return directory;
+ }
+
+ // Add all files
+ foreach (var item in Directory.EnumerateFiles(DirectoryInfo.FullName))
+ {
+ if (PathHelpers.IsCoreFile(Path.GetFileName(item)))
+ continue;
+
+ var file = _store.CreateFromPath(item);
+ if (file is null)
+ continue;
+
+ yield return file;
+ }
+ }
+
+ ///
+ public async Task CreateItemAsync(string name, Stream stream, bool overwrite, CancellationToken cancellationToken)
+ {
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Return error
+ if (!IsWritable)
+ return new StoreItemResult(DavStatusCode.PreconditionFailed);
+
+ // Determine the destination path
+ var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(UniqueKey, name), _specifics);
+
+ // Check if the file can be overwritten
+ if (File.Exists(destinationPath) && !overwrite)
+ return new StoreItemResult(DavStatusCode.PreconditionFailed);
+
+ try
+ {
+ var file = File.Create(destinationPath);
+ await using (file.ConfigureAwait(false))
+ {
+ await stream.CopyToAsync(file, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ catch (Exception exc)
+ {
+ // Log exception
+ _logger.LogError(exc, "Unable to create '{Path}' file.", destinationPath);
+ return new StoreItemResult(DavStatusCode.InternalServerError);
+ }
+
+ // Return result
+ var item = _store.CreateFromPath(destinationPath);
+ return new StoreItemResult(DavStatusCode.Created, item);
+ }
+
+ ///
+ public async Task CreateCollectionAsync(string name, bool overwrite, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+ cancellationToken.ThrowIfCancellationRequested();
+
+ // Return error
+ if (!IsWritable)
+ return new StoreCollectionResult(DavStatusCode.PreconditionFailed);
+
+ // Determine the destination path
+ var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(DirectoryInfo.FullName, name), _specifics);
+
+ // Check if the directory can be overwritten
+ DavStatusCode result;
+ if (Directory.Exists(destinationPath))
+ {
+ // Check if overwrite is allowed
+ if (!overwrite)
+ return new StoreCollectionResult(DavStatusCode.PreconditionFailed);
+
+ // Overwrite existing
+ result = DavStatusCode.NoContent;
+ }
+ else
+ {
+ // Created new directory
+ result = DavStatusCode.Created;
+ }
+
+ // Attempt to create the directory
+ Directory.CreateDirectory(destinationPath);
+
+ // Return the collection
+ return new StoreCollectionResult(result, _store.CreateFromPath(destinationPath));
+ }
+
+ ///
+ public async Task CopyAsync(IStoreCollection destinationCollection, string name, bool overwrite, CancellationToken cancellationToken)
+ {
+ // Just create the folder itself
+ var result = await destinationCollection.CreateCollectionAsync(name, overwrite, cancellationToken).ConfigureAwait(false);
+ return new StoreItemResult(result.Result, result.Collection);
+ }
+
+ ///
+ public bool SupportsFastMove(IStoreCollection destination, string destinationName, bool overwrite)
+ {
+ // We can only move disk-store collections
+ return destination is CipherStoreCollection;
+ }
+
+ ///
+ public async Task MoveItemAsync(string sourceName, IStoreCollection destinationCollection, string destinationName, bool overwrite, CancellationToken cancellationToken)
+ {
+ // Return error
+ if (!IsWritable)
+ return new StoreItemResult(DavStatusCode.PreconditionFailed);
+
+ // Determine the object that is being moved
+ var item = await GetItemAsync(sourceName, cancellationToken).ConfigureAwait(false);
+ if (item == null)
+ return new StoreItemResult(DavStatusCode.NotFound);
+
+ try
+ {
+ // If the destination collection is a directory too, then we can simply move the file
+ if (destinationCollection is DiskStoreCollection destinationDiskStoreCollection)
+ {
+ // Return error
+ if (!destinationDiskStoreCollection.IsWritable)
+ return new StoreItemResult(DavStatusCode.PreconditionFailed);
+
+ // Determine source and destination paths
+ var sourcePath = NativePathHelpers.GetCiphertextPath(Path.Combine(DirectoryInfo.FullName, sourceName), _specifics);
+ var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(destinationDiskStoreCollection.DirectoryInfo.FullName, destinationName), _specifics);
+
+ // Check if the file already exists
+ DavStatusCode result;
+ if (File.Exists(destinationPath))
+ {
+ // Remove the file if it already exists (if allowed)
+ if (!overwrite)
+ return new StoreItemResult(DavStatusCode.Forbidden);
+
+ // The file will be overwritten
+ File.Delete(destinationPath);
+ result = DavStatusCode.NoContent;
+ }
+ else if (Directory.Exists(destinationPath))
+ {
+ // Remove the directory if it already exists (if allowed)
+ if (!overwrite)
+ return new StoreItemResult(DavStatusCode.Forbidden);
+
+ // The file will be overwritten
+ Directory.Delete(destinationPath, true);
+ result = DavStatusCode.NoContent;
+ }
+ else
+ {
+ // The file will be "created"
+ result = DavStatusCode.Created;
+ }
+
+ switch (item)
+ {
+ case DiskStoreItem _:
+ // Move the file
+ File.Move(sourcePath, destinationPath);
+ return new StoreItemResult(result, _store.CreateFromPath(destinationPath));
+
+ case DiskStoreCollection _:
+ // Move the directory
+ Directory.Move(sourcePath, destinationPath);
+ return new StoreItemResult(result, _store.CreateFromPath(destinationPath));
+
+ default:
+ // Invalid item
+ Debug.Fail($"Invalid item {item.GetType()} inside the {nameof(DiskStoreCollection)}.");
+ return new StoreItemResult(DavStatusCode.InternalServerError);
+ }
+ }
+ else
+ {
+ // Attempt to copy the item to the destination collection
+ var result = await item.CopyAsync(destinationCollection, destinationName, overwrite, cancellationToken).ConfigureAwait(false);
+ if (result.Result == DavStatusCode.Created || result.Result == DavStatusCode.NoContent)
+ await DeleteItemAsync(sourceName, cancellationToken).ConfigureAwait(false);
+
+ // Return the result
+ return result;
+ }
+ }
+ catch (UnauthorizedAccessException)
+ {
+ return new StoreItemResult(DavStatusCode.Forbidden);
+ }
+ }
+
+ ///
+ public async Task DeleteItemAsync(string name, CancellationToken cancellationToken)
+ {
+ await Task.CompletedTask;
+
+ // Return error
+ if (!IsWritable)
+ return DavStatusCode.PreconditionFailed;
+
+ // Determine the full path
+ var fullPath = NativePathHelpers.GetCiphertextPath(Path.Combine(DirectoryInfo.FullName, name), _specifics);
+ try
+ {
+ // Check if the file exists
+ if (File.Exists(fullPath))
+ {
+ // Delete the file
+ File.Delete(fullPath);
+ return DavStatusCode.Ok;
+ }
+
+ // Check if the directory exists
+ if (Directory.Exists(fullPath))
+ {
+ // Delete the directory
+ Directory.Delete(fullPath, true);
+ return DavStatusCode.Ok;
+ }
+
+ // Item not found
+ return DavStatusCode.NotFound;
+ }
+ catch (UnauthorizedAccessException)
+ {
+ return DavStatusCode.Forbidden;
+ }
+ catch (Exception exc)
+ {
+ // Log exception
+ _logger.LogError(exc, "Unable to delete '{Path}' directory.", fullPath);
+ return DavStatusCode.InternalServerError;
+ }
+ }
+ }
+}
diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs b/src/SecureFolderFS.Core.WebDav/Store/CipherStoreItem.cs
similarity index 89%
rename from src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs
rename to src/SecureFolderFS.Core.WebDav/Store/CipherStoreItem.cs
index 487e62917..7c6b06406 100644
--- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs
+++ b/src/SecureFolderFS.Core.WebDav/Store/CipherStoreItem.cs
@@ -10,12 +10,13 @@
using System.Threading;
using System.Threading.Tasks;
-namespace SecureFolderFS.Core.WebDav.EncryptingStorage2
+namespace SecureFolderFS.Core.WebDav.Store
{
- internal class EncryptingDiskStoreItem : IStoreItem
+ internal class CipherStoreItem : IStoreItem
{
private readonly FileSystemSpecifics _specifics;
- private readonly ILogger _logger;
+ private readonly CipherStore _store;
+ private readonly ILogger _logger;
///
public string Name { get; }
@@ -29,10 +30,11 @@ internal class EncryptingDiskStoreItem : IStoreItem
public FileInfo FileInfo { get; } // TODO: Not from interface
public bool IsWritable { get; } // TODO: Not from interface
- public EncryptingDiskStoreItem(FileInfo fileInfo, DiskStoreItemPropertyManager propertyManager, FileSystemSpecifics specifics, ILogger logger)
+ public CipherStoreItem(FileInfo fileInfo, IPropertyManager propertyManager, FileSystemSpecifics specifics, CipherStore store, ILogger logger)
{
- _logger = logger;
_specifics = specifics;
+ _store = store;
+ _logger = logger;
IsWritable = !specifics.FileSystemOptions.IsReadOnly;
UniqueKey = NativePathHelpers.GetPlaintextPath(fileInfo.FullName, specifics) ?? string.Empty;
Name = Path.GetFileName(UniqueKey);
@@ -79,13 +81,13 @@ public async Task CopyAsync(IStoreCollection destination, strin
{
// If the destination is also a disk-store, then we can use the FileCopy API
// (it's probably a bit more efficient than copying in C#)
- if (destination is EncryptingDiskStoreCollection diskCollection)
+ if (destination is CipherStoreCollection diskCollection)
{
// Check if the collection is writable
if (!diskCollection.IsWritable)
return new StoreItemResult(DavStatusCode.PreconditionFailed);
- var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(diskCollection.FullPath, name), _specifics);
+ var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(diskCollection.UniqueKey, name), _specifics);
// Check if the file already exists
var fileExists = File.Exists(destinationPath);
diff --git a/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs b/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
index 6034d55fc..ddec336bc 100644
--- a/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
+++ b/src/SecureFolderFS.Core.WebDav/WebDavFileSystem.cs
@@ -1,21 +1,18 @@
-using Microsoft.AspNetCore.Builder;
-using Microsoft.Extensions.DependencyInjection;
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
using NWebDav.Server;
using OwlCore.Storage;
using SecureFolderFS.Core.Cryptography;
using SecureFolderFS.Core.FileSystem;
using SecureFolderFS.Core.WebDav.AppModels;
-using SecureFolderFS.Core.WebDav.EncryptingStorage2;
using SecureFolderFS.Core.WebDav.Extensions;
using SecureFolderFS.Core.WebDav.Helpers;
using SecureFolderFS.Shared.ComponentModel;
using SecureFolderFS.Storage.Enums;
using SecureFolderFS.Storage.VirtualFileSystem;
-using System;
-using System.Collections.Generic;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
namespace SecureFolderFS.Core.WebDav
{
@@ -52,7 +49,7 @@ public virtual async Task MountAsync(IFolder folder, IDisposable unloc
var builder = WebApplication.CreateBuilder();
builder.Services
.AddNWebDav()
- .AddEncryptingDiskStore(x =>
+ .AddCipherStore(x =>
{
x.Specifics = specifics;
});
@@ -65,11 +62,6 @@ public virtual async Task MountAsync(IFolder folder, IDisposable unloc
webDavOptions,
webDavInstance,
cancellationToken);
-
-
- // TODO: Remove the following line once the new DavStorage is fully implemented.
- var encryptingDiskStore = new EncryptingDiskStore(specifics.ContentFolder.Id, specifics, !specifics.FileSystemOptions.IsReadOnly);
- var dispatcher = new WebDavDispatcher(new RootDiskStore(specifics.FileSystemOptions.VolumeName, encryptingDiskStore), davFolder, new RequestHandlerProvider(), null);
}
protected abstract Task MountAsync(
diff --git a/src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs b/src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs
deleted file mode 100644
index db6db58b2..000000000
--- a/src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs
+++ /dev/null
@@ -1,66 +0,0 @@
-using NWebDav.Server.Dispatching;
-using NWebDav.Server.HttpListener;
-using SecureFolderFS.Core.WebDav.Helpers;
-using System;
-using System.Diagnostics;
-using System.Net;
-using System.Threading;
-using System.Threading.Tasks;
-
-namespace SecureFolderFS.Core.WebDav
-{
- public sealed class WebDavWrapper
- {
- private Thread? _fsThead;
- private readonly HttpListener _httpListener;
- private readonly IRequestDispatcher _requestDispatcher;
- private readonly CancellationTokenSource _fileSystemCts;
- private readonly string? _mountPath;
-
- public WebDavWrapper(HttpListener httpListener, IRequestDispatcher requestDispatcher, string? mountPath = null)
- {
- _httpListener = httpListener;
- _requestDispatcher = requestDispatcher;
- _fileSystemCts = new();
- _mountPath = mountPath;
- }
-
- public void StartFileSystem()
- {
- var ts = new ThreadStart(async () => await EnsureFileSystemAsync());
- _fsThead = new Thread(ts);
- _fsThead.Start();
- }
-
- private async Task EnsureFileSystemAsync()
- {
- try
- {
- _httpListener.Start();
- while (!_fileSystemCts.IsCancellationRequested && (await _httpListener.GetContextAsync() is var httpListenerContext))
- {
- if (httpListenerContext.Request.IsAuthenticated)
- Debugger.Break();
-
- var context = new HttpContext(httpListenerContext);
- await _requestDispatcher.DispatchRequestAsync(context, _fileSystemCts.Token);
- }
- }
- catch (Exception ex)
- {
- _ = ex;
- }
- }
-
- public async Task CloseFileSystemAsync()
- {
- _httpListener.Close();
- await _fileSystemCts.CancelAsync();
-
- if (_mountPath is not null)
- DriveMappingHelpers.DisconnectNetworkDrive(_mountPath, true);
-
- return true;
- }
- }
-}