-
Notifications
You must be signed in to change notification settings - Fork 744
Open
Description
Bug Description
Recently, I encountered an issue in my fresh project where static files fail to load if their path contains spaces – for example, accessing localhost:5173/image%20.png would not load the image. This issue went unnoticed initially, causing broken image rendering on my website without any obvious error cues.
I spent six hours troubleshooting this problem:
- Learned to debug Deno applications in VSCode
- Configured VSCode to debug Vite and fresh-vite-plugin
- Attempted to debug fresh-core, only to find it was evaluated via the vite-plugin (making direct debugging impossible)
Root Causes Identified
- plugin-vite not decode URI components using
decodeURIComponent(), which blocks static resource loading at the primary layer (this appears to be the main mechanism for static file retrieval). - The
staticFilecomponent imported via{ staticFiles } from "fresh"(see staticFile) is effectively non-functional: it requires manual registration, yet the API is not publicly exposed – making it impossible to configure or inject custom logic.
Custom Solution Code
const MIME_TYPES: Record<string, string> = {
txt: "text/plain",
html: "text/html",
css: "text/css",
js: "application/javascript",
json: "application/json",
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
gif: "image/gif",
webp: "image/webp",
ico: "image/x-icon",
svg: "image/svg+xml",
pdf: "application/pdf",
mp4: "video/mp4",
};
app.use(async (ctx) => {
const { req, url, config } = ctx;
// Decode URI component to handle spaces in file paths
let pathname = decodeURIComponent(url.pathname);
// Normalize base path if configured
if (config.basePath) {
pathname = pathname !== config.basePath
? pathname.slice(config.basePath.length)
: "/";
}
// 安全校验:防止路径遍历攻击(避免访问上级目录文件)Resolve to static directory
pathname = `/static/${pathname}`;
// Security: Prevent path traversal attacks (e.g., ../../../../)
const safePath = path.resolve(Deno.cwd(), pathname.slice(1));
const rootDir = path.resolve(Deno.cwd());
if (!safePath.startsWith(rootDir)) {
return new Response("Forbidden", { status: 403 });
}
// Check if file exists
let fileInfo: Deno.FileInfo | undefined;
try {
fileInfo = await Deno.stat(safePath);
} catch (_error) {
return await ctx.next();
}
if (!fileInfo || !fileInfo.isFile) {
return await ctx.next();
}
// Serve the file with proper headers
try {
const file = await Deno.open(safePath, { read: true });
const ext = path.extname(safePath).toLowerCase().replace(".", "");
const contentType = MIME_TYPES[ext];
if (!contentType) {
return await ctx.next();
}
const headers = new Headers({
"Content-Type": contentType,
"Content-Length": fileInfo.size.toString(),
"Vary": "If-None-Match",
"Last-Modified": fileInfo.mtime?.toUTCString() || new Date().toUTCString(),
});
return new Response(file.readable, { headers });
} catch (_error) {
return await ctx.next();
}
});version
$ deno -v
deno 2.6.0
$ deno task dev -v
Task dev vite "-v"
vite/7.3.0 linux-x64 node-v24.2.0
$ cat deno.json |grep fresh
"start": "deno serve -A _fresh/server.js",
"update": "deno run -A -r jsr:@fresh/update ."
"fresh",
"**/_fresh/*"
"fresh": "jsr:@fresh/core@^2.2.0",
"@fresh/plugin-vite": "jsr:@fresh/plugin-vite@^1.0.8",Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
No labels