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 ): Promise { 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 ): Promise { 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 ): Promise { 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 ): Promise { 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', })), }, }); } } }