mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-03 08:15:38 +00:00
fix: list files on Stalwart 1.0 when the account holds more nodes than one get allows #1385
This commit is contained in:
@@ -44,6 +44,8 @@ function fakeFilesServer(nodes: FakeNode[], opts: {
|
||||
// Stalwart before 0.16.6 returns leaf files only.
|
||||
queryOmitsFolders?: boolean;
|
||||
queryFails?: boolean;
|
||||
// Stalwart 1.0 refuses `ids: null` past maxObjectsInGet instead of cutting it short.
|
||||
getAllRefused?: boolean;
|
||||
}) {
|
||||
const sent: MethodCall[][] = [];
|
||||
vi.spyOn(globalThis, 'fetch').mockImplementation((async (_url: string, init: RequestInit) => {
|
||||
@@ -52,6 +54,7 @@ function fakeFilesServer(nodes: FakeNode[], opts: {
|
||||
const methodResponses = methodCalls.map(([method, args, callId]) => {
|
||||
if (method === 'FileNode/get') {
|
||||
const ids = args.ids as string[] | null;
|
||||
if (ids === null && opts.getAllRefused && nodes.length > opts.maxObjectsInGet) return ['error', { type: 'requestTooLarge' }, callId];
|
||||
if (ids === null) return [method, { list: nodes.slice(0, opts.maxObjectsInGet), notFound: [] }, callId];
|
||||
if (ids.length > opts.maxObjectsInGet) return ['error', { type: 'requestTooLarge' }, callId];
|
||||
return [method, { list: nodes.filter(n => ids.includes(n.id)), notFound: ids.filter(id => !nodes.some(n => n.id === id)) }, callId];
|
||||
@@ -147,6 +150,22 @@ describe('JMAPClient FileNode listing past maxObjectsInGet (#1069)', () => {
|
||||
expect(sortedIds(nodes)).toEqual(['f0', 'f1']);
|
||||
});
|
||||
|
||||
it('lists everything from the query when Stalwart 1.0 refuses the return-all /get (#1385)', async () => {
|
||||
const all = [folder('d1'), file('f0', 'd1'), file('f1'), file('f2', 'd1')];
|
||||
const client = createLimitedClient({ maxObjectsInGet: 2 });
|
||||
fakeFilesServer(all, { maxObjectsInGet: 2, getAllRefused: true });
|
||||
|
||||
const nodes = await client.listAllFileNodes();
|
||||
expect(sortedIds(nodes)).toEqual(sortedIds(all));
|
||||
});
|
||||
|
||||
it('reports the failure when the return-all /get is refused and the query fails too', async () => {
|
||||
const client = createLimitedClient({ maxObjectsInGet: 2 });
|
||||
fakeFilesServer([file('f0'), file('f1'), file('f2')], { maxObjectsInGet: 2, getAllRefused: true, queryFails: true });
|
||||
|
||||
await expect(client.listAllFileNodes()).rejects.toThrow();
|
||||
});
|
||||
|
||||
it('lists shared accounts past the limit too', async () => {
|
||||
const all = [file('f0'), file('f1'), file('f2'), file('f3')];
|
||||
const client = createLimitedClient({ maxObjectsInGet: 2 });
|
||||
|
||||
+8
-3
@@ -9272,13 +9272,16 @@ export class JMAPClient implements IJMAPClient {
|
||||
);
|
||||
|
||||
const getResult = response.methodResponses?.find(r => r[0] === "FileNode/get" || (r[0] === "error" && r[2] === "fng0"));
|
||||
if (!getResult || getResult[0] === "error") {
|
||||
// Stalwart 1.0 refuses the return-all /get instead of cutting it short
|
||||
// (#1385); then every node comes from the query below.
|
||||
const tooMany = getResult?.[0] === "error" && getResult[1]?.type === "requestTooLarge";
|
||||
if (!getResult || (getResult[0] === "error" && !tooMany)) {
|
||||
console.error('[Files] FileNode/get error:', getResult?.[1]);
|
||||
throw new Error(getResult?.[1]?.description || "FileNode list failed");
|
||||
}
|
||||
const firstPage = ((getResult[1].list || []) as FileNode[]).map(fromDraftFileNode);
|
||||
const firstPage = tooMany ? [] : ((getResult[1].list || []) as FileNode[]).map(fromDraftFileNode);
|
||||
const maxObjects = this.getMaxObjectsInGet();
|
||||
if (firstPage.length < maxObjects) return firstPage;
|
||||
if (!tooMany && firstPage.length < maxObjects) return firstPage;
|
||||
|
||||
const known = new Map(firstPage.map(node => [node.id, node]));
|
||||
// Several /get calls share one request, so a large account costs a few
|
||||
@@ -9334,6 +9337,8 @@ export class JMAPClient implements IJMAPClient {
|
||||
}
|
||||
} catch (error) {
|
||||
// A partial tree beats none: keep what was read, as before #1069.
|
||||
// With nothing read there is no tree to show, so say why.
|
||||
if (known.size === 0) throw error;
|
||||
console.warn(`[Files] Listing for account ${accountId} is incomplete past ${maxObjects} nodes:`, error);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user