From 974195262354217987d7250a6907dc9f10738366 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 13:46:09 +1300 Subject: [PATCH 01/13] Update Dockerfile: - Updated to .NET 7 images as project changed to use .NET 7 (from .NET 6) - Fixed COPY where it was copying WebDavService.Application CSPROJ that doesn't exist (or was renamed previously) --- WebDavServer.WebApi/Dockerfile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WebDavServer.WebApi/Dockerfile b/WebDavServer.WebApi/Dockerfile index 9143b4c..40710ab 100644 --- a/WebDavServer.WebApi/Dockerfile +++ b/WebDavServer.WebApi/Dockerfile @@ -1,17 +1,17 @@ #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. -FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base +FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base WORKDIR /app EXPOSE 80 -FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build +FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build ENV ASPNETCORE_ENVIRONMENT=Staging WORKDIR /src COPY ["WebDavServer.WebApi/WebDavServer.WebApi.csproj", "WebDavServer.WebApi/"] COPY ["WebDavServer.Infrastructure/WebDavServer.Infrastructure.csproj", "WebDavServer.Infrastructure/"] COPY ["WebDavServer.Infrastructure.FileStorage/WebDavServer.Infrastructure.FileStorage.csproj", "WebDavServer.Infrastructure.FileStorage/"] -COPY ["WebDavService.Application/WebDavService.Application.csproj", "WebDavService.Application/"] +COPY ["WebDavServer.Application/WebDavServer.Application.csproj", "WebDavService.Application/"] COPY ["WebDavServer.Infrastructure.WebDav/WebDavServer.Infrastructure.WebDav.csproj", "WebDavServer.Infrastructure.WebDav/"] COPY ["WebDavServer.Infrastructure.Cache/WebDavServer.Infrastructure.Cache.csproj", "WebDavServer.Infrastructure.Cache/"] RUN dotnet restore "WebDavServer.WebApi/WebDavServer.WebApi.csproj" From 8f3054ac3fd93fe071dad7e565a6e844b1c6146e Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 14:00:57 +1300 Subject: [PATCH 02/13] - Instead of using First() or Last(), changed it to use the index instead to get rid of some unnecessary overhead (nitpicky I know) - Made the isDirectory check more reliable, found that in a WebDAV environment (Windows PC mapped drive to server), it picked up requests as not being a directory, causing some issues as the actual request (path) didn't end with "/" from GetPath() on the entry-point - Added an if-check to check if the item is, in fact, a directory before removing it from the list of directories. This made navigation possible to the last most child directory, otherwise it would show nothing --- .../Services/PathService.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs index 213c08e..4e96188 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs @@ -27,7 +27,7 @@ public async Task GetDestinationPathInfoAsync(string relativePath, Can if (directories.Any()) { - var nextDirectory = directories.First(); + var nextDirectory = directories[0]; var otherDirectories = directories.Skip(1).ToList(); var directoryInfo = await GetItemAsync(null, string.Empty, nextDirectory, otherDirectories, cancellationToken); @@ -50,12 +50,15 @@ public async Task GetDestinationPathInfoAsync(string relativePath, Can var directories = relativePathTrim.Split("/").Where(x => !string.IsNullOrEmpty(x)).ToList(); - var isDirectory = relativePathTrim.EndsWith("/"); + var isDirectory = relativePathTrim == "/" || !Path.HasExtension(relativePathTrim); directories.Insert(0, RootDirectory); - var resourceName = directories.Last(); - directories.RemoveAt(directories.Count - 1); + var resourceName = directories[directories.Count - 1]; + if (!isDirectory) + { + directories.RemoveAt(directories.Count - 1); + } return (resourceName, directories, isDirectory); } From c33961e8f92f1f9be4d325e55f2074a17da73079 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 14:10:11 +1300 Subject: [PATCH 03/13] Updated Readme with steps to get this up and running with the Postgres example --- ReadMe.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 95bd880..7b010fc 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -3,5 +3,6 @@ Implemented as a clean architecture, to replace the FileStorage, replace the [We ## Installation -1. Use docker-compose to start application in docker (http://localhost:5000/swagger) -2. Use [BitKinex client](http://www.bitkinex.com/) for connect with webdav server +1. Navigate to the Database folder in the command-line and add the migrations by executing add-migrations.ps1 with an argument of the name you'd like to give it or dotnet ef migrations add postgres --startup-project ./../WebDavServer.WebApi --project ./../WebDavServer.EF.Postgres.FileStorage -c FileStoragePostgresDbContext -o Migrations +2. Use docker-compose to start application in docker, you can navigate to http://localhost:5000/swagger for the exposed API end-points. +3. Use any WebDAV client you'd like and connect to http://localhost:5000/, alternatively on Windows, you can map a drive and give it that location. From 5fbc3070b1d329b94f70cb331abb3a5527829c69 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 14:13:23 +1300 Subject: [PATCH 04/13] - Fixed an issue where, when running MKCOL, a PROPFIND check is done first to see if the directory exists, if it didn't gave an error because the database is expected to always return a value --- .../Services/VirtualStorageService.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs index fea44d5..50c0ff0 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs @@ -88,11 +88,11 @@ public async Task> GetDirectoryInfoAsync(PathInfo pathInfo, bool with .Where(x => x.IsDirectory) .Where(x => x.Title == pathInfo.ResourceName) .Where(x => x.DirectoryId == directoryId) - .FirstAsync(cancellationToken); - - var result = new List { directory }; + .FirstOrDefaultAsync(cancellationToken); + + var result = directory != null ? new List { directory } : new List(); - if (withContent) + if (directory is not null && withContent) { var contents = await GetDirectoryAsync(directory.Id, cancellationToken); result.AddRange(contents); From 5679637c5d3e147eb0b814ded7f1437ad8282e4a Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 14:38:20 +1300 Subject: [PATCH 05/13] - Fixes an issue where an exception is thrown when propertiesList is empty. This happens when the directory or file doesn't exist. As part of the sequence (maybe specific to Windows mapped drives), it does a PROPFIND before running MKCOL/Get etc. --- .../Services/WebDavService.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/WebDavServer.Infrastructure.WebDav/Services/WebDavService.cs b/WebDavServer.Infrastructure.WebDav/Services/WebDavService.cs index e2a48e5..aaec0ef 100644 --- a/WebDavServer.Infrastructure.WebDav/Services/WebDavService.cs +++ b/WebDavServer.Infrastructure.WebDav/Services/WebDavService.cs @@ -70,8 +70,12 @@ public async Task PropfindAsync(PropfindRequest r, CancellationToken can var xMultiStatus = XmlHelper.GetRoot(ns, "multistatus", dictNamespaces); - var xResponse = GetPropfindXmlResponse(ns, new List(), propertiesList.First(), r.Url); - xMultiStatus.Add(xResponse); + XElement xResponse; + if (propertiesList.Count > 0) + { + xResponse = GetPropfindXmlResponse(ns, new List(), propertiesList.First(), r.Url); + xMultiStatus.Add(xResponse); + } foreach (var properties in propertiesList.Skip(1)) { From ee8bc93f93bc046d8b4bc8892ea83103d629eb88 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 14:41:47 +1300 Subject: [PATCH 06/13] - Fixed an issue where an exception was thrown that's unhandled in the WebDAV response when an item doesn't exist. PROPFIND runs before MKCOL so it's important not to throw an exception in this sequence --- WebDavServer.Infrastructure.FileStorage/Services/PathService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs index 4e96188..b85402c 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs @@ -80,7 +80,7 @@ private async Task GetItemAsync( if (item == null) { - throw new FileStorageException(ErrorCodes.PartOfPathNotExists); + return default; } var virtualPath = $"{relativePath}/{directoryName}"; From 57e174ceeeaf6377570b6b74b6f533510bf3f94d Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 15:00:10 +1300 Subject: [PATCH 07/13] - Changed PROPFIND to be an IActionResult to return 404 NotFound when requesting dumb windows files (Usually through Mapped Drive on Windows machines) --- WebDavServer.WebApi/Controllers/WebDavController.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WebDavServer.WebApi/Controllers/WebDavController.cs b/WebDavServer.WebApi/Controllers/WebDavController.cs index 8de304e..f24b47c 100644 --- a/WebDavServer.WebApi/Controllers/WebDavController.cs +++ b/WebDavServer.WebApi/Controllers/WebDavController.cs @@ -48,8 +48,17 @@ public async Task GetAsync(string? path, CancellationToken cancellationToken = d [ApiExplorerSettings(IgnoreApi = true)] [AcceptVerbs("PROPFIND")] - public async Task PropfindAsync(string? path, CancellationToken cancellationToken) + public async Task PropfindAsync(string? path, CancellationToken cancellationToken) { + if (path is not null && (path.Contains("desktop.ini") || + path.Contains("folder.gif") || + path.Contains("folder.jpg") || + path.Contains("thumbs.db"))) + { + + return StatusCode((int)HttpStatusCode.NotFound); + } + var returnXml = await _webDavService.PropfindAsync(new PropfindRequest { Url = $"{Request.GetDisplayUrl().TrimEnd('/')}/", @@ -59,7 +68,7 @@ public async Task PropfindAsync(string? path, CancellationToken cancella Response.StatusCode = (int)HttpStatusCode.MultiStatus; - return returnXml; + return Content(returnXml, "application/xml", Encoding.UTF8); } [HttpHead] From 7f92ef2a0a20a77b8b2d84d36c654b12ffe3f188 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 15:02:05 +1300 Subject: [PATCH 08/13] - Fixed an issue where the RegEx ignored valid filenames. Important when Windows, by default, creates "New Folder" for you to rename when creatng. This should be cross-platform compatible. --- .../Services/FileStorageService.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs b/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs index 82e216d..6c77f67 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs @@ -1,5 +1,4 @@ using Microsoft.AspNetCore.StaticFiles; -using System.Text.RegularExpressions; using WebDavServer.Application.Contracts.Cache; using WebDavServer.Application.Contracts.FileStorage; using WebDavServer.Application.Contracts.FileStorage.Enums; @@ -354,7 +353,7 @@ private async Task CreateDirectoryAsync(string path, CancellationToke { var pathInfo = await _pathService.GetDestinationPathInfoAsync(path, cancellationToken); - if (!Regex.IsMatch(pathInfo.ResourceName, @"^[a-zA-Z0-9_]+$", RegexOptions.Compiled)) + if (!HasInvalidChars(pathInfo.ResourceName)) { return ErrorType.PartResourcePathNotExists; } @@ -447,5 +446,9 @@ private string GetContentType(string fileName) return "text/plain"; } + private bool HasInvalidChars(string directoryName) + { + return (!string.IsNullOrEmpty(directoryName) && directoryName.IndexOfAny(Path.GetInvalidPathChars()) >= 0); + } } } From 63b789cd8370ad5ba39b0641d5aef3d9f0e774be Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 15:12:25 +1300 Subject: [PATCH 09/13] - Fixed an issue where, it's checking the parent Id when getting the directory info instead of the actual directory referenced --- .../Services/VirtualStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs index 50c0ff0..26bbe66 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs @@ -87,7 +87,7 @@ public async Task> GetDirectoryInfoAsync(PathInfo pathInfo, bool with var directory = await _dbContext.Set() .Where(x => x.IsDirectory) .Where(x => x.Title == pathInfo.ResourceName) - .Where(x => x.DirectoryId == directoryId) + .Where(x => x.Id == directoryId) .FirstOrDefaultAsync(cancellationToken); var result = directory != null ? new List { directory } : new List(); From a9c554ad49064805877585b8865f3ec1d29121f0 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 15:14:45 +1300 Subject: [PATCH 10/13] - Align with previous changes for consistency --- .../Services/PathService.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs index b85402c..90cc43e 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs @@ -32,7 +32,7 @@ public async Task GetDestinationPathInfoAsync(string relativePath, Can var directoryInfo = await GetItemAsync(null, string.Empty, nextDirectory, otherDirectories, cancellationToken); - directory = GetLastChild(directoryInfo); + directory = GetLastChild(directoryInfo!); } return new PathInfo @@ -63,7 +63,7 @@ public async Task GetDestinationPathInfoAsync(string relativePath, Can return (resourceName, directories, isDirectory); } - private async Task GetItemAsync( + private async Task GetItemAsync( long? parentDirectoryId, string relativePath, string directoryName, @@ -87,7 +87,7 @@ private async Task GetItemAsync( if (nextDirectories.Any()) { - var nextDirectory = nextDirectories.First(); + var nextDirectory = nextDirectories[0]; var otherDirectories = nextDirectories.Skip(1).ToList(); child = await GetItemAsync(item.Id, virtualPath, nextDirectory, otherDirectories, cancellationToken); From 0647bdeb72db8c03d9ac9897d360f3d9ab8e4476 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 15:21:40 +1300 Subject: [PATCH 11/13] Update ReadMe.md --- ReadMe.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ReadMe.md b/ReadMe.md index 7b010fc..cfa1be7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -3,6 +3,7 @@ Implemented as a clean architecture, to replace the FileStorage, replace the [We ## Installation -1. Navigate to the Database folder in the command-line and add the migrations by executing add-migrations.ps1 with an argument of the name you'd like to give it or dotnet ef migrations add postgres --startup-project ./../WebDavServer.WebApi --project ./../WebDavServer.EF.Postgres.FileStorage -c FileStoragePostgresDbContext -o Migrations -2. Use docker-compose to start application in docker, you can navigate to http://localhost:5000/swagger for the exposed API end-points. -3. Use any WebDAV client you'd like and connect to http://localhost:5000/, alternatively on Windows, you can map a drive and give it that location. +1. Navigate to the Database folder in the command-line and add the migrations by executing add-migrations.ps1 with an argument of the name you'd like to give it or + ```dotnet ef migrations add postgres --startup-project ./../WebDavServer.WebApi --project ./../WebDavServer.EF.Postgres.FileStorage -c FileStoragePostgresDbContext -o Migrations``` +3. Use docker-compose to start application in docker, you can navigate to http://localhost:5000/swagger for the exposed API end-points. +4. Use any WebDAV client you'd like and connect to http://localhost:5000/, alternatively on Windows, you can map a drive and give it that location. From 808e8563aa38e26ec37ecb5db25f2183a8fccc36 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 15:57:03 +1300 Subject: [PATCH 12/13] Accidentially inverted the check for HasInvalidChars --- .../Services/FileStorageService.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs b/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs index 6c77f67..08f1b36 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs @@ -353,7 +353,7 @@ private async Task CreateDirectoryAsync(string path, CancellationToke { var pathInfo = await _pathService.GetDestinationPathInfoAsync(path, cancellationToken); - if (!HasInvalidChars(pathInfo.ResourceName)) + if (HasInvalidChars(pathInfo.ResourceName)) { return ErrorType.PartResourcePathNotExists; } From 21fc159079e92573dfb42df4ba54e79599ffdb51 Mon Sep 17 00:00:00 2001 From: AcidRaZor Date: Fri, 22 Dec 2023 16:10:20 +1300 Subject: [PATCH 13/13] - Fixes an issue on directory rename where it was referencing the parent instead of the directory being renamed - Updated Name to match Title --- .../Services/VirtualStorageService.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs index 26bbe66..6c7b1ee 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs @@ -140,7 +140,7 @@ public async Task MoveDirectoryAsync(PathInfo srcPath, PathInfo dstPath, Cancell var item = await _dbContext.Set() .Where(x => x.IsDirectory) .Where(x => x.Title == srcPath.ResourceName) - .Where(x => x.DirectoryId == directoryId) + .Where(x => x.Id == directoryId) .FirstOrDefaultAsync(cancellationToken); if (item is null) @@ -150,6 +150,7 @@ public async Task MoveDirectoryAsync(PathInfo srcPath, PathInfo dstPath, Cancell item.DirectoryId = dstPath.Directory.Id; item.Title = dstPath.ResourceName; + item.Name = dstPath.ResourceName; await _dbContext.SaveChangesAsync(cancellationToken); }