Skip to content

Commit f7ab225

Browse files
committed
Update LocalBucketController to be compatible with all FileBucket instances
By allowing the bucket to validate & parse the request, we maintain compatibility with the interface instead of assuming a concrete. The urls sent to the bucket to validate here, will always fail S3Bucket validation. So we don't need to worry about this being used in production.
1 parent 337e753 commit f7ab225

File tree

1 file changed

+27
-17
lines changed

1 file changed

+27
-17
lines changed

src/components/file/local-bucket.controller.ts

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,55 +3,65 @@ import {
33
Get,
44
Headers,
55
Put,
6-
Query,
76
Request,
87
Response,
98
} from '@nestjs/common';
109
import { Request as IRequest, Response as IResponse } from 'express';
1110
import { DateTime } from 'luxon';
11+
import { URL } from 'node:url';
1212
import rawBody from 'raw-body';
13-
import { InputException, ServerException } from '../../common';
14-
import { FileBucket, LocalBucket } from './bucket';
13+
import { InputException } from '~/common';
14+
import { FileBucket, InvalidSignedUrlException } from './bucket';
1515

16+
/**
17+
* This fakes S3 web hosting for use with LocalBuckets.
18+
*/
1619
@Controller(LocalBucketController.path)
1720
export class LocalBucketController {
1821
static path = '/local-bucket';
1922

20-
private readonly bucket: LocalBucket | undefined;
21-
constructor(bucket: FileBucket) {
22-
this.bucket = bucket instanceof LocalBucket ? bucket : undefined;
23-
}
23+
constructor(private readonly bucket: FileBucket) {}
2424

2525
@Put()
2626
async upload(
2727
@Headers('content-type') contentType: string,
28-
@Query('signed') signed: string,
2928
@Request() req: IRequest,
3029
) {
31-
if (!this.bucket) {
32-
throw new ServerException('Cannot upload file here');
33-
}
3430
// Chokes on json files because they are parsed with body-parser.
3531
// Need to disable it for this path or create a workaround.
3632
const contents = await rawBody(req);
3733
if (!contents) {
3834
throw new InputException();
3935
}
4036

41-
await this.bucket.upload(signed, {
37+
const url = new URL(`https://localhost${req.url}`);
38+
const parsed = await this.bucket.parseSignedUrl(url);
39+
if (parsed.operation !== 'PutObject') {
40+
throw new InvalidSignedUrlException();
41+
}
42+
await this.bucket.putObject({
43+
Key: parsed.Key,
4244
Body: contents,
4345
ContentType: contentType,
4446
});
47+
4548
return { ok: true };
4649
}
4750

4851
@Get()
49-
async download(@Query('signed') signed: string, @Response() res: IResponse) {
50-
if (!this.bucket) {
51-
throw new ServerException('Cannot download file here');
52+
async download(@Request() req: IRequest, @Response() res: IResponse) {
53+
const url = new URL(`https://localhost${req.url}`);
54+
const { Key, operation, ...rest } = await this.bucket.parseSignedUrl(url);
55+
if (operation !== 'GetObject') {
56+
throw new InvalidSignedUrlException();
5257
}
53-
54-
const out = await this.bucket.download(signed);
58+
const signedParams = Object.fromEntries(
59+
Object.entries(rest).flatMap(([k, v]) =>
60+
v != null ? [[k.replace(/^Response/, ''), v]] : [],
61+
),
62+
);
63+
const bucketObject = await this.bucket.getObject(Key);
64+
const out = { ...bucketObject, ...signedParams };
5565

5666
const headers = {
5767
'Cache-Control': out.CacheControl,

0 commit comments

Comments
 (0)