const express = require('express'); const path = require('path'); const PORT = process.env.PORT || 3000; const PASTE_TTL_HOURS = 3; const PASTE_TTL_MS = PASTE_TTL_HOURS * 60 * 60 * 1000; const pastes = new Map(); const ipLastPaste = new Map(); // Tracks the last paste time for each IP const RATE_LIMIT_MS = 10 * 1000; // 10 seconds rate limit const IP_CLEANUP_INTERVAL = 30 * 60 * 1000; // Clean up IP records every 30 minutes const app = express(); app.use(express.text({ limit: '10mb', type: 'text/plain' })); app.use(express.json()); app.use(express.static('static')); function generateUniqueId() { const timestamp = Date.now().toString(36); const randomPart = Math.random().toString(36).substring(2, 10); return `${timestamp}-${randomPart}`; } app.post('/api/pastes', (req, res) => { const content = req.body; const clientIp = req.ip; const now = Date.now(); // Check rate limit if (ipLastPaste.has(clientIp)) { const lastPasteTime = ipLastPaste.get(clientIp); const timeElapsed = now - lastPasteTime; if (timeElapsed < RATE_LIMIT_MS) { return res.status(429).json({ error: 'Rate limit exceeded. Please wait before creating another paste.', retryAfter: Math.ceil((RATE_LIMIT_MS - timeElapsed) / 1000) }); } } if (!content || typeof content !== 'string' || content.trim() === '') { return res.status(400).json({ error: 'Paste content cannot be empty.' }); } // Calculate paste size in bytes const pasteSize = Buffer.byteLength(content, 'utf8'); const sizeInKB = (pasteSize / 1024).toFixed(2); const id = generateUniqueId(); pastes.set(id, content); ipLastPaste.set(clientIp, now); console.log(`[${new Date().toISOString()}] Paste created with ID: ${id} | Size: ${sizeInKB} KB (${pasteSize} bytes)`); setTimeout(() => { if (pastes.has(id)) { pastes.delete(id); console.log(`[${new Date().toISOString()}] Paste expired and deleted: ${id}`); } }, PASTE_TTL_MS); res.status(201).json({ id: id }); }); app.get('/api/pastes/:id', (req, res) => { const id = req.params.id; if (pastes.has(id)) { const content = pastes.get(id); res.setHeader('Content-Type', 'text/plain; charset=utf-8'); res.status(200).send(content); } else { res.status(404).json({ error: 'Paste not found or has expired.' }); } }); app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // Periodically clean up the IP tracking map to prevent memory leak setInterval(() => { const now = Date.now(); let cleanupCount = 0; // Remove IPs that haven't posted in the last hour (much longer than rate limit) ipLastPaste.forEach((timestamp, ip) => { if (now - timestamp > RATE_LIMIT_MS * 360) { // 1 hour = 360 * 10 seconds ipLastPaste.delete(ip); cleanupCount++; } }); if (cleanupCount > 0) { console.log(`[${new Date().toISOString()}] Cleaned up ${cleanupCount} IP records from rate limiter`); } }, IP_CLEANUP_INTERVAL); app.listen(PORT, () => { console.log(`Paste server listening on port ${PORT}`); console.log(`Pastes will be stored for ${PASTE_TTL_HOURS} hours.`); console.log(`Open http://localhost:${PORT} in your browser to use the application`); }); process.on('SIGINT', () => { console.log('\nShutting down server...'); process.exit(0); });