diff --git a/.pnp.cjs b/.pnp.cjs index 3e8ade1d3..0c80c74f4 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -3274,7 +3274,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ],\ [\ "fast-xml-parser",\ - "npm:3.21.1"\ + "npm:4.2.7"\ ],\ [\ "fastest-levenshtein",\ @@ -15259,6 +15259,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["escape-regexp", "npm:0.0.1"],\ ["eslint", "npm:8.45.0"],\ ["execa", "npm:6.1.0"],\ + ["fast-xml-parser", "npm:4.2.7"],\ ["feed", "npm:4.2.2"],\ ["file-type", "npm:17.1.6"],\ ["fluent-ffmpeg", "npm:2.1.2"],\ @@ -20798,6 +20799,14 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["strnum", "npm:1.0.5"]\ ],\ "linkType": "HARD"\ + }],\ + ["npm:4.2.7", {\ + "packageLocation": "./.yarn/cache/fast-xml-parser-npm-4.2.7-c57a954c1f-d8b0c9e047.zip/node_modules/fast-xml-parser/",\ + "packageDependencies": [\ + ["fast-xml-parser", "npm:4.2.7"],\ + ["strnum", "npm:1.0.5"]\ + ],\ + "linkType": "HARD"\ }]\ ]],\ ["fastest-levenshtein", [\ diff --git a/.yarn/cache/fast-xml-parser-npm-4.2.7-c57a954c1f-d8b0c9e047.zip b/.yarn/cache/fast-xml-parser-npm-4.2.7-c57a954c1f-d8b0c9e047.zip new file mode 100644 index 000000000..947fbfd93 --- /dev/null +++ b/.yarn/cache/fast-xml-parser-npm-4.2.7-c57a954c1f-d8b0c9e047.zip @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9fe77d5753249b1a0caae702b0aa81b6908ece1a8c0ba4fd2342b1fe68c7cd4b +size 37126 diff --git a/packages/backend/package.json b/packages/backend/package.json index 5b660f0cf..745abb250 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -64,6 +64,7 @@ "decompress": "^4.2.1", "deep-email-validator": "0.1.21", "escape-regexp": "0.0.1", + "fast-xml-parser": "^4.2.7", "feed": "4.2.2", "file-type": "17.1.6", "fluent-ffmpeg": "2.1.2", diff --git a/packages/backend/src/remote/webfinger.ts b/packages/backend/src/remote/webfinger.ts index 1e929c8ff..3fc2baf80 100644 --- a/packages/backend/src/remote/webfinger.ts +++ b/packages/backend/src/remote/webfinger.ts @@ -1,6 +1,8 @@ import { URL } from "node:url"; -import { getJson } from "@/misc/fetch.js"; +import {getJson, getResponse} from "@/misc/fetch.js"; import { query as urlQuery } from "@/prelude/url.js"; +import config from "@/config/index.js"; +import { XMLParser } from "fast-xml-parser"; type ILink = { href: string; @@ -13,7 +15,9 @@ type IWebFinger = { }; export default async function (query: string): Promise { - const url = genUrl(query); + const hostMetaUrl = queryToHostMetaUrl(query); + const webFingerTemplate = await hostMetaToWebFingerTemplate(hostMetaUrl) ?? queryToWebFingerTemplate(query); + const url = genWebFingerUrl(query, webFingerTemplate); return (await getJson( url, @@ -21,20 +25,71 @@ export default async function (query: string): Promise { )) as IWebFinger; } -function genUrl(query: string) { +async function hostMetaToWebFingerTemplate(url: string) { + try { + const res = await getResponse({ + url, + method: "GET", + headers: Object.assign( + { + "User-Agent": config.userAgent, + Accept: "application/xml, text/xml, */*", + }, + {}, + ), + timeout: 10000, + }); + const parser = new XMLParser({ignoreAttributes: false}); + const hostMeta = parser.parse(await res.text()); + const template = hostMeta['XRD']['Link']['@_template']; + return template.indexOf('{uri}') < 0 ? null : template; + } + catch { + return null; + } +} + +function queryToWebFingerTemplate(query: string) { if (query.match(/^https?:\/\//)) { const u = new URL(query); - return `${u.protocol}//${u.hostname}/.well-known/webfinger?${urlQuery({ - resource: query, - })}`; + return `${u.protocol}//${u.hostname}/.well-known/webfinger?resource={uri}`; } const m = query.match(/^([^@]+)@(.*)/); if (m) { const hostname = m[2]; - return `https://${hostname}/.well-known/webfinger?${urlQuery({ - resource: `acct:${query}`, - })}`; + return `https://${hostname}/.well-known/webfinger?resource={uri}`; + } + + throw new Error(`Invalid query (${query})`); +} + +function queryToHostMetaUrl(query: string) { + if (query.match(/^https?:\/\//)) { + const u = new URL(query); + return `${u.protocol}//${u.hostname}/.well-known/host-meta`; + } + + const m = query.match(/^([^@]+)@(.*)/); + if (m) { + const hostname = m[2]; + return `https://${hostname}/.well-known/host-meta`; + } + + throw new Error(`Invalid query (${query})`); +} + +function genWebFingerUrl(query: string, webFingerTemplate: string) { + if (webFingerTemplate.indexOf('{uri}') < 0) + throw new Error(`Invalid webFingerUrl: ${webFingerTemplate}`); + + if (query.match(/^https?:\/\//)) { + return webFingerTemplate.replace('{uri}', encodeURIComponent(query)); + } + + const m = query.match(/^([^@]+)@(.*)/); + if (m) { + return webFingerTemplate.replace('{uri}', encodeURIComponent(`acct:${query}`)); } throw new Error(`Invalid query (${query})`); diff --git a/yarn.lock b/yarn.lock index 955107231..3036cd7d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5917,6 +5917,7 @@ __metadata: escape-regexp: 0.0.1 eslint: ^8.44.0 execa: 6.1.0 + fast-xml-parser: ^4.2.7 feed: 4.2.2 file-type: 17.1.6 fluent-ffmpeg: 2.1.2 @@ -10363,6 +10364,17 @@ __metadata: languageName: node linkType: hard +"fast-xml-parser@npm:^4.2.7": + version: 4.2.7 + resolution: "fast-xml-parser@npm:4.2.7" + dependencies: + strnum: ^1.0.5 + bin: + fxparser: src/cli/cli.js + checksum: d8b0c9e04756f6c43fa0399428f30149acadae21350e42e26e8fe98e24e6afa6b9b00aa554453795036b00e9fee974a1b556fe2ba18be391d51a9bf1ab790e7c + languageName: node + linkType: hard + "fastest-levenshtein@npm:^1.0.12": version: 1.0.16 resolution: "fastest-levenshtein@npm:1.0.16" @@ -20888,7 +20900,7 @@ __metadata: languageName: node linkType: hard -"strnum@npm:^1.0.4": +"strnum@npm:^1.0.4, strnum@npm:^1.0.5": version: 1.0.5 resolution: "strnum@npm:1.0.5" checksum: 651b2031db5da1bf4a77fdd2f116a8ac8055157c5420f5569f64879133825915ad461513e7202a16d7fec63c54fd822410d0962f8ca12385c4334891b9ae6dd2