From 82d55a97f971af6325fb3c86b49ab1d8dd745cb4 Mon Sep 17 00:00:00 2001 From: JustMe Date: Fri, 16 Oct 2026 19:42:33 +0800 Subject: [PATCH] fix(api): let authors and owners delete unfinished uploads --- server/src/WebDrop.Core/ChannelService.cs | 4 +++- .../WebDrop.Core.Tests/ChannelCryptoTests.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/server/src/WebDrop.Core/ChannelService.cs b/server/src/WebDrop.Core/ChannelService.cs index da6fcce..d62c0fc 100644 --- a/server/src/WebDrop.Core/ChannelService.cs +++ b/server/src/WebDrop.Core/ChannelService.cs @@ -515,7 +515,9 @@ public sealed partial class ChannelService( var crypto = tx.GetWorkspaceCrypto(id); var obj = crypto.Objects.Find(o => o.ItemId == itemId.ToBase64Url()); - if (creds.RequireSignedContent && obj is not null) + // 没传完的上传(末帧从未入库)没有已验签的清单,也就不可能有删除事件:作者或空间主可以直接清理,否则它会永久占着空间配额 + bool unfinished = obj is not null && obj.Event is null; + if (creds.RequireSignedContent && obj is not null && !unfinished) { if (signedEvent is null || obj?.Event is null || signedEvent.Username != principal.AccountName || signedEvent.DeviceId != creds.AccountDeviceId || signedEvent.ChannelId != obj.ChannelId diff --git a/server/tests/WebDrop.Core.Tests/ChannelCryptoTests.cs b/server/tests/WebDrop.Core.Tests/ChannelCryptoTests.cs index bc074a3..6530b33 100644 --- a/server/tests/WebDrop.Core.Tests/ChannelCryptoTests.cs +++ b/server/tests/WebDrop.Core.Tests/ChannelCryptoTests.cs @@ -98,6 +98,22 @@ public sealed class ChannelCryptoTests Assert.Single((await env.Service.ReadLogAsync(space.Id, member, 0, 100)).Records); } + [Fact] + public async Task AnUnfinishedUploadCanBeDeletedWithoutASignedEvent() + { + using var env = new TestEnv(); var ownerEndpoint = TestEnv.Endpoint(); var memberEndpoint = TestEnv.Endpoint(); + var space = env.Create(ownerEndpoint, ChannelMode.Raw); var owner = space.Owner(ownerEndpoint) with { RequireSignedContent = true }; + var channelId = env.Service.GetWorkspace(space.Id, owner).Channels.Single().Id; + var member = space.Member(memberEndpoint) with { AccountName = "member", RequireSignedContent = true }; env.Service.Join(space.Id, member); + var file = TestEnv.NewId(); + env.Service.DeclareObject(space.Id, member, new ObjectEnvelope { ItemId = file.ToBase64Url(), ChannelId = channelId, Domain = CryptoDomain.Files, Epoch = 0, KeyEnvelope = "" }); + // 只传了第 0 帧,没有末帧:上传被打断 + await env.Service.AppendFrameAsync(space.Id, member, TestEnv.Frame(file, 0, FrameFlags.Gated, 16), null); + Assert.Throws(() => env.Service.DeleteItem(space.Id, space.Member(TestEnv.Endpoint()) with { AccountName = "stranger", RequireSignedContent = true }, file)); + env.Service.DeleteItem(space.Id, owner, file); + Assert.Null(env.Service.ReadCrypto(space.Id, owner).Objects.Find(o => o.ItemId == file.ToBase64Url())?.Event); + } + private static void Bootstrap(TestEnv env, TestChannel space, Credentials owner, string channelId) { foreach (var domain in new[] { CryptoDomain.Chat, CryptoDomain.Files }) env.Service.PublishDomain(space.Id, owner, new DomainPublishRequest