Files

25 lines
1.8 KiB
JavaScript

import { readFile, realpath, stat } from 'node:fs/promises';
import path from 'node:path';
const sourceFolders = ['UnrealEngine', 'godot', 'UnityCsReference', 'blender-source'];
const extensions = /\.(md|json|version|cpp|h|hpp|c|cc|cs|usf|ush|glsl|hlsl|py|txt|inl|xml|gd|godot|sln|cmake)$/i;
const denied = () => Object.assign(new Error('File not allowed'), { status: 403 });
const contained = (target, root) => target.startsWith(root + path.sep);
export async function readDocument(projectRoot, requestedPath) {
if (!requestedPath || path.isAbsolute(requestedPath) || /^[a-z]:/i.test(requestedPath) || requestedPath.includes('\\')) throw denied();
if (requestedPath.split('/').some(part => (part.startsWith('.') && part !== '.' && part !== '..') || part === 'node_modules')) throw denied();
const realProject = await realpath(projectRoot);
const target = await realpath(path.resolve(projectRoot, requestedPath));
const roots = [path.join(projectRoot, 'docs'), ...sourceFolders.map(name => path.resolve(projectRoot, '..', name))];
const realRoots = await Promise.all(roots.map(root => realpath(root).catch(() => null)));
if (realRoots[0] && !contained(realRoots[0], realProject)) realRoots[0] = null;
const projectDocuments = ['README.md', 'PLAN.md'].map(name => path.join(realProject, name));
if ((!realRoots.some(root => root && contained(target, root)) && !projectDocuments.includes(target))
|| (!extensions.test(target) && path.basename(target) !== 'SConstruct')
|| target.split(path.sep).some(part => part.startsWith('.') || part === 'node_modules')) throw denied();
const info = await stat(target);
if (!info.isFile() || info.size > 8 * 1024 * 1024) throw denied();
return { path: path.relative(realProject, target).split(path.sep).join('/'), text: await readFile(target, 'utf8') };
}