mirror of
https://github.com/zoriya/flood.git
synced 2026-06-01 02:29:01 +00:00
server: allow restriction on file operations by paths
Bug: Flood-UI/flood#588
This commit is contained in:
@@ -68,6 +68,10 @@ const {argv} = require('yargs')
|
||||
hidden: true,
|
||||
type: 'string',
|
||||
})
|
||||
.option('allowedpath', {
|
||||
describe: 'Allowed path for file operations, can be called multiple times',
|
||||
type: 'string',
|
||||
})
|
||||
.option('dbclean', {
|
||||
default: 1000 * 60 * 60,
|
||||
describe: 'ADVANCED: Interval between database purge',
|
||||
@@ -144,6 +148,7 @@ const CONFIG = {
|
||||
ssl: argv.ssl,
|
||||
sslKey: argv.sslkey || path.resolve(path.join(argv.rundir, 'key.pem')),
|
||||
sslCert: argv.sslcert || path.resolve(path.join(argv.rundir, 'fullchain.pem')),
|
||||
allowedPaths: argv.allowedpath ? [].concat(argv.allowedpath) : null,
|
||||
};
|
||||
|
||||
module.exports = CONFIG;
|
||||
|
||||
+3
-1
@@ -67,7 +67,9 @@ const CONFIG = {
|
||||
// watchMountPoints: [
|
||||
// "/mnt/disk"
|
||||
// ]
|
||||
}
|
||||
},
|
||||
// Allowed paths for file operations
|
||||
// allowedPaths: ['/mnt/download', '/data/download'],
|
||||
};
|
||||
// Do not remove the below line.
|
||||
module.exports = CONFIG;
|
||||
|
||||
@@ -67,17 +67,6 @@ class ClientRequest {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Move this to util, doesn't belong here
|
||||
createDirectory(options) {
|
||||
if (options.path) {
|
||||
fs.mkdir(options.path, {recursive: true}, (error) => {
|
||||
if (error) {
|
||||
console.trace('Error creating directory.', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
clearRequestQueue() {
|
||||
this.requests = [];
|
||||
}
|
||||
|
||||
@@ -2,15 +2,23 @@ const fs = require('fs');
|
||||
const os = require('os');
|
||||
const path = require('path');
|
||||
|
||||
const fileUtil = require('../util/fileUtil');
|
||||
|
||||
const getDirectoryList = (options, callback) => {
|
||||
const sourcePath = (options.path || '/').replace(/^~/, os.homedir());
|
||||
|
||||
const resolvedPath = path.resolve(sourcePath);
|
||||
if (!fileUtil.isAllowedPath(resolvedPath)) {
|
||||
callback(null, fileUtil.accessDeniedError());
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const directories = [];
|
||||
const files = [];
|
||||
|
||||
fs.readdirSync(sourcePath).forEach((item) => {
|
||||
const joinedPath = path.join(sourcePath, item);
|
||||
fs.readdirSync(resolvedPath).forEach((item) => {
|
||||
const joinedPath = path.join(resolvedPath, item);
|
||||
if (fs.existsSync(joinedPath)) {
|
||||
if (fs.statSync(joinedPath).isDirectory()) {
|
||||
directories.push(item);
|
||||
@@ -20,13 +28,13 @@ const getDirectoryList = (options, callback) => {
|
||||
}
|
||||
});
|
||||
|
||||
const hasParent = /^.{0,}:?(\/|\\){1,1}\S{1,}/.test(sourcePath);
|
||||
const hasParent = /^.{0,}:?(\/|\\){1,1}\S{1,}/.test(resolvedPath);
|
||||
|
||||
callback({
|
||||
directories,
|
||||
files,
|
||||
hasParent,
|
||||
path: sourcePath,
|
||||
path: resolvedPath,
|
||||
separator: path.sep,
|
||||
});
|
||||
} catch (error) {
|
||||
|
||||
+24
-6
@@ -6,6 +6,7 @@ const tar = require('tar-stream');
|
||||
const ClientRequest = require('./ClientRequest');
|
||||
const clientResponseUtil = require('../util/clientResponseUtil');
|
||||
const clientSettingsMap = require('../../shared/constants/clientSettingsMap');
|
||||
const fileUtil = require('../util/fileUtil');
|
||||
const settings = require('./settings');
|
||||
const torrentFilePropsMap = require('../../shared/constants/torrentFilePropsMap');
|
||||
const torrentPeerPropsMap = require('../../shared/constants/torrentPeerPropsMap');
|
||||
@@ -23,7 +24,13 @@ const client = {
|
||||
tags = tags.split(',');
|
||||
}
|
||||
|
||||
request.createDirectory({path: destinationPath});
|
||||
const resolvedPath = path.resolve(destinationPath);
|
||||
if (!fileUtil.isAllowedPath(resolvedPath)) {
|
||||
callback(null, fileUtil.accessDeniedError());
|
||||
return;
|
||||
}
|
||||
|
||||
fileUtil.createDirectory({path: resolvedPath});
|
||||
request.send();
|
||||
|
||||
// Each torrent is sent individually because rTorrent accepts a total
|
||||
@@ -35,7 +42,7 @@ const client = {
|
||||
const fileRequest = new ClientRequest(user, services);
|
||||
fileRequest.addFiles({
|
||||
files: file,
|
||||
path: destinationPath,
|
||||
path: resolvedPath,
|
||||
isBasePath,
|
||||
start,
|
||||
tags,
|
||||
@@ -58,10 +65,15 @@ const client = {
|
||||
addUrls(user, services, data, callback) {
|
||||
const {urls, destination, isBasePath, start, tags} = data;
|
||||
const request = new ClientRequest(user, services);
|
||||
request.createDirectory({path: destination});
|
||||
const resolvedPath = path.resolve(destination);
|
||||
if (!fileUtil.isAllowedPath(resolvedPath)) {
|
||||
callback(null, fileUtil.accessDeniedError());
|
||||
return;
|
||||
}
|
||||
fileUtil.createDirectory({path: resolvedPath});
|
||||
request.addURLs({
|
||||
urls,
|
||||
path: destination,
|
||||
path: resolvedPath,
|
||||
isBasePath,
|
||||
start,
|
||||
tags,
|
||||
@@ -230,6 +242,12 @@ const client = {
|
||||
const {isBasePath, hashes, filenames, moveFiles, sourcePaths, isCheckHash} = data;
|
||||
const mainRequest = new ClientRequest(user, services);
|
||||
|
||||
const resolvedPath = path.resolve(destinationPath);
|
||||
if (!fileUtil.isAllowedPath(resolvedPath)) {
|
||||
callback(null, fileUtil.accessDeniedError());
|
||||
return;
|
||||
}
|
||||
|
||||
const hashesToRestart = hashes.filter(
|
||||
(hash) => !services.torrentService.getTorrent(hash).status.includes(torrentStatusMap.stopped),
|
||||
);
|
||||
@@ -266,7 +284,7 @@ const client = {
|
||||
moveTorrentsRequest.moveTorrents({
|
||||
filenames,
|
||||
sourcePaths,
|
||||
destinationPath,
|
||||
resolvedPath,
|
||||
});
|
||||
};
|
||||
|
||||
@@ -277,7 +295,7 @@ const client = {
|
||||
}
|
||||
|
||||
mainRequest.stopTorrents({hashes});
|
||||
mainRequest.setDownloadPath({hashes, path: destinationPath, isBasePath});
|
||||
mainRequest.setDownloadPath({hashes, path: resolvedPath, isBasePath});
|
||||
mainRequest.onComplete(afterSetPath);
|
||||
mainRequest.send();
|
||||
},
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
const fs = require('fs');
|
||||
|
||||
const config = require('../../config');
|
||||
|
||||
const createDirectory = (options) => {
|
||||
if (options.path) {
|
||||
fs.mkdir(options.path, {recursive: true}, (error) => {
|
||||
if (error) {
|
||||
console.trace('Error creating directory.', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const isAllowedPath = (resolvedPath) => {
|
||||
if (config.allowedPaths == null) {
|
||||
return true;
|
||||
}
|
||||
return config.allowedPaths.some((allowedPath) => {
|
||||
if (resolvedPath.startsWith(allowedPath)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
const accessDeniedError = () => {
|
||||
const error = new Error();
|
||||
error.code = 'EACCES';
|
||||
return error;
|
||||
};
|
||||
|
||||
const fileUtil = {
|
||||
createDirectory,
|
||||
isAllowedPath,
|
||||
accessDeniedError,
|
||||
};
|
||||
|
||||
module.exports = fileUtil;
|
||||
Reference in New Issue
Block a user