Source-candidate: poe d9a564e6d20f5f0fd75ce45b12cbbf8bb4c9d568\nSource-candidate: magica38ba5762b06716db1dee00d0afec162656488f5c\nValidated321whole-file phase plus whole bus1349/model-proxy100/webui490; final gate tooling checks pending, not landed.
28 lines
1.3 KiB
JavaScript
28 lines
1.3 KiB
JavaScript
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { createHash } from 'node:crypto';
|
|
import { ProxyError, refuse, validManagementKey, proxyTiming } from './validation.mjs';
|
|
export { ProxyError, refuse, validManagementKey, proxyTiming };
|
|
export const sha256 = value => createHash('sha256').update(value).digest('hex');
|
|
export const PIN = Object.freeze(JSON.parse(fs.readFileSync(new URL('../PIN', import.meta.url), 'utf8')));
|
|
// Refuse symlink components before handling any state. Callers only create owned new leaves.
|
|
export function safePath(file, code) {
|
|
if (typeof file !== 'string' || !path.isAbsolute(file) || path.resolve(file) !== file) refuse(code);
|
|
let current = path.parse(file).root;
|
|
for (const part of file.slice(current.length).split('/').filter(Boolean)) {
|
|
current = path.join(current, part);
|
|
try { if (fs.lstatSync(current).isSymbolicLink()) refuse(code); }
|
|
catch (e) { if (e.code !== 'ENOENT') throw e; }
|
|
}
|
|
}
|
|
export function regularBytes(file, code) {
|
|
safePath(file, code);
|
|
let fd;
|
|
try {
|
|
fd = fs.openSync(file, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
|
|
if (!fs.fstatSync(fd).isFile()) refuse(code);
|
|
return fs.readFileSync(fd);
|
|
} catch { refuse(code); }
|
|
finally { if (fd !== undefined) fs.closeSync(fd); }
|
|
}
|