Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 11 additions & 14 deletions apps/web/app/api/domains/[domain]/validate/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { isValidDomain } from "@/lib/api/domains/is-valid-domain";
import { domainExists } from "@/lib/api/domains/utils";
import { validateDubLinkSubdomain } from "@/lib/api/domains/validate-dub-link-subdomain";
import { safeFetch } from "@/lib/api/safe-fetch";
import { withSession } from "@/lib/auth";
import dns from "dns/promises";
import { NextResponse } from "next/server";
Expand Down Expand Up @@ -50,25 +51,21 @@ export const GET = withSession(async ({ params }) => {
// Helper function to check if a site is active on the domain
async function hasSiteConfigured(domain: string): Promise<boolean> {
try {
// Try HTTP HEAD request first (both HTTP and HTTPS)
// Try HTTP HEAD request first (both HTTP and HTTPS).
// safeFetch enforces SSRF guards on the user-supplied domain (the path
// param flows in here) so this can't be used to probe internal hosts.
const urls = [`https://${domain}`, `http://${domain}`];

for (const url of urls) {
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 3000); // 3 second timeout
const response = await fetch(url, {
method: "HEAD",
signal: controller.signal,
});
clearTimeout(timeoutId);
const response = await safeFetch(
url,
{ method: "HEAD" },
{ timeoutMs: 3000 },
);
if (response.ok) return true;
} catch (e) {
if (e instanceof DOMException && e.name === "AbortError") {
// If request was aborted due to timeout, continue to next check
continue;
}
// Continue to next URL if this one fails
} catch {
// Continue to next URL if this one fails (timeout, SSRF guard, etc.)
continue;
}
}
Expand Down
7 changes: 6 additions & 1 deletion apps/web/app/api/links/iframeable/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { safeFetch } from "@/lib/api/safe-fetch";
import { ratelimitOrThrow } from "@/lib/api/utils";
import {
getDomainQuerySchema,
Expand All @@ -17,7 +18,11 @@ export async function GET(req: NextRequest) {

await ratelimitOrThrow(req, "iframeable");

const iframeable = await isIframeable({ url, requestDomain: domain });
const res = await safeFetch(url);
const iframeable = isIframeable({
headers: res.headers,
requestDomain: domain,
});

return NextResponse.json({ iframeable });
} catch (error) {
Expand Down
5 changes: 3 additions & 2 deletions apps/web/app/api/links/metatags/utils.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import { safeFetch } from "@/lib/api/safe-fetch";
import { recordMetatags } from "@/lib/upstash";
import { linkPreviewImageBase64PrefixRegex } from "@/lib/zod/schemas/images";
import { fetchWithTimeout, isValidUrl } from "@dub/utils";
import { isValidUrl } from "@dub/utils";
import { waitUntil } from "@vercel/functions";
import he from "he";
import { parse } from "node-html-parser";

export const getHtml = async (url: string) => {
try {
const response = await fetchWithTimeout(url);
const response = await safeFetch(url);

if (!response.ok) {
// If we get a 406 or other error, check if it's a Cloudflare-protected site
Expand Down
12 changes: 6 additions & 6 deletions apps/web/app/api/providers/route.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { handleAndReturnErrorResponse } from "@/lib/api/errors";
import { safeFetch } from "@/lib/api/safe-fetch";
import { ratelimitOrThrow } from "@/lib/api/utils";
import { getUrlQuerySchema } from "@/lib/zod/schemas/links";
import { fetchWithTimeout } from "@dub/utils";
Expand Down Expand Up @@ -41,12 +42,11 @@ export async function GET(req: NextRequest) {

urlObject.pathname = "/xyz";

const headers = await fetchWithTimeout(urlObject.toString(), {
headers: {
method: "HEAD",
},
redirect: "manual",
})
const headers = await safeFetch(
urlObject.toString(),
{ method: "HEAD" },
{ maxRedirects: 0 },
)
Comment thread
coderabbitai[bot] marked this conversation as resolved.
.then((r) => ({
engine: r.headers.get("engine"),
poweredBy: r.headers.get("x-powered-by"),
Expand Down
Loading
Loading