src/server/utils.ts (91 lines of code) (raw):
import express = require('express');
import * as fs from 'fs';
import * as path from 'path';
var escape = require('escape-html');
import { SERVER_ERROR, SUCCESS } from './serverBase';
export const fetchDrivesOnWindows = (res: express.Response) => {
const exec = require('child_process').exec;
exec('wmic logicaldisk get name', (error: any, stdout: any, stderr: any) => { // tslint:disable-line:no-any
if (!error && !stderr) {
res.status(SUCCESS).send(stdout);
}
else {
res.status(SERVER_ERROR).send();
}
});
};
// Dynamically determine a "Safe Root Directory"
const getSafeRoot = (): string => {
if (process.platform === "win32") {
return "C:\\Users"; // Restrict access to user directories only
}
return "/home"; // Restrict access to home directories on Linux/macOS
};
export const SAFE_ROOT = getSafeRoot();
export const fetchDirectories = (dir: string, res: express.Response) => {
try {
const resolvedPath = checkPath(dir);
const result: string[] = [];
for (const item of fs.readdirSync(resolvedPath)) {
try {
const itemPath = fs.realpathSync(path.join(resolvedPath, item));
// Ensure itemPath is still inside resolvedPath (protects against symlink attacks)
if (itemPath.startsWith(resolvedPath + path.sep)) {
const stat = fs.statSync(itemPath);
if (stat.isDirectory()) {
result.push(escape(item));
}
}
} catch {
// Ignore errors and continue
}
}
res.status(200).send(result);
} catch {
res.status(500).send({ error: "Failed to fetch directories." });
}
};
// tslint:disable-next-line:cyclomatic-complexity
export const findMatchingFile = (filePath: string, fileNames: string[], expectedFileName: string): string => {
const filesWithParsingError = [];
for (const fileName of fileNames) {
if (isFileExtensionJson(fileName)) {
try {
const data = readFileFromLocal(filePath, fileName);
const parsedData = JSON.parse(data);
if (parsedData) {
if (Array.isArray(parsedData)) { // if parsedData is array, it is using expanded dtdl format
for (const pd of parsedData) {
if (pd['@id']?.toString() === expectedFileName) {
return pd;
}
}
}
else {
if (parsedData['@id']?.toString() === expectedFileName) {
return data;
}
}
}
}
catch (error) {
filesWithParsingError.push(`${fileName}: ${error.message}`); // swallow error and continue the loop
}
}
}
if (filesWithParsingError.length > 0) {
throw new Error(filesWithParsingError.join(', '));
}
return null;
};
const isFileExtensionJson = (fileName: string) => {
const i = fileName.lastIndexOf('.');
return i > 0 && fileName.substr(i) === '.json';
};
export const readFileFromLocal = (filePath: string, fileName: string) => {
// Resolve the requested directory relative to the safe root
const resolvedPath = checkPath(`${filePath}/${fileName}`);
return fs.readFileSync(resolvedPath, 'utf-8');
}
export const checkPath = (filePath: string) => {
// Resolve the requested directory relative to the safe root
const resolvedPath = fs.realpathSync(path.resolve(SAFE_ROOT, path.relative(SAFE_ROOT, filePath)));
// Ensure resolvedPath is still inside SAFE_ROOT (prevents traversal attacks)
if (!resolvedPath.startsWith(SAFE_ROOT)) {
throw new Error("Access denied. Unsafe directory.");
}
return resolvedPath;
}