67 lines
1.6 KiB
TypeScript
67 lines
1.6 KiB
TypeScript
import type { APIRoute } from "astro";
|
|
import { promises as fs, constants } from "fs";
|
|
import path from "path";
|
|
|
|
export const GET: APIRoute = async ({ params }) => {
|
|
const filePathParam = params.path;
|
|
|
|
if (!filePathParam) {
|
|
return new Response("Not found", { status: 404 });
|
|
}
|
|
|
|
const dataDir = process.env.DATA_DIR
|
|
? process.env.DATA_DIR
|
|
: import.meta.env.DATA_DIR;
|
|
|
|
if (!dataDir) {
|
|
return new Response("DATA_DIR environment variable is not set", {
|
|
status: 500,
|
|
});
|
|
}
|
|
|
|
const uploadDir = path.join(dataDir, "uploads");
|
|
|
|
const safePath = path.normalize(filePathParam).replace(/^(\.\.[\/\\])+/, "");
|
|
const fullPath = path.join(uploadDir, safePath);
|
|
|
|
if (!fullPath.startsWith(uploadDir)) {
|
|
return new Response("Forbidden", { status: 403 });
|
|
}
|
|
|
|
try {
|
|
await fs.access(fullPath, constants.R_OK);
|
|
const fileStats = await fs.stat(fullPath);
|
|
|
|
if (!fileStats.isFile()) {
|
|
return new Response("Not found", { status: 404 });
|
|
}
|
|
|
|
const fileContent = await fs.readFile(fullPath);
|
|
|
|
const ext = path.extname(fullPath).toLowerCase();
|
|
let contentType = "application/octet-stream";
|
|
|
|
switch (ext) {
|
|
case ".png":
|
|
contentType = "image/png";
|
|
break;
|
|
case ".jpg":
|
|
case ".jpeg":
|
|
contentType = "image/jpeg";
|
|
break;
|
|
case ".gif":
|
|
contentType = "image/gif";
|
|
break;
|
|
}
|
|
|
|
return new Response(fileContent, {
|
|
headers: {
|
|
"Content-Type": contentType,
|
|
"Cache-Control": "public, max-age=31536000, immutable",
|
|
},
|
|
});
|
|
} catch (error) {
|
|
return new Response("Not found", { status: 404 });
|
|
}
|
|
};
|