API: directory-list: handle empty input case and improve error responses

This commit is contained in:
Jesse Chan
2021-02-15 23:09:12 +08:00
parent 945c593393
commit ad535e2cde
3 changed files with 57 additions and 51 deletions
+53 -11
View File
@@ -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
View File
@@ -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, ''));
};