Skip to content

Commit 4dfeeef

Browse files
Merge pull request #25 from robin-drexler/validation
valdiate urls
2 parents e22a936 + 699bba4 commit 4dfeeef

File tree

6 files changed

+108
-1
lines changed

6 files changed

+108
-1
lines changed

src/middleware/proxyMiddleware.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,17 @@
1+
import { validateUrl } from "../utils/validateUrl.js";
2+
13
export default async (req, res, next) => {
24
const proxyUrl = req.query.proxyUrl;
35

46
if (!proxyUrl) {
57
return next();
68
}
79

10+
const validation = validateUrl(proxyUrl);
11+
if (!validation.valid) {
12+
return res.status(400).json({ error: validation.error });
13+
}
14+
815
req.url = proxyUrl;
916
const host = new URL(proxyUrl).host;
1017
const headers = { ...req.headers, host };

src/middleware/redirectMiddleware.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { validateUrl } from "../utils/validateUrl.js";
2+
13
const getRedirectUrl = (req) => {
24
const referer = req.headers["referer"] || "";
35
const redirectUrl = req.query.redirectUrl;
@@ -22,5 +24,11 @@ export default (req, res, next) => {
2224
return next();
2325
}
2426

25-
return res.redirect(301, getRedirectUrl(req));
27+
const finalUrl = getRedirectUrl(req);
28+
const validation = validateUrl(finalUrl);
29+
if (!validation.valid) {
30+
return res.status(400).json({ error: validation.error });
31+
}
32+
33+
return res.redirect(301, finalUrl);
2634
};

src/tests/proxy.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,4 +18,12 @@ describe("proxy", () => {
1818
);
1919
expect(response.status).toEqual(418);
2020
});
21+
22+
it("returns 400 for invalid URL", async () => {
23+
const proxyUrl = "not-a-valid-url";
24+
const response = await fetch(
25+
`http://localhost:3000/0?proxyUrl=${proxyUrl}`
26+
);
27+
expect(response.status).toEqual(400);
28+
});
2129
});

src/tests/redirect.test.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,4 +51,13 @@ describe("redirecting to resources integration", () => {
5151
expect(response.status).toEqual(301);
5252
expect(response.headers.get("location")).toEqual(redirectUrl);
5353
});
54+
55+
it("returns 400 for invalid redirect URL", async () => {
56+
const redirectUrl = "not-a-valid-url";
57+
const response = await fetch(
58+
`http://localhost:3000/0?redirectUrl=${redirectUrl}`,
59+
{ redirect: "manual" }
60+
);
61+
expect(response.status).toEqual(400);
62+
});
5463
});

src/tests/validateUrl.test.js

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { describe, it, expect } from "bun:test";
2+
import { validateUrl } from "../utils/validateUrl.js";
3+
4+
describe("validateUrl", () => {
5+
it("accepts valid http URL", () => {
6+
const result = validateUrl("http://example.com");
7+
expect(result.valid).toBe(true);
8+
expect(result.error).toBeUndefined();
9+
});
10+
11+
it("accepts valid https URL", () => {
12+
const result = validateUrl("https://example.com/path?query=1");
13+
expect(result.valid).toBe(true);
14+
expect(result.error).toBeUndefined();
15+
});
16+
17+
it("rejects empty URL", () => {
18+
const result = validateUrl("");
19+
expect(result.valid).toBe(false);
20+
expect(result.error).toBe("URL is required");
21+
});
22+
23+
it("rejects null URL", () => {
24+
const result = validateUrl(null);
25+
expect(result.valid).toBe(false);
26+
expect(result.error).toBe("URL is required");
27+
});
28+
29+
it("rejects undefined URL", () => {
30+
const result = validateUrl(undefined);
31+
expect(result.valid).toBe(false);
32+
expect(result.error).toBe("URL is required");
33+
});
34+
35+
it("rejects invalid URL format", () => {
36+
const result = validateUrl("not-a-valid-url");
37+
expect(result.valid).toBe(false);
38+
expect(result.error).toBe("Invalid URL format");
39+
});
40+
41+
it("rejects file:// protocol", () => {
42+
const result = validateUrl("file:///etc/passwd");
43+
expect(result.valid).toBe(false);
44+
expect(result.error).toBe("URL must use http or https protocol");
45+
});
46+
47+
it("rejects javascript: protocol", () => {
48+
const result = validateUrl("javascript:alert(1)");
49+
expect(result.valid).toBe(false);
50+
expect(result.error).toBe("URL must use http or https protocol");
51+
});
52+
});

src/utils/validateUrl.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Validates that a URL is a valid absolute URL with http or https protocol.
3+
* @param {string} url - The URL to validate
4+
* @returns {{ valid: boolean, error?: string }} - Validation result
5+
*/
6+
export function validateUrl(url) {
7+
if (!url) {
8+
return { valid: false, error: "URL is required" };
9+
}
10+
11+
try {
12+
const parsed = new URL(url);
13+
if (!["http:", "https:"].includes(parsed.protocol)) {
14+
return {
15+
valid: false,
16+
error: "URL must use http or https protocol",
17+
};
18+
}
19+
return { valid: true };
20+
} catch {
21+
return { valid: false, error: "Invalid URL format" };
22+
}
23+
}

0 commit comments

Comments
 (0)