UNPKG

holesail-file-system

Version:

A P2P based node package to expose your files access on the Holepunch protocol

403 lines (379 loc) 15.1 kB
#!/usr/bin/env node const DHT = require('holesail-server') //require module to start server on local port const goodbye = require('graceful-goodbye') const HyperDHT = require('hyperdht') const argv = require('minimist')(process.argv.slice(2)) //required to parse cli arguments const { createHash } = require('crypto'); //for connectors //setting up the command hierarchy if (argv.help) { const help = require('./includes/help.js'); help.printHelp(help.helpMessage); process.exit(-1) } const localServer = new DHT(); if (argv.live) { const http = require('http'); const fs = require('fs'); const path = require('path'); const qs = require('querystring'); const server = http.createServer((req, res) => { const basePath = '.'; // Base directory to list directories const urlPath = decodeURIComponent(req.url); const fullPath = path.join(basePath, urlPath); if (req.method === 'GET') { // Handle GET requests fs.stat(fullPath, (err, stats) => { if (err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Internal Server Error'); return; } if (stats.isDirectory()) { fs.readdir(fullPath, { withFileTypes: true }, (err, files) => { if (err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Internal Server Error'); return; } const directoryList = files .map(file => { const filePath = path.join(urlPath, file.name); const downloadButton = `<a href="${filePath}" download>Download</a>`; const deleteButton = `<button class="btn btn-danger" onclick="deleteFile('${file.name}')">Delete</button>`; return `<tr><td><a href="${filePath}">${file.name}</a></td><td>${downloadButton} ${deleteButton}</td></tr>`; }) .join(''); const htmlResponse = ` <!DOCTYPE html> <html> <head> <title>Directory Listing: ${urlPath}</title> <style> body { font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0; } h1 { color: #333; } table { width: 100%; border-collapse: collapse; margin-bottom: 20px; } th, td { padding: 8px; text-align: left; border-bottom: 1px solid #ddd; } th { background-color: #f2f2f2; } tr:hover { background-color: #f5f5f5; } .btn { background-color: #4CAF50; border: none; color: white; padding: 6px 12px; text-align: center; text-decoration: none; display: inline-block; font-size: 14px; margin: 2px 0; cursor: pointer; border-radius: 4px; } .btn-danger { background-color: #f44336; } .btn:hover { background-color: #45a049; } </style> </head> <body> <h1>Directory Listing: ${urlPath}</h1> <table> <tr> <th>Name</th> <th>Actions</th> </tr> ${directoryList} </table> <form method="POST" action="${urlPath}"> <label for="item_type">New Item Type:</label> <select name="item_type" id="item_type"> <option value="folder">Folder</option> <option value="file">File</option> </select> <label for="name">Name:</label> <input type="text" name="name" id="name" required> <label for="directory">Select Directory:</label> <select name="directory" id="directory"> ${getDirectoryOptions(basePath)} <option value="." selected>Current Directory</option> </select> <button class="btn" type="submit">Create</button> </form> <script> function deleteFile(fileName) { if (confirm("Are you sure you want to delete '" + fileName + "'?")) { window.location.href = "${urlPath}?delete_file=" + encodeURIComponent(fileName); } } </script> </body> </html> `; res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(htmlResponse); }); } else if (stats.isFile()) { const extension = path.extname(fullPath).toLowerCase(); const contentType = getContentType(extension); if (contentType) { fs.readFile(fullPath, (err, data) => { if (err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Error reading file.'); return; } res.writeHead(200, { 'Content-Type': contentType }); res.end(data); }); } else { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Unsupported file type.'); } } }); } else if (req.method === 'POST') { // Handle POST requests for creating items and deleting files let body = ''; req.on('data', chunk => { body += chunk.toString(); }); req.on('end', () => { const formData = qs.parse(body); const item_type = formData['item_type']; const name = formData['name']; const directory = formData['directory']; const newFullPath = path.join(basePath, directory, name); if (item_type === 'folder') { fs.mkdir(newFullPath, err => { if (err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Error creating folder.'); return; } res.writeHead(302, { 'Location': urlPath }); res.end(); }); } else if (item_type === 'file') { fs.writeFile(newFullPath, '', err => { if (err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Error creating file.'); return; } res.writeHead(302, { 'Location': urlPath }); res.end(); }); } else if (req.url.includes('?delete_file=')) { const deleteFileName = qs.parse(req.url.split('?')[1])['delete_file']; if (!deleteFileName) { res.writeHead(400, { 'Content-Type': 'text/plain' }); res.end('Invalid delete request: File name is missing.'); return; } const deleteFilePath = path.join(basePath, deleteFileName); fs.unlink(deleteFilePath, err => { if (err) { res.writeHead(500, { 'Content-Type': 'text/plain' }); res.end('Error deleting file.'); return; } res.writeHead(302, { 'Location': urlPath }); res.end(); }); } }); } }); function getContentType(extension) { // Content type mapping switch (extension) { case '.html': return 'text/html'; case '.css': return 'text/css'; case '.js': return 'application/javascript'; case '.json': case '.map': return 'application/json'; case '.xml': return 'application/xml'; case '.txt': case '.log': return 'text/plain'; case '.md': return 'text/markdown'; case '.py': return 'text/x-python'; case '.c': return 'text/x-c'; case '.cpp': return 'text/x-c++src'; case '.java': return 'text/x-java'; case '.rb': return 'text/x-ruby'; case '.php': return 'text/x-php'; case '.pl': return 'text/x-perl'; case '.sh': return 'text/x-shellscript'; case '.yaml': case '.yml': return 'text/yaml'; case '.csv': return 'text/csv'; case '.pptx': return 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; case '.tsv': return 'text/tab-separated-values'; case '.pdf': return 'application/pdf'; case '.doc': return 'application/msword'; case '.docx': return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'; case '.xls': return 'application/vnd.ms-excel'; case '.xlsx': return 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'; case '.ppt': return 'application/vnd.ms-powerpoint'; case '.pptx': return 'application/vnd.openxmlformats-officedocument.presentationml.presentation'; case '.jpg': case '.jpeg': return 'image/jpeg'; case '.png': return 'image/png'; case '.gif': return 'image/gif'; case '.bmp': return 'image/bmp'; case '.tiff': case '.tif': return 'image/tiff'; case '.svg': return 'image/svg+xml'; case '.mp3': return 'audio/mpeg'; case '.ogg': return 'audio/ogg'; case '.wav': return 'audio/wav'; case '.aac': return 'audio/aac'; case '.flac': return 'audio/flac'; case '.mp4': return 'video/mp4'; case '.webm': return 'video/webm'; case '.avi': return 'video/x-msvideo'; case '.wmv': return 'video/x-ms-wmv'; case '.mov': return 'video/quicktime'; case '.mkv': return 'video/x-matroska'; default: return 'text/plain'; } } function getDirectoryOptions(basePath) { let options = ''; const directories = fs.readdirSync(basePath, { withFileTypes: true }).filter(dirent => dirent.isDirectory()); for (const dir of directories) { options += `<option value="${dir.name}">${dir.name}</option>`; } return options; } const PORT = argv.live; server.listen(PORT, () => { console.log(`File manager is running on port ${PORT}`); }); // --host if (argv.host) { host = argv.host } else { host = '127.0.0.1' } //to preserve seed if (argv.connector) { if (argv.connector.length === 64) { connector = argv.connector } else { connector = createHash('sha256').update(argv.connector.toString()).digest('hex'); } } else { connector = null } localServer.serve(argv.live, host, () => { console.log(`Server started, Now listening on ${host}:` + argv.live); console.log(`Your connector is: ${argv.connector}`); console.log('Server public key:', localServer.getPublicKey()); }, connector); } else if (argv.connect) { //give priority to connector instead of connection seed if (argv.connector) { if (argv.connector.length === 64) { connector = argv.connector } else { connector = createHash('sha256').update(argv.connector.toString()).digest('hex'); const seed = Buffer.from(connector, 'hex'); //the keypair here is not a reference to the function above connector = HyperDHT.keyPair(seed).publicKey.toString('hex'); } } else { connector = argv.connect } if (!argv.port) { port = 8989 } else { port = argv.port } //--host if (argv.host) { host = argv.host } else { host = '127.0.0.1' } const holesailClient = require('holesail-client') const pubClient = new holesailClient(connector) pubClient.connect(port, host, () => { console.log(`Client setup, access on ${host}:${port}`); console.log(`Your connector is: ${argv.connector}`); console.log('Connected to public key:', connector); } ) } else { console.log(helpMessage); process.exit(-1) } goodbye(async () => { await localServer.destroy() })