diff --git a/ReadMe.md b/ReadMe.md index 95bd880..cfa1be7 100644 --- a/ReadMe.md +++ b/ReadMe.md @@ -3,5 +3,7 @@ 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``` +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. diff --git a/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs b/WebDavServer.Infrastructure.FileStorage/Services/FileStorageService.cs index 82e216d..08f1b36 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); + } } } diff --git a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs index 213c08e..90cc43e 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/PathService.cs @@ -27,12 +27,12 @@ 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); - directory = GetLastChild(directoryInfo); + directory = GetLastChild(directoryInfo!); } return new PathInfo @@ -50,17 +50,20 @@ 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); } - private async Task GetItemAsync( + private async Task GetItemAsync( long? parentDirectoryId, string relativePath, string directoryName, @@ -77,14 +80,14 @@ private async Task GetItemAsync( if (item == null) { - throw new FileStorageException(ErrorCodes.PartOfPathNotExists); + return default; } var virtualPath = $"{relativePath}/{directoryName}"; 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); diff --git a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs index fea44d5..6c7b1ee 100644 --- a/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs +++ b/WebDavServer.Infrastructure.FileStorage/Services/VirtualStorageService.cs @@ -87,12 +87,12 @@ 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) - .FirstAsync(cancellationToken); - - var result = new List { directory }; + .Where(x => x.Id == directoryId) + .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); @@ -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); } 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)) { 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] 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"