UNPKG

gingee

Version:

Gingee, a secure, batteries included, feature rich, multi-database, enterprise ready application server, co-authored by a human architect and a Generative AI partner.

563 lines (527 loc) 24.9 kB
const nodeFs = require('fs'); const nodeFsPromises = require('fs/promises'); const path = require('path'); const { SCOPES, resolveSecurePath } = require('./internal_utils.js'); /** * @module fs * @description A secure file system module for Gingee that provides secure sandboxed synchronous and asynchronous file operations. * <b>NOTE:</b> path with leading slash indicates path from scope root, path without leading slash indicates path relative to the executing script * <b>IMPORTANT:</b> Requires explicit permission to use the module. See docs/permissions-guide for more details. */ // FS Wrapper functions /** * @function readFileSync * @memberof module:fs * @description Synchronously reads the entire contents of a file. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {object | string} [options] - The encoding or an options object. * @returns {string | Buffer} The contents of the file. * @throws {Error} If the file does not exist or is outside the secure scope. * @example * const content = fs.readFileSync(fs.BOX, 'data/myfile.txt', 'utf8'); * console.log(content); // Outputs the content of myfile.txt */ function readFileSync(scope, filePath, options) { const absolutePath = resolveSecurePath(scope, filePath); return nodeFs.readFileSync(absolutePath, options); } /** * @function readJSONSync * @memberof module:fs * @description Synchronously reads a JSON file and parses it. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {object | string} [options] - The encoding or an options object. * @returns {object} The parsed JSON object. * @throws {Error} If the file does not exist or is outside the secure scope or it is not valid JSON. * @example * const data = fs.readJSONSync(fs.BOX, 'data/myfile.json'); * console.log(data); // Outputs the parsed JSON object */ function readJSONSync(scope, filePath, options) { const absolutePath = resolveSecurePath(scope, filePath); const data = nodeFs.readFileSync(absolutePath, options); return JSON.parse(data); } /** * @function writeFileSync * @memberof module:fs * @description Synchronously writes data to a file, creating directories as needed. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {string | Buffer} data - The data to write to the file. * @param {object | string} [options] - The encoding or an options object. * @returns {void} * @throws {Error} If the file path is outside the secure scope or if the directory cannot be created. * @example * fs.writeFileSync(fs.BOX, 'data/myfile.txt', 'Hello, World!', 'utf8'); */ function writeFileSync(scope, filePath, data, options) { const absolutePath = resolveSecurePath(scope, filePath); const dir = path.dirname(absolutePath); nodeFs.mkdirSync(dir, { recursive: true }); return nodeFs.writeFileSync(absolutePath, data, options); } /** * @function appendFileSync * @memberof module:fs * @description Synchronously appends data to a file, creating directories as needed. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {string | Buffer} data - The data to append to the file. * @param {object | string} [options] - The encoding or an options object. * @returns {void} * @throws {Error} If the file path is outside the secure scope or if the directory cannot be created. * @example * fs.appendFileSync(fs.BOX, 'data/myfile.txt', 'Hello, World!', 'utf8'); */ function appendFileSync(scope, filePath, data, options) { const absolutePath = resolveSecurePath(scope, filePath); const dir = path.dirname(absolutePath); nodeFs.mkdirSync(dir, { recursive: true }); return nodeFs.appendFileSync(absolutePath, data, options); } /** * @function writeJSONSync * @memberof module:fs * @description Synchronously writes a JSON object to a file, creating directories as needed. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {object} data - The JSON object to write to the file. * @param {object | string} [options] - The encoding or an options object. * @returns {void} * @throws {Error} If the file path is outside the secure scope or if the directory cannot be created. * @example * fs.writeJSONSync(fs.BOX, 'data/myfile.json', { key: 'value' }); */ function writeJSONSync(scope, filePath, data, options) { const absolutePath = resolveSecurePath(scope, filePath); const dir = path.dirname(absolutePath); nodeFs.mkdirSync(dir, { recursive: true }); return nodeFs.writeFileSync(absolutePath, JSON.stringify(data, null, 2), options); } /** * @function existsSync * @memberof module:fs * @description Synchronously checks if a file exists. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @returns {boolean} True if the file exists, false otherwise. * @example * const exists = fs.existsSync(fs.BOX, 'data/myfile.txt'); * console.log(exists); // Outputs true if myfile.txt exists, false otherwise */ function existsSync(scope, filePath) { try { const absolutePath = resolveSecurePath(scope, filePath); return nodeFs.existsSync(absolutePath); } catch (e) { return false; } } /** * @function deleteFileSync * @memberof module:fs * @description Synchronously deletes a file. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @returns {void} * @throws {Error} If the file does not exist or is outside the secure scope. * @example * fs.deleteFileSync(fs.BOX, 'data/myfile.txt'); */ function deleteFileSync(scope, filePath) { const absolutePath = resolveSecurePath(scope, filePath); return nodeFs.unlinkSync(absolutePath); } /** * @function moveFileSync * @memberof module:fs * @description Synchronously moves a file from one location to another within the same scope. * @param {string} sourceScope - The scope of the source file (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source file, relative to the source scope. * @param {string} destScope - The scope of the destination file (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination file, relative to the destination scope. * @returns {string} The new absolute path of the moved file. * @throws {Error} if the source file does not exist. * @example * const newPath = fs.moveFileSync(fs.BOX, 'data/myfile.txt', fs.BOX, 'data/archived/myfile.txt'); */ function moveFileSync(sourceScope, sourcePath, destScope, destPath) { //check for source path existence if (!existsSync(sourceScope, sourcePath)) { throw new Error(`Source file ${sourcePath} does not exist.`); } const sourceAbsolutePath = resolveSecurePath(sourceScope, sourcePath); const destAbsolutePath = resolveSecurePath(destScope, destPath); nodeFs.mkdirSync(path.dirname(destAbsolutePath), { recursive: true }); return nodeFs.renameSync(sourceAbsolutePath, destAbsolutePath); } /** * @function copyFileSync * @memberof module:fs * @description Synchronously copies a file from one location to another within the same scope. * @param {string} sourceScope - The scope of the source file (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source file, relative to the source scope. * @param {string} destScope - The scope of the destination file (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination file, relative to the destination scope. * @returns {void} * @throws {Error} if the source file does not exist. * @example * fs.copyFileSync(fs.BOX, 'data/myfile.txt', fs.BOX, 'data/backup/myfile.txt'); */ function copyFileSync(sourceScope, sourcePath, destScope, destPath) { // Check if the source file exists if (!existsSync(sourceScope, sourcePath)) { throw new Error(`Source file ${sourcePath} does not exist.`); } const sourceAbsolutePath = resolveSecurePath(sourceScope, sourcePath); const destAbsolutePath = resolveSecurePath(destScope, destPath); nodeFs.mkdirSync(path.dirname(destAbsolutePath), { recursive: true }); return nodeFs.copyFileSync(sourceAbsolutePath, destAbsolutePath); } /** * @function mkdirSync * @memberof module:fs * @description Synchronously creates a directory and its parent directories if they do not exist. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} dirPath - The path to the directory, relative to the scope or script. * @returns {void} * @throws {Error} If the directory path is outside the secure scope or if the directory cannot be created. * @example * fs.mkdirSync(fs.BOX, 'data/newdir'); */ function mkdirSync(scope, dirPath) { const absolutePath = resolveSecurePath(scope, dirPath); // Using { recursive: true } is a safe and common default. return nodeFs.mkdirSync(absolutePath, { recursive: true }); } /** * @function rmdirSync * @memberof module:fs * @description Synchronously removes a directory. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} dirPath - The path to the directory, relative to the scope or script. * @param {object} [options] - Options for the removal. * - `recursive`: If true, removes the directory and its contents recursively. * @returns {void} * @throws {Error} If the directory is not empty and `recursive` is false. * @example * fs.rmdirSync(fs.BOX, 'data/oldDir', { recursive: true }); */ function rmdirSync(scope, dirPath, options = {}) { const absolutePath = resolveSecurePath(scope, dirPath); if (options.recursive) { // fs.rmSync is the modern way to remove recursively. return nodeFs.rmSync(absolutePath, { recursive: true, force: true }); } return nodeFs.rmdirSync(absolutePath); } /** * @function moveDirSync * @memberof module:fs * @description Synchronously moves a directory from one location to another within the same scope. * @param {string} sourceScope - The scope of the source directory (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source directory, relative to the source scope. * @param {string} destScope - The scope of the destination directory (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination directory, relative to the destination scope. * @returns {string} The new absolute path of the moved directory. * @throws {Error} if the source directory does not exist. * @example * fs.moveDirSync(fs.BOX, 'data/oldDir', fs.BOX, 'data/newDir'); */ const moveDirSync = moveFileSync; // Alias for consistency with moveFileSync /** * @function copyDirSync * @memberof module:fs * @description Synchronously copies a directory from one location to another within the same scope. * @param {string} sourceScope - The scope of the source directory (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source directory, relative to the source scope. * @param {string} destScope - The scope of the destination directory (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination directory, relative to the destination scope. * @returns {void} * @throws {Error} if the source directory does not exist. * @example * fs.copyDirSync(fs.BOX, 'data/oldDir', fs.BOX, 'data/newDir'); */ function copyDirSync(sourceScope, sourcePath, destScope, destPath) { const sourceAbsolutePath = resolveSecurePath(sourceScope, sourcePath); const destAbsolutePath = resolveSecurePath(destScope, destPath); // fs.cpSync handles directory creation and recursive copying. return nodeFs.cpSync(sourceAbsolutePath, destAbsolutePath, { recursive: true }); } //ASync FS Wrapper functions /** * @function readFile * @memberof module:fs * @description Asynchronously reads the entire contents of a file. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {object | string} [options] - The encoding or an options object. * @returns {Promise<string | Buffer>} A Promise that resolves with the contents of the file. * @throws {Error} If the file does not exist or is outside the secure scope. * @example * fs.readFile(fs.BOX, 'data/file.txt', 'utf8').then(contents => { * console.log(contents); * }); */ async function readFile(scope, filePath, options) { const absolutePath = resolveSecurePath(scope, filePath); return nodeFsPromises.readFile(absolutePath, options); } /** * @function writeFile * @memberof module:fs * @description Asynchronously writes data to a file, replacing the file if it already exists. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {string | Buffer} data - The data to write. * @param {object | string} [options] - The encoding or an options object. * @returns {Promise<void>} A Promise that resolves when the write operation is complete. * @throws {Error} If the file path is outside the secure scope or if the directory cannot be created. * @example * fs.writeFile(fs.BOX, 'data/file.txt', 'Hello, world!', 'utf8').then(() => { * console.log('File written successfully'); * }); */ async function writeFile(scope, filePath, data, options) { const absolutePath = resolveSecurePath(scope, filePath); const dir = path.dirname(absolutePath); await nodeFsPromises.mkdir(dir, { recursive: true }); return nodeFsPromises.writeFile(absolutePath, data, options); } /** * @function appendFile * @memberof module:fs * @description Asynchronously appends data to a file, creating directories as needed. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @param {string | Buffer} data - The data to append. * @param {object | string} [options] - The encoding or an options object. * @returns {Promise<void>} A Promise that resolves when the append operation is complete. * @throws {Error} If the file path is outside the secure scope or if the directory cannot be created. * @example * fs.appendFile(fs.BOX, 'data/file.txt', 'Hello, world!', 'utf8').then(() => { * console.log('File appended successfully'); * }); */ async function appendFile(scope, filePath, data, options) { // 1. Get the secure, absolute path. const absolutePath = resolveSecurePath(scope, filePath); // 2. Ensure the directory exists before appending. const dir = path.dirname(absolutePath); await nodeFsPromises.mkdir(dir, { recursive: true }); // 3. Call the real async fs.appendFile. return nodeFsPromises.appendFile(absolutePath, data, options); } /** * @function exists * @memberof module:fs * @description Asynchronously checks if a file exists. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @returns {Promise<boolean>} A Promise that resolves with true if the file exists, false otherwise. * @throws {Error} If the file path is outside the secure scope. * @example * fs.exists(fs.BOX, 'data/file.txt').then(exists => { * console.log(exists); * }); */ async function exists(scope, filePath) { const absolutePath = resolveSecurePath(scope, filePath); try { await nodeFsPromises.access(absolutePath); return true; } catch (e) { return false; } } /** * @function deleteFile * @memberof module:fs * @description Asynchronously deletes a file. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} filePath - The path to the file, relative to the scope or script. * @returns {Promise<void>} A Promise that resolves when the file is deleted. * @throws {Error} If the file does not exist or is outside the secure scope. * @example * fs.deleteFile(fs.BOX, 'data/file.txt').then(() => { * console.log('File deleted successfully'); * }); */ async function deleteFile(scope, filePath) { const absolutePath = resolveSecurePath(scope, filePath); return nodeFsPromises.unlink(absolutePath); } /** * @function moveFile * @memberof module:fs * @description Asynchronously moves a file from one location to another within the same scope. * @param {string} sourceScope - The scope of the source file (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source file, relative to the source scope. * @param {string} destScope - The scope of the destination file (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination file, relative to the destination scope. * @returns {Promise<string>} A Promise that resolves with the new absolute path of the moved file. * @throws {Error} If the source and destination scopes are different. * @throws {Error} If the source file does not exist. * @example * fs.moveFile(fs.BOX, 'data/file.txt', fs.BOX, 'data/newfile.txt').then(newPath => { * console.log('File moved to:', newPath); * }); */ async function moveFile(sourceScope, sourcePath, destScope, destPath) { if (sourceScope !== destScope) { throw new Error("Security Error: rename/move operations must be within the same scope (BOX to BOX, or WEB to WEB)."); } // Check if the source file exists if (!await exists(sourceScope, sourcePath)) { throw new Error(`Source file ${sourcePath} does not exist.`); } const sourceAbsolutePath = resolveSecurePath(sourceScope, sourcePath); const destAbsolutePath = resolveSecurePath(destScope, destPath); await nodeFsPromises.mkdir(path.dirname(destAbsolutePath), { recursive: true }); return nodeFsPromises.rename(sourceAbsolutePath, destAbsolutePath); } /** * @function copyFile * @memberof module:fs * @description Asynchronously copies a file from one location to another within the same scope. * @param {string} sourceScope - The scope of the source file (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source file, relative to the source scope. * @param {string} destScope - The scope of the destination file (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination file, relative to the destination scope. * @return {Promise<void>} A Promise that resolves when the file is copied. * @throws {Error} if the source file does not exist. * @example * fs.copyFile(fs.BOX, 'data/file.txt', fs.BOX, 'data/copy.txt').then(() => { * console.log('File copied successfully'); * }); */ async function copyFile(sourceScope, sourcePath, destScope, destPath) { // Check if the source file exists if (!await exists(sourceScope, sourcePath)) { throw new Error(`Source file ${sourcePath} does not exist.`); } const sourceAbsolutePath = resolveSecurePath(sourceScope, sourcePath); const destAbsolutePath = resolveSecurePath(destScope, destPath); await nodeFsPromises.mkdir(path.dirname(destAbsolutePath), { recursive: true }); return nodeFsPromises.copyFile(sourceAbsolutePath, destAbsolutePath); } /** * @function mkdir * @memberof module:fs * @description Asynchronously creates a directory and its parent directories if they do not exist. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} dirPath - The path to the directory, relative to the scope or script. * @returns {Promise<void>} A Promise that resolves when the directory is created. * @throws {Error} If the directory path is outside the secure scope or if the directory cannot be created. * @example * fs.mkdir(fs.BOX, 'data/newdir').then(() => { * console.log('Directory created successfully'); * }); */ async function mkdir(scope, dirPath) { const absolutePath = resolveSecurePath(scope, dirPath); return nodeFsPromises.mkdir(absolutePath, { recursive: true }); } /** * @function rmdir * @memberof module:fs * @description Asynchronously removes a directory. * @param {string} scope - The scope to operate in (fs.BOX or fs.WEB). * @param {string} dirPath - The path to the directory, relative to the scope or script. * @param {object} [options] - Options for the removal. * - `recursive`: If true, removes the directory and its contents recursively. * @return {Promise<void>} A Promise that resolves when the directory is removed. * @throws {Error} If the directory does not exist or is outside the secure scope. * @example * fs.rmdir(fs.BOX, 'data/oldDir', { recursive: true }).then(() => { * console.log('Directory removed successfully'); * }); */ async function rmdir(scope, dirPath, options = {}) { const absolutePath = resolveSecurePath(scope, dirPath); if (options.recursive) { return nodeFsPromises.rm(absolutePath, { recursive: true, force: true }); } return nodeFsPromises.rmdir(absolutePath); } /** * @function moveDir * @memberof module:fs * @description Asynchronously moves a directory from one location to another within the same scope. * @param {string} sourceScope - The scope of the source directory (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source directory, relative to the source scope. * @param {string} destScope - The scope of the destination directory (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination directory, relative to the destination scope. * @returns {Promise<string>} A Promise that resolves with the new absolute path of the moved directory. * @throws {Error} if the source directory does not exist. * @example * fs.moveDir(fs.BOX, 'data/oldDir', fs.BOX, 'data/newDir').then(newPath => { * console.log('Directory moved to:', newPath); * }); * */ const moveDir = moveFile; // Alias for consistency with moveFile /** * @function copyDir * @memberof module:fs * @description Asynchronously copies a directory from one location to another within the same scope. * @param {string} sourceScope - The scope of the source directory (fs.BOX or fs.WEB). * @param {string} sourcePath - The path to the source directory, relative to the source scope. * @param {string} destScope - The scope of the destination directory (fs.BOX or fs.WEB). * @param {string} destPath - The path to the destination directory, relative to the destination scope. * @returns {Promise<void>} A Promise that resolves when the directory is copied. * @throws {Error} if the source directory does not exist. * @example * fs.copyDir(fs.BOX, 'data/oldDir', fs.BOX, 'data/newDir').then(() => { * console.log('Directory copied successfully'); * }); */ async function copyDir(sourceScope, sourcePath, destScope, destPath) { const sourceAbsolutePath = resolveSecurePath(sourceScope, sourcePath); const destAbsolutePath = resolveSecurePath(destScope, destPath); return nodeFsPromises.cp(sourceAbsolutePath, destAbsolutePath, { recursive: true }); } module.exports = { /** * @constant BOX * @memberof module:fs * @description Constant for the BOX scope. * This constant can be used to specify the BOX scope when working with file system operations. * It represents the application box directory, typically used for sandboxed data and server scripts that should not be accessible from the web. */ BOX: SCOPES.BOX, /** * @constant WEB * @memberof module:fs * @description Constant for the WEB scope. * This constant can be used to specify the WEB scope when working with file system operations. * It represents the web directory, typically used for web assets. */ WEB: SCOPES.WEB, // Synchronous versions readFileSync, writeFileSync, appendFileSync, existsSync, deleteFileSync, copyFileSync, moveFileSync, mkdirSync, rmdirSync, moveDirSync, copyDirSync, // Asynchronous versions readFile, writeFile, appendFile, exists, deleteFile, copyFile, moveFile, mkdir, rmdir, moveDir, copyDir };