diff --git a/lib/nwebdav b/lib/nwebdav index d4b2cf86e..0590862fb 160000 --- a/lib/nwebdav +++ b/lib/nwebdav @@ -1 +1 @@ -Subproject commit d4b2cf86ef8e773b97e55bd701e03b0569aa3ed7 +Subproject commit 0590862fb04a49ac66477c94f790926f94c57225 diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs index 6dca8fe64..149c6a4f8 100644 --- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs +++ b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStore.cs @@ -1,10 +1,10 @@ -using NWebDav.Server.Http; -using NWebDav.Server.Locking; +using NWebDav.Server.Locking; using NWebDav.Server.Stores; using SecureFolderFS.Core.FileSystem; using SecureFolderFS.Core.FileSystem.Helpers.Paths.Native; using System; using System.IO; +using System.Threading; using System.Threading.Tasks; namespace SecureFolderFS.Core.WebDav.EncryptingStorage2 @@ -19,24 +19,24 @@ public EncryptingDiskStore(string directory, FileSystemSpecifics specifics, bool _specifics = specifics; } - public override Task GetItemAsync(Uri uri, IHttpContext context) + public override Task GetItemAsync(Uri uri, CancellationToken cancellationToken) { // 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)); + 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)); + return Task.FromResult(new EncryptingDiskStoreFile(LockingManager, new FileInfo(path), IsWritable, _specifics)); // The item doesn't exist - return Task.FromResult(null); + return Task.FromResult(null); } - public override Task GetCollectionAsync(Uri uri, IHttpContext context) + public override Task GetCollectionAsync(Uri uri, CancellationToken cancellationToken) { // Determine the path from the uri var path = GetPathFromUri(uri); diff --git a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs index 5fbcb3c15..e9e449823 100644 --- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs +++ b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreCollection.cs @@ -1,9 +1,9 @@ using NWebDav.Server; using NWebDav.Server.Enums; -using NWebDav.Server.Http; using NWebDav.Server.Locking; using NWebDav.Server.Props; using NWebDav.Server.Stores; +using OwlCore.Storage; using SecureFolderFS.Core.FileSystem; using SecureFolderFS.Core.FileSystem.Helpers.Paths; using SecureFolderFS.Core.FileSystem.Helpers.Paths.Native; @@ -13,25 +13,250 @@ using System.IO; using System.Linq; using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; 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; private readonly FileSystemSpecifics _specifics; + /// + public string Id { get; } + + /// + public string Name { get; } + public EncryptingDiskStoreCollection(ILockingManager lockingManager, DirectoryInfo directoryInfo, bool isWritable, FileSystemSpecifics specifics) { - LockingManager = lockingManager; + _specifics = specifics; _directoryInfo = directoryInfo; + + Id = NativePathHelpers.GetPlaintextPath(_directoryInfo.FullName, _specifics) ?? string.Empty; + Name = Path.GetFileName(Id); + LockingManager = lockingManager; IsWritable = isWritable; - _specifics = specifics; } + /// + public async IAsyncEnumerable GetItemsAsync(StorableType type = StorableType.All, [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + await Task.CompletedTask; + cancellationToken.ThrowIfCancellationRequested(); + + switch (type) + { + case StorableType.File: + { + foreach (var file in _directoryInfo.GetFiles()) + { + if (PathHelpers.IsCoreName(file.Name)) + continue; + + yield return new DiskStoreFile(LockingManager, file, IsWritable); + } + + break; + } + + case StorableType.Folder: + { + foreach (var folder in _directoryInfo.GetDirectories()) + { + if (PathHelpers.IsCoreName(folder.Name)) + continue; + + yield return new DiskStoreCollection(LockingManager, folder, IsWritable); + } + + break; + } + + case StorableType.All: + { + foreach (var folder in _directoryInfo.GetDirectories()) + { + if (PathHelpers.IsCoreName(folder.Name)) + continue; + + + yield return new EncryptingDiskStoreCollection(LockingManager, folder, IsWritable, _specifics); + } + + foreach (var file in _directoryInfo.GetFiles()) + { + if (PathHelpers.IsCoreName(file.Name)) + continue; + + yield return new EncryptingDiskStoreFile(LockingManager, file, IsWritable, _specifics); + } + + break; + } + } + } + + /// + public async Task GetFirstByNameAsync(string name, CancellationToken cancellationToken) + { + await Task.CompletedTask; + cancellationToken.ThrowIfCancellationRequested(); + + // Determine the path + var id = NativePathHelpers.GetCiphertextPath(Path.Combine(Id, name), _specifics); + + // Check if the item is a file + if (File.Exists(id)) + return new EncryptingDiskStoreFile(LockingManager, new(id), IsWritable, _specifics); + + // Check if the item is a directory + if (Directory.Exists(id)) + return new EncryptingDiskStoreCollection(LockingManager, new(id), IsWritable, _specifics); + + // Item not found + throw new FileNotFoundException($"An item was not found. Name: '{name}'."); + } + + /// + public async Task MoveItemAsync(IStoreItem storeItem, IStoreCollection destinationCollection, string destinationName, bool overwrite, CancellationToken cancellationToken) + { + // Return error + if (!IsWritable) + throw new HttpListenerException((int)HttpStatusCode.PreconditionFailed); + + 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) + throw new HttpListenerException((int)HttpStatusCode.PreconditionFailed); + + // Determine source and destination paths + var sourcePath = NativePathHelpers.GetCiphertextPath(storeItem.Id, _specifics); + var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(destinationDiskStoreCollection.Id, 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) + throw new HttpListenerException((int)HttpStatusCode.Forbidden); + + // 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) + throw new HttpListenerException((int)HttpStatusCode.Forbidden); + + // The file will be overwritten + Directory.Delete(destinationPath, true); + result = HttpStatusCode.NoContent; + } + else + { + // The file will be "created" + result = HttpStatusCode.Created; + } + + switch (storeItem) + { + case EncryptingDiskStoreFile _: + // Move the file + File.Move(sourcePath, destinationPath); + return new EncryptingDiskStoreFile(LockingManager, new FileInfo(destinationPath), IsWritable, _specifics); + + case EncryptingDiskStoreCollection _: + // Move the directory + Directory.Move(sourcePath, destinationPath); + return new EncryptingDiskStoreCollection(LockingManager, new DirectoryInfo(destinationPath), IsWritable, _specifics); + + default: + // Invalid item + Debug.Fail($"Invalid item {storeItem.GetType()} inside the {nameof(DiskStoreCollection)}."); + throw new HttpListenerException((int)HttpStatusCode.InternalServerError); + } + } + else + { + // Attempt to copy the item to the destination collection + var result = await storeItem.CopyAsync(destinationCollection, destinationName, overwrite, cancellationToken).ConfigureAwait(false); + if (result.Result == HttpStatusCode.Created || result.Result == HttpStatusCode.NoContent) + { + await DeleteAsync(storeItem, cancellationToken).ConfigureAwait(false); + return result.Item!; + } + else + { + throw new HttpListenerException((int)result.Result); + } + } + } + catch (UnauthorizedAccessException) + { + throw new HttpListenerException((int)HttpStatusCode.Forbidden); + } + } + + /// + public async Task DeleteAsync(IStoreItem storeItem, CancellationToken cancellationToken) + { + await Task.CompletedTask; + + // Return error + if (!IsWritable) + throw new HttpListenerException((int)HttpStatusCode.PreconditionFailed); + + // Determine the full path + var fullPath = NativePathHelpers.GetCiphertextPath(Path.Combine(Id, storeItem.Name), _specifics); + try + { + // Check if the file exists + if (File.Exists(fullPath)) + { + // Delete the file + File.Delete(fullPath); + return; + } + + // Check if the directory exists + if (Directory.Exists(fullPath)) + { + // Delete the directory + Directory.Delete(fullPath, true); + return; + } + + // Item not found + throw new HttpListenerException((int)HttpStatusCode.NotFound); + } + catch (UnauthorizedAccessException) + { + throw new HttpListenerException((int)HttpStatusCode.Forbidden); + } + catch (Exception) + { + // Log exception + // TODO(wd): Add logging + //s_log.Log(LogLevel.Error, () => $"Unable to delete '{fullPath}' directory.", exc); + throw new HttpListenerException((int)HttpStatusCode.InternalServerError); + } + } + + + + public static PropertyManager DefaultPropertyManager { get; } = new(new DavProperty[] { // RFC-2518 properties @@ -152,68 +377,17 @@ public EncryptingDiskStoreCollection(ILockingManager lockingManager, DirectoryIn }); 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.IsCoreName(subDirectory.Name)) - continue; - - yield return new EncryptingDiskStoreCollection(LockingManager, subDirectory, IsWritable, _specifics); - } - - // Add all files - foreach (var file in _directoryInfo.GetFiles()) - { - if (PathHelpers.IsCoreName(file.Name)) - continue; - - yield return new EncryptingDiskStoreItem(LockingManager, file, IsWritable, _specifics); - } - } - - return Task.FromResult(GetItemsInternal()); - } - - public Task CreateItemAsync(string name, bool overwrite, IHttpContext context) + public Task CreateItemAsync(string name, bool overwrite, CancellationToken cancellationToken) { // Return error if (!IsWritable) return Task.FromResult(new StoreItemResult(HttpStatusCode.Forbidden)); // Determine the destination path - var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(FullPath, name), _specifics); + var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(Id, name), _specifics); // Determine result HttpStatusCode result; @@ -245,17 +419,17 @@ public Task CreateItemAsync(string name, bool overwrite, IHttpC } // Return result - return Task.FromResult(new StoreItemResult(result, new EncryptingDiskStoreItem(LockingManager, new FileInfo(destinationPath), IsWritable, _specifics))); + return Task.FromResult(new StoreItemResult(result, new EncryptingDiskStoreFile(LockingManager, new FileInfo(destinationPath), IsWritable, _specifics))); } - public Task CreateCollectionAsync(string name, bool overwrite, IHttpContext context) + public Task CreateCollectionAsync(string name, bool overwrite, CancellationToken cancellationToken) { // Return error if (!IsWritable) return Task.FromResult(new StoreCollectionResult(HttpStatusCode.Forbidden)); // Determine the destination path - var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(FullPath, name), _specifics); + var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(Id, name), _specifics); // Check if the directory can be overwritten HttpStatusCode result; @@ -302,161 +476,19 @@ public Task CreateCollectionAsync(string name, bool overw 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) + public async Task CopyAsync(IStoreCollection destinationCollection, string name, bool overwrite, CancellationToken cancellationToken) { // Just create the folder itself - var result = await destinationCollection.CreateCollectionAsync(name, overwrite, context).ConfigureAwait(false); + 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, IHttpContext context) + public bool SupportsFastMove(IStoreCollection destination, string destinationName, bool overwrite) { // 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/EncryptingStorage2/EncryptingDiskStoreItem.cs b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreFile.cs similarity index 74% rename from src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs rename to src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreFile.cs index 1b56e28f2..0790d57f1 100644 --- a/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreItem.cs +++ b/src/SecureFolderFS.Core.WebDav/EncryptingStorage2/EncryptingDiskStoreFile.cs @@ -1,5 +1,4 @@ using NWebDav.Server.Helpers; -using NWebDav.Server.Http; using NWebDav.Server.Locking; using NWebDav.Server.Props; using NWebDav.Server.Stores; @@ -8,16 +7,17 @@ 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 EncryptingDiskStoreFile : IStoreFile { private readonly FileSystemSpecifics _specifics; private readonly FileInfo _fileInfo; - public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo, bool isWritable, FileSystemSpecifics specifics) + public EncryptingDiskStoreFile(ILockingManager lockingManager, FileInfo fileInfo, bool isWritable, FileSystemSpecifics specifics) { LockingManager = lockingManager; IsWritable = isWritable; @@ -25,10 +25,10 @@ public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo _specifics = specifics; } - public static PropertyManager DefaultPropertyManager { get; } = new(new DavProperty[] + public static PropertyManager DefaultPropertyManager { get; } = new(new DavProperty[] { // RFC-2518 properties - new DavCreationDate + new DavCreationDate { Getter = (context, item) => item._fileInfo.CreationTimeUtc, Setter = (context, item, value) => @@ -37,23 +37,23 @@ public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo return HttpStatusCode.OK; } }, - new DavDisplayName + new DavDisplayName { Getter = (context, item) => item.Name }, - new DavGetContentLength + new DavGetContentLength { Getter = (context, item) => Math.Max(0, item._specifics.Security.ContentCrypt.CalculatePlaintextSize(item._fileInfo.Length - item._specifics.Security.HeaderCrypt.HeaderCiphertextSize)) }, - new DavGetContentType + new DavGetContentType { Getter = (context, item) => item.DetermineContentType() }, - new DavGetEtag + new DavGetEtag { Getter = (context, item) => $"{item._fileInfo.Length}-{item._fileInfo.LastWriteTimeUtc.ToFileTime()}" }, - new DavGetLastModified + new DavGetLastModified { Getter = (context, item) => item._fileInfo.LastWriteTimeUtc, Setter = (context, item, value) => @@ -62,24 +62,24 @@ public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo return HttpStatusCode.OK; } }, - new DavGetResourceType + new DavGetResourceType { Getter = (context, item) => null }, // Default locking property handling via the LockingManager - new DavLockDiscoveryDefault(), - new DavSupportedLockDefault(), + new DavLockDiscoveryDefault(), + new DavSupportedLockDefault(), // Hopmann/Lippert collection properties // (although not a collection, the IsHidden property might be valuable) - new DavExtCollectionIsHidden + new DavExtCollectionIsHidden { Getter = (context, item) => (item._fileInfo.Attributes & FileAttributes.Hidden) != 0 }, // Win32 extensions - new Win32CreationTime + new Win32CreationTime { Getter = (context, item) => item._fileInfo.CreationTimeUtc, Setter = (context, item, value) => @@ -88,7 +88,7 @@ public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo return HttpStatusCode.OK; } }, - new Win32LastAccessTime + new Win32LastAccessTime { Getter = (context, item) => item._fileInfo.LastAccessTimeUtc, Setter = (context, item, value) => @@ -97,7 +97,7 @@ public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo return HttpStatusCode.OK; } }, - new Win32LastModifiedTime + new Win32LastModifiedTime { Getter = (context, item) => item._fileInfo.LastWriteTimeUtc, Setter = (context, item, value) => @@ -106,7 +106,7 @@ public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo return HttpStatusCode.OK; } }, - new Win32FileAttributes + new Win32FileAttributes { Getter = (context, item) => item._fileInfo.Attributes, Setter = (context, item, value) => @@ -118,13 +118,12 @@ public EncryptingDiskStoreItem(ILockingManager lockingManager, FileInfo fileInfo }); 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 string Name => Path.GetFileName(Id); + public string Id => NativePathHelpers.GetPlaintextPath(_fileInfo.FullName, _specifics) ?? string.Empty; + public Task GetReadableStreamAsync(CancellationToken cancellationToken) => Task.FromResult(_specifics.StreamsAccess.OpenPlaintextStream(_fileInfo.FullName, _fileInfo.OpenRead())); + public Task GetWritableStreamAsync(CancellationToken cancellationToken) => Task.FromResult(_specifics.StreamsAccess.OpenPlaintextStream(_fileInfo.FullName, _fileInfo.Open(FileMode.OpenOrCreate, FileAccess.ReadWrite))); - public async Task UploadFromStreamAsync(IHttpContext context, Stream inputStream) + public async Task UploadFromStreamAsync(Stream inputStream, CancellationToken cancellationToken) { // Check if the item is writable if (!IsWritable) @@ -134,7 +133,7 @@ public async Task UploadFromStreamAsync(IHttpContext context, St try { // Copy the information to the destination stream - using (var outputStream = await GetWritableStreamAsync(context).ConfigureAwait(false)) + using (var outputStream = await GetWritableStreamAsync(cancellationToken).ConfigureAwait(false)) { await inputStream.CopyToAsync(outputStream).ConfigureAwait(false); } @@ -155,7 +154,7 @@ public async Task UploadFromStreamAsync(IHttpContext context, St 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 { @@ -167,7 +166,7 @@ public async Task CopyAsync(IStoreCollection destination, strin if (!diskCollection.IsWritable) return new StoreItemResult(HttpStatusCode.Forbidden); - var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(diskCollection.FullPath, name), _specifics); + var destinationPath = NativePathHelpers.GetCiphertextPath(Path.Combine(diskCollection.Id, name), _specifics); // Check if the file already exists var fileExists = File.Exists(destinationPath); @@ -183,18 +182,23 @@ public async Task CopyAsync(IStoreCollection destination, strin else { // Create the item in the destination collection - var result = await destination.CreateItemAsync(name, overwrite, context).ConfigureAwait(false); + var result = await destination.CreateItemAsync(name, overwrite, cancellationToken).ConfigureAwait(false); // Check if the item could be created - if (result.Item != null) + if (result.Item is IStoreFile storeFile) { - using (var sourceStream = await GetWritableStreamAsync(context).ConfigureAwait(false)) + using (var sourceStream = await GetWritableStreamAsync(cancellationToken).ConfigureAwait(false)) { - var copyResult = await result.Item.UploadFromStreamAsync(context, sourceStream).ConfigureAwait(false); + var copyResult = await storeFile.UploadFromStreamAsync(sourceStream, cancellationToken).ConfigureAwait(false); if (copyResult != HttpStatusCode.OK) return new StoreItemResult(copyResult, result.Item); } } + else + { + // Item is directory + return new(HttpStatusCode.Conflict, result.Item); + } // Return result return new StoreItemResult(result.Result, result.Item); @@ -215,7 +219,7 @@ public override int GetHashCode() public override bool Equals(object? obj) { - if (obj is not EncryptingDiskStoreItem storeItem) + if (obj is not EncryptingDiskStoreFile storeItem) return false; return storeItem._fileInfo.FullName.Equals(_fileInfo.FullName, StringComparison.CurrentCultureIgnoreCase); diff --git a/src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs b/src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs index db6db58b2..267175b94 100644 --- a/src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs +++ b/src/SecureFolderFS.Core.WebDav/WebDavWrapper.cs @@ -1,5 +1,4 @@ using NWebDav.Server.Dispatching; -using NWebDav.Server.HttpListener; using SecureFolderFS.Core.WebDav.Helpers; using System; using System.Diagnostics; @@ -42,8 +41,7 @@ private async Task EnsureFileSystemAsync() if (httpListenerContext.Request.IsAuthenticated) Debugger.Break(); - var context = new HttpContext(httpListenerContext); - await _requestDispatcher.DispatchRequestAsync(context, _fileSystemCts.Token); + await _requestDispatcher.DispatchRequestAsync(httpListenerContext, _fileSystemCts.Token); } } catch (Exception ex)