summaryrefslogtreecommitdiff
path: root/cgithub/src
diff options
context:
space:
mode:
Diffstat (limited to 'cgithub/src')
-rw-r--r--cgithub/src/app.test.ts50
-rw-r--r--cgithub/src/app.ts35
2 files changed, 71 insertions, 14 deletions
diff --git a/cgithub/src/app.test.ts b/cgithub/src/app.test.ts
new file mode 100644
index 0000000..498d8a6
--- /dev/null
+++ b/cgithub/src/app.test.ts
@@ -0,0 +1,50 @@
+import assert from "node:assert";
+import path from "node:path";
+import { describe, it } from "node:test";
+import { Eta } from "eta";
+import { createApp } from "./app.ts";
+
+// The routes exercised here redirect without scraping, so no network is needed.
+const eta = new Eta({ views: path.join(import.meta.dirname, "..", "views") });
+const app = createApp(eta);
+
+describe("redirects to GitHub", () => {
+ // An HTTP redirect would be caught by the extension's declarativeNetRequest
+ // rules and bounced straight back here.
+ it("should redirect unknown paths with a meta refresh", async () => {
+ const res = await app.request("http://cgithub.example/a/b/c?x=1");
+
+ assert.strictEqual(res.status, 200);
+ assert.strictEqual(res.headers.get("location"), null);
+ assert.strictEqual(res.headers.get("Referrer-Policy"), "no-referrer");
+
+ const body = await res.text();
+ assert.match(
+ body,
+ /<meta http-equiv="refresh" content="2; url=https:\/\/github.com\/a\/b\/c\?x=1">/,
+ );
+ assert.doesNotMatch(body, /<script/);
+ });
+
+ it("should redirect unhandled search types", async () => {
+ const res = await app.request(
+ "http://cgithub.example/actions/deploy-pages/search?q=x&type=code",
+ );
+
+ assert.strictEqual(res.status, 200);
+ assert.match(
+ await res.text(),
+ /https:\/\/github.com\/actions\/deploy-pages\/search\?q=x&amp;type=code/,
+ );
+ });
+
+ it("should redirect raw file requests to raw.githubusercontent.com", async () => {
+ const res = await app.request("http://cgithub.example/actions/deploy-pages/raw/main/README.md");
+
+ assert.strictEqual(res.status, 200);
+ assert.match(
+ await res.text(),
+ /url=https:\/\/raw.githubusercontent.com\/actions\/deploy-pages\/main\/README.md"/,
+ );
+ });
+});
diff --git a/cgithub/src/app.ts b/cgithub/src/app.ts
index 0218918..9a7ee8e 100644
--- a/cgithub/src/app.ts
+++ b/cgithub/src/app.ts
@@ -19,9 +19,22 @@ import {
RedirectError,
} from "./scraper.ts";
+// Our URLs mirror GitHub's, so the same path there is the page we scraped.
+function githubUrlFor(c: Context) {
+ const { pathname, search } = new URL(c.req.url);
+ return `https://github.com${pathname}${search}`;
+}
+
export function createApp(eta: Eta) {
const app = new Hono();
+ // Use a meta refresh to avoid redirect loops in certain situations; we become
+ // the initiator origin even if we are the target of a redirection.
+ function redirectToGitHub(c: Context, location: string) {
+ c.header("Referrer-Policy", "no-referrer");
+ return c.html(eta.render("redirect.eta", { location }));
+ }
+
app.get("/", async (c) => {
return c.html(eta.render("home.eta", {}));
});
@@ -33,7 +46,7 @@ export function createApp(eta: Eta) {
} catch (e) {
if (e instanceof RedirectError) {
if (e.location.startsWith("https://")) {
- c.header("Referrer-Policy", "no-referrer");
+ return redirectToGitHub(c, e.location);
}
return c.redirect(e.location);
} else if (e instanceof GitHubHTTPError) {
@@ -45,10 +58,7 @@ export function createApp(eta: Eta) {
return c.html(eta.render("error.eta", { message: "" + e }));
}
}
- // Our URLs mirror GitHub's, so the page we scraped is the same path there.
- const { pathname, search } = new URL(c.req.url);
- const githubUrl = `https://github.com${pathname}${search}`;
- return c.html(eta.render(template, { ...data, githubUrl, commit }));
+ return c.html(eta.render(template, { ...data, githubUrl: githubUrlFor(c), commit }));
}
// For fragments fetched asynchronously by client-side JS (see public/static/refs.js):
@@ -96,8 +106,10 @@ export function createApp(eta: Eta) {
app.get("/:owner/:repo/raw/:branch/:path{.*}", async (c) => {
const { owner, repo, branch, path } = c.req.param();
- c.header("Referrer-Policy", "no-referrer");
- return c.redirect(`https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`);
+ return redirectToGitHub(
+ c,
+ `https://raw.githubusercontent.com/${owner}/${repo}/${branch}/${path}`,
+ );
});
app.get("/:owner/:repo/issues", async (c) => {
@@ -131,10 +143,7 @@ export function createApp(eta: Eta) {
}
// Redirect unhandled search types (like code, which requires sign in anyway).
- c.header("Referrer-Policy", "no-referrer");
- return c.redirect(
- `https://github.com/${owner}/${repo}/search?${new URLSearchParams(c.req.query()).toString()}`,
- );
+ return redirectToGitHub(c, githubUrlFor(c));
});
app.get("/:owner/:repo/commits/:branch/:path{.*}?", async (c) => {
@@ -179,9 +188,7 @@ export function createApp(eta: Eta) {
});
app.all("*", async (c) => {
- const path = c.req.path;
- c.header("Referrer-Policy", "no-referrer");
- return c.redirect(`https://github.com${path}`);
+ return redirectToGitHub(c, githubUrlFor(c));
});
return app;