|
|
|
@ -3,6 +3,7 @@ import path from 'path';
|
|
|
|
|
import { promisify } from 'util'; |
|
|
|
|
import mkdirp from 'mkdirp'; |
|
|
|
|
import axios from 'axios'; |
|
|
|
|
import { NcError } from '../../helpers/catchError'; |
|
|
|
|
import { getToolDir } from '../../utils/nc-config'; |
|
|
|
|
import type { IStorageAdapterV2, XcFile } from 'nc-plugin'; |
|
|
|
|
import type { Readable } from 'stream'; |
|
|
|
@ -11,7 +12,7 @@ export default class Local implements IStorageAdapterV2 {
|
|
|
|
|
constructor() {} |
|
|
|
|
|
|
|
|
|
public async fileCreate(key: string, file: XcFile): Promise<any> { |
|
|
|
|
const destPath = path.join(getToolDir(), ...key.split('/')); |
|
|
|
|
const destPath = this.validateAndNormalisePath(key); |
|
|
|
|
try { |
|
|
|
|
await mkdirp(path.dirname(destPath)); |
|
|
|
|
const data = await promisify(fs.readFile)(file.path); |
|
|
|
@ -24,7 +25,7 @@ export default class Local implements IStorageAdapterV2 {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
async fileCreateByUrl(key: string, url: string): Promise<any> { |
|
|
|
|
const destPath = path.join(getToolDir(), ...key.split('/')); |
|
|
|
|
const destPath = this.validateAndNormalisePath(key); |
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
axios |
|
|
|
|
.get(url, { |
|
|
|
@ -71,7 +72,7 @@ export default class Local implements IStorageAdapterV2 {
|
|
|
|
|
stream: Readable, |
|
|
|
|
): Promise<void> { |
|
|
|
|
return new Promise((resolve, reject) => { |
|
|
|
|
const destPath = path.join(getToolDir(), ...key.split('/')); |
|
|
|
|
const destPath = this.validateAndNormalisePath(key); |
|
|
|
|
try { |
|
|
|
|
mkdirp(path.dirname(destPath)).then(() => { |
|
|
|
|
const writableStream = fs.createWriteStream(destPath); |
|
|
|
@ -86,12 +87,12 @@ export default class Local implements IStorageAdapterV2 {
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public async fileReadByStream(key: string): Promise<Readable> { |
|
|
|
|
const srcPath = path.join(getToolDir(), ...key.split('/')); |
|
|
|
|
const srcPath = this.validateAndNormalisePath(key); |
|
|
|
|
return fs.createReadStream(srcPath, { encoding: 'utf8' }); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public async getDirectoryList(key: string): Promise<string[]> { |
|
|
|
|
const destDir = path.join(getToolDir(), ...key.split('/')); |
|
|
|
|
const destDir = this.validateAndNormalisePath(key); |
|
|
|
|
return fs.promises.readdir(destDir); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
@ -103,7 +104,7 @@ export default class Local implements IStorageAdapterV2 {
|
|
|
|
|
public async fileRead(filePath: string): Promise<any> { |
|
|
|
|
try { |
|
|
|
|
const fileData = await fs.promises.readFile( |
|
|
|
|
path.join(getToolDir(), ...filePath.split('/')), |
|
|
|
|
this.validateAndNormalisePath(filePath, true), |
|
|
|
|
); |
|
|
|
|
return fileData; |
|
|
|
|
} catch (e) { |
|
|
|
@ -118,4 +119,29 @@ export default class Local implements IStorageAdapterV2 {
|
|
|
|
|
test(): Promise<boolean> { |
|
|
|
|
return Promise.resolve(false); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// method for validate/normalise the path for avoid path traversal attack
|
|
|
|
|
protected validateAndNormalisePath( |
|
|
|
|
fileOrFolderPath: string, |
|
|
|
|
throw404 = false, |
|
|
|
|
): string { |
|
|
|
|
// Get the absolute path to the base directory
|
|
|
|
|
const absoluteBasePath = path.resolve(getToolDir(), 'nc'); |
|
|
|
|
|
|
|
|
|
// Get the absolute path to the file
|
|
|
|
|
const absolutePath = path.resolve( |
|
|
|
|
path.join(getToolDir(), ...fileOrFolderPath.split('/')), |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
// Check if the resolved path is within the intended directory
|
|
|
|
|
if (!absolutePath.startsWith(absoluteBasePath)) { |
|
|
|
|
if (throw404) { |
|
|
|
|
NcError.notFound(); |
|
|
|
|
} else { |
|
|
|
|
NcError.badRequest('Invalid path'); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return absolutePath; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|