Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^6.6.3",
"stream-buffers": "^3.0.2"
"stream-buffers": "^3.0.2",
"shell-quote": "^1.8.1"
},
"devDependencies": {
"@mikro-orm/cli": "^4.3.3",
Expand Down
4 changes: 3 additions & 1 deletion src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
ApiTags,
} from '@nestjs/swagger';
import { spawn } from 'child_process';
import * as shellQuote from 'shell-quote';
import * as dotT from 'dot';
import { parseXml } from 'libxmljs';
import { AppConfig } from './app.config.api';
Expand Down Expand Up @@ -112,7 +113,8 @@ export class AppController {

return new Promise((res, rej) => {
try {
const [exec, ...args] = command.split(' ');
const parsedCommand = shellQuote.parse(command);
const [exec, ...args] = parsedCommand;
const ps = spawn(exec, args);

ps.stdout.on('data', (data: Buffer) => {
Expand Down
12 changes: 9 additions & 3 deletions src/file/file.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,17 @@ export class FileController {
description: 'save raw content on server as a file',
})
@Put('raw')
async uploadFile(@Query('path') file, @Body() raw: Buffer): Promise<void> {
async uploadFile(@Query('path') filePath: string, @Body() raw: Buffer): Promise<void> {
const SAFE_ROOT = '/safe/root/directory';
try {
if (typeof raw === 'string' || Buffer.isBuffer(raw)) {
await fs.promises.access(path.dirname(file), W_OK);
await fs.promises.writeFile(file, raw);
const resolvedPath = path.resolve(SAFE_ROOT, filePath);
if (!resolvedPath.startsWith(SAFE_ROOT)) {
this.logger.error('Invalid file path');
return;
}
await fs.promises.access(path.dirname(resolvedPath), W_OK);
await fs.promises.writeFile(resolvedPath, raw);
}
} catch (err) {
this.logger.error(err.message);
Expand Down
31 changes: 20 additions & 11 deletions src/file/file.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ export class FileService {
private readonly logger = new Logger(FileService.name);
private cloudProviders = new CloudProvidersMetaData();

private readonly ROOT_DIR = path.resolve(process.cwd(), 'safe_root_directory');

async getFile(file: string): Promise<Stream> {
this.logger.log(`Reading file: ${file}`);

if (file.startsWith('/')) {
await fs.promises.access(file, R_OK);

return fs.createReadStream(file);
file = path.resolve(file);
} else if (file.startsWith('http')) {
const content = this.cloudProviders.get(file);

Expand All @@ -26,23 +26,32 @@ export class FileService {
throw new Error(`no such file or directory, access '${file}'`);
}
} else {
file = path.resolve(process.cwd(), file);

await fs.promises.access(file, R_OK);
file = path.resolve(this.ROOT_DIR, file);
}

return fs.createReadStream(file);
if (!file.startsWith(this.ROOT_DIR)) {
throw new Error('Access to the specified file is not allowed');
}

await fs.promises.access(file, R_OK);

return fs.createReadStream(file);
}

async deleteFile(file: string): Promise<boolean> {
if (file.startsWith('/')) {
throw new Error('cannot delete file from this location');
file = path.resolve(file);
} else if (file.startsWith('http')) {
throw new Error('cannot delete file from this location');
} else {
file = path.resolve(process.cwd(), file);
await fs.promises.unlink(file);
return true;
file = path.resolve(this.ROOT_DIR, file);
}

if (!file.startsWith(this.ROOT_DIR)) {
throw new Error('Access to the specified file is not allowed');
}

await fs.promises.unlink(file);
return true;
}
}
Loading