summaryrefslogtreecommitdiff
path: root/packages/backend/src/core/HttpRequestService.ts
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/core/HttpRequestService.ts')
-rw-r--r--packages/backend/src/core/HttpRequestService.ts45
1 files changed, 38 insertions, 7 deletions
diff --git a/packages/backend/src/core/HttpRequestService.ts b/packages/backend/src/core/HttpRequestService.ts
index 151097095d..bd72fefe4f 100644
--- a/packages/backend/src/core/HttpRequestService.ts
+++ b/packages/backend/src/core/HttpRequestService.ts
@@ -17,9 +17,9 @@ import { StatusError } from '@/misc/status-error.js';
import { bindThis } from '@/decorators.js';
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
import type { IObject, IObjectWithId } from '@/core/activitypub/type.js';
-import { ApUtilityService } from './activitypub/ApUtilityService.js';
+import { UtilityService } from '@/core/UtilityService.js';
+import { ApUtilityService } from '@/core/activitypub/ApUtilityService.js';
import type { Response } from 'node-fetch';
-import type { URL } from 'node:url';
import type { Socket } from 'node:net';
export type HttpRequestSendOptions = {
@@ -27,7 +27,27 @@ export type HttpRequestSendOptions = {
validators?: ((res: Response) => void)[];
};
-export function isPrivateIp(allowedPrivateNetworks: PrivateNetwork[] | undefined, ip: string, port?: number): boolean {
+export async function isPrivateUrl(url: URL, lookup: net.LookupFunction): Promise<boolean> {
+ const ip = await resolveIp(url, lookup);
+ return ip.range() !== 'unicast';
+}
+
+export async function resolveIp(url: URL, lookup: net.LookupFunction) {
+ if (ipaddr.isValid(url.hostname)) {
+ return ipaddr.parse(url.hostname);
+ }
+
+ const resolvedIp = await new Promise<string>((resolve, reject) => {
+ lookup(url.hostname, {}, (err, address) => {
+ if (err) reject(err);
+ else resolve(address as string);
+ });
+ });
+
+ return ipaddr.parse(resolvedIp);
+}
+
+export function isAllowedPrivateIp(allowedPrivateNetworks: PrivateNetwork[] | undefined, ip: string, port?: number): boolean {
const parsedIp = ipaddr.parse(ip);
for (const { cidr, ports } of allowedPrivateNetworks ?? []) {
@@ -44,7 +64,7 @@ export function isPrivateIp(allowedPrivateNetworks: PrivateNetwork[] | undefined
export function validateSocketConnect(allowedPrivateNetworks: PrivateNetwork[] | undefined, socket: Socket): void {
const address = socket.remoteAddress;
if (address && ipaddr.isValid(address)) {
- if (isPrivateIp(allowedPrivateNetworks, address, socket.remotePort)) {
+ if (isAllowedPrivateIp(allowedPrivateNetworks, address, socket.remotePort)) {
socket.destroy(new Error(`Blocked address: ${address}`));
}
}
@@ -128,10 +148,16 @@ export class HttpRequestService {
*/
public readonly httpsAgent: https.Agent;
+ /**
+ * Get shared DNS resolver
+ */
+ public readonly lookup: net.LookupFunction;
+
constructor(
@Inject(DI.config)
private config: Config,
private readonly apUtilityService: ApUtilityService,
+ private readonly utilityService: UtilityService,
) {
const cache = new CacheableLookup({
maxTtl: 3600, // 1hours
@@ -139,6 +165,8 @@ export class HttpRequestService {
lookup: false, // nativeのdns.lookupにfallbackしない
});
+ this.lookup = cache.lookup as unknown as net.LookupFunction;
+
const agentOption = {
keepAlive: true,
keepAliveMsecs: 30 * 1000,
@@ -236,8 +264,6 @@ export class HttpRequestService {
@bindThis
public async getActivityJson(url: string, isLocalAddressAllowed = false, allowAnonymous = false): Promise<IObjectWithId> {
- this.apUtilityService.assertApUrl(url);
-
const res = await this.send(url, {
method: 'GET',
headers: {
@@ -303,6 +329,7 @@ export class HttpRequestService {
timeout?: number,
size?: number,
isLocalAddressAllowed?: boolean,
+ allowHttp?: boolean,
} = {},
extra: HttpRequestSendOptions = {
throwErrorWhenResponseNotOk: true,
@@ -311,6 +338,10 @@ export class HttpRequestService {
): Promise<Response> {
const timeout = args.timeout ?? 5000;
+ const parsedUrl = new URL(url);
+ const allowHttp = args.allowHttp || await isPrivateUrl(parsedUrl, this.lookup);
+ this.utilityService.assertUrl(parsedUrl, allowHttp);
+
const controller = new AbortController();
setTimeout(() => {
controller.abort();
@@ -318,7 +349,7 @@ export class HttpRequestService {
const isLocalAddressAllowed = args.isLocalAddressAllowed ?? false;
- const res = await fetch(url, {
+ const res = await fetch(parsedUrl, {
method: args.method ?? 'GET',
headers: {
'User-Agent': this.config.userAgent,