core: storage: reject . as filename

This commit is contained in:
undefined committed 2022-12-17 06:51:41 +08:00
1 parent bec7137440
commit 86cbc9d802
1 file changed
+23 -22
+23 -22
View File
@@ -60,6 +60,14 @@ export function encodeRFC5987ValueChars(str: string) {
); );
} }
const convertPath = (p: string) => {
p = p.trim();
if (p.includes('..') || p.includes('//') || p.endsWith('/.') || p === '.' || p.includes('/./')) {
throw new Error('Invalid path');
}
return p;
};
class RemoteStorageService { class RemoteStorageService {
public client: S3Client; public client: S3Client;
public error = ''; public error = '';
@@ -113,7 +121,7 @@ class RemoteStorageService {
} }
async put(target: string, file: string | Buffer | Readable, meta: Record<string, string> = {}) { async put(target: string, file: string | Buffer | Readable, meta: Record<string, string> = {}) {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = convertPath(target);
if (typeof file === 'string') file = createReadStream(file); if (typeof file === 'string') file = createReadStream(file);
const params: PutObjectCommandInput = { const params: PutObjectCommandInput = {
Bucket: this.opts.bucket, Bucket: this.opts.bucket,
@@ -138,7 +146,7 @@ class RemoteStorageService {
} }
async get(target: string, path?: string) { async get(target: string, path?: string) {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = convertPath(target);
const res = await this.client.send(new GetObjectCommand({ const res = await this.client.send(new GetObjectCommand({
Bucket: this.opts.bucket, Bucket: this.opts.bucket,
Key: target, Key: target,
@@ -163,9 +171,8 @@ class RemoteStorageService {
} }
async del(target: string | string[]) { async del(target: string | string[]) {
if (typeof target === 'string') { if (typeof target === 'string') target = convertPath(target);
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); else target = target.map(convertPath);
} else if (target.find((t) => t.includes('..') || t.includes('//'))) throw new Error('Invalid path');
if (typeof target === 'string') { if (typeof target === 'string') {
return await this.client.send(new DeleteObjectCommand({ return await this.client.send(new DeleteObjectCommand({
Bucket: this.opts.bucket, Bucket: this.opts.bucket,
@@ -186,7 +193,7 @@ class RemoteStorageService {
} }
async getMeta(target: string) { async getMeta(target: string) {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = convertPath(target);
const res = await this.client.send(new HeadObjectCommand({ const res = await this.client.send(new HeadObjectCommand({
Bucket: this.opts.bucket, Bucket: this.opts.bucket,
Key: target, Key: target,
@@ -200,7 +207,7 @@ class RemoteStorageService {
} }
async signDownloadLink(target: string, filename?: string, noExpire = false, useAlternativeEndpointFor?: 'user' | 'judge'): Promise<string> { async signDownloadLink(target: string, filename?: string, noExpire = false, useAlternativeEndpointFor?: 'user' | 'judge'): Promise<string> {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = convertPath(target);
const url = await getSignedUrl(this.client, new GetObjectCommand({ const url = await getSignedUrl(this.client, new GetObjectCommand({
Bucket: this.opts.bucket, Bucket: this.opts.bucket,
Key: target, Key: target,
@@ -234,8 +241,8 @@ class RemoteStorageService {
} }
async copy(src: string, target: string) { async copy(src: string, target: string) {
if (src.includes('..') || src.includes('//')) throw new Error('Invalid path'); src = convertPath(src);
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = convertPath(target);
return await this.client.send(new CopyObjectCommand({ return await this.client.send(new CopyObjectCommand({
Bucket: this.opts.bucket, Bucket: this.opts.bucket,
Key: target, Key: target,
@@ -262,8 +269,7 @@ class LocalStorageService {
} }
async put(target: string, file: string | Buffer | Readable) { async put(target: string, file: string | Buffer | Readable) {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = resolve(this.dir, convertPath(target));
target = resolve(this.dir, target);
await ensureDir(dirname(target)); await ensureDir(dirname(target));
if (typeof file === 'string') await copyFile(file, target); if (typeof file === 'string') await copyFile(file, target);
else if (file instanceof Buffer) await writeFile(target, file); else if (file instanceof Buffer) await writeFile(target, file);
@@ -271,22 +277,19 @@ class LocalStorageService {
} }
async get(target: string, path?: string) { async get(target: string, path?: string) {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = resolve(this.dir, convertPath(target));
target = resolve(this.dir, target);
if (!existsSync(target)) throw new Error('File not found'); if (!existsSync(target)) throw new Error('File not found');
if (path) await copyFile(target, path); if (path) await copyFile(target, path);
return createReadStream(target); return createReadStream(target);
} }
async del(target: MaybeArray<string>) { async del(target: MaybeArray<string>) {
const targets = typeof target === 'string' ? [target] : target; const targets = (typeof target === 'string' ? [target] : target).map(convertPath);
if (targets.find((i) => i.includes('..') || i.includes('//'))) throw new Error('Invalid path');
await Promise.all(targets.map((i) => remove(resolve(this.dir, i)))); await Promise.all(targets.map((i) => remove(resolve(this.dir, i))));
} }
async getMeta(target: string) { async getMeta(target: string) {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = resolve(this.dir, convertPath(target));
target = resolve(this.dir, target);
const file = await stat(target); const file = await stat(target);
return { return {
size: file.size, size: file.size,
@@ -302,7 +305,7 @@ class LocalStorageService {
} }
async signDownloadLink(target: string, filename = '', noExpire = false, useAlternativeEndpointFor?: 'user' | 'judge'): Promise<string> { async signDownloadLink(target: string, filename = '', noExpire = false, useAlternativeEndpointFor?: 'user' | 'judge'): Promise<string> {
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = convertPath(target);
const url = new URL('https://localhost/storage'); const url = new URL('https://localhost/storage');
url.searchParams.set('target', target); url.searchParams.set('target', target);
if (filename) url.searchParams.set('filename', filename); if (filename) url.searchParams.set('filename', filename);
@@ -322,10 +325,8 @@ class LocalStorageService {
} }
async copy(src: string, target: string) { async copy(src: string, target: string) {
if (src.includes('..') || src.includes('//')) throw new Error('Invalid path'); src = resolve(this.dir, convertPath(src));
if (target.includes('..') || target.includes('//')) throw new Error('Invalid path'); target = resolve(this.dir, convertPath(target));
src = resolve(this.dir, src);
target = resolve(this.dir, target);
await copyFile(src, target); await copyFile(src, target);
return { etag: target, lastModified: new Date() }; return { etag: target, lastModified: new Date() };
} }