131 lines
3.7 KiB
TypeScript
131 lines
3.7 KiB
TypeScript
import * as plugins from '../plugins.js';
|
|
import type { S3Context } from '../classes/context.js';
|
|
|
|
/**
|
|
* Bucket-level operations
|
|
*/
|
|
export class BucketController {
|
|
/**
|
|
* HEAD /:bucket - Check if bucket exists
|
|
*/
|
|
public static async headBucket(
|
|
req: plugins.http.IncomingMessage,
|
|
res: plugins.http.ServerResponse,
|
|
ctx: S3Context,
|
|
params: Record<string, string>
|
|
): Promise<void> {
|
|
const { bucket } = params;
|
|
|
|
if (await ctx.store.bucketExists(bucket)) {
|
|
ctx.status(200).send('');
|
|
} else {
|
|
ctx.throw('NoSuchBucket', 'The specified bucket does not exist');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* PUT /:bucket - Create bucket
|
|
*/
|
|
public static async createBucket(
|
|
req: plugins.http.IncomingMessage,
|
|
res: plugins.http.ServerResponse,
|
|
ctx: S3Context,
|
|
params: Record<string, string>
|
|
): Promise<void> {
|
|
const { bucket } = params;
|
|
|
|
await ctx.store.createBucket(bucket);
|
|
ctx.status(200).send('');
|
|
}
|
|
|
|
/**
|
|
* DELETE /:bucket - Delete bucket
|
|
*/
|
|
public static async deleteBucket(
|
|
req: plugins.http.IncomingMessage,
|
|
res: plugins.http.ServerResponse,
|
|
ctx: S3Context,
|
|
params: Record<string, string>
|
|
): Promise<void> {
|
|
const { bucket } = params;
|
|
|
|
await ctx.store.deleteBucket(bucket);
|
|
ctx.status(204).send('');
|
|
}
|
|
|
|
/**
|
|
* GET /:bucket - List objects
|
|
* Supports both V1 and V2 listing (V2 uses list-type=2 query param)
|
|
*/
|
|
public static async listObjects(
|
|
req: plugins.http.IncomingMessage,
|
|
res: plugins.http.ServerResponse,
|
|
ctx: S3Context,
|
|
params: Record<string, string>
|
|
): Promise<void> {
|
|
const { bucket } = params;
|
|
const isV2 = ctx.query['list-type'] === '2';
|
|
|
|
const result = await ctx.store.listObjects(bucket, {
|
|
prefix: ctx.query.prefix,
|
|
delimiter: ctx.query.delimiter,
|
|
maxKeys: ctx.query['max-keys'] ? parseInt(ctx.query['max-keys']) : 1000,
|
|
continuationToken: ctx.query['continuation-token'],
|
|
});
|
|
|
|
if (isV2) {
|
|
// List Objects V2 response
|
|
await ctx.sendXML({
|
|
ListBucketResult: {
|
|
'@_xmlns': 'http://s3.amazonaws.com/doc/2006-03-01/',
|
|
Name: bucket,
|
|
Prefix: result.prefix || '',
|
|
MaxKeys: result.maxKeys,
|
|
KeyCount: result.contents.length,
|
|
IsTruncated: result.isTruncated,
|
|
...(result.delimiter && { Delimiter: result.delimiter }),
|
|
...(result.nextContinuationToken && {
|
|
NextContinuationToken: result.nextContinuationToken,
|
|
}),
|
|
...(result.commonPrefixes.length > 0 && {
|
|
CommonPrefixes: result.commonPrefixes.map((prefix) => ({
|
|
Prefix: prefix,
|
|
})),
|
|
}),
|
|
Contents: result.contents.map((obj) => ({
|
|
Key: obj.key,
|
|
LastModified: obj.lastModified.toISOString(),
|
|
ETag: `"${obj.md5}"`,
|
|
Size: obj.size,
|
|
StorageClass: 'STANDARD',
|
|
})),
|
|
},
|
|
});
|
|
} else {
|
|
// List Objects V1 response
|
|
await ctx.sendXML({
|
|
ListBucketResult: {
|
|
'@_xmlns': 'http://s3.amazonaws.com/doc/2006-03-01/',
|
|
Name: bucket,
|
|
Prefix: result.prefix || '',
|
|
MaxKeys: result.maxKeys,
|
|
IsTruncated: result.isTruncated,
|
|
...(result.delimiter && { Delimiter: result.delimiter }),
|
|
...(result.commonPrefixes.length > 0 && {
|
|
CommonPrefixes: result.commonPrefixes.map((prefix) => ({
|
|
Prefix: prefix,
|
|
})),
|
|
}),
|
|
Contents: result.contents.map((obj) => ({
|
|
Key: obj.key,
|
|
LastModified: obj.lastModified.toISOString(),
|
|
ETag: `"${obj.md5}"`,
|
|
Size: obj.size,
|
|
StorageClass: 'STANDARD',
|
|
})),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|