Skip to content
File

Blob: tests/worker/protocol/carddav/addressbook-report-limits.workers.test.ts

typescript117 lines
1import { describe, expect, it } from "vitest";
2 
3import { createDavFixture, davRequest } from "@tests/worker/helpers/dav";
4import { fetchWorker } from "@tests/worker/helpers/http";
5import { withEnvBinding } from "@tests/worker/helpers/env";
6import { reportBody, uid, vcard } from "./helpers";
7 
8describe("CardDAV report limits and origins", () => {
9 it("applies the configured REPORT result limit to addressbook-query and addressbook-multiget", async () => {
10 const fixture = await createDavFixture(["carddav.full"]);
11 const firstName = `limited-a-${crypto.randomUUID()}.vcf`;
12 const secondName = `limited-b-${crypto.randomUUID()}.vcf`;
13 expect(
14 await fetchWorker(
15 davRequest(fixture, `/addressbooks/default/${firstName}`, {
16 method: "PUT",
17 body: vcard({ uid: uid(), fn: "Limited A" }),
18 }),
19 ),
20 ).toMatchObject({ status: 201 });
21 expect(
22 await fetchWorker(
23 davRequest(fixture, `/addressbooks/default/${secondName}`, {
24 method: "PUT",
25 body: vcard({ uid: uid(), fn: "Limited B" }),
26 }),
27 ),
28 ).toMatchObject({ status: 201 });
29 
30 await withEnvBinding("MAX_REPORT_RESULTS", "1", async () => {
31 // RFC 6352 8.3: when an addressbook-query result set exceeds the configured
32 // limit, return a DAV:number-of-matches-within-limits precondition error.
33 const query = await fetchWorker(
34 davRequest(fixture, "/addressbooks/default/", {
35 method: "REPORT",
36 headers: { "content-type": "application/xml" },
37 body: reportBody("addressbook-query", "<D:prop><D:getetag/></D:prop><CARD:filter/>"),
38 }),
39 );
40 expect(query.status).toBe(403);
41 await expect(query.text()).resolves.toContain("number-of-matches-within-limits");
42 
43 const multiget = await fetchWorker(
44 davRequest(fixture, "/addressbooks/default/", {
45 method: "REPORT",
46 headers: { "content-type": "application/xml" },
47 body: reportBody(
48 "addressbook-multiget",
49 `<D:prop><D:getetag/></D:prop><D:href>/addressbooks/default/${firstName}</D:href><D:href>/addressbooks/default/${secondName}</D:href>`,
50 ),
51 }),
52 );
53 expect(multiget.status).toBe(403);
54 });
55 });
56 
57 it("rejects unsupported addressbook-query text-match options", async () => {
58 const fixture = await createDavFixture(["carddav.full"]);
59 
60 const unsupportedCollation = await fetchWorker(
61 davRequest(fixture, "/addressbooks/default/", {
62 method: "REPORT",
63 headers: { "content-type": "application/xml" },
64 body: reportBody(
65 "addressbook-query",
66 '<D:prop><D:getetag/></D:prop><CARD:filter><CARD:prop-filter name="EMAIL"><CARD:text-match collation="i;octet">alice</CARD:text-match></CARD:prop-filter></CARD:filter>',
67 ),
68 }),
69 );
70 expect(unsupportedCollation.status).toBe(400);
71 await expect(unsupportedCollation.text()).resolves.toContain("Unsupported CardDAV text-match collation");
72 
73 const tooLarge = await fetchWorker(
74 davRequest(fixture, "/addressbooks/default/", {
75 method: "REPORT",
76 headers: { "content-type": "application/xml" },
77 body: reportBody(
78 "addressbook-query",
79 `<D:prop><D:getetag/></D:prop><CARD:filter><CARD:prop-filter name="FN"><CARD:text-match>${"x".repeat(1025)}</CARD:text-match></CARD:prop-filter></CARD:filter>`,
80 ),
81 }),
82 );
83 expect(tooLarge.status).toBe(400);
84 await expect(tooLarge.text()).resolves.toContain("CardDAV text-match is limited");
85 });
86 
87 it("rejects addressbook-multiget absolute hrefs outside the subject origin", async () => {
88 const fixture = await createDavFixture(["carddav.full"]);
89 const name = `absolute-origin-${crypto.randomUUID()}.vcf`;
90 const contactUid = uid();
91 expect(
92 await fetchWorker(
93 davRequest(fixture, `/addressbooks/default/${name}`, {
94 method: "PUT",
95 body: vcard({ uid: contactUid, fn: "Absolute Origin" }),
96 }),
97 ),
98 ).toMatchObject({ status: 201 });
99 
100 const multiget = await fetchWorker(
101 davRequest(fixture, "/addressbooks/default/", {
102 method: "REPORT",
103 headers: { "content-type": "application/xml" },
104 body: reportBody(
105 "addressbook-multiget",
106 `<D:prop><D:getetag/><CARD:address-data/></D:prop><D:href>https://other.example.test/addressbooks/default/${name}</D:href>`,
107 ),
108 }),
109 );
110 expect(multiget.status).toBe(207);
111 const xml = await multiget.text();
112 expect(xml).toContain(`https://other.example.test/addressbooks/default/${name}`);
113 expect(xml).toContain("HTTP/1.1 404 Not Found");
114 expect(xml).not.toContain(contactUid);
115 });
116});