mirror of
https://github.com/zoriya/flood.git
synced 2026-06-01 02:29:01 +00:00
API: directory-list: handle empty input case and improve error responses
This commit is contained in:
+53
-11
@@ -1,5 +1,7 @@
|
||||
import express from 'express';
|
||||
import express, {Response} from 'express';
|
||||
import fs from 'fs';
|
||||
import passport from 'passport';
|
||||
import path from 'path';
|
||||
import rateLimit from 'express-rate-limit';
|
||||
|
||||
import {contentTokenSchema} from '@shared/schema/api/torrents';
|
||||
@@ -9,6 +11,7 @@ import type {HistorySnapshot} from '@shared/constants/historySnapshotTypes';
|
||||
import type {NotificationFetchOptions} from '@shared/types/Notification';
|
||||
import type {SetFloodSettingsOptions} from '@shared/types/api/index';
|
||||
|
||||
import {accessDeniedError, fileNotFoundError, isAllowedPath, sanitizePath} from '../../util/fileUtil';
|
||||
import appendUserServices from '../../middleware/appendUserServices';
|
||||
import authRoutes from './auth';
|
||||
import clientRoutes from './client';
|
||||
@@ -16,7 +19,6 @@ import clientActivityStream from '../../middleware/clientActivityStream';
|
||||
import eventStream from '../../middleware/eventStream';
|
||||
import feedMonitorRoutes from './feed-monitor';
|
||||
import {getAuthToken, verifyToken} from '../../util/authUtil';
|
||||
import {getDirectoryList} from '../../util/fileUtil';
|
||||
import {getResponseFn} from '../../util/ajaxUtil';
|
||||
import torrentsRoutes from './torrents';
|
||||
|
||||
@@ -88,16 +90,56 @@ router.use('/torrents', torrentsRoutes);
|
||||
*/
|
||||
router.get('/activity-stream', eventStream, clientActivityStream);
|
||||
|
||||
router.get<unknown, unknown, unknown, {path: string}>('/directory-list', (req, res) => {
|
||||
const callback = getResponseFn(res);
|
||||
getDirectoryList(req.query.path)
|
||||
.then((data) => {
|
||||
callback(data);
|
||||
})
|
||||
.catch((error) => {
|
||||
callback(null, error);
|
||||
/**
|
||||
* GET /api/directory-list
|
||||
* @summary Lists a directory
|
||||
* @tags Flood
|
||||
* @security User
|
||||
* @return {object} 200 - success response - application/json
|
||||
* @return {Error} 403 - access denied - application/json
|
||||
* @return {Error} 404 - entity not found - application/json
|
||||
*/
|
||||
router.get<unknown, unknown, unknown, {path: string}>(
|
||||
'/directory-list',
|
||||
(req, res): Response<unknown> => {
|
||||
const {path: inputPath} = req.query;
|
||||
|
||||
if (typeof inputPath !== 'string' || !inputPath) {
|
||||
const {code, message} = fileNotFoundError();
|
||||
return res.status(404).json({code, message});
|
||||
}
|
||||
|
||||
const resolvedPath = sanitizePath(inputPath);
|
||||
if (!isAllowedPath(resolvedPath)) {
|
||||
const {code, message} = accessDeniedError();
|
||||
return res.status(403).json({code, message});
|
||||
}
|
||||
|
||||
const directories: Array<string> = [];
|
||||
const files: Array<string> = [];
|
||||
|
||||
fs.readdirSync(resolvedPath).forEach((item) => {
|
||||
const joinedPath = path.join(resolvedPath, item);
|
||||
if (fs.existsSync(joinedPath)) {
|
||||
if (fs.statSync(joinedPath).isDirectory()) {
|
||||
directories.push(item);
|
||||
} else {
|
||||
files.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const hasParent = /^.{0,}:?(\/|\\){1,1}\S{1,}/.test(resolvedPath);
|
||||
|
||||
return res.status(200).json({
|
||||
directories,
|
||||
files,
|
||||
hasParent,
|
||||
path: resolvedPath,
|
||||
separator: path.sep,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* GET /api/history
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {homedir} from 'os';
|
||||
import {sanitizePath} from '../../util/fileUtil';
|
||||
|
||||
import type {RTorrentConnectionSettings} from '@shared/schema/ClientConnectionSettings';
|
||||
|
||||
@@ -23,7 +23,7 @@ class ClientRequestManager {
|
||||
if (connectionSettings.type === 'socket') {
|
||||
this.connectionSettings = {
|
||||
...connectionSettings,
|
||||
socket: connectionSettings.socket.replace(/^~/, homedir()),
|
||||
socket: sanitizePath(connectionSettings.socket),
|
||||
};
|
||||
} else {
|
||||
this.connectionSettings = connectionSettings;
|
||||
|
||||
+2
-38
@@ -50,42 +50,6 @@ export const sanitizePath = (input?: string): string => {
|
||||
|
||||
// eslint-disable-next-line no-control-regex
|
||||
const controlRe = /[\x00-\x1f\x80-\x9f]/g;
|
||||
return path.resolve(input).replace(controlRe, '');
|
||||
};
|
||||
|
||||
export const getDirectoryList = async (inputPath: string) => {
|
||||
if (typeof inputPath !== 'string') {
|
||||
throw fileNotFoundError();
|
||||
}
|
||||
|
||||
const sourcePath = inputPath.replace(/^~/, homedir());
|
||||
|
||||
const resolvedPath = sanitizePath(sourcePath);
|
||||
if (!isAllowedPath(resolvedPath)) {
|
||||
throw accessDeniedError();
|
||||
}
|
||||
|
||||
const directories: Array<string> = [];
|
||||
const files: Array<string> = [];
|
||||
|
||||
fs.readdirSync(resolvedPath).forEach((item) => {
|
||||
const joinedPath = path.join(resolvedPath, item);
|
||||
if (fs.existsSync(joinedPath)) {
|
||||
if (fs.statSync(joinedPath).isDirectory()) {
|
||||
directories.push(item);
|
||||
} else {
|
||||
files.push(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const hasParent = /^.{0,}:?(\/|\\){1,1}\S{1,}/.test(resolvedPath);
|
||||
|
||||
return {
|
||||
directories,
|
||||
files,
|
||||
hasParent,
|
||||
path: resolvedPath,
|
||||
separator: path.sep,
|
||||
};
|
||||
|
||||
return path.resolve(input.replace(/^~/, homedir()).replace(controlRe, ''));
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user