Browse Source

refactor(media): improve directory handling and path normalization

- Updated functions to allow both relative and absolute paths, enhancing flexibility in directory configuration.
- Renamed `ensureRelativeDir` to `ensureConfigurableDir` for clarity and updated its implementation to normalize paths and prevent directory traversal.
- Adjusted related constants to reflect the new path handling logic, ensuring consistent behavior across the application.
main
npmrun 6 days ago
parent
commit
b3a7560a6c
  1. 30
      server/constants/media.ts
  2. 7
      server/middleware/00.public.ts

30
server/constants/media.ts

@ -1,3 +1,5 @@
import path from "node:path";
function trimSlashes(input: string): string { function trimSlashes(input: string): string {
return input.trim().replace(/^\/+|\/+$/g, ""); return input.trim().replace(/^\/+|\/+$/g, "");
} }
@ -9,16 +11,18 @@ function hasParentSegment(input: string): boolean {
.some((part) => part === ".."); .some((part) => part === "..");
} }
function ensureRelativeDir(input: string, fallback: string, envName: string): string { /** 允许相对项目根或绝对路径;禁止含 `..` 的路径片段(防止配置逃逸)。 */
const value = input.trim(); function ensureConfigurableDir(input: string, fallback: string, envName: string): string {
if (!value) { const raw = input.trim();
if (!raw) {
return fallback; return fallback;
} }
// 仅允许相对目录;绝对路径会绕过项目根约束。 const normalized = path.normalize(raw);
if (value.startsWith("/")) { const segments = normalized.split(path.sep);
throw new Error(`${envName} must be a relative directory path`); if (segments.some((part) => part === "..")) {
throw new Error(`${envName} must not contain ".." path segments`);
} }
return value; return normalized;
} }
function ensureSafeSubdir(input: string, fallback: string, envName: string): string { function ensureSafeSubdir(input: string, fallback: string, envName: string): string {
@ -35,8 +39,8 @@ function ensureSafeSubdir(input: string, fallback: string, envName: string): str
/** 静态资源 URL 前缀固定为 `/static`,不允许通过环境变量覆写。 */ /** 静态资源 URL 前缀固定为 `/static`,不允许通过环境变量覆写。 */
export const STATIC_PUBLIC_PREFIX = "/static"; export const STATIC_PUBLIC_PREFIX = "/static";
/** 静态资源根目录,默认 `static` */ /** 静态资源根目录(相对项目根或绝对路径),默认 `static` */
export const STATIC_DIR = ensureRelativeDir(process.env.STATIC_DIR ?? "static", "static", "STATIC_DIR"); export const STATIC_DIR = ensureConfigurableDir(process.env.STATIC_DIR ?? "static", "static", "STATIC_DIR");
/** 媒体上传子目录(相对 STATIC_DIR),默认 `media` */ /** 媒体上传子目录(相对 STATIC_DIR),默认 `media` */
export const MEDIA_UPLOAD_SUBDIR = ensureSafeSubdir( export const MEDIA_UPLOAD_SUBDIR = ensureSafeSubdir(
@ -45,11 +49,11 @@ export const MEDIA_UPLOAD_SUBDIR = ensureSafeSubdir(
"MEDIA_UPLOAD_SUBDIR", "MEDIA_UPLOAD_SUBDIR",
); );
/** 媒体上传目录(相对项目根),默认 `static/media` */ /** 媒体上传目录(与 STATIC_DIR 同为相对或绝对),默认 `static/media` */
export const RELATIVE_ASSETS_DIR = `${STATIC_DIR}/${MEDIA_UPLOAD_SUBDIR}`; export const RELATIVE_ASSETS_DIR = path.join(STATIC_DIR, MEDIA_UPLOAD_SUBDIR);
/** 临时目录(相对项目根),默认 `.tmp` */ /** 临时目录(相对项目根或绝对路径),默认 `.tmp` */
export const RELATIVE_TMP_DIR = ensureRelativeDir(process.env.TMP_DIR ?? ".tmp", ".tmp", "TMP_DIR"); export const RELATIVE_TMP_DIR = ensureConfigurableDir(process.env.TMP_DIR ?? ".tmp", ".tmp", "TMP_DIR");
/** 与 `media` 返回及静态路径一致,无前导 host */ /** 与 `media` 返回及静态路径一致,无前导 host */
export const POST_MEDIA_PUBLIC_PREFIX = `${STATIC_PUBLIC_PREFIX}/${MEDIA_UPLOAD_SUBDIR}/`; export const POST_MEDIA_PUBLIC_PREFIX = `${STATIC_PUBLIC_PREFIX}/${MEDIA_UPLOAD_SUBDIR}/`;

7
server/middleware/00.public.ts

@ -1,4 +1,4 @@
import { resolve, join, sep, extname } from "node:path"; import { resolve, join, sep, extname, relative } from "node:path";
import { promises as fsp } from "node:fs"; import { promises as fsp } from "node:fs";
import { import {
decodePath, decodePath,
@ -38,8 +38,9 @@ export default eventHandler(async (event: H3Event) => {
const targetPath = join(SAFE_BASE_DIR, pathname); const targetPath = join(SAFE_BASE_DIR, pathname);
const resolvedPath = resolve(targetPath); const resolvedPath = resolve(targetPath);
// 安全校验 // 安全校验(支持 STATIC_DIR 为绝对路径;relative 在越界时为 `..` 开头)
if (!resolvedPath.startsWith(SAFE_BASE_DIR + sep)) { const rel = relative(SAFE_BASE_DIR, resolvedPath);
if (rel.startsWith("..") || rel === "..") {
res.statusCode = FORBIDDEN; res.statusCode = FORBIDDEN;
return "Forbidden"; return "Forbidden";
} }

Loading…
Cancel
Save