mirror of
https://example.com
synced 2024-11-22 11:16:38 +09:00
fix (backend): improve URL check
13ea67bee4
da12d5b079
Co-authored-by: naskya <m@naskya.net>
This commit is contained in:
parent
e6e8aac224
commit
79533ceec9
4 changed files with 46 additions and 1 deletions
|
@ -8,10 +8,15 @@ import chalk from "chalk";
|
||||||
import Logger from "@/services/logger.js";
|
import Logger from "@/services/logger.js";
|
||||||
import IPCIDR from "ip-cidr";
|
import IPCIDR from "ip-cidr";
|
||||||
import PrivateIp from "private-ip";
|
import PrivateIp from "private-ip";
|
||||||
|
import { isValidUrl } from "./is-valid-url.js";
|
||||||
|
|
||||||
const pipeline = util.promisify(stream.pipeline);
|
const pipeline = util.promisify(stream.pipeline);
|
||||||
|
|
||||||
export async function downloadUrl(url: string, path: string): Promise<void> {
|
export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
|
if (!isValidUrl(url)) {
|
||||||
|
throw new StatusError("Invalid URL", 400);
|
||||||
|
}
|
||||||
|
|
||||||
const logger = new Logger("download");
|
const logger = new Logger("download");
|
||||||
|
|
||||||
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
logger.info(`Downloading ${chalk.cyan(url)} ...`);
|
||||||
|
@ -44,6 +49,12 @@ export async function downloadUrl(url: string, path: string): Promise<void> {
|
||||||
limit: 0,
|
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) => {
|
.on("response", (res: Got.Response) => {
|
||||||
if (
|
if (
|
||||||
(process.env.NODE_ENV === "production" ||
|
(process.env.NODE_ENV === "production" ||
|
||||||
|
|
|
@ -5,6 +5,7 @@ import CacheableLookup from "cacheable-lookup";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
|
import { HttpProxyAgent, HttpsProxyAgent } from "hpagent";
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
|
import { isValidUrl } from "./is-valid-url.js";
|
||||||
|
|
||||||
export async function getJson(
|
export async function getJson(
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -58,6 +59,10 @@ export async function getResponse(args: {
|
||||||
timeout?: number;
|
timeout?: number;
|
||||||
size?: number;
|
size?: number;
|
||||||
}) {
|
}) {
|
||||||
|
if (!isValidUrl(args.url)) {
|
||||||
|
throw new StatusError("Invalid URL", 400);
|
||||||
|
}
|
||||||
|
|
||||||
const timeout = args.timeout || 10 * 1000;
|
const timeout = args.timeout || 10 * 1000;
|
||||||
|
|
||||||
const controller = new AbortController();
|
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;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
20
packages/backend/src/misc/is-valid-url.ts
Normal file
20
packages/backend/src/misc/is-valid-url.ts
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
import config from "@/config/index.js";
|
import config from "@/config/index.js";
|
||||||
import { getUserKeypair } from "@/misc/keypair-store.js";
|
import { getUserKeypair } from "@/misc/keypair-store.js";
|
||||||
import type { User, ILocalUser } from "@/models/entities/user.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 { createSignedPost, createSignedGet } from "./ap-request.js";
|
||||||
import type { Response } from "node-fetch";
|
import type { Response } from "node-fetch";
|
||||||
import type { IObject } from "./type.js";
|
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) => {
|
export default async (user: { id: User["id"] }, url: string, object: any) => {
|
||||||
const body = JSON.stringify(object);
|
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
|
* @param url URL to fetch
|
||||||
*/
|
*/
|
||||||
export async function apGet(url: string, user?: ILocalUser): Promise<IObject> {
|
export async function apGet(url: string, user?: ILocalUser): Promise<IObject> {
|
||||||
|
if (!isValidUrl(url)) {
|
||||||
|
throw new StatusError("Invalid URL", 400);
|
||||||
|
}
|
||||||
|
|
||||||
let res: Response;
|
let res: Response;
|
||||||
|
|
||||||
if (user != null) {
|
if (user != null) {
|
||||||
|
|
Loading…
Reference in a new issue