@geek-fun/serverlessinsight
Version:
Full life cycle cross providers serverless application management for your fast-growing business.
227 lines (226 loc) • 8.27 kB
JavaScript
;
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;