tune-fs
Version:
Filesystem middlewares for tune
52 lines (43 loc) • 2.03 kB
JavaScript
const path = require("path");
const fs = require("fs");
function toArray(x) { return Array.isArray(x) ? x : (x ? [x] : []); }
function canon(p) {
try { return fs.realpathSync.native ? fs.realpathSync.native(p) : fs.realpathSync(p); }
catch { return path.resolve(p); }
}
function isInside(base, target) {
base = path.resolve(base);
target = path.resolve(target);
if (process.platform === "win32") { base = base.toLowerCase(); target = target.toLowerCase(); }
const rel = path.relative(base, target);
return rel && !rel.startsWith("..") && !path.isAbsolute(rel);
}
function createWriterMiddleware(opts = {}) {
const allowed = toArray(opts.allowed).map(canon).filter(Boolean);
const base = path.resolve(opts.path || process.cwd())
return async function write(filename, data, ctx) {
const abs = path.resolve(base, filename);
// Resolve symlinks for existing parts to guard against writes escaping allowed roots
let dir = path.dirname(abs);
let probe = dir, prev;
while (!fs.existsSync(probe)) { prev = probe; probe = path.dirname(probe); if (probe === prev) break; }
let realBase = probe && fs.existsSync(probe) ? canon(probe) : probe || dir;
const remainder = path.relative(probe || dir, abs);
let realTarget = remainder ? path.join(realBase || "", remainder) : (fs.existsSync(abs) ? canon(abs) : abs);
// If the file itself exists as a symlink, resolve it
try {
const st = fs.lstatSync(abs);
if (st.isSymbolicLink()) realTarget = canon(abs);
} catch {}
if (allowed.length) {
const ok = allowed.some(base => isInside(base, realTarget) || path.resolve(base) === path.resolve(realTarget) || isInside(base, path.dirname(realTarget)));
if (!ok) throw new Error(`write not allowed: ${filename}`);
}
const directory = path.dirname(abs);
fs.mkdirSync(directory, { recursive: true });
fs.writeFileSync(abs, data);
return true;
};
}
module.exports = createWriterMiddleware;
// TODO limit paths to where it can write