Unverified Commit 3cf01d68 authored by Hong Minhee's avatar Hong Minhee
Browse files

Merge tag '1.3.14' into 1.4-maintenance

Fedify 1.3.14
parents cf0a878e fddfa44c
Loading
Loading
Loading
Loading
+64 −1
Original line number Diff line number Diff line
@@ -8,6 +8,13 @@ Version 1.4.7

To be released.

 -  Fixed a bug of WebFinger handler where it had failed to match
    `acct:` URIs with a host having a port number.
    [[#218], [#219] by Revath S Kumar]

 -  Fixed a server error thrown when an invalid URL was passed to the `base-url`
    parameter of the followers collection.  [[#217]]


Version 1.4.6
-------------
@@ -172,6 +179,19 @@ Released on February 5, 2025.
[#195]: https://github.com/fedify-dev/fedify/issues/195


Version 1.3.14
--------------

Released on March 20, 2025.

 -  Fixed a bug of WebFinger handler where it had failed to match
    `acct:` URIs with a host having a port number.
    [[#218], [#219] by Revath S Kumar]

 -  Fixed a server error thrown when an invalid URL was passed to the `base-url`
    parameter of the followers collection.  [[#217]]


Version 1.3.13
--------------

@@ -451,6 +471,19 @@ Released on November 30, 2024.
[#193]: https://github.com/fedify-dev/fedify/issues/193


Version 1.2.18
--------------

Released on March 20, 2025.

 -  Fixed a bug of WebFinger handler where it had failed to match
    `acct:` URIs with a host having a port number.
    [[#218], [#219] by Revath S Kumar]

 -  Fixed a server error thrown when an invalid URL was passed to the `base-url`
    parameter of the followers collection.  [[#217]]


Version 1.2.17
--------------

@@ -779,6 +812,19 @@ Released on October 31, 2024.
[#118]: https://github.com/fedify-dev/fedify/issues/118


Version 1.1.18
--------------

Released on March 20, 2025.

 -  Fixed a bug of WebFinger handler where it had failed to match
    `acct:` URIs with a host having a port number.
    [[#218], [#219] by Revath S Kumar]

 -  Fixed a server error thrown when an invalid URL was passed to the `base-url`
    parameter of the followers collection.  [[#217]]


Version 1.1.17
--------------

@@ -1148,6 +1194,23 @@ Released on October 20, 2024.
[#150]: https://github.com/fedify-dev/fedify/issues/150


Version 1.0.21
--------------

Released on March 20, 2025.

 -  Fixed a bug of WebFinger handler where it had failed to match
    `acct:` URIs with a host having a port number.
    [[#218], [#219] by Revath S Kumar]

 -  Fixed a server error thrown when an invalid URL was passed to the `base-url`
    parameter of the followers collection.  [[#217]]

[#217]: https://github.com/fedify-dev/fedify/issues/217
[#218]: https://github.com/fedify-dev/fedify/issues/218
[#219]: https://github.com/fedify-dev/fedify/pull/219


Version 1.0.20
--------------

@@ -3313,4 +3376,4 @@ Version 0.1.0
Initial release.  Released on March 8, 2024.

<!-- cSpell: ignore Dogeon Fabien Wressell Emelia Fróði Karlsson -->
<!-- cSpell: ignore Hana Heesun Kyunghee Jiyu -->
<!-- cSpell: ignore Hana Heesun Kyunghee Jiyu Revath Kumar -->
+6 −2
Original line number Diff line number Diff line
@@ -2422,8 +2422,12 @@ export class FederationImpl<TContextData> implements Federation<TContextData> {
      case "followers": {
        let baseUrl = url.searchParams.get("base-url");
        if (baseUrl != null) {
          const u = new URL(baseUrl);
          baseUrl = `${u.origin}/`;
          try {
            baseUrl = `${new URL(baseUrl).origin}/`;
          } catch {
            // If base-url is invalid, set to null to behave as if it wasn't provided
            baseUrl = null;
          }
        }
        return await handleCollection(request, {
          name: "followers",
+148 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@ import type {
  ActorDispatcher,
  ActorHandleMapper,
} from "../federation/callback.ts";
import type { RequestContext } from "../federation/context.ts";
import { createRequestContext } from "../testing/context.ts";
import { test } from "../testing/mod.ts";
import type { Actor } from "../vocab/actor.ts";
@@ -342,3 +343,150 @@ test("handleWebFinger()", async () => {
  });
  assertEquals(response.status, 404);
});

test("handleWebFinger() with port", async (t) => {
  function createContext(url: URL): RequestContext<void> {
    const context = createRequestContext<void>({
      url,
      data: undefined,
      getActorUri(identifier) {
        return new URL(`${url.origin}/users/${identifier}`);
      },
      async getActor(handle): Promise<Actor | null> {
        return await actorDispatcher(
          context,
          handle,
        );
      },
      parseUri(uri) {
        if (uri == null) return null;
        if (uri.protocol === "acct:") return null;
        if (!uri.pathname.startsWith("/users/")) return null;
        const paths = uri.pathname.split("/");
        const identifier = paths[paths.length - 1];
        return {
          type: "actor",
          identifier,
          get handle(): string {
            throw new Error("ParseUriResult.handle is deprecated!");
          },
        };
      },
    });
    return context;
  }

  const actorDispatcher: ActorDispatcher<void> = (ctx, identifier) => {
    if (identifier !== "someone" && identifier !== "someone2") return null;
    const actorUri = ctx.getActorUri(identifier);
    return new Person({
      id: actorUri,
      name: identifier === "someone" ? "Someone" : "Someone 2",
      preferredUsername: identifier === "someone"
        ? null
        : identifier === "someone2"
        ? "bar"
        : null,
      icon: new Image({
        url: new URL(`${actorUri.origin}/icon.jpg`),
        mediaType: "image/jpeg",
      }),
      urls: [
        new URL(`${actorUri.origin}/@${identifier}`),
        new Link({
          href: new URL(`${actorUri.origin}/@${identifier}`),
          rel: "alternate",
          mediaType: "text/html",
        }),
      ],
    });
  };

  const onNotFound = () => {
    return new Response("Not found", { status: 404 });
  };

  const expectedForLocalhostWithPort = {
    subject: "acct:someone@localhost:8000",
    aliases: ["https://localhost:8000/users/someone"],
    links: [
      {
        href: "https://localhost:8000/users/someone",
        rel: "self",
        type: "application/activity+json",
      },
      {
        href: "https://localhost:8000/@someone",
        rel: "http://webfinger.net/rel/profile-page",
      },
      {
        href: "https://localhost:8000/@someone",
        rel: "alternate",
        type: "text/html",
      },
      {
        href: "https://localhost:8000/icon.jpg",
        rel: "http://webfinger.net/rel/avatar",
        type: "image/jpeg",
      },
    ],
  };

  await t.step("on localhost with port, ok: resource=acct:...", async () => {
    const u = new URL("https://localhost:8000/.well-known/webfinger");
    u.searchParams.set("resource", "acct:someone@localhost:8000");
    const context = createContext(u);
    const request = context.request;
    const response = await handleWebFinger(request, {
      context,
      actorDispatcher,
      onNotFound,
    });
    assertEquals(response.status, 200);
    assertEquals(response.headers.get("Content-Type"), "application/jrd+json");
    assertEquals(response.headers.get("Access-Control-Allow-Origin"), "*");
    assertEquals(await response.json(), expectedForLocalhostWithPort);
  });

  const expectedForHostnameWithPort = {
    subject: "acct:someone@example.com:8000",
    aliases: ["http://example.com:8000/users/someone"],
    links: [
      {
        href: "http://example.com:8000/users/someone",
        rel: "self",
        type: "application/activity+json",
      },
      {
        href: "http://example.com:8000/@someone",
        rel: "http://webfinger.net/rel/profile-page",
      },
      {
        href: "http://example.com:8000/@someone",
        rel: "alternate",
        type: "text/html",
      },
      {
        href: "http://example.com:8000/icon.jpg",
        rel: "http://webfinger.net/rel/avatar",
        type: "image/jpeg",
      },
    ],
  };

  await t.step("on hostname with port, ok: resource=acct:...", async () => {
    const u = new URL("http://example.com:8000/.well-known/webfinger");
    u.searchParams.set("resource", "acct:someone@example.com:8000");
    const context = createContext(u);
    const request = context.request;
    const response = await handleWebFinger(request, {
      context,
      actorDispatcher,
      onNotFound,
    });
    assertEquals(response.status, 200);
    assertEquals(response.headers.get("Content-Type"), "application/jrd+json");
    assertEquals(response.headers.get("Access-Control-Allow-Origin"), "*");
    assertEquals(await response.json(), expectedForHostnameWithPort);
  });
});
+11 −4
Original line number Diff line number Diff line
@@ -159,12 +159,19 @@ async function handleWebFingerInternal<TContextData>(
          result.username,
        );
      }
    } else if (domainToASCII(match[2].toLowerCase()) != context.url.host) {
    } else {
      const portMatch = /:\d+$/.exec(match[2]);
      const normalizedHost = portMatch == null
        ? domainToASCII(match[2].toLowerCase())
        : domainToASCII(match[2].substring(0, portMatch.index).toLowerCase()) +
          portMatch[0];
      if (normalizedHost != context.url.host) {
        return await onNotFound(request);
      } else {
        identifier = await mapUsernameToIdentifier(match[1]);
        resourceUrl = new URL(`acct:${match[1]}@${context.url.host}`);
      }
    }
  } else {
    identifier = uriParsed.identifier;
  }