|
1 | 1 | import type { NextApiRequest, NextApiResponse } from "next"; |
2 | 2 |
|
| 3 | +function isValidExternalUrl(url: string): boolean { |
| 4 | + try { |
| 5 | + const parsed = new URL(url); |
| 6 | + |
| 7 | + // Only allow HTTP and HTTPS protocols |
| 8 | + if (!['http:', 'https:'].includes(parsed.protocol)) { |
| 9 | + return false; |
| 10 | + } |
| 11 | + |
| 12 | + // Block private/internal IP ranges |
| 13 | + const hostname = parsed.hostname; |
| 14 | + |
| 15 | + // Block localhost and loopback |
| 16 | + if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '::1') { |
| 17 | + return false; |
| 18 | + } |
| 19 | + |
| 20 | + // Block private IP ranges (RFC 1918) |
| 21 | + const privateRanges = [ |
| 22 | + /^10\./, // 10.0.0.0/8 |
| 23 | + /^172\.(1[6-9]|2[0-9]|3[0-1])\./, // 172.16.0.0/12 |
| 24 | + /^192\.168\./, // 192.168.0.0/16 |
| 25 | + /^169\.254\./, // Link-local |
| 26 | + /^::1$/, // IPv6 loopback |
| 27 | + /^fc00:/, // IPv6 private |
| 28 | + /^fe80:/, // IPv6 link-local |
| 29 | + ]; |
| 30 | + |
| 31 | + if (privateRanges.some(range => range.test(hostname))) { |
| 32 | + return false; |
| 33 | + } |
| 34 | + |
| 35 | + return true; |
| 36 | + } catch { |
| 37 | + return false; |
| 38 | + } |
| 39 | +} |
| 40 | + |
3 | 41 | function extractMeta(html: string, property: string): string | null { |
4 | 42 | const propRegex = new RegExp(`<meta[^>]+property=["']${property}["'][^>]*content=["']([^"']+)["'][^>]*>`, "i"); |
5 | 43 | const nameRegex = new RegExp(`<meta[^>]+name=["']${property}["'][^>]*content=["']([^"']+)["'][^>]*>`, "i"); |
@@ -46,6 +84,10 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse) |
46 | 84 | if (!url) { |
47 | 85 | return res.status(400).json({ error: "Missing url parameter" }); |
48 | 86 | } |
| 87 | + |
| 88 | + if (!isValidExternalUrl(url)) { |
| 89 | + return res.status(400).json({ error: "Invalid or unsafe URL" }); |
| 90 | + } |
49 | 91 |
|
50 | 92 | try { |
51 | 93 | const controller = new AbortController(); |
|
0 commit comments