UNPKG

@geek-fun/serverlessinsight

Version:

Full life cycle cross providers serverless application management for your fast-growing business.

227 lines (226 loc) 8.27 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.bucketsHandler = void 0; const common_1 = require("../../common"); const node_path_1 = __importDefault(require("node:path")); const node_fs_1 = __importDefault(require("node:fs")); const TEXT_MIME_TYPES = new Set([ 'text/plain', 'text/html', 'text/css', 'text/javascript', 'text/markdown', 'text/xml', 'application/json', 'application/javascript', 'application/xml', ]); const getMimeType = (filename) => { const ext = node_path_1.default.extname(filename).toLowerCase(); const mimeTypes = { '.html': 'text/html', '.htm': 'text/html', '.css': 'text/css', '.js': 'application/javascript', '.json': 'application/json', '.xml': 'application/xml', '.txt': 'text/plain', '.md': 'text/markdown', '.jpg': 'image/jpeg', '.jpeg': 'image/jpeg', '.png': 'image/png', '.gif': 'image/gif', '.svg': 'image/svg+xml', '.ico': 'image/x-icon', '.pdf': 'application/pdf', '.zip': 'application/zip', '.tar': 'application/x-tar', '.gz': 'application/gzip', }; return mimeTypes[ext] || 'application/octet-stream'; }; const listDirectory = (dirPath, bucketPath) => { try { const entries = node_fs_1.default.readdirSync(dirPath, { withFileTypes: true }); return entries.map((entry) => { const fullPath = node_path_1.default.join(dirPath, entry.name); const relativePath = node_path_1.default.relative(bucketPath, fullPath); const stats = node_fs_1.default.statSync(fullPath); return { name: entry.name, type: entry.isDirectory() ? 'directory' : 'file', size: entry.isDirectory() ? 0 : stats.size, path: relativePath.replace(/\\/g, '/'), // Normalize path separators }; }); } catch (error) { common_1.logger.error(`Error listing directory: ${error}`); return []; } }; const getAllFiles = (dirPath, bucketPath, fileList = []) => { try { const entries = node_fs_1.default.readdirSync(dirPath, { withFileTypes: true }); for (const entry of entries) { const fullPath = node_path_1.default.join(dirPath, entry.name); const relativePath = node_path_1.default.relative(bucketPath, fullPath); if (entry.isDirectory()) { // Recursively list files in subdirectories getAllFiles(fullPath, bucketPath, fileList); } else { const stats = node_fs_1.default.statSync(fullPath); fileList.push({ name: entry.name, type: 'file', size: stats.size, path: relativePath.replace(/\\/g, '/'), }); } } return fileList; } catch (error) { common_1.logger.error(`Error getting all files: ${error}`); return fileList; } }; const bucketsHandler = async (req, parsed, iac) => { common_1.logger.info(`Bucket request received by local server -> ${req.method} ${parsed.identifier ?? '/'} ${parsed.url}`); // Find the bucket definition const bucketDef = iac.buckets?.find((bucket) => bucket.key === parsed.identifier); if (!bucketDef) { return { statusCode: 404, body: { error: 'Bucket not found', bucketKey: parsed.identifier }, }; } // Determine the bucket path - use website.code if available, otherwise use bucket name let bucketBasePath; if (bucketDef.website?.code) { bucketBasePath = node_path_1.default.resolve(process.cwd(), bucketDef.website.code); } else { // Fallback: create a directory based on bucket name (for non-website buckets) bucketBasePath = node_path_1.default.resolve(process.cwd(), bucketDef.name); } // Check if bucket path exists if (!node_fs_1.default.existsSync(bucketBasePath)) { return { statusCode: 404, body: { error: 'Bucket directory not found', bucketKey: bucketDef.key, bucketName: bucketDef.name, expectedPath: bucketBasePath, }, }; } // Root path lists all files in bucket if (parsed.url === '/') { try { const files = getAllFiles(bucketBasePath, bucketBasePath); return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: { bucket: bucketDef.name, bucketKey: bucketDef.key, files, count: files.length, }, }; } catch (error) { common_1.logger.error(`Error listing bucket files: ${error}`); return { statusCode: 500, body: { error: 'Failed to list bucket files', message: error instanceof Error ? error.message : String(error), }, }; } } // Otherwise, serve the requested file const requestedPath = parsed.url.startsWith('/') ? parsed.url.slice(1) : parsed.url; const filePath = node_path_1.default.join(bucketBasePath, requestedPath); // Security check: ensure the requested file is within the bucket directory const normalizedFilePath = node_path_1.default.normalize(filePath); const normalizedBucketPath = node_path_1.default.normalize(bucketBasePath); if (!normalizedFilePath.startsWith(normalizedBucketPath)) { return { statusCode: 403, body: { error: 'Access denied: Path traversal attempt detected' }, }; } // Check if file exists if (!node_fs_1.default.existsSync(filePath)) { return { statusCode: 404, body: { error: 'File not found', path: requestedPath, bucketKey: bucketDef.key, }, }; } // If it's a directory, list its contents const stats = node_fs_1.default.statSync(filePath); if (stats.isDirectory()) { try { const files = listDirectory(filePath, bucketBasePath); return { statusCode: 200, headers: { 'Content-Type': 'application/json' }, body: { bucket: bucketDef.name, bucketKey: bucketDef.key, directory: requestedPath, files, count: files.length, }, }; } catch (error) { common_1.logger.error(`Error listing directory: ${error}`); return { statusCode: 500, body: { error: 'Failed to list directory', message: error instanceof Error ? error.message : String(error), }, }; } } // Serve the file try { const fileContent = node_fs_1.default.readFileSync(filePath); const mimeType = getMimeType(filePath); // For text files, return as string; for binary files, return as base64 const isTextFile = TEXT_MIME_TYPES.has(mimeType); return { statusCode: 200, headers: { 'Content-Type': mimeType, 'Content-Length': String(fileContent.length), }, body: isTextFile ? fileContent.toString('utf-8') : fileContent.toString('base64'), }; } catch (error) { common_1.logger.error(`Error reading file: ${error}`); return { statusCode: 500, body: { error: 'Failed to read file', message: error instanceof Error ? error.message : String(error), }, }; } }; exports.bucketsHandler = bucketsHandler;