forked from hikmet4434/winninghunter
59 lines
2.5 KiB
TypeScript
59 lines
2.5 KiB
TypeScript
import { isIP } from "node:net";
|
|
import { lookup } from "node:dns/promises";
|
|
|
|
function isPrivateIpv4(address: string) {
|
|
const parts = address.split(".").map(Number);
|
|
if (parts.length !== 4 || parts.some((part) => !Number.isInteger(part) || part < 0 || part > 255)) return true;
|
|
return parts[0] === 10 ||
|
|
parts[0] === 127 ||
|
|
(parts[0] === 169 && parts[1] === 254) ||
|
|
(parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31) ||
|
|
(parts[0] === 192 && parts[1] === 168) ||
|
|
parts[0] === 0;
|
|
}
|
|
|
|
function isPrivateIpv6(address: string) {
|
|
const normalized = address.toLowerCase();
|
|
return normalized === "::1" || normalized === "::" || normalized.startsWith("fc") || normalized.startsWith("fd") || normalized.startsWith("fe8") || normalized.startsWith("fe9") || normalized.startsWith("fea") || normalized.startsWith("feb");
|
|
}
|
|
|
|
export function isSafeMediaHostname(hostname: string) {
|
|
const normalized = hostname.toLowerCase().replace(/^\[|\]$/g, "");
|
|
if (!normalized || normalized === "localhost" || normalized.endsWith(".localhost") || normalized.endsWith(".local") || normalized.endsWith(".internal")) return false;
|
|
const version = isIP(normalized);
|
|
if (version === 4) return !isPrivateIpv4(normalized);
|
|
if (version === 6) return !isPrivateIpv6(normalized);
|
|
return true;
|
|
}
|
|
|
|
async function assertPublicTarget(url: URL) {
|
|
if (url.protocol !== "https:" || !isSafeMediaHostname(url.hostname)) throw new Error("UNSAFE_MEDIA_URL");
|
|
const addresses = await lookup(url.hostname, { all: true, verbatim: true });
|
|
if (!addresses.length || addresses.some(({ address, family }) => family === 4 ? isPrivateIpv4(address) : isPrivateIpv6(address))) {
|
|
throw new Error("UNSAFE_MEDIA_URL");
|
|
}
|
|
}
|
|
|
|
export async function fetchMedia(urlValue: string, range: string | null) {
|
|
let url = new URL(urlValue);
|
|
for (let redirect = 0; redirect < 4; redirect += 1) {
|
|
await assertPublicTarget(url);
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
...(range ? { range } : {}),
|
|
accept: "video/*,*/*;q=0.8",
|
|
referer: "https://www.facebook.com/",
|
|
"user-agent": "Mozilla/5.0 (compatible; AdSeeQMedia/1.0)"
|
|
},
|
|
redirect: "manual",
|
|
cache: "no-store",
|
|
signal: AbortSignal.timeout(30_000)
|
|
});
|
|
if (response.status < 300 || response.status >= 400) return response;
|
|
const location = response.headers.get("location");
|
|
if (!location) return response;
|
|
url = new URL(location, url);
|
|
}
|
|
throw new Error("TOO_MANY_REDIRECTS");
|
|
}
|