diff --git a/src/remote/activitypub/ap-request.ts b/src/remote/activitypub/ap-request.ts index 99048f24ee..f2726f63f6 100644 --- a/src/remote/activitypub/ap-request.ts +++ b/src/remote/activitypub/ap-request.ts @@ -47,7 +47,7 @@ export function createSignedGet(args: { key: PrivateKey, url: string, additional url: u.href, method: 'GET', headers: objectAssignWithLcKey({ - 'Accept': 'application/activity+json, application/ld+json', + 'Accept': 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', 'Date': new Date().toUTCString(), 'Host': new URL(args.url).hostname, }, args.additionalHeaders), diff --git a/src/remote/activitypub/request.ts b/src/remote/activitypub/request.ts index 37da1c458e..b2b41b1db8 100644 --- a/src/remote/activitypub/request.ts +++ b/src/remote/activitypub/request.ts @@ -4,6 +4,7 @@ import { createSignedPost, createSignedGet } from './ap-request'; import { ILocalUser } from '../../models/entities/user'; import { UserKeypairs } from '../../models'; import { ensure } from '../../prelude/ensure'; +import type { Response } from 'got'; export default async (user: ILocalUser, url: string, object: any, digest?: string) => { const body = typeof object === 'string' ? object : JSON.stringify(object); @@ -37,33 +38,59 @@ export default async (user: ILocalUser, url: string, object: any, digest?: strin }; /** - * Get AP object with http-signature + * Get AP object * @param user http-signature user * @param url URL to fetch */ -export async function signedGet(url: string, user: ILocalUser) { - const keypair = await UserKeypairs.findOne({ - userId: user.id - }).then(ensure); +export async function apGet(url: string, user?: ILocalUser) { + let res: Response; - const req = createSignedGet({ - key: { - privateKeyPem: keypair.privateKey, - keyId: `${config.url}/users/${user.id}#main-key` - }, - url, - additionalHeaders: { - 'User-Agent': config.userAgent, - } - }); + if (user) { + const keypair = await UserKeypairs.findOne({ + userId: user.id + }).then(ensure); - const res = await getResponse({ - url, - method: req.request.method, - headers: req.request.headers - }); + const req = createSignedGet({ + key: { + privateKeyPem: keypair.privateKey, + keyId: `${config.url}/users/${user.id}#main-key` + }, + url, + additionalHeaders: { + 'User-Agent': config.userAgent, + } + }); + + res = await getResponse({ + url, + method: req.request.method, + headers: req.request.headers + }); + } else { + res = await getResponse({ + url, + method: 'GET', + headers: { + 'Accept': 'application/activity+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"', + 'User-Agent': config.userAgent, + }, + }); + } + + if (validateContentType(res.headers['content-type']) !== true) { + throw new Error('Invalid Content Type'); + } if (res.body.length > 65536) throw new Error('too large JSON'); return await JSON.parse(res.body); } + +function validateContentType(contentType: string | null | undefined): boolean { + if (contentType == null) return false; + + const parts = contentType.split(/\s*;\s*/); + if (parts[0] === 'application/activity+json') return true; + if (parts[0] !== 'application/ld+json') return false; + return parts.slice(1).some(part => part.trim() === 'profile="https://www.w3.org/ns/activitystreams"'); +} diff --git a/src/remote/activitypub/resolver.ts b/src/remote/activitypub/resolver.ts index e78862ab9e..e377e9b95a 100644 --- a/src/remote/activitypub/resolver.ts +++ b/src/remote/activitypub/resolver.ts @@ -1,8 +1,7 @@ import config from '../../config'; -import { getJson } from '../../misc/fetch'; import { ILocalUser } from '../../models/entities/user'; import { getInstanceActor } from '../../services/instance-actor'; -import { signedGet } from './request'; +import { apGet } from './request'; import { IObject, isCollectionOrOrderedCollection, ICollection, IOrderedCollection } from './type'; import { fetchMeta } from '../../misc/fetch-meta'; import { extractDbHost } from '../../misc/convert-host'; @@ -62,9 +61,7 @@ export default class Resolver { this.user = await getInstanceActor(); } - const object = this.user - ? await signedGet(value, this.user) - : await getJson(value, 'application/activity+json, application/ld+json'); + const object = await apGet(value, this.user); if (object == null || ( Array.isArray(object['@context']) ?