1
0
Fork 1
mirror of https://example.com synced 2024-11-22 06:46:40 +09:00

fix (backend): improve URL check

13ea67bee4
da12d5b079

Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
mei23 2024-03-26 21:05:29 +09:00 committed by naskya
parent e6e8aac224
commit 79533ceec9
Signed by: naskya
GPG key ID: 712D413B3A9FED5C
4 changed files with 46 additions and 1 deletions

View file

@ -8,10 +8,15 @@ import chalk from "chalk";
import Logger from "@/services/logger.js";
import IPCIDR from "ip-cidr";
import PrivateIp from "private-ip";
import { isValidUrl } from "./is-valid-url.js";
const pipeline = util.promisify(stream.pipeline);
export async function downloadUrl(url: string, path: string): Promise<void> {
if (!isValidUrl(url)) {
throw new StatusError("Invalid URL", 400);
}
const logger = new Logger("download");
logger.info(`Downloading ${chalk.cyan(url)} ...`);
@ -44,6 +49,12 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
limit: 0,
},
})
.on("redirect", (res: Got.Response, opts: Got.NormalizedOptions) => {
if (!isValidUrl(opts.url)) {
logger.warn(`Invalid URL: ${opts.url}`);
req.destroy();
}
})
.on("response", (res: Got.Response) => {
if (
(process.env.NODE_ENV === "production" ||

View file

@ -5,6 +5,7 @@ import CacheableLookup from "cacheable-lookup";
import fetch from "node-fetch";
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
import config from "@/config/index.js";
import { isValidUrl } from "./is-valid-url.js";
export async function getJson(
url: string,
@ -58,6 +59,10 @@ export async function getResponse(args: {
timeout?: number;
size?: number;
}) {
if (!isValidUrl(args.url)) {
throw new StatusError("Invalid URL", 400);
}
const timeout = args.timeout || 10 * 1000;
const controller = new AbortController();
@ -83,6 +88,10 @@ export async function getResponse(args: {
);
}
if (res.redirected && !isValidUrl(res.url)) {
throw new StatusError("Invalid URL", 400);
}
return res;
}

View file

@ -0,0 +1,20 @@
export function isValidUrl(url: string | URL | undefined): boolean {
if (process.env.NODE_ENV !== "production") return true;
try {
if (url == null) return false;
const u = typeof url === "string" ? new URL(url) : url;
if (!u.protocol.match(/^https?:$/) || u.hostname === "unix") {
return false;
}
if (u.port !== "" && !["80", "443"].includes(u.port)) {
return false;
}
return true;
} catch {
return false;
}
}

View file

@ -1,10 +1,11 @@
import config from "@/config/index.js";
import { getUserKeypair } from "@/misc/keypair-store.js";
import type { User, ILocalUser } from "@/models/entities/user.js";
import { getResponse } from "@/misc/fetch.js";
import { StatusError, getResponse } from "@/misc/fetch.js";
import { createSignedPost, createSignedGet } from "./ap-request.js";
import type { Response } from "node-fetch";
import type { IObject } from "./type.js";
import { isValidUrl } from "@/misc/is-valid-url.js";
export default async (user: { id: User["id"] }, url: string, object: any) => {
const body = JSON.stringify(object);
@ -37,6 +38,10 @@ export default async (user: { id: User["id"] }, url: string, object: any) => {
* @param url URL to fetch
*/
export async function apGet(url: string, user?: ILocalUser): Promise<IObject> {
if (!isValidUrl(url)) {
throw new StatusError("Invalid URL", 400);
}
let res: Response;
if (user != null) {