Skip to content

Commit

Permalink
Make HTTP requests with the proper User-Agent
Browse files Browse the repository at this point in the history
  • Loading branch information
dahlia committed Nov 4, 2024
1 parent dad1ca1 commit 15f8a16
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 3 deletions.
4 changes: 4 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ Version 1.3.0

To be released.

- Fedify now makes HTTP requests with the proper `User-Agent` header.

- Added `getUserAgent()` function.


Version 1.2.2
-------------
Expand Down
15 changes: 13 additions & 2 deletions src/nodeinfo/client.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { getLogger } from "@logtape/logtape";
import { parse, type SemVer } from "@std/semver";
import { getUserAgent } from "../runtime/docloader.ts";
import type { ResourceDescriptor } from "../webfinger/jrd.ts";
import type {
InboundService,
Expand Down Expand Up @@ -83,7 +84,12 @@ export async function getNodeInfo(
let nodeInfoUrl: URL | string = url;
if (!options.direct) {
const wellKnownUrl = new URL("/.well-known/nodeinfo", url);
const wellKnownResponse = await fetch(wellKnownUrl);
const wellKnownResponse = await fetch(wellKnownUrl, {
headers: {
Accept: "application/json",
"User-Agent": getUserAgent(),
},
});
if (!wellKnownResponse.ok) {
logger.error("Failed to fetch {url}: {status} {statusText}", {
url: wellKnownUrl.href,
Expand All @@ -110,7 +116,12 @@ export async function getNodeInfo(
}
nodeInfoUrl = link.href;
}
const response = await fetch(nodeInfoUrl);
const response = await fetch(nodeInfoUrl, {
headers: {
Accept: "application/json",
"User-Agent": getUserAgent(),
},
});
if (!response.ok) {
logger.error(
"Failed to fetch NodeInfo document from {url}: {status} {statusText}",
Expand Down
62 changes: 62 additions & 0 deletions src/runtime/docloader.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { assertEquals, assertRejects, assertThrows } from "@std/assert";
import * as mf from "mock_fetch";
import process from "node:process";
import metadata from "../deno.json" with { type: "json" };
import { MemoryKvStore } from "../federation/kv.ts";
import { verifyRequest } from "../sig/http.ts";
import { mockDocumentLoader } from "../testing/docloader.ts";
Expand All @@ -10,6 +12,7 @@ import {
fetchDocumentLoader,
FetchError,
getAuthenticatedDocumentLoader,
getUserAgent,
kvCache,
} from "./docloader.ts";
import { UrlError } from "./url.ts";
Expand Down Expand Up @@ -480,3 +483,62 @@ test("kvCache()", async (t) => {
);
});
});

test("getUserAgent()", () => {
if ("Deno" in globalThis) {
assertEquals(
getUserAgent(),
`Fedify/${metadata.version} (Deno/${Deno.version.deno})`,
);
assertEquals(
getUserAgent("MyApp/1.0.0"),
`MyApp/1.0.0 (Fedify/${metadata.version}; Deno/${Deno.version.deno})`,
);
assertEquals(
getUserAgent(null, "https://example.com/"),
`Fedify/${metadata.version} (Deno/${Deno.version.deno}; +https://example.com/)`,
);
assertEquals(
getUserAgent("MyApp/1.0.0", new URL("https://example.com/")),
`MyApp/1.0.0 (Fedify/${metadata.version}; Deno/${Deno.version.deno}; +https://example.com/)`,
);
} else if ("Bun" in globalThis) {
assertEquals(
getUserAgent(),
// @ts-ignore: `Bun` is a global variable in Bun
`Fedify/${metadata.version} (Bun/${Bun.version})`,
);
assertEquals(
getUserAgent("MyApp/1.0.0"),
// @ts-ignore: `Bun` is a global variable in Bun
`MyApp/1.0.0 (Fedify/${metadata.version}; Bun/${Bun.version})`,
);
assertEquals(
getUserAgent(null, "https://example.com/"),
// @ts-ignore: `Bun` is a global variable in Bun
`Fedify/${metadata.version} (Bun/${Bun.version}; +https://example.com/)`,
);
assertEquals(
getUserAgent("MyApp/1.0.0", new URL("https://example.com/")),
// @ts-ignore: `Bun` is a global variable in Bun
`MyApp/1.0.0 (Fedify/${metadata.version}; Bun/${Bun.version}; +https://example.com/)`,
);
} else {
assertEquals(
getUserAgent(),
`Fedify/${metadata.version} (Node.js/${process.version})`,
);
assertEquals(
getUserAgent("MyApp/1.0.0"),
`MyApp/1.0.0 (Fedify/${metadata.version}; Node.js/${process.version})`,
);
assertEquals(
getUserAgent(null, "https://example.com/"),
`Fedify/${metadata.version} (Node.js/${process.version}; +https://example.com/)`,
);
assertEquals(
getUserAgent("MyApp/1.0.0", new URL("https://example.com/")),
`MyApp/1.0.0 (Fedify/${metadata.version}; Node.js/${process.version}; +https://example.com/)`,
);
}
});
31 changes: 31 additions & 0 deletions src/runtime/docloader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { HTTPHeaderLink } from "@hugoalh/http-header-link";
import { getLogger } from "@logtape/logtape";
import metadata from "../deno.json" with { type: "json" };
import type { KvKey, KvStore } from "../federation/kv.ts";
import { signRequest } from "../sig/http.ts";
import { validateCryptoKey } from "../sig/key.ts";
Expand Down Expand Up @@ -75,6 +76,7 @@ function createRequest(url: string): Request {
return new Request(url, {
headers: {
Accept: "application/activity+json, application/ld+json",
"User-Agent": getUserAgent(),
},
redirect: "manual",
});
Expand Down Expand Up @@ -394,3 +396,32 @@ export function kvCache(
return cache;
};
}

/**
* Gets the user agent string for the given application and URL.
* @param app An optional application name and version, e.g., `"Hollo/1.0.0"`.
* @param url An optional URL to append to the user agent string.
* Usually the URL of the ActivityPub instance.
* @returns The user agent string.
* @since 1.3.0
*/
export function getUserAgent(
app?: string | null,
url?: string | URL | null,
): string {
const fedify = `Fedify/${metadata.version}`;
const runtime = "Deno" in globalThis
? `Deno/${Deno.version.deno}`
: "Bun" in globalThis
// @ts-ignore: `Bun` is a global variable in Bun
? `Bun/${Bun.version}`
: "process" in globalThis
// deno-lint-ignore no-process-globals
? `Node.js/${process.version}`
: null;
const userAgent = app == null ? [fedify] : [app, fedify];
if (runtime != null) userAgent.push(runtime);
if (url != null) userAgent.push(`+${url.toString()}`);
const first = userAgent.shift();
return `${first} (${userAgent.join("; ")})`;
}
6 changes: 5 additions & 1 deletion src/webfinger/lookup.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { getLogger } from "@logtape/logtape";
import { getUserAgent } from "../runtime/docloader.ts";
import type { ResourceDescriptor } from "./jrd.ts";

const logger = getLogger(["fedify", "webfinger", "lookup"]);
Expand Down Expand Up @@ -34,7 +35,10 @@ export async function lookupWebFinger(
let response: Response;
try {
response = await fetch(url, {
headers: { Accept: "application/jrd+json" },
headers: {
Accept: "application/jrd+json",
"User-Agent": getUserAgent(),
},
redirect: "manual",
});
} catch (error) {
Expand Down

0 comments on commit 15f8a16

Please sign in to comment.