Unverified Commit fb33da0e authored by Hong Minhee's avatar Hong Minhee
Browse files

Recognize `alternate` ActivityStreams objects in the `Link` header

parent e48f97c8
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -11,6 +11,8 @@ To be released.
 -  Removed `expand` option of `Object.toJsonLd()` method, which was deprecated
    in version 0.14.0.  Use `format: "expand"` option instead.
 -  Added `Context.lookupObject()` method.
 -  Default document loaders now recognize `alternate` ActivityStreams objects
    in the `Link` header.
 -  Added `allowPrivateAddress` option to `CreateFederationOptions` interface.
 -  Renamed the short option `-c` for `--compact` of `fedify lookup` command to
    `-C` to avoid conflict with the short option `-c` for `--cache-dir`.
+30 −5
Original line number Diff line number Diff line
@@ -52,16 +52,17 @@ test("fetchDocumentLoader()", async (t) => {
    });
  });

  mf.mock("GET@/object2", (_req) =>
  mf.mock("GET@/link-ctx", (_req) =>
    new Response(
      JSON.stringify({
        id: "https://example.com/object2",
        id: "https://example.com/link-ctx",
        name: "Fetched object",
        type: "Object",
      }),
      {
        status: 200,
        headers: {
          "Content-Type": "application/activity+json",
          Link: "<https://www.w3.org/ns/activitystreams>; " +
            'rel="http://www.w3.org/ns/json-ld#context"; ' +
            'type="application/ld+json"',
@@ -69,12 +70,36 @@ test("fetchDocumentLoader()", async (t) => {
      },
    ));

  mf.mock("GET@/link-obj", (_req) =>
    new Response(
      "",
      {
        status: 200,
        headers: {
          "Content-Type": "text/html; charset=utf-8",
          Link: '<https://example.com/object>; rel="alternate"; ' +
            'type="application/activity+json"',
        },
      },
    ));

  await t.step("Link header", async () => {
    assertEquals(await fetchDocumentLoader("https://example.com/object2"), {
    assertEquals(await fetchDocumentLoader("https://example.com/link-ctx"), {
      contextUrl: "https://www.w3.org/ns/activitystreams",
      documentUrl: "https://example.com/object2",
      documentUrl: "https://example.com/link-ctx",
      document: {
        id: "https://example.com/link-ctx",
        name: "Fetched object",
        type: "Object",
      },
    });

    assertEquals(await fetchDocumentLoader("https://example.com/link-obj"), {
      contextUrl: null,
      documentUrl: "https://example.com/object",
      document: {
        id: "https://example.com/object2",
        "@context": "https://www.w3.org/ns/activitystreams",
        id: "https://example.com/object",
        name: "Fetched object",
        type: "Object",
      },
+35 −7
Original line number Diff line number Diff line
@@ -94,6 +94,7 @@ function logRequest(request: Request) {
async function getRemoteDocument(
  url: string,
  response: Response,
  fetch: (url: string) => Promise<RemoteDocument>,
): Promise<RemoteDocument> {
  const documentUrl = response.url === "" ? url : response.url;
  if (!response.ok) {
@@ -110,10 +111,16 @@ async function getRemoteDocument(
      `HTTP ${response.status}: ${documentUrl}`,
    );
  }
  const contentType = response.headers.get("Content-Type");
  const jsonLd = contentType == null ||
    contentType === "application/activity+json" ||
    contentType === "application/ld+json" ||
    contentType.startsWith("application/ld+json;");
  const linkHeader = response.headers.get("Link");
  let contextUrl: string | null = null;
  if (linkHeader != null) {
    const link = new HTTPHeaderLink(linkHeader);
    if (jsonLd) {
      const entries = link.getByRel("http://www.w3.org/ns/json-ld#context");
      for (const [uri, params] of entries) {
        if ("type" in params && params.type === "application/ld+json") {
@@ -121,6 +128,23 @@ async function getRemoteDocument(
          break;
        }
      }
    } else {
      const entries = link.getByRel("alternate");
      for (const [uri, params] of entries) {
        if (
          "type" in params &&
          (params.type === "application/activity+json" ||
            params.type === "application/ld+json" ||
            params.type.startsWith("application/ld+json;"))
        ) {
          logger.debug(
            "Found alternate document: {alternateUrl} from {url}",
            { alternateUrl: uri, url: documentUrl },
          );
          return await fetch(uri);
        }
      }
    }
  }
  logger.debug(
    "Fetched document: {status} {url} {headers}",
@@ -189,7 +213,11 @@ export async function fetchDocumentLoader(
  ) {
    return fetchDocumentLoader(response.headers.get("Location")!);
  }
  return getRemoteDocument(url, response);
  return getRemoteDocument(
    url,
    response,
    (url) => fetchDocumentLoader(url, allowPrivateAddress),
  );
}

/**
@@ -236,7 +264,7 @@ export function getAuthenticatedDocumentLoader(
    ) {
      return load(response.headers.get("Location")!);
    }
    return getRemoteDocument(url, response);
    return getRemoteDocument(url, response, load);
  }
  return load;
}