import { HandlerContext, Handlers, PageProps } from "$fresh/server.ts"; import { Head } from "$fresh/runtime.ts"; import { decodePath, encodePath, removePrefixFromPathname, } from "../../util/util.ts"; import { join } from "path/posix.ts"; import DirList, { EntryInfo } from "../../islands/DirList.tsx"; import FileViewer from "../../islands/FileViewer.tsx"; import RenderView from "../../islands/ContentRenderer.tsx"; import { serveFile } from "http/file_server.ts"; type DirProps = { type: "dir"; path: string; stat: Deno.FileInfo; files: EntryInfo[]; }; type FileProps = { type: "file"; path: string; stat: Deno.FileInfo; }; type DirOrFileProps = DirProps | FileProps; async function renderFile(req: Request, path: string) { try { const fileInfo = await Deno.stat(path); if (fileInfo.isDirectory) { // if index.html exists, serve it. // otherwise, serve a directory listing. const indexPath = join(path, "/index.html"); try { await Deno.stat(indexPath); const res = await serveFile(req, indexPath); return res; } catch (e) { if (e instanceof Deno.errors.NotFound) { const list: Deno.DirEntry[] = []; for await (const entry of Deno.readDir(path)) { list.push(entry); } return new Response( JSON.stringify( list, ), { headers: { "content-type": "application/json", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE", "Access-Control-Allow-Headers": "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With", }, status: 200, }, ); } } } const res = await serveFile(req, path, { fileInfo, }); return res; } catch (e) { if (e instanceof Deno.errors.NotFound) { return new Response("Not Found", { status: 404, }); } throw e; } } async function renderPage(_req: Request, path: string, ctx: HandlerContext) { try { const stat = await Deno.stat(path); if (stat.isDirectory) { const filesIter = await Deno.readDir(path); const files: EntryInfo[] = []; for await (const file of filesIter) { const fileStat = await Deno.stat(join(path, file.name)); files.push({ ...file, lastModified: fileStat.mtime ? new Date(fileStat.mtime) : undefined, size: fileStat.size, }); } return await ctx.render({ type: "dir", stat, files, path, }); } else { return await ctx.render({ type: "file", stat, path, }); } } catch (e) { if (e instanceof Deno.errors.NotFound) { return await ctx.renderNotFound(); } throw e; } } async function GET(req: Request, ctx: HandlerContext): Promise { const authRequired = Deno.env.get("AUTH_REQUIRED") === "true"; if (authRequired) { const login = ctx.state["login"]; //console.log("login", login); if (!login) { return new Response(null, { status: 302, headers: { "Location": "/login", "content-type": "text/plain", "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "GET,HEAD,PUT,PATCH,POST,DELETE", "Access-Control-Allow-Headers": "Content-Type, Access-Control-Allow-Headers, Authorization, X-Requested-With", }, }); } } const url = new URL(req.url); const path = removePrefixFromPathname(decodePath(url.pathname), "/dir"); if (url.searchParams.has("pretty")) { return await renderPage(req, path, ctx); } else { return await renderFile(req, path); } } export const handler: Handlers = { GET, }; function isImageFile(path: string) { return /\.(jpg|jpeg|png|gif|webp|svg|bmp|ico|tiff)$/i.test(path); } function searchFiles( path: EntryInfo[], fn: (path: EntryInfo) => boolean, opt: { order?: (a: EntryInfo, b: EntryInfo) => number } = {}, ) { const candiate = path.filter(fn); if (candiate.length > 0) { if (opt.order) { candiate.sort(opt.order); } return candiate[0]; } return null; } export default function DirLists(props: PageProps) { const data = props.data; let cover = null, index = null, content = null; if (data.type === "dir") { cover = searchFiles(data.files, (f) => isImageFile(f.name)); index = searchFiles(data.files, (f) => f.name === "index.html"); const contentFilenameCandidate = [ "SUMMARY.md", "README.md", "readme.md", "README.txt", "readme.txt", ]; const contentFilenameCandidateSet = new Set(contentFilenameCandidate); content = searchFiles( data.files, (f) => contentFilenameCandidateSet.has(f.name), { order: (a, b) => { const aIndex = contentFilenameCandidate.indexOf(a.name); const bIndex = contentFilenameCandidate.indexOf(b.name); return (aIndex - bIndex); }, }, ); } return ( <> Simple file server : {data.path}
{data.type === "dir" ? : } {index ? ( {cover ? ( ) : ( Index )} ) : null} {content ? (
) : null}
); }