Skip to content

Commit

Permalink
perf: mediainfo router should use async/await to get file info (#656)
Browse files Browse the repository at this point in the history
  • Loading branch information
trim21 authored Nov 29, 2023
1 parent 572ce1e commit 713b1d9
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 3 deletions.
14 changes: 11 additions & 3 deletions server/routes/api/torrents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@ import {
reannounceTorrentsSchema,
setTorrentsTagsSchema,
} from '../../../shared/schema/api/torrents';
import {accessDeniedError, fileNotFoundError, isAllowedPath, sanitizePath} from '../../util/fileUtil';
import {
accessDeniedError,
existAsync,
fileNotFoundError,
isAllowedPath,
isAllowedPathAsync,
sanitizePath,
} from '../../util/fileUtil';
import {getTempPath} from '../../models/TemporaryStorage';
import {getToken} from '../../util/authUtil';
import {asyncFilter} from '../../util/async';

const getDestination = async (
services: Express.Request['services'],
Expand Down Expand Up @@ -891,13 +899,13 @@ router.get<{hash: string}>(
sanitizePath(path.join(torrentDirectory, content.path)),
);

torrentContentPaths = torrentContentPaths.filter((contentPath) => isAllowedPath(contentPath));
torrentContentPaths = await asyncFilter(torrentContentPaths, (contentPath) => isAllowedPathAsync(contentPath));
if (torrentContentPaths.length < 1) {
const {code, message} = accessDeniedError();
return res.status(403).json({code, message});
}

torrentContentPaths = torrentContentPaths.filter((contentPath) => fs.existsSync(contentPath));
torrentContentPaths = await asyncFilter(torrentContentPaths, (contentPath) => existAsync(contentPath));
if (torrentContentPaths.length < 1) {
const {code, message} = fileNotFoundError();
return res.status(404).json({code, message});
Expand Down
14 changes: 14 additions & 0 deletions server/util/async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export async function asyncFilter<T>(
array: Array<T>,
predicate: (item: T, index: number) => Promise<boolean>,
): Promise<Array<T>> {
const results: T[] = [];

for (const [index, item] of array.entries()) {
if (await predicate(item, index)) {
results.push(item);
}
}

return results;
}
41 changes: 41 additions & 0 deletions server/util/fileUtil.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import fs from 'fs';
import {promises as fsp} from 'fs';
import {homedir} from 'os';
import path from 'path';

Expand Down Expand Up @@ -43,6 +44,46 @@ export const isAllowedPath = (resolvedPath: string) => {
});
};

export async function isAllowedPathAsync(resolvedPath: string) {
if (config.allowedPaths == null) {
return true;
}

let realPath: string | null = null;
let parentPath: string = resolvedPath;
while (realPath == null) {
try {
realPath = await fsp.realpath(parentPath);
} catch (e) {
if ((e as NodeJS.ErrnoException).code === 'ENOENT') {
parentPath = path.resolve(parentPath, '..');
} else {
return false;
}
}
}

return config.allowedPaths.some((allowedPath) => {
if (realPath?.startsWith(allowedPath)) {
return true;
}
return false;
});
}

export async function existAsync(path: string): Promise<boolean> {
try {
await fsp.stat(path);
} catch (err: unknown) {
if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
return false;
}
throw err;
}

return true;
}

export const sanitizePath = (input?: string): string => {
if (typeof input !== 'string') {
throw accessDeniedError();
Expand Down

0 comments on commit 713b1d9

Please sign in to comment.