[workers] Import upload worker (#2164)
This commit is contained in:
10
infra/workers/uploader/package.json
Normal file
10
infra/workers/uploader/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "uploader",
|
||||
"private": true,
|
||||
"devDependencies": {
|
||||
"@cloudflare/workers-types": "^4.20240614.0",
|
||||
"typescript": "^5",
|
||||
"wrangler": "^3"
|
||||
},
|
||||
"packageManager": "yarn@1.22.22"
|
||||
}
|
||||
123
infra/workers/uploader/src/index.ts
Normal file
123
infra/workers/uploader/src/index.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Proxy file uploads.
|
||||
*
|
||||
* See: https://ente.io/blog/tech/making-uploads-faster/
|
||||
*/
|
||||
|
||||
export default {
|
||||
async fetch(request: Request) {
|
||||
switch (request.method) {
|
||||
case "OPTIONS":
|
||||
return handleOPTIONS(request);
|
||||
case "POST":
|
||||
return handlePOSTOrPUT(request);
|
||||
case "PUT":
|
||||
return handlePOSTOrPUT(request);
|
||||
default:
|
||||
console.log(`Unsupported HTTP method ${request.method}`);
|
||||
return new Response(null, { status: 405 });
|
||||
}
|
||||
},
|
||||
} satisfies ExportedHandler;
|
||||
|
||||
const handleOPTIONS = (request: Request) => {
|
||||
const origin = request.headers.get("Origin");
|
||||
if (!isAllowedOrigin(origin)) console.warn("Unknown origin", origin);
|
||||
const headers = request.headers.get("Access-Control-Request-Headers");
|
||||
if (!areAllowedHeaders(headers))
|
||||
console.warn("Unknown header in list", headers);
|
||||
return new Response("", {
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
"Access-Control-Allow-Methods": "POST, PUT, OPTIONS",
|
||||
"Access-Control-Max-Age": "86400",
|
||||
// "Access-Control-Allow-Headers": "Content-Type", "UPLOAD-URL, X-Client-Package",
|
||||
"Access-Control-Allow-Headers": "*",
|
||||
"Access-Control-Expose-Headers": "X-Request-Id, CF-Ray",
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const isAllowedOrigin = (origin: string | null) => {
|
||||
const desktopApp = "ente://app";
|
||||
const allowedHostnames = [
|
||||
"web.ente.io",
|
||||
"photos.ente.io",
|
||||
"photos.ente.sh",
|
||||
"localhost",
|
||||
];
|
||||
|
||||
if (!origin) return false;
|
||||
try {
|
||||
const url = new URL(origin);
|
||||
return origin == desktopApp || allowedHostnames.includes(url.hostname);
|
||||
} catch {
|
||||
// origin is likely an invalid URL
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const areAllowedHeaders = (headers: string | null) => {
|
||||
const allowed = ["Content-Type", "UPLOAD-URL", "X-Client-Package"];
|
||||
|
||||
if (!headers) return true;
|
||||
for (const header of headers.split(",")) {
|
||||
if (!allowed.includes(header.trim().toLowerCase())) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const handlePOSTOrPUT = async (request: Request) => {
|
||||
const url = new URL(request.url);
|
||||
|
||||
const uploadURL = request.headers.get("UPLOAD-URL");
|
||||
if (!uploadURL) {
|
||||
console.error("No uploadURL provided");
|
||||
return new Response(null, { status: 400 });
|
||||
}
|
||||
|
||||
let response: Response;
|
||||
switch (url.pathname) {
|
||||
case "/file-upload":
|
||||
response = await fetch(uploadURL, {
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
});
|
||||
break;
|
||||
case "/multipart-upload":
|
||||
response = await fetch(uploadURL, {
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
});
|
||||
if (response.ok) {
|
||||
const etag = response.headers.get("etag");
|
||||
if (etag === null) {
|
||||
console.log("No etag in response", response);
|
||||
response = new Response(null, { status: 500 });
|
||||
} else {
|
||||
response = new Response(JSON.stringify({ etag }));
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "/multipart-complete":
|
||||
response = await fetch(uploadURL, {
|
||||
method: request.method,
|
||||
body: request.body,
|
||||
headers: {
|
||||
"Content-Type": "text/xml",
|
||||
},
|
||||
});
|
||||
break;
|
||||
default:
|
||||
response = new Response(null, { status: 404 });
|
||||
break;
|
||||
}
|
||||
|
||||
response = new Response(response.body, response);
|
||||
response.headers.set("Access-Control-Allow-Origin", "*");
|
||||
response.headers.set(
|
||||
"Access-Control-Expose-Headers",
|
||||
"X-Request-Id, CF-Ray"
|
||||
);
|
||||
return response;
|
||||
};
|
||||
1
infra/workers/uploader/tsconfig.json
Normal file
1
infra/workers/uploader/tsconfig.json
Normal file
@@ -0,0 +1 @@
|
||||
{ "extends": "../tsconfig.base.json", "include": ["src"] }
|
||||
7
infra/workers/uploader/wrangler.toml
Normal file
7
infra/workers/uploader/wrangler.toml
Normal file
@@ -0,0 +1,7 @@
|
||||
name = "uploader"
|
||||
main = "src/index.ts"
|
||||
compatibility_date = "2024-06-14"
|
||||
|
||||
routes = [{ pattern = "uploader.ente.io", custom_domain = true }]
|
||||
|
||||
tail_consumers = [{ service = "tail" }]
|
||||
Reference in New Issue
Block a user