Skip to content

Commit

Permalink
perf: mediainfo router should use async/await to get file info
Browse files Browse the repository at this point in the history
  • Loading branch information
trim21 committed Jul 26, 2023
1 parent 3b8a66c commit 735ea29
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 8 deletions.
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@
"@types/jsonwebtoken": "^9.0.2",
"@types/lodash": "^4.14.195",
"@types/morgan": "^1.9.4",
"@types/node": "^12.20.55",
"@types/node": "^18.17.1",
"@types/parse-torrent": "^5.8.4",
"@types/passport": "^1.0.12",
"@types/passport-jwt": "^3.0.8",
Expand Down
15 changes: 12 additions & 3 deletions server/routes/api/torrents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import path from 'path';
import rateLimit from 'express-rate-limit';
import sanitize from 'sanitize-filename';
import tar, {Pack} from 'tar-fs';
import * as fsp from 'node:fs/promises';

Check failure on line 10 in server/routes/api/torrents.ts

View workflow job for this annotation

GitHub Actions / check (20, lint)

'fsp' is defined but never used

import type {
AddTorrentByFileOptions,
Expand Down Expand Up @@ -35,9 +36,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 +900,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
8 changes: 8 additions & 0 deletions server/util/async.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export async function asyncFilter<T>(
arr: Array<T>,
predicate: (item: T, index: number) => Promise<boolean>,
): Promise<Array<T>> {
const results = await Promise.all(arr.map(predicate));

return arr.filter((_v, index) => results[index]);
}
41 changes: 41 additions & 0 deletions server/util/fileUtil.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import fs from 'fs';
import {homedir} from 'os';
import path from 'path';
import * as fsp from 'node:fs/promises';

import config from '../../config';

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;

Check warning on line 61 in server/util/fileUtil.ts

View check run for this annotation

Codecov / codecov/patch

server/util/fileUtil.ts#L61

Added line #L61 was not covered by tests
}
}
}

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 {code?: string}).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 735ea29

Please sign in to comment.