Skip to content

Commit 68f5f51

Browse files
committed
Redirect non-approved repos to github assets
1 parent 5d1036b commit 68f5f51

2 files changed

Lines changed: 12 additions & 94 deletions

File tree

handleRequest.test.ts

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, it } from "vitest";
2-
import { createRequestHandler, resolvePluginOrSchemaUrl, rewriteGithubUrls } from "./handleRequest.js";
2+
import { createRequestHandler, resolvePluginOrSchemaUrl } from "./handleRequest.js";
33

44
it("should get info.json", { timeout: 10_000 }, async () => {
55
const { handleRequest } = createRequestHandler();
@@ -364,50 +364,6 @@ it("should return 404 for asset not found", async () => {
364364
expect(response.status).toEqual(404);
365365
});
366366

367-
it("rewriteGithubUrls", () => {
368-
const githubUrl = "https://github.com/dprint/dprint-plugin-exec/releases/download/0.6.0/plugin.json";
369-
const origin = "https://plugins.dprint.dev";
370-
const json = JSON.stringify({
371-
"schemaVersion": 2,
372-
"kind": "process",
373-
"darwin-aarch64": {
374-
"reference":
375-
"https://github.com/dprint/dprint-plugin-exec/releases/download/0.6.0/dprint-plugin-exec-aarch64-apple-darwin.zip",
376-
"checksum": "abc123",
377-
},
378-
"linux-x86_64": {
379-
"reference":
380-
"https://github.com/dprint/dprint-plugin-exec/releases/download/0.6.0/dprint-plugin-exec-x86_64-unknown-linux-gnu.zip",
381-
"checksum": "def456",
382-
},
383-
});
384-
const body = new TextEncoder().encode(json).buffer as ArrayBuffer;
385-
const result = rewriteGithubUrls(githubUrl, body, origin);
386-
expect(typeof result).toEqual("string");
387-
const parsed = JSON.parse(result as string);
388-
expect(parsed["darwin-aarch64"].reference).toEqual(
389-
"https://plugins.dprint.dev/dprint/dprint-plugin-exec/0.6.0/asset/dprint-plugin-exec-aarch64-apple-darwin.zip",
390-
);
391-
expect(parsed["linux-x86_64"].reference).toEqual(
392-
"https://plugins.dprint.dev/dprint/dprint-plugin-exec/0.6.0/asset/dprint-plugin-exec-x86_64-unknown-linux-gnu.zip",
393-
);
394-
});
395-
396-
it("rewriteGithubUrls should not rewrite non-plugin.json", () => {
397-
const githubUrl = "https://github.com/dprint/dprint-plugin-exec/releases/download/0.6.0/schema.json";
398-
const body = new TextEncoder().encode("{}").buffer as ArrayBuffer;
399-
const result = rewriteGithubUrls(githubUrl, body, "https://plugins.dprint.dev");
400-
expect(result).toBe(body);
401-
});
402-
403-
it("rewriteGithubUrls should return original buffer when no URLs match", () => {
404-
const githubUrl = "https://github.com/dprint/dprint-plugin-exec/releases/download/0.6.0/plugin.json";
405-
const json = JSON.stringify({ "key": "value" });
406-
const body = new TextEncoder().encode(json).buffer as ArrayBuffer;
407-
const result = rewriteGithubUrls(githubUrl, body, "https://plugins.dprint.dev");
408-
expect(result).toBe(body);
409-
});
410-
411367
// todo: mock github api for these tests
412368

413369
it("tryResolveSchemaUrl", async () => {

handleRequest.ts

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import { renderHome } from "./home.jsx";
22
import oldMappings from "./old_redirects.json" with { type: "json" };
33
import {
4-
isAssetAllowedRepo,
54
tryResolveAssetUrl,
65
tryResolveLatestJson,
76
tryResolvePluginUrl,
@@ -26,7 +25,7 @@ const contentTypes = {
2625
};
2726

2827
export function createRequestHandler() {
29-
const memoryCache = new LruCache<string, { body: ArrayBuffer | string; contentType: string }>({ size: 50 });
28+
const memoryCache = new LruCache<string, { body: ArrayBuffer; contentType: string }>({ size: 50 });
3029
return {
3130
async handleRequest(request: Request, ctx?: ExecutionContext) {
3231
const url = new URL(request.url);
@@ -35,7 +34,7 @@ export function createRequestHandler() {
3534
if (!assetResult.shouldCache) {
3635
return Response.redirect(assetResult.githubUrl, 302);
3736
}
38-
return servePlugin(request, url, assetResult.githubUrl, ctx);
37+
return servePlugin(request, assetResult.githubUrl, ctx);
3938
}
4039

4140
const githubUrl = await resolvePluginOrSchemaUrl(url);
@@ -48,7 +47,7 @@ export function createRequestHandler() {
4847
return Response.redirect(`${url.origin}${assetPath}`, 302);
4948
}
5049
}
51-
return servePlugin(request, url, githubUrl, ctx);
50+
return servePlugin(request, githubUrl, ctx);
5251
}
5352

5453
const userLatestInfo = await tryResolveLatestJson(url);
@@ -116,8 +115,8 @@ export function createRequestHandler() {
116115
},
117116
};
118117

119-
async function servePlugin(request: Request, requestUrl: URL, githubUrl: string, ctx?: ExecutionContext) {
120-
const result = await resolveBodyWithMemoryCache(githubUrl, requestUrl, ctx);
118+
async function servePlugin(request: Request, githubUrl: string, ctx?: ExecutionContext) {
119+
const result = await resolveBodyWithMemoryCache(githubUrl, ctx);
121120
return new Response(result.body, {
122121
headers: {
123122
"content-type": result.contentType,
@@ -129,35 +128,26 @@ export function createRequestHandler() {
129128

130129
async function resolveBodyWithMemoryCache(
131130
githubUrl: string,
132-
requestUrl: URL,
133131
ctx?: ExecutionContext,
134-
): Promise<{ body: ArrayBuffer | ReadableStream | string | null; status: number; contentType: string }> {
132+
): Promise<{ body: ArrayBuffer | ReadableStream | null; status: number; contentType: string }> {
135133
// L1: in-memory cache (already rewritten)
136134
const cached = memoryCache.get(githubUrl);
137135
if (cached != null) {
138136
return { body: cached.body, status: 200, contentType: cached.contentType };
139137
}
140138

141-
const result = await fetchBody(githubUrl, requestUrl, ctx);
142-
if (result.status !== 200 || !(result.body instanceof ArrayBuffer)) {
143-
return result;
139+
const result = await fetchBody(githubUrl, ctx);
140+
if (result.status === 200 && result.body instanceof ArrayBuffer && result.body.byteLength <= MAX_MEM_CACHE_BODY_SIZE) {
141+
memoryCache.set(githubUrl, { body: result.body, contentType: result.contentType });
144142
}
145143

146-
// rewrite GitHub URLs in JSON files to use the local asset path
147-
const body = rewriteGithubUrls(githubUrl, result.body, requestUrl.origin);
148-
const size = typeof body === "string" ? body.length : body.byteLength;
149-
if (size <= MAX_MEM_CACHE_BODY_SIZE) {
150-
memoryCache.set(githubUrl, { body, contentType: result.contentType });
151-
}
152-
153-
return { body, status: 200, contentType: result.contentType };
144+
return result;
154145
}
155146

156147
async function fetchBody(
157148
githubUrl: string,
158-
requestUrl: URL,
159149
ctx?: ExecutionContext,
160-
): Promise<{ body: ArrayBuffer | ReadableStream | string | null; status: number; contentType: string }> {
150+
): Promise<{ body: ArrayBuffer | ReadableStream | null; status: number; contentType: string }> {
161151
// L2: R2 (stores original content)
162152
const r2Object = await r2Get(githubUrl);
163153
if (r2Object != null) {
@@ -227,7 +217,6 @@ function createJsonResponse(text: string, request: Request) {
227217
// converts a GitHub release URL to a local asset path
228218
// e.g. https://github.com/dprint/dprint-plugin-prettier/releases/download/0.7.0/plugin.json
229219
// -> /dprint/dprint-plugin-prettier/0.7.0/asset/plugin.json
230-
const githubReleasePatternGlobal = /https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download\/([^/]+)\/([^\s"]+)/g;
231220
const githubReleasePattern = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\/releases\/download\/([^/]+)\/(.+)$/;
232221
function githubUrlToAssetPath(githubUrl: string) {
233222
const match = githubReleasePattern.exec(githubUrl);
@@ -238,33 +227,6 @@ function githubUrlToAssetPath(githubUrl: string) {
238227
return `/${username}/${repo}/${tag}/asset/${fileName}`;
239228
}
240229

241-
const MAX_JSON_REWRITE_SIZE = 1024 * 1024; // 1MB
242-
243-
export function rewriteGithubUrls(githubUrl: string, body: ArrayBuffer, origin: string): ArrayBuffer | string {
244-
const match = githubReleasePattern.exec(githubUrl);
245-
if (
246-
!githubUrl.endsWith("/plugin.json")
247-
|| body.byteLength > MAX_JSON_REWRITE_SIZE
248-
|| match == null
249-
|| !isAssetAllowedRepo(match[1], match[2])
250-
) {
251-
return body;
252-
}
253-
const text = new TextDecoder().decode(body);
254-
const rewritten = text.replaceAll(
255-
githubReleasePatternGlobal,
256-
(match, username, repo, tag, fileName) => {
257-
if (!isAssetAllowedRepo(username, repo)) {
258-
return match;
259-
}
260-
return `${origin}/${username}/${repo}/${tag}/asset/${fileName}`;
261-
},
262-
);
263-
if (rewritten === text) {
264-
return body;
265-
}
266-
return rewritten;
267-
}
268230

269231
function contentTypeForUrl(url: string) {
270232
if (url.endsWith(".wasm")) return contentTypes.wasm;

0 commit comments

Comments
 (0)