@fedify/fedify
Version:
An ActivityPub server framework
179 lines (178 loc) • 6.45 kB
JavaScript
import "@js-temporal/polyfill";
import "urlpattern-polyfill";
globalThis.addEventListener = () => {};
import { t as assertEquals } from "../assert_equals-Ew3jOFa3.mjs";
import { t as assertNotEquals } from "../assert_not_equals--wG9hV7u.mjs";
import { t as normalizePublicAudience } from "../public-audience-DYFHzm_c.mjs";
import { test } from "@fedify/fixture";
import { Create, Note, PUBLIC_COLLECTION } from "@fedify/vocab";
import { getDocumentLoader, preloadedContexts } from "@fedify/vocab-runtime";
//#region src/compat/public-audience.test.ts
const PUBLIC_URI = PUBLIC_COLLECTION.href;
const AS_CONTEXT = "https://www.w3.org/ns/activitystreams";
test("normalizePublicAudience() rewrites every addressing field and both CURIE forms", async () => {
const output = await normalizePublicAudience({
"@context": AS_CONTEXT,
type: "Note",
id: "https://example.com/notes/1",
to: "as:Public",
cc: ["as:Public", "https://example.com/bob"],
bto: "Public",
bcc: ["Public"],
audience: ["as:Public", "Public"]
});
assertEquals(output.to, PUBLIC_URI);
assertEquals(output.cc, [PUBLIC_URI, "https://example.com/bob"]);
assertEquals(output.bto, PUBLIC_URI);
assertEquals(output.bcc, [PUBLIC_URI]);
assertEquals(output.audience, [PUBLIC_URI, PUBLIC_URI]);
});
test("normalizePublicAudience() normalises activities serialized by @fedify/vocab", async () => {
const compact = await new Create({
id: new URL("https://example.com/activities/1"),
actor: new URL("https://example.com/alice"),
object: new Note({
id: new URL("https://example.com/notes/1"),
content: "Hello, world!",
tos: [PUBLIC_COLLECTION]
}),
tos: [PUBLIC_COLLECTION],
ccs: [new URL("https://example.com/followers")]
}).toJsonLd({ format: "compact" });
assertEquals(compact.to, "as:Public");
const normalized = await normalizePublicAudience(compact, getDocumentLoader());
assertEquals(normalized.to, PUBLIC_URI);
const nestedObject = normalized.object;
assertEquals(nestedObject.to, PUBLIC_URI);
});
test("normalizePublicAudience() is a no-op without the CURIE", async () => {
const input = {
"@context": AS_CONTEXT,
type: "Note",
id: "https://example.com/notes/3",
to: PUBLIC_URI
};
assertEquals(await normalizePublicAudience(input), input);
});
test("normalizePublicAudience() leaves non-addressing fields untouched", async () => {
const output = await normalizePublicAudience({
"@context": AS_CONTEXT,
type: "Note",
id: "https://example.com/notes/4",
name: "as:Public",
to: "as:Public"
});
assertEquals(output.name, "as:Public");
assertEquals(output.to, PUBLIC_URI);
});
test("normalizePublicAudience() rewrites without canonicalization for known-safe contexts", async () => {
const rejecting = () => {
throw new Error("contextLoader should not be called for a known-safe @context");
};
assertEquals((await normalizePublicAudience({
"@context": AS_CONTEXT,
type: "Note",
id: "https://example.com/notes/fast1",
to: "as:Public"
}, rejecting)).to, PUBLIC_URI);
assertEquals((await normalizePublicAudience({
"@context": [AS_CONTEXT, "https://w3id.org/security/data-integrity/v1"],
type: "Note",
id: "https://example.com/notes/fast2",
to: "as:Public"
}, rejecting)).to, PUBLIC_URI);
});
test("normalizePublicAudience() falls back to canonicalization for unknown-URL contexts", async () => {
let loaderCalls = 0;
const loader = (url) => {
loaderCalls++;
return Promise.resolve({
contextUrl: null,
documentUrl: url,
document: preloadedContexts[AS_CONTEXT]
});
};
assertEquals((await normalizePublicAudience({
"@context": [AS_CONTEXT, "https://custom.example/ctx"],
type: "Note",
id: "https://example.com/notes/unknown",
to: "as:Public"
}, loader)).to, PUBLIC_URI);
assertEquals(loaderCalls > 0, true);
});
test("normalizePublicAudience() leaves @context subtrees untouched", async () => {
const inlineCtx = (await normalizePublicAudience({
"@context": [AS_CONTEXT, { "customTerm": "as:Public" }],
type: "Note",
id: "https://example.com/notes/context",
to: PUBLIC_URI
}))["@context"][1];
assertEquals(inlineCtx.customTerm, "as:Public");
});
test("normalizePublicAudience() does not traverse prototype-polluted keys", async () => {
const polluted = Object.create({ to: "as:Public" });
polluted["@context"] = AS_CONTEXT;
polluted.type = "Note";
polluted.id = "https://example.com/notes/proto";
const output = await normalizePublicAudience(polluted);
assertEquals(Object.hasOwn(output, "to"), false);
});
test("normalizePublicAudience() stops before blowing the stack on pathological nesting", async () => {
let deep = { to: "as:Public" };
for (let i = 0; i < 256; i++) deep = { nested: deep };
assertEquals(typeof await normalizePublicAudience({
"@context": AS_CONTEXT,
type: "Note",
id: "https://example.com/notes/deep",
to: "as:Public",
object: deep
}), "object");
});
test("normalizePublicAudience() does not poison the global prototype via a __proto__ key", async () => {
await normalizePublicAudience(JSON.parse(`{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Note",
"id": "https://example.com/notes/proto-pollution",
"to": "as:Public",
"__proto__": { "polluted": true }
}`));
assertEquals(Object.prototype.polluted, void 0);
});
test("normalizePublicAudience() bails out on nested @context that redefines as:", async () => {
const output = await normalizePublicAudience({
"@context": AS_CONTEXT,
type: "Create",
id: "https://example.com/activities/nested",
actor: "https://example.com/alice",
to: "as:Public",
object: {
"@context": { "as": "https://not-activitystreams.example/" },
type: "https://www.w3.org/ns/activitystreams#Note",
id: "https://example.com/objects/nested",
to: "as:Public"
}
});
assertEquals(output.to, "as:Public");
const nested = output.object;
assertEquals(nested.to, "as:Public");
});
test("normalizePublicAudience() bails out when the rewrite changes semantics", async () => {
const output = await normalizePublicAudience({
"@context": {
"as": "https://not-activitystreams.example/",
"to": {
"@id": "https://www.w3.org/ns/activitystreams#to",
"@type": "@id"
},
"type": "@type",
"id": "@id"
},
type: "https://www.w3.org/ns/activitystreams#Note",
id: "https://example.com/notes/5",
to: "as:Public"
});
assertEquals(output.to, "as:Public");
assertNotEquals(output.to, PUBLIC_URI);
});
//#endregion
export {};