@restroom-mw/files
Advanced tools
Comparing version 0.13.1-dea4e20.32 to 0.13.1-e20f69a.78
export declare const DOWNLOAD = "download the {} and extract it into {}"; | ||
export declare const STORE_RESULT = "store {} in the file {}"; | ||
export declare const READ = "read the content of {}"; | ||
export declare const READ_AND_SAVE = "read the content of {} and save the output into {}"; | ||
export declare const LS = "list the content of directory {} as {}"; | ||
//# sourceMappingURL=actions.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.READ = exports.STORE_RESULT = exports.DOWNLOAD = void 0; | ||
exports.LS = exports.READ_AND_SAVE = exports.READ = exports.STORE_RESULT = exports.DOWNLOAD = void 0; | ||
exports.DOWNLOAD = "download the {} and extract it into {}"; | ||
exports.STORE_RESULT = "store {} in the file {}"; | ||
exports.READ = "read the content of {}"; | ||
exports.READ_AND_SAVE = "read the content of {} and save the output into {}"; | ||
exports.LS = "list the content of directory {} as {}"; |
@@ -19,5 +19,7 @@ "use strict"; | ||
const fs_1 = __importDefault(require("fs")); | ||
const promises_1 = require("node:fs/promises"); | ||
const os_1 = __importDefault(require("os")); | ||
const extract_zip_1 = __importDefault(require("extract-zip")); | ||
const path_1 = __importDefault(require("path")); | ||
const utils_1 = require("@restroom-mw/utils"); | ||
require("dotenv").config(); | ||
@@ -47,13 +49,3 @@ /** | ||
// save path must be subdirs of FILES_DIR | ||
const validatePath = (p) => { | ||
if (!exports.FILES_DIR) | ||
throw new Error(`FILES_DIR is not defined`); | ||
if (exports.FILES_DIR != "/") { | ||
const relative = path_1.default.relative(exports.FILES_DIR, p); | ||
const isSubdir = relative && !relative.startsWith('..') && !path_1.default.isAbsolute(relative); | ||
if (!isSubdir) { | ||
throw new Error(`Result path outside ${exports.FILES_DIR}`); | ||
} | ||
} | ||
}; | ||
const validatePath = (0, utils_1.validateSubdir)(exports.FILES_DIR); | ||
exports.default = (req, res, next) => { | ||
@@ -63,15 +55,74 @@ const rr = new core_1.Restroom(req, res); | ||
const { zencode, keys, data } = params; | ||
input = rr.combineDataKeys(data, keys); | ||
const readFromFile = (file) => { | ||
let content; | ||
const f = input[file] || file; | ||
const absoluteFile = path_1.default.resolve(path_1.default.join(exports.FILES_DIR, f)); | ||
validatePath(absoluteFile); | ||
try { | ||
content = fs_1.default.readFileSync(absoluteFile, 'utf8'); | ||
} | ||
catch (e) { | ||
// TODO: add not-fatal (warning) log in restroom | ||
//throw new Error(`[FILES] error while reading the file ${absoluteFile}`); | ||
content = "{}"; | ||
} | ||
return JSON.parse(content); | ||
}; | ||
if (zencode.match(actions_3.READ)) { | ||
const params = zencode.paramsOf(actions_3.READ); | ||
for (const file of params) { | ||
validatePath(file); | ||
const absoluteFile = path_1.default.join(exports.FILES_DIR, file); | ||
for (const f of params) { | ||
Object.assign(data, readFromFile(f)); | ||
} | ||
} | ||
; | ||
if (zencode.match(actions_3.READ_AND_SAVE)) { | ||
const chkParams = zencode.chunkedParamsOf(actions_3.READ_AND_SAVE, 2); | ||
for (const [f, outputVariable] of chkParams) { | ||
data[outputVariable] = readFromFile(f); | ||
} | ||
} | ||
; | ||
if (zencode.match(actions_3.LS)) { | ||
const params = zencode.chunkedParamsOf(actions_3.LS, 2); | ||
const fileStats = {}; | ||
const allLs = (yield Promise.all(params.map(([p, name]) => __awaiter(void 0, void 0, void 0, function* () { | ||
const f = input[p] || p; | ||
validatePath(f); | ||
try { | ||
const content = fs_1.default.readFileSync(absoluteFile, 'utf8'); | ||
Object.assign(data, JSON.parse(content)); | ||
const content = yield (0, promises_1.readdir)(f); | ||
// I am not checking if `name` is used multiple times | ||
fileStats[name] = []; | ||
return content.map((current) => [name, f, current]); | ||
} | ||
catch (e) { | ||
throw new Error(`[FILES] error while reading the file ${absoluteFile}`); | ||
throw new Error(`[FILES] error while reading the file ${f}`); | ||
} | ||
})))).flat(); | ||
// list with all files I want to see the stats of | ||
const allStats = yield Promise.all(allLs.map(([name, p, current]) => __awaiter(void 0, void 0, void 0, function* () { | ||
const currentFile = path_1.default.join(p, current); | ||
const fileStat = yield (0, promises_1.stat)(currentFile); | ||
return [name, current, fileStat]; | ||
}))); | ||
for (const [name, current, currentStat] of allStats) { | ||
// see https://unix.stackexchange.com/questions/317855/file-mode-on-macosx#317907 | ||
// for the meaning of the mode field | ||
fileStats[name].push({ | ||
'name': current, | ||
'mode': currentStat.mode.toString(8), | ||
'dev': currentStat.dev, | ||
'nlink': currentStat.nlink, | ||
'uid': currentStat.uid, | ||
'gid': currentStat.gid, | ||
'size': currentStat.size, | ||
'blksize': currentStat.blksize, | ||
'blocks': currentStat.blocks, | ||
'atime': currentStat.atime.toISOString(), | ||
'mtime': currentStat.mtime.toISOString(), | ||
'ctime': currentStat.ctime.toISOString(), | ||
'birthtime': currentStat.birthtime.toISOString(), | ||
}); | ||
} | ||
Object.assign(data, fileStats); | ||
} | ||
@@ -85,4 +136,6 @@ input = rr.combineDataKeys(data, keys); | ||
for (let i = 0; i < allPassOutputs.length; i += 2) { | ||
const file = result[allPassOutputs[i]]; | ||
const folder = input[allPassOutputs[i + 1]]; | ||
const file = result[allPassOutputs[i]] || | ||
input[allPassOutputs[i]]; | ||
const folder = result[allPassOutputs[i + 1]] || | ||
input[allPassOutputs[i + 1]]; | ||
if (!file) { | ||
@@ -116,4 +169,6 @@ throw new Error(`[FILES] url not defined`); | ||
for (let i = 0; i < allPassOutputs.length; i += 2) { | ||
const variable = result[allPassOutputs[i]]; | ||
const file = input[allPassOutputs[i + 1]]; | ||
const variable = result[allPassOutputs[i]] || | ||
input[allPassOutputs[i]]; | ||
const file = result[allPassOutputs[i + 1]] || | ||
input[allPassOutputs[i + 1]]; | ||
if (!variable) { | ||
@@ -120,0 +175,0 @@ throw new Error(`[FILES] variable not defined`); |
{ | ||
"name": "@restroom-mw/files", | ||
"version": "0.13.1-dea4e20.32+dea4e20", | ||
"version": "0.13.1-e20f69a.78+e20f69a", | ||
"description": "Utilities middleware to work with files for Restroom", | ||
@@ -35,3 +35,3 @@ "author": "Alberto Lerda <alberto@dyne.org>", | ||
}, | ||
"gitHead": "dea4e208910c34cba25e7f19eb0dbf5ed6ef2c0a" | ||
"gitHead": "e20f69a2b6a2742ea3a3f060c0b1ef4163ba3e38" | ||
} |
export const DOWNLOAD = "download the {} and extract it into {}"; | ||
export const STORE_RESULT = "store {} in the file {}"; | ||
export const READ = "read the content of {}"; | ||
export const READ_AND_SAVE = "read the content of {} and save the output into {}"; | ||
export const LS = "list the content of directory {} as {}"; |
108
src/index.ts
@@ -5,2 +5,3 @@ import {Restroom} from "@restroom-mw/core"; | ||
import fs from 'fs' | ||
import { readdir, stat } from 'node:fs/promises'; | ||
import os from 'os' | ||
@@ -10,2 +11,3 @@ import extract from 'extract-zip'; | ||
import {ObjectLiteral} from "@restroom-mw/types"; | ||
import {validateSubdir} from "@restroom-mw/utils" | ||
@@ -26,6 +28,5 @@ require("dotenv").config(); | ||
*/ | ||
import {STORE_RESULT} from "./actions"; | ||
import {READ} from "./actions"; | ||
import {READ, READ_AND_SAVE, LS} from "./actions"; | ||
@@ -43,34 +44,81 @@ | ||
// save path must be subdirs of FILES_DIR | ||
const validatePath = (p: string) => { | ||
if (!FILES_DIR) | ||
throw new Error(`FILES_DIR is not defined`); | ||
const validatePath = validateSubdir(FILES_DIR); | ||
if (FILES_DIR != "/") { | ||
const relative = path.relative(FILES_DIR, p); | ||
const isSubdir = relative && !relative.startsWith('..') && !path.isAbsolute(relative); | ||
if (!isSubdir) { | ||
throw new Error(`Result path outside ${FILES_DIR}`) | ||
} | ||
} | ||
} | ||
export default (req: Request, res: Response, next: NextFunction) => { | ||
const rr = new Restroom(req, res); | ||
rr.onBefore(async (params) => { | ||
const { zencode, keys, data } = params; | ||
input = rr.combineDataKeys(data, keys); | ||
const readFromFile = ( file: string ) => { | ||
let content | ||
const f = input[file] || file; | ||
const absoluteFile = path.resolve(path.join(FILES_DIR, f)); | ||
validatePath(absoluteFile); | ||
try { | ||
content = fs.readFileSync(absoluteFile, 'utf8'); | ||
} catch(e) { | ||
// TODO: add not-fatal (warning) log in restroom | ||
//throw new Error(`[FILES] error while reading the file ${absoluteFile}`); | ||
content = "{}"; | ||
} | ||
return JSON.parse(content); | ||
} | ||
if (zencode.match(READ)) { | ||
const params = zencode.paramsOf(READ); | ||
for(const file of params) { | ||
validatePath(file); | ||
const absoluteFile = path.join(FILES_DIR, file) | ||
try { | ||
const content = fs.readFileSync(absoluteFile, 'utf8') | ||
Object.assign(data, JSON.parse(content)) | ||
} catch(e) { | ||
throw new Error(`[FILES] error while reading the file ${absoluteFile}`); | ||
} | ||
for(const f of params) { | ||
Object.assign(data, readFromFile(f)); | ||
} | ||
}; | ||
if (zencode.match(READ_AND_SAVE)) { | ||
const chkParams = zencode.chunkedParamsOf(READ_AND_SAVE, 2); | ||
for(const [f, outputVariable] of chkParams) { | ||
data[ outputVariable ] = readFromFile(f); | ||
} | ||
}; | ||
if (zencode.match(LS)) { | ||
const params = zencode.chunkedParamsOf(LS, 2); | ||
const fileStats: Record<string, any> = {} | ||
const allLs = (await Promise.all(params.map( | ||
async ([p, name]: string[]) => { | ||
const f = input[p] || p; | ||
validatePath(f); | ||
try { | ||
const content = await readdir(f) | ||
// I am not checking if `name` is used multiple times | ||
fileStats[name] = [] | ||
return content.map((current) => [name, f, current]); | ||
} catch(e) { | ||
throw new Error(`[FILES] error while reading the file ${f}`); | ||
} | ||
}))).flat() | ||
// list with all files I want to see the stats of | ||
const allStats = await Promise.all(allLs.map(async ([name, p, current]) => { | ||
const currentFile = path.join(p, current) | ||
const fileStat = await stat(currentFile) | ||
return [name, current, fileStat] | ||
})) | ||
for(const [name, current, currentStat] of allStats) { | ||
// see https://unix.stackexchange.com/questions/317855/file-mode-on-macosx#317907 | ||
// for the meaning of the mode field | ||
fileStats[name].push({ | ||
'name': current, | ||
'mode': currentStat.mode.toString(8), | ||
'dev': currentStat.dev, | ||
'nlink': currentStat.nlink, | ||
'uid': currentStat.uid, | ||
'gid': currentStat.gid, | ||
'size': currentStat.size, | ||
'blksize': currentStat.blksize, | ||
'blocks': currentStat.blocks, | ||
'atime': currentStat.atime.toISOString(), | ||
'mtime': currentStat.mtime.toISOString(), | ||
'ctime': currentStat.ctime.toISOString(), | ||
'birthtime': currentStat.birthtime.toISOString(), | ||
}) | ||
} | ||
Object.assign(data, fileStats) | ||
} | ||
@@ -85,4 +133,6 @@ input = rr.combineDataKeys(data, keys); | ||
for (let i = 0; i < allPassOutputs.length; i += 2) { | ||
const file = result[allPassOutputs[i]] | ||
const folder = input[allPassOutputs[i + 1]]; | ||
const file = result[allPassOutputs[i]] || | ||
input[allPassOutputs[i]]; | ||
const folder = result[allPassOutputs[i + 1]] || | ||
input[allPassOutputs[i + 1]]; | ||
@@ -118,4 +168,6 @@ if(!file) { | ||
for (let i = 0; i < allPassOutputs.length; i += 2) { | ||
const variable = result[allPassOutputs[i]] | ||
const file = input[allPassOutputs[i + 1]] | ||
const variable = result[allPassOutputs[i]] || | ||
input[allPassOutputs[i]]; | ||
const file = result[allPassOutputs[i + 1]] || | ||
input[allPassOutputs[i + 1]]; | ||
@@ -122,0 +174,0 @@ if(!variable) { |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
52946
391