From cfb9fb208475ae4ba6a8490b38a1d88fb5eabda6 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Rapha=C3=ABl=20Van=20Dyck?= Date: Sun, 2 Aug 2026 14:26:22 +0200 Subject: [PATCH] streamline code accessing local files --- file-systems/native.js | 42 +++++-------- file-systems/router.js | 138 +++++++++++++---------------------------- 2 files changed, 60 insertions(+), 120 deletions(-) diff --git a/file-systems/native.js b/file-systems/native.js index 1164699..653e32b 100644 --- a/file-systems/native.js +++ b/file-systems/native.js @@ -4,31 +4,23 @@ import path from 'path'; import fs from 'fs'; -function pathnameToNativePathname(root, pathname) { - // pathname: [''], ['', ], ['', , ], ... - // !== '' - // !== '.' - // !== '..' - return path.join(root, ...pathname); +function checkIsWritable(options) { + if (!options.writable) { + throw new Error('The file system is not writable.'); + } } -function checkIsDirectory(nativePathname) { - const stat = fs.statSync(nativePathname, {throwIfNoEntry: false}); - if (stat === undefined) { - throw new Error('The file does not exist.'); - } - if (!stat.isDirectory()) { - throw new Error('The file is not a directory.'); - } +function toNativePathname(pathnameSegments, options) { + return path.join(options.root, ...pathnameSegments); } -function checkIsNotDirectory(nativePathname) { +function checkIsRegularFile(nativePathname) { const stat = fs.statSync(nativePathname, {throwIfNoEntry: false}); if (stat === undefined) { throw new Error('The file does not exist.'); } - if (stat.isDirectory()) { - throw new Error('The file is a directory.'); + if (!stat.isFile()) { + throw new Error('The file is not a regular file.'); } } @@ -36,17 +28,15 @@ export function getCapabilities(options) { return {writable: options.writable}; } -export function getFileContents(pathname, options) { - const nativePathname = pathnameToNativePathname(options.root, pathname); - checkIsNotDirectory(nativePathname); +export function getFileContents(pathnameSegments, options) { + const nativePathname = toNativePathname(pathnameSegments, options); + checkIsRegularFile(nativePathname); return fs.readFileSync(nativePathname); } -export function putFileContents(pathname, contents, options) { - if (!options.writable) { - throw new Error('The file system is not writable.'); - } - const nativePathname = pathnameToNativePathname(options.root, pathname); - checkIsNotDirectory(nativePathname); +export function putFileContents(pathnameSegments, contents, options) { + checkIsWritable(options); + const nativePathname = toNativePathname(pathnameSegments, options); + checkIsRegularFile(nativePathname); fs.writeFileSync(nativePathname, contents); } diff --git a/file-systems/router.js b/file-systems/router.js index 2540000..75c2b8d 100644 --- a/file-systems/router.js +++ b/file-systems/router.js @@ -2,126 +2,76 @@ // SPDX-License-Identifier: BSD-3-Clause import express from 'express'; +import fs from 'fs'; +import { Buffer } from 'buffer'; class ClientError extends Error {} -function isString(value) { - return typeof value === 'string'; +function invalidPathname() { + throw new Error('Invalid pathname.'); } -function invalidStringParameter() { - throw new Error('Invalid string parameter.'); -} - -function validateStringParameter(value) { - if (!isString(value)) { - invalidStringParameter(); +function getPathnameSegments(req) { + const pathname = req.query.pathname; + if (typeof pathname !== 'string') { + invalidPathname(); } - return value; -} - -function invalidNonNegativeIntegerParameter() { - throw new Error('Invalid non-negative integer parameter.'); -} - -function validateNonNegativeIntegerParameter(value) { - if (!isString(value) || !/^(0|[1-9][0-9]*)$/.test(value)) { - invalidNonNegativeIntegerParameter(); + const pathnameSegments = pathname.split('/'); + if (pathnameSegments.length === 1) { + invalidPathname(); } - return Number.parseInt(value); -} - -function invalidBooleanParameter() { - throw new Error('Invalid boolean parameter.'); -} - -function validateBooleanParameter(value) { - if (!isString(value)) { - invalidBooleanParameter(); + if (pathnameSegments[0] !== '') { + invalidPathname(); } - switch (value) { - case 'true': - return true; - case 'false': - return false; - default: - invalidBooleanParameter(); + for (let i = 1; i < pathnameSegments.length; i++) { + if (pathnameSegments[i] === '..') { + invalidPathname(); + } } + return pathnameSegments.slice(1); } -function isValidName(name) { - return !name.includes('\x00') && - !name.includes('/') && - name !== '' && - name !== '.' && - name !== '..'; +function invalidContents() { + throw new Error('Invalid contents.'); } -function invalidNameParameter() { - throw new Error('Invalid name parameter.'); -} - -function validateNameParameter(name) { - if (!isString(name) || !isValidName(name)) { - invalidNameParameter(); +function getContents(req) { + const contents = req.body; + if (!Buffer.isBuffer(contents)) { + invalidContents(); } - return name; -} - -function invalidNamesParameter() { - throw new Error('Invalid names parameter.'); + return contents; } -function validateNamesParameter(names) { - if (!Array.isArray(names)) { - invalidNamesParameter(); - } - if (names.length === 0) { - invalidNamesParameter(); - } - for (const name of names) { - if (!isString(name) || !isValidName(name)) { - invalidNamesParameter(); +function makeRouter(fileSystem, options) { + let root = null; + try { + root = fs.realpathSync.native(options.root); + } catch (exception) { + if (exception.code = 'ENOENT') { + throw new Error('The root does not exist.'); + } else { + throw exception; } } - if (new Set(names).size !== names.length) { - invalidNamesParameter(); + const stat = fs.statSync(root); + if (!stat.isDirectory()) { + throw new Error('The root is not a directory.'); } - names.sort(); - return names; -} - -function invalidPathnameParameter() { - throw new Error('Invalid pathname parameter.'); -} - -function validatePathnameParameter(pathname) { - if (!isString(pathname)) { - invalidPathnameParameter(); - } - const names = pathname.split('/'); - if (names[0] !== '') { - invalidPathnameParameter(); - } - for (let i = 1; i < names.length; i++) { - if (!isValidName(names[i])) { - invalidPathnameParameter(); - } - } - return names; -} - -function makeRouter(fileSystem, options) { + options.root = root; options.ClientError = ClientError; const router = express.Router(); router.get('/get-capabilities', (req, res) => { res.json(fileSystem.getCapabilities(options)); }); router.get('/get-file-contents', (req, res) => { - res.send(fileSystem.getFileContents(validatePathnameParameter(req.query.pathname), options)); + const pathnameSegments = getPathnameSegments(req); + res.send(fileSystem.getFileContents(pathnameSegments, options)); }); - router.put('/put-file-contents', express.raw({limit: '250kb'}), (req, res) => { - fileSystem.putFileContents(validatePathnameParameter(req.query.pathname), req.body, options); + router.put('/put-file-contents', express.raw({limit: '1mb'}), (req, res) => { + const pathnameSegments = getPathnameSegments(req); + const contents = getContents(req); + fileSystem.putFileContents(pathnameSegments, contents, options); res.end(); }); router.use((err, req, res, next) => { -- 2.39.5