69 lines
2.3 KiB
JavaScript
69 lines
2.3 KiB
JavaScript
|
|
const http = require('node:http');
|
||
|
|
const fs = require('node:fs');
|
||
|
|
const path = require('node:path');
|
||
|
|
const crypto = require('node:crypto');
|
||
|
|
|
||
|
|
const PORT = process.env.PORT || 3000;
|
||
|
|
const DATA_DIR = path.join(__dirname, 'data');
|
||
|
|
const MAX_SIZE_BYTES = 10 * 1024 * 1024; // 10MB
|
||
|
|
|
||
|
|
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR);
|
||
|
|
|
||
|
|
const sendJSON = (res, status, data) => {
|
||
|
|
res.writeHead(status, { 'Content-Type': 'application/json' });
|
||
|
|
res.end(JSON.stringify(data));
|
||
|
|
};
|
||
|
|
|
||
|
|
const server = http.createServer((req, res) => {
|
||
|
|
const { method, url } = req;
|
||
|
|
|
||
|
|
// Serve Frontend
|
||
|
|
if (method === 'GET' && (url === '/' || url.startsWith('/view'))) {
|
||
|
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
||
|
|
fs.createReadStream(path.join(__dirname, 'index.html')).pipe(res);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// CREATE Secret (Public POST)
|
||
|
|
if (method === 'POST' && url === '/api/secret') {
|
||
|
|
const contentLength = parseInt(req.headers['content-length'], 10);
|
||
|
|
if (contentLength > MAX_SIZE_BYTES) {
|
||
|
|
res.writeHead(413); return res.end('Too large');
|
||
|
|
}
|
||
|
|
|
||
|
|
const id = crypto.randomUUID(); // Unique and Random URL component
|
||
|
|
const filePath = path.join(DATA_DIR, id);
|
||
|
|
const writeStream = fs.createWriteStream(filePath);
|
||
|
|
|
||
|
|
req.pipe(writeStream);
|
||
|
|
req.on('end', () => sendJSON(res, 201, { id }));
|
||
|
|
writeStream.on('error', () => { res.writeHead(500); res.end(); });
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
// READ & BURN Secret
|
||
|
|
if (method === 'GET' && url.startsWith('/api/secret/')) {
|
||
|
|
const id = url.split('/').pop();
|
||
|
|
if (!/^[a-z0-9-]+$/i.test(id)) { res.writeHead(400); return res.end(); }
|
||
|
|
|
||
|
|
const filePath = path.join(DATA_DIR, id);
|
||
|
|
if (!fs.existsSync(filePath)) {
|
||
|
|
res.writeHead(404); return res.end('Burned or Not Found');
|
||
|
|
}
|
||
|
|
|
||
|
|
const readStream = fs.createReadStream(filePath);
|
||
|
|
res.writeHead(200, { 'Content-Type': 'application/octet-stream' });
|
||
|
|
readStream.pipe(res);
|
||
|
|
|
||
|
|
// Delete immediately after stream ends
|
||
|
|
readStream.on('end', () => {
|
||
|
|
fs.unlink(filePath, (err) => { if (err) console.error("Burn failed", err); });
|
||
|
|
});
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
|
||
|
|
res.writeHead(404); res.end();
|
||
|
|
});
|
||
|
|
|
||
|
|
server.listen(PORT, () => console.log(`Public Burn Server: http://localhost:${PORT}`));
|