diff --git a/src/Dav.AspNetCore.Server.Extensions.Ado/SqlLockManager.cs b/src/Dav.AspNetCore.Server.Extensions.Ado/SqlLockManager.cs index 85e6c35..5069787 100644 --- a/src/Dav.AspNetCore.Server.Extensions.Ado/SqlLockManager.cs +++ b/src/Dav.AspNetCore.Server.Extensions.Ado/SqlLockManager.cs @@ -67,12 +67,13 @@ public async ValueTask LockAsync( timeout, DateTime.UtcNow); - var depth = uri.LocalPath.Count(x => x == '/') - 1; + var pathForDepth = uri.GetPath(); + var depth = pathForDepth.Count(x => x == '/') - 1; await using var command = GetInsertCommand( connection.Value, newLock.Id.AbsoluteUri, - newLock.Uri.LocalPath, + newLock.Uri.GetPath(), newLock.LockType, newLock.Owner, newLock.Recursive, @@ -111,7 +112,7 @@ public async ValueTask RefreshLockAsync( await using var command = GetActiveLockByIdCommand( connection.Value, token.AbsoluteUri, - uri.LocalPath, + uri.GetPath(), (long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds); await using var reader = await command.ExecuteReaderAsync(cancellationToken); @@ -153,7 +154,7 @@ public async ValueTask UnlockAsync( await using var command = GetActiveLockByIdCommand( connection.Value, token.AbsoluteUri, - uri.LocalPath, + uri.GetPath(), (long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds); await using var reader = await command.ExecuteReaderAsync(cancellationToken); @@ -186,11 +187,12 @@ public async ValueTask> GetLocksAsync( await connection.Value.OpenAsync(cancellationToken); var allActiveLocks = new List(); - var depth = uri.LocalPath.Count(x => x == '/') - 1; + var pathForDepth = uri.GetPath(); + var depth = pathForDepth.Count(x => x == '/') - 1; await using var command = GetActiveLocksCommand( connection.Value, - uri.LocalPath, + uri.GetPath(), depth, (long)(DateTime.UtcNow - DateTime.UnixEpoch).TotalSeconds); diff --git a/src/Dav.AspNetCore.Server.Extensions.Ado/SqlPropertyStore.cs b/src/Dav.AspNetCore.Server.Extensions.Ado/SqlPropertyStore.cs index e5ac40e..2aac26a 100644 --- a/src/Dav.AspNetCore.Server.Extensions.Ado/SqlPropertyStore.cs +++ b/src/Dav.AspNetCore.Server.Extensions.Ado/SqlPropertyStore.cs @@ -43,7 +43,7 @@ public async ValueTask SaveChangesAsync(CancellationToken cancellationToken = de if (!writeLookup.ContainsKey(entry.Key)) continue; - await using var deleteCommand = GetDeleteCommand(connection.Value, entry.Key.Uri.LocalPath); + await using var deleteCommand = GetDeleteCommand(connection.Value, entry.Key.Uri.GetPath()); await deleteCommand.ExecuteNonQueryAsync(cancellationToken); foreach (var propertyData in entry.Value) @@ -65,7 +65,7 @@ public async ValueTask SaveChangesAsync(CancellationToken cancellationToken = de await using var insertCommand = GetInsertCommand( connection.Value, - entry.Key.Uri.LocalPath, + entry.Key.Uri.GetPath(), propertyData.Key.LocalName, propertyData.Key.NamespaceName, propertyValue); @@ -90,7 +90,7 @@ public async ValueTask DeletePropertiesAsync(IStoreItem item, CancellationToken await using var deleteCommand = GetDeleteCommand( connection.Value, - item.Uri.LocalPath); + item.Uri.GetPath()); await deleteCommand.ExecuteNonQueryAsync(cancellationToken); @@ -117,8 +117,8 @@ public async ValueTask CopyPropertiesAsync( await using var copyCommand = GetCopyCommand( connection.Value, - source.Uri.LocalPath, - destination.Uri.LocalPath); + source.Uri.GetPath(), + destination.Uri.GetPath()); await copyCommand.ExecuteNonQueryAsync(cancellationToken); @@ -184,7 +184,7 @@ public async ValueTask> GetPropertiesAsync( if (connection.Value.State != ConnectionState.Open) await connection.Value.OpenAsync(cancellationToken); - await using var command = GetSelectCommand(connection.Value, item.Uri.LocalPath); + await using var command = GetSelectCommand(connection.Value, item.Uri.GetPath()); var propertyDataList = new List(); diff --git a/src/Dav.AspNetCore.Server/Handlers/CopyHandler.cs b/src/Dav.AspNetCore.Server/Handlers/CopyHandler.cs index 555e021..9c2dad8 100644 --- a/src/Dav.AspNetCore.Server/Handlers/CopyHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/CopyHandler.cs @@ -41,7 +41,11 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT if (!string.IsNullOrWhiteSpace(Context.Request.PathBase)) { if (Context.Request.PathBase.HasValue) - destination = new Uri(destination.LocalPath.Substring(Context.Request.PathBase.Value.Length)); + { + var destPath = destination.IsAbsoluteUri ? destination.LocalPath : destination.OriginalString; + var trimmed = destPath.Substring(Context.Request.PathBase.Value.Length); + destination = new Uri(trimmed, UriKind.Relative); + } } var overwrite = WebDavHeaders.Overwrite ?? false; @@ -54,7 +58,8 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT return; } - var destinationItemName = destination.GetRelativeUri(destinationParentUri).LocalPath.Trim('/'); + var destRel = destination.GetRelativeUri(destinationParentUri); + var destinationItemName = (destRel.IsAbsoluteUri ? destRel.LocalPath : destRel.OriginalString).Trim('/'); var destinationItem = await Store.GetItemAsync(destination, cancellationToken); if (destinationItem != null && !overwrite) { @@ -84,7 +89,7 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT var responses = new List(); foreach (var davError in errors) { - var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{davError.Uri.AbsolutePath}"); + var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{davError.Uri.GetPath()}"); var status = new XElement(XmlNames.Status, $"HTTP/1.1 {(int)davError.StatusCode} {davError.StatusCode.GetDisplayName()}"); var response = new XElement(XmlNames.Response, href, status); @@ -154,7 +159,8 @@ private async Task CopyItemRecursiveAsync( if (destinationCopy == null) throw new InvalidOperationException("If the copied item is a collection, the copy result must also be a collection."); - var itemName = subItem.Uri.GetRelativeUri(collection.Uri).LocalPath.TrimStart('/'); + var subRel = subItem.Uri.GetRelativeUri(collection.Uri); + var itemName = (subRel.IsAbsoluteUri ? subRel.LocalPath : subRel.OriginalString).TrimStart('/'); var error = await CopyItemRecursiveAsync(subItem, destinationCopy, itemName, recursive, errors, cancellationToken); if (!error) subItemError = true; diff --git a/src/Dav.AspNetCore.Server/Handlers/DeleteHandler.cs b/src/Dav.AspNetCore.Server/Handlers/DeleteHandler.cs index 1f533a7..a078c6e 100644 --- a/src/Dav.AspNetCore.Server/Handlers/DeleteHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/DeleteHandler.cs @@ -28,7 +28,7 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT var responses = new List(); foreach (var davError in errors) { - var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{davError.Uri.AbsolutePath}"); + var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{davError.Uri.GetPath()}"); var status = new XElement(XmlNames.Status, $"HTTP/1.1 {(int)davError.StatusCode} {davError.StatusCode.GetDisplayName()}"); var response = new XElement(XmlNames.Response, href, status); diff --git a/src/Dav.AspNetCore.Server/Handlers/GetHandler.cs b/src/Dav.AspNetCore.Server/Handlers/GetHandler.cs index 5bb0095..c91507f 100644 --- a/src/Dav.AspNetCore.Server/Handlers/GetHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/GetHandler.cs @@ -20,6 +20,18 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT return; } + if (Item is IRedirectableStoreItem redirectableItem) + { + var redirectUri = await redirectableItem.GetRedirectUriAsync(Context, cancellationToken); + if (redirectUri != null) + { + Context.Response.Headers["Location"] = redirectUri.ToString(); + Context.Response.Headers["Cache-Control"] = "no-store"; + Context.Response.StatusCode = StatusCodes.Status302Found; + return; + } + } + var contentType = await GetNonExpensivePropertyAsync(Item, XmlNames.GetContentType, cancellationToken); if (!string.IsNullOrWhiteSpace(contentType)) Context.Response.Headers["Content-Type"] = contentType; @@ -148,4 +160,4 @@ private static async Task SendDataAsync( context.SetResult(DavStatusCode.Ok); await stream.CopyToAsync(context.Response.Body, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Dav.AspNetCore.Server/Handlers/LockHandler.cs b/src/Dav.AspNetCore.Server/Handlers/LockHandler.cs index c0ce96f..3e6b7d9 100644 --- a/src/Dav.AspNetCore.Server/Handlers/LockHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/LockHandler.cs @@ -163,7 +163,7 @@ private XDocument BuildDocument(ResourceLock resourceLock) : $"Second-{resourceLock.Timeout.TotalSeconds:F0}"); var lockToken = new XElement(XmlNames.LockToken, new XElement(XmlNames.Href, resourceLock.Id.AbsoluteUri)); - var lockRoot = new XElement(XmlNames.LockRoot, new XElement(XmlNames.Href, $"{Context.Request.PathBase}{resourceLock.Uri.AbsolutePath}")); + var lockRoot = new XElement(XmlNames.LockRoot, new XElement(XmlNames.Href, $"{Context.Request.PathBase}{resourceLock.Uri.GetPath()}")); var activeLock = new XElement(XmlNames.ActiveLock, lockType, lockScope, depth, owner, timeout, lockToken, lockRoot); var lockDiscovery = new XElement(XmlNames.LockDiscovery, activeLock); var prop = new XElement(XmlNames.Property, lockDiscovery); diff --git a/src/Dav.AspNetCore.Server/Handlers/MkColHandler.cs b/src/Dav.AspNetCore.Server/Handlers/MkColHandler.cs index 06ec74a..51b9b56 100644 --- a/src/Dav.AspNetCore.Server/Handlers/MkColHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/MkColHandler.cs @@ -12,7 +12,8 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT var requestUri = Context.Request.Path.ToUri(); var parentUri = requestUri.GetParent(); - var collectionName = requestUri.GetRelativeUri(parentUri).LocalPath.Trim('/'); + var rel = requestUri.GetRelativeUri(parentUri); + var collectionName = (rel.IsAbsoluteUri ? rel.LocalPath : rel.OriginalString).Trim('/'); var result = await Collection.CreateCollectionAsync(collectionName, cancellationToken); Context.SetResult(result.StatusCode); } diff --git a/src/Dav.AspNetCore.Server/Handlers/MoveHandler.cs b/src/Dav.AspNetCore.Server/Handlers/MoveHandler.cs index dd4ba82..c3dba68 100644 --- a/src/Dav.AspNetCore.Server/Handlers/MoveHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/MoveHandler.cs @@ -32,7 +32,11 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT if (!string.IsNullOrWhiteSpace(Context.Request.PathBase)) { if (Context.Request.PathBase.HasValue) - destination = new Uri(destination.LocalPath.Substring(Context.Request.PathBase.Value.Length)); + { + var destPath = destination.IsAbsoluteUri ? destination.LocalPath : destination.OriginalString; + var trimmed = destPath.Substring(Context.Request.PathBase.Value.Length); + destination = new Uri(trimmed, UriKind.Relative); + } } var overwrite = WebDavHeaders.Overwrite ?? false; @@ -45,7 +49,8 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT return; } - var destinationItemName = destination.GetRelativeUri(destinationParentUri).LocalPath.Trim('/'); + var destRel = destination.GetRelativeUri(destinationParentUri); + var destinationItemName = (destRel.IsAbsoluteUri ? destRel.LocalPath : destRel.OriginalString).Trim('/'); var destinationItem = await Store.GetItemAsync(destination, cancellationToken); if (destinationItem != null && !overwrite) { @@ -75,7 +80,7 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT var responses = new List(); foreach (var davError in errors) { - var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{davError.Uri.AbsolutePath}"); + var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{davError.Uri.GetPath()}"); var status = new XElement(XmlNames.Status, $"HTTP/1.1 {(int)davError.StatusCode} {davError.StatusCode.GetDisplayName()}"); var response = new XElement(XmlNames.Response, href, status); @@ -145,7 +150,8 @@ private async Task MoveItemRecursiveAsync( if (destinationMove == null) throw new InvalidOperationException("If the copied item is a collection, the copy result must also be a collection."); - var subItemName = subItem.Uri.GetRelativeUri(collectionToMove.Uri).LocalPath.Trim('/'); + var subRel = subItem.Uri.GetRelativeUri(collectionToMove.Uri); + var subItemName = (subRel.IsAbsoluteUri ? subRel.LocalPath : subRel.OriginalString).Trim('/'); var error = await MoveItemRecursiveAsync(collectionToMove, subItem, destinationMove, subItemName, errors, cancellationToken); if (!error) subItemError = true; @@ -155,7 +161,8 @@ private async Task MoveItemRecursiveAsync( return false; } - var itemName = item.Uri.GetRelativeUri(collection.Uri).LocalPath.Trim('/'); + var itemRel = item.Uri.GetRelativeUri(collection.Uri); + var itemName = (itemRel.IsAbsoluteUri ? itemRel.LocalPath : itemRel.OriginalString).Trim('/'); var status = await collection.DeleteItemAsync(itemName, cancellationToken); if (status != DavStatusCode.NoContent) { diff --git a/src/Dav.AspNetCore.Server/Handlers/PropFindHandler.cs b/src/Dav.AspNetCore.Server/Handlers/PropFindHandler.cs index b81f6ee..705ef3b 100644 --- a/src/Dav.AspNetCore.Server/Handlers/PropFindHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/PropFindHandler.cs @@ -49,7 +49,7 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT foreach (var item in items) { var response = new XElement(XmlNames.Response); - response.Add(new XElement(XmlNames.Href, $"{Context.Request.PathBase}{item.Uri.AbsolutePath}")); + response.Add(new XElement(XmlNames.Href, $"{Context.Request.PathBase}{item.Uri.GetPath()}")); var propertyValues = await GetPropertiesAsync( item, diff --git a/src/Dav.AspNetCore.Server/Handlers/PropPatchHandler.cs b/src/Dav.AspNetCore.Server/Handlers/PropPatchHandler.cs index 8ae8e7a..28aee1b 100644 --- a/src/Dav.AspNetCore.Server/Handlers/PropPatchHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/PropPatchHandler.cs @@ -79,7 +79,7 @@ protected override async Task HandleRequestAsync(CancellationToken cancellationT } } - var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{Item.Uri.AbsolutePath}"); + var href = new XElement(XmlNames.Href, $"{Context.Request.PathBase}{Item.Uri.GetPath()}"); var response = new XElement(XmlNames.Response, href); var multiStatus = new XElement(XmlNames.MultiStatus, response); var document = new XDocument( diff --git a/src/Dav.AspNetCore.Server/Handlers/PutHandler.cs b/src/Dav.AspNetCore.Server/Handlers/PutHandler.cs index e37d934..6903d3a 100644 --- a/src/Dav.AspNetCore.Server/Handlers/PutHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/PutHandler.cs @@ -10,7 +10,8 @@ internal class PutHandler : RequestHandler protected override async Task HandleRequestAsync(CancellationToken cancellationToken = default) { var requestUri = Context.Request.Path.ToUri(); - var itemName = requestUri.GetRelativeUri(Collection.Uri).LocalPath.Trim('/'); + var rel = requestUri.GetRelativeUri(Collection.Uri); + var itemName = (rel.IsAbsoluteUri ? rel.LocalPath : rel.OriginalString).Trim('/'); var result = await Collection.CreateItemAsync(itemName, cancellationToken); if (result.Item == null) { diff --git a/src/Dav.AspNetCore.Server/Handlers/RequestHandler.cs b/src/Dav.AspNetCore.Server/Handlers/RequestHandler.cs index 4bdf8a8..07161df 100644 --- a/src/Dav.AspNetCore.Server/Handlers/RequestHandler.cs +++ b/src/Dav.AspNetCore.Server/Handlers/RequestHandler.cs @@ -102,7 +102,8 @@ public async Task HandleRequestAsync( } else { - var itemName = requestUri.GetRelativeUri(Collection.Uri).LocalPath.Trim('/'); + var relativeUri = requestUri.GetRelativeUri(Collection.Uri); + var itemName = (relativeUri.IsAbsoluteUri ? relativeUri.LocalPath : relativeUri.OriginalString).Trim('/'); Item = await collection.GetItemAsync(itemName, cancellationToken); } @@ -216,7 +217,8 @@ protected async Task DeleteItemRecursiveAsync( if (error) return false; - var itemName = item.Uri.GetRelativeUri(collection.Uri).LocalPath.TrimStart('/'); + var itemUri = item.Uri.GetRelativeUri(collection.Uri); + var itemName = (itemUri.IsAbsoluteUri ? itemUri.LocalPath : itemUri.OriginalString).TrimStart('/'); var status = await collection.DeleteItemAsync(itemName, cancellationToken); if (status != DavStatusCode.NoContent) { @@ -249,8 +251,9 @@ async ValueTask ValidateConditionAsync(IfHeaderValueCondition condition) var collection = await Store.GetCollectionAsync(parentUri, cancellationToken); if (collection != null) { - var itemName = resourceUri.GetRelativeUri(collection.Uri).LocalPath.Trim('/'); - if (string.IsNullOrWhiteSpace(itemName)) + var itemUri = resourceUri.GetRelativeUri(collection.Uri); + var itemName = (itemUri.IsAbsoluteUri ? itemUri.LocalPath : itemUri.OriginalString).Trim('/'); + if (string.IsNullOrEmpty(itemName)) { items[resourceUri] = collection; } diff --git a/src/Dav.AspNetCore.Server/Http/Headers/DestinationHeaderValue.cs b/src/Dav.AspNetCore.Server/Http/Headers/DestinationHeaderValue.cs index f35b1cd..ebdebff 100644 --- a/src/Dav.AspNetCore.Server/Http/Headers/DestinationHeaderValue.cs +++ b/src/Dav.AspNetCore.Server/Http/Headers/DestinationHeaderValue.cs @@ -49,14 +49,14 @@ public static bool TryParse(string? input, [NotNullWhen(true)] out DestinationHe if (Uri.TryCreate(input, UriKind.Absolute, out var uri)) { - var pathString = new PathString(uri.LocalPath); + var pathString = new PathString(uri.GetPath()); parsedValue = new DestinationHeaderValue(pathString.ToUri()); return true; } if(!input.StartsWith("/") && Uri.TryCreate($"/{input}", UriKind.Absolute, out var uri2)) { - var pathString = new PathString(uri2.LocalPath); + var pathString = new PathString(uri2.GetPath()); parsedValue = new DestinationHeaderValue(pathString.ToUri()); return true; } diff --git a/src/Dav.AspNetCore.Server/Locks/InMemoryLockManager.cs b/src/Dav.AspNetCore.Server/Locks/InMemoryLockManager.cs index 7b6cd66..638d7cb 100644 --- a/src/Dav.AspNetCore.Server/Locks/InMemoryLockManager.cs +++ b/src/Dav.AspNetCore.Server/Locks/InMemoryLockManager.cs @@ -146,19 +146,18 @@ public ValueTask> GetLocksAsync( ArgumentNullException.ThrowIfNull(uri, nameof(uri)); var allActiveLocks = new List(); - var pathParts = uri.LocalPath.Split('/'); - if (uri.AbsoluteUri.Equals("/")) - pathParts = new[] { "" }; - + var path = uri.GetPath().Trim('/'); + string[] pathParts = string.IsNullOrEmpty(path) ? new[] { "" } : path.Split('/'); + var currentPath = string.Empty; - + for (var i = 0; i < pathParts.Length; i++) { - currentPath += currentPath.Equals("/") ? pathParts[i] : $"/{pathParts[i]}"; + currentPath += string.IsNullOrEmpty(currentPath) || currentPath == "/" ? $"/{pathParts[i]}" : $"/{pathParts[i]}"; var activeLocks = locks.Values - .Where(x => x.Uri.LocalPath == currentPath && x.IsActive && (x.Recursive || i == pathParts.Length - 1)) + .Where(x => x.Uri.GetPath() == currentPath && x.IsActive && (x.Recursive || i == pathParts.Length - 1)) .ToList(); - + allActiveLocks.AddRange(activeLocks); } diff --git a/src/Dav.AspNetCore.Server/PathStringExtensions.cs b/src/Dav.AspNetCore.Server/PathStringExtensions.cs index 21fc44c..1d129e0 100644 --- a/src/Dav.AspNetCore.Server/PathStringExtensions.cs +++ b/src/Dav.AspNetCore.Server/PathStringExtensions.cs @@ -7,12 +7,27 @@ internal static class PathStringExtensions public static Uri ToUri(this PathString path) { if (string.IsNullOrWhiteSpace(path)) - return new Uri("/"); + return new Uri("/", UriKind.Relative); - var uri = Uri.UnescapeDataString(path.ToUriComponent().TrimEnd('/')); + // Normalizza il percorso: sostituisci backslash con forward slash (per Windows) + var normalized = path.ToUriComponent().Replace("\\", "/").TrimEnd('/'); + var uri = Uri.UnescapeDataString(normalized); + if (string.IsNullOrWhiteSpace(uri)) - return new Uri("/"); + return new Uri("/", UriKind.Relative); - return new Uri(uri); + // Assicurati che inizi con / + if (!uri.StartsWith("/")) + uri = "/" + uri; + + try + { + return new Uri(uri, UriKind.Relative); + } + catch (UriFormatException) + { + // Se l'URI è ancora invalido, ritorna il percorso root + return new Uri("/", UriKind.Relative); + } } } \ No newline at end of file diff --git a/src/Dav.AspNetCore.Server/Store/Files/File.cs b/src/Dav.AspNetCore.Server/Store/Files/File.cs index 7ac833b..7a15f79 100644 --- a/src/Dav.AspNetCore.Server/Store/Files/File.cs +++ b/src/Dav.AspNetCore.Server/Store/Files/File.cs @@ -101,7 +101,7 @@ public async Task CopyAsync( private static string GetMimeTypeForFileExtension(Uri uri) { var provider = new FileExtensionContentTypeProvider(); - if (!provider.TryGetContentType(uri.AbsolutePath, out var contentType)) + if (!provider.TryGetContentType(uri.GetPath(), out var contentType)) { contentType = "application/octet-stream"; } diff --git a/src/Dav.AspNetCore.Server/Store/Files/LocalFileStore.cs b/src/Dav.AspNetCore.Server/Store/Files/LocalFileStore.cs index 8ca0cdf..51a4212 100644 --- a/src/Dav.AspNetCore.Server/Store/Files/LocalFileStore.cs +++ b/src/Dav.AspNetCore.Server/Store/Files/LocalFileStore.cs @@ -16,19 +16,19 @@ public LocalFileStore(LocalFileStoreOptions options) public override ValueTask DirectoryExistsAsync(Uri uri, CancellationToken cancellationToken = default) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); return ValueTask.FromResult(System.IO.Directory.Exists(path)); } public override ValueTask FileExistsAsync(Uri uri, CancellationToken cancellationToken = default) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); return ValueTask.FromResult(System.IO.File.Exists(path)); } public override ValueTask DeleteDirectoryAsync(Uri uri, CancellationToken cancellationToken = default) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); System.IO.Directory.Delete(path); return ValueTask.CompletedTask; @@ -36,7 +36,7 @@ public override ValueTask DeleteDirectoryAsync(Uri uri, CancellationToken cancel public override ValueTask DeleteFileAsync(Uri uri, CancellationToken cancellationToken = default) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); System.IO.File.Delete(path); return ValueTask.CompletedTask; @@ -44,7 +44,7 @@ public override ValueTask DeleteFileAsync(Uri uri, CancellationToken cancellatio public override ValueTask GetDirectoryPropertiesAsync(Uri uri, CancellationToken cancellationToken = default) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); var directoryInfo = new DirectoryInfo(path); var directoryProperties = new DirectoryProperties( uri, @@ -57,7 +57,7 @@ public override ValueTask GetDirectoryPropertiesAsync(Uri u public override ValueTask GetFilePropertiesAsync(Uri uri, CancellationToken cancellationToken = default) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); var fileInfo = new FileInfo(path); var fileProperties = new FileProperties( uri, @@ -71,7 +71,7 @@ public override ValueTask GetFilePropertiesAsync(Uri uri, Cancel public override ValueTask OpenFileStreamAsync(Uri uri, OpenFileMode mode, CancellationToken cancellationToken = default) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); return ValueTask.FromResult(mode == OpenFileMode.Read ? System.IO.File.OpenRead(path) : System.IO.File.OpenWrite(path)); @@ -79,7 +79,7 @@ public override ValueTask OpenFileStreamAsync(Uri uri, OpenFileMode mode public override ValueTask CreateDirectoryAsync(Uri uri, CancellationToken cancellationToken) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); System.IO.Directory.CreateDirectory(path); return ValueTask.CompletedTask; @@ -87,21 +87,21 @@ public override ValueTask CreateDirectoryAsync(Uri uri, CancellationToken cancel public override ValueTask GetFilesAsync(Uri uri, CancellationToken cancellationToken) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); return ValueTask.FromResult(System.IO.Directory.GetFiles(path).Select(x => { var relativePath = $"/{Path.GetRelativePath(options.RootPath, x)}"; - return new Uri(relativePath); + return new Uri(relativePath, UriKind.Relative); }).ToArray()); } public override ValueTask GetDirectoriesAsync(Uri uri, CancellationToken cancellationToken) { - var path = Path.Combine(options.RootPath, uri.LocalPath.TrimStart('/')); + var path = Path.Combine(options.RootPath, uri.GetPath().TrimStart('/')); return ValueTask.FromResult(System.IO.Directory.GetDirectories(path).Select(x => { var relativePath = $"/{Path.GetRelativePath(options.RootPath, x)}"; - return new Uri(relativePath); + return new Uri(relativePath, UriKind.Relative); }).ToArray()); } } \ No newline at end of file diff --git a/src/Dav.AspNetCore.Server/Store/IRedirectableStoreItem.cs b/src/Dav.AspNetCore.Server/Store/IRedirectableStoreItem.cs new file mode 100644 index 0000000..db38aa8 --- /dev/null +++ b/src/Dav.AspNetCore.Server/Store/IRedirectableStoreItem.cs @@ -0,0 +1,8 @@ +using Microsoft.AspNetCore.Http; + +namespace Dav.AspNetCore.Server.Store; + +public interface IRedirectableStoreItem +{ + Task GetRedirectUriAsync(HttpContext context, CancellationToken cancellationToken = default); +} diff --git a/src/Dav.AspNetCore.Server/Store/Properties/Property.cs b/src/Dav.AspNetCore.Server/Store/Properties/Property.cs index 87f8aab..d7685d4 100644 --- a/src/Dav.AspNetCore.Server/Store/Properties/Property.cs +++ b/src/Dav.AspNetCore.Server/Store/Properties/Property.cs @@ -153,7 +153,7 @@ public static Property RegisterLockDiscoveryProperty() where TOwner : cl activelock.Add(new XElement(XmlNames.Owner, new XElement(XmlNames.Href, resourceLock.Owner))); activelock.Add(new XElement(XmlNames.Timeout, timeoutValue)); activelock.Add(new XElement(XmlNames.LockToken, new XElement(XmlNames.Href, $"urn:uuid:{resourceLock.Id:D}"))); - activelock.Add(new XElement(XmlNames.LockRoot, new XElement(XmlNames.Href, $"{httpContextAccessor?.HttpContext?.Request.PathBase}{resourceLock.Uri.AbsolutePath}"))); + activelock.Add(new XElement(XmlNames.LockRoot, new XElement(XmlNames.Href, $"{httpContextAccessor?.HttpContext?.Request.PathBase}{resourceLock.Uri.GetPath()}"))); activeLocks.Add(activelock); } diff --git a/src/Dav.AspNetCore.Server/Store/Properties/XmlFilePropertyStore.cs b/src/Dav.AspNetCore.Server/Store/Properties/XmlFilePropertyStore.cs index 2bd2e32..4cce927 100644 --- a/src/Dav.AspNetCore.Server/Store/Properties/XmlFilePropertyStore.cs +++ b/src/Dav.AspNetCore.Server/Store/Properties/XmlFilePropertyStore.cs @@ -44,7 +44,7 @@ public async ValueTask SaveChangesAsync(CancellationToken cancellationToken = de propertyStore.Add(new XElement(Property, new XElement(propertyData.Value.Name, propertyData.Value.CurrentValue))); } - var xmlFilePath = Path.Combine(options.RootPath, entry.Key.Uri.LocalPath.TrimStart('/') + ".xml"); + var xmlFilePath = Path.Combine(options.RootPath, entry.Key.Uri.GetPath().TrimStart('/') + ".xml"); var fileInfo = new FileInfo(xmlFilePath); if (fileInfo.Directory?.Exists == false) fileInfo.Directory.Create(); @@ -64,7 +64,7 @@ public ValueTask DeletePropertiesAsync( IStoreItem item, CancellationToken cancellationToken = default) { - var xmlFilePath = Path.Combine(options.RootPath, item.Uri.LocalPath.TrimStart('/') + ".xml"); + var xmlFilePath = Path.Combine(options.RootPath, item.Uri.GetPath().TrimStart('/') + ".xml"); if (File.Exists(xmlFilePath)) File.Delete(xmlFilePath); @@ -85,8 +85,8 @@ public ValueTask CopyPropertiesAsync( IStoreItem destination, CancellationToken cancellationToken = default) { - var sourceXmlFilePath = Path.Combine(options.RootPath, source.Uri.LocalPath.TrimStart('/') + ".xml"); - var destinationXmlFilePath = Path.Combine(options.RootPath, destination.Uri.LocalPath.TrimStart('/') + ".xml"); + var sourceXmlFilePath = Path.Combine(options.RootPath, source.Uri.GetPath().TrimStart('/') + ".xml"); + var destinationXmlFilePath = Path.Combine(options.RootPath, destination.Uri.GetPath().TrimStart('/') + ".xml"); if (File.Exists(sourceXmlFilePath)) { @@ -158,7 +158,7 @@ public async ValueTask> GetPropertiesAsync( if (propertyCache.TryGetValue(item, out var propertyMap)) return propertyMap.Values; - var xmlFilePath = Path.Combine(options.RootPath, item.Uri.LocalPath.TrimStart('/') + ".xml"); + var xmlFilePath = Path.Combine(options.RootPath, item.Uri.GetPath().TrimStart('/') + ".xml"); if (!File.Exists(xmlFilePath)) { propertyCache[item] = new Dictionary(); diff --git a/src/Dav.AspNetCore.Server/UriHelper.cs b/src/Dav.AspNetCore.Server/UriHelper.cs index 723fa5c..c1b1c5c 100644 --- a/src/Dav.AspNetCore.Server/UriHelper.cs +++ b/src/Dav.AspNetCore.Server/UriHelper.cs @@ -2,29 +2,43 @@ namespace Dav.AspNetCore.Server; internal static class UriHelper { + public static string GetPath(this Uri uri) + { + // Estrai il percorso da un URI che può essere relativo o assoluto + ArgumentNullException.ThrowIfNull(uri, nameof(uri)); + return uri.IsAbsoluteUri ? uri.AbsolutePath : uri.OriginalString; + } + public static Uri GetParent(this Uri uri) { ArgumentNullException.ThrowIfNull(uri, nameof(uri)); - if (uri.Segments.Length == 1) - return new Uri(uri.Segments[0]); + // Normalizza l'URI per assicurarti che sia in formato relativo + var path = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString; + path = path.TrimEnd('/'); - var uriString = string.Empty; - for (var i = 0; i < uri.Segments.Length - 1; i++) - { - uriString += uri.Segments[i]; - } + if (string.IsNullOrEmpty(path) || path == "/") + return new Uri("/", UriKind.Relative); - return new Uri(uriString); + var lastSlash = path.LastIndexOf('/'); + if (lastSlash <= 0) + return new Uri("/", UriKind.Relative); + + var parentPath = path.Substring(0, lastSlash); + if (string.IsNullOrEmpty(parentPath)) + parentPath = "/"; + + return new Uri(parentPath, UriKind.Relative); } public static Uri Combine(Uri uri, string path) { - var localPath = uri.LocalPath; + var localPath = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString; if (!localPath.EndsWith("/")) localPath += "/"; - return new Uri($"{localPath}{path.TrimStart('/')}"); + var combinedPath = $"{localPath}{path.TrimStart('/')}"; + return new Uri(combinedPath, UriKind.Relative); } public static Uri GetRelativeUri(this Uri relativeTo, Uri uri) @@ -32,26 +46,22 @@ public static Uri GetRelativeUri(this Uri relativeTo, Uri uri) ArgumentNullException.ThrowIfNull(relativeTo, nameof(relativeTo)); ArgumentNullException.ThrowIfNull(uri, nameof(uri)); - if (uri.Segments.Length > relativeTo.Segments.Length) + var relativeToPath = relativeTo.IsAbsoluteUri ? relativeTo.LocalPath : relativeTo.OriginalString; + var uriPath = uri.IsAbsoluteUri ? uri.LocalPath : uri.OriginalString; + + relativeToPath = relativeToPath.TrimEnd('/'); + uriPath = uriPath.TrimEnd('/'); + + if (!relativeToPath.StartsWith(uriPath)) return uri; - - // validate root - for (var i = 0; i < uri.Segments.Length; i++) - { - if (relativeTo.Segments[i].Trim('/') != uri.Segments[i].Trim('/')) - return uri; - } - - var relativePath = string.Join("", relativeTo.Segments.Skip(uri.Segments.Length)); - if (!relativePath.StartsWith("/")) - relativePath = $"/{relativePath}"; - if (relativePath.EndsWith("/")) - relativePath = relativePath.TrimEnd('/'); + if (relativeToPath.Length == uriPath.Length) + return new Uri("/", UriKind.Relative); - if (string.IsNullOrWhiteSpace(relativePath)) - return new Uri("/"); - - return new Uri(relativePath); + var relativePath = relativeToPath.Substring(uriPath.Length); + if (!relativePath.StartsWith("/")) + relativePath = "/" + relativePath; + + return new Uri(relativePath, UriKind.Relative); } } \ No newline at end of file