import * as plugins from '../plugins.js'; import { S3Error } from './s3-error.js'; import { createXml } from '../utils/xml.utils.js'; import type { FilesystemStore } from './filesystem-store.js'; import type { Readable } from 'stream'; /** * S3 request context with helper methods */ export class S3Context { public method: string; public url: URL; public headers: plugins.http.IncomingHttpHeaders; public params: Record = {}; public query: Record = {}; public store: FilesystemStore; private req: plugins.http.IncomingMessage; private res: plugins.http.ServerResponse; private statusCode: number = 200; private responseHeaders: Record = {}; constructor( req: plugins.http.IncomingMessage, res: plugins.http.ServerResponse, store: FilesystemStore ) { this.req = req; this.res = res; this.store = store; this.method = req.method || 'GET'; this.headers = req.headers; // Parse URL and query string const fullUrl = `http://${req.headers.host || 'localhost'}${req.url || '/'}`; this.url = new URL(fullUrl); // Parse query string into object this.url.searchParams.forEach((value, key) => { this.query[key] = value; }); } /** * Set response status code */ public status(code: number): this { this.statusCode = code; return this; } /** * Set response header */ public setHeader(name: string, value: string | number): this { this.responseHeaders[name] = value.toString(); return this; } /** * Send response body (string, Buffer, or Stream) */ public async send(body: string | Buffer | Readable | NodeJS.ReadableStream): Promise { // Write status and headers this.res.writeHead(this.statusCode, this.responseHeaders); // Handle different body types if (typeof body === 'string' || body instanceof Buffer) { this.res.end(body); } else if (body && typeof (body as any).pipe === 'function') { // It's a stream (body as Readable).pipe(this.res); } else { this.res.end(); } } /** * Send XML response */ public async sendXML(obj: any): Promise { const xml = createXml(obj, { format: true }); this.setHeader('Content-Type', 'application/xml'); this.setHeader('Content-Length', Buffer.byteLength(xml)); await this.send(xml); } /** * Throw an S3 error */ public throw(code: string, message: string, detail?: Record): never { throw new S3Error(code, message, detail); } /** * Read and parse request body as string */ public async readBody(): Promise { return new Promise((resolve, reject) => { const chunks: Buffer[] = []; this.req.on('data', (chunk) => chunks.push(chunk)); this.req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))); this.req.on('error', reject); }); } /** * Get the request stream (for streaming uploads) */ public getRequestStream(): NodeJS.ReadableStream { return this.req; } }