#!/usr/bin/env node // vim: set ts=2 sw=2 et tw=80: const http = require('http'); const url = require('url'); const fs = require('fs'); const formidable = require('formidable'); const routes = Object.create(null); function noSubPaths(req, path) { const p = url.parse(req.url); if (p.pathname != path && p.pathname != path) { error(res, 404, `Only "${path}" path is accessible under this prefix`); return true; } else { return false; } } function error(res, code, text = '') { res.writeHead(code, { 'Content-Type': 'text/html' }); res.end(` Error ${code}

Error ${code}

${text}


SA3 - HW4

`); } function fileData(reqUrl, prefix, options = {}) { const uri = decodeURIComponent( !options.isPath ? url.parse(reqUrl).pathname.substring(prefix.length) : reqUrl ).replace(/\/+$/, ''); const file = __dirname + '/NodeStaticFiles' + uri; const name = file.substring(file.lastIndexOf('/') + 1); const ext = !options.noExt ? name.substring(name.indexOf('.') + 1) : null; return { uri: !uri ? '/' : uri, file: file, name: name, ext: ext }; } routes['explore'] = (req, res) => { if (req.method != 'GET') { error(res, 405, 'Use this URL with only GET requests'); return; } const { uri, file, name } = fileData(req.url, '/explore', { noExt: true }); fs.readdir(file, { withFileTypes: true }, (err, dir) => { if (err) { error(res, 404, 'Directory not found'); return; } const list = [{ name: '.', path: '/explore' + (uri == '/' ? '' : uri) + '/' }]; if (uri != '/') { const parentUri = uri.substring(0, uri.length - name.length - 1); list.push({ name: '..', path: '/explore' + parentUri }); } for (const e of dir) { list.push({ name: e.name, dir: !e.isFile(), path: (e.isFile() ? '/file' : '/explore') + uri + (uri == '/' ? '' : '/') + e.name }); } res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(` ${uri} [DIR]

${uri} [DIR]

`); }); }; routes['file'] = (req, res) => { const FILE_TYPES = { html: 'text/html', css: 'text/css', txt: 'text/plain', mp4: 'video/mp4', ogv: 'video/ogg', gif: 'image/gif', jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png', mp3: 'audio/mpeg', js: 'application/javascript', json: 'application/json', pdf: 'application/pdf', zip: 'application/zip' }; if (req.method != 'GET') { error(res, 405, 'Use this URL with only GET requests'); return; } const { file, name, ext } = fileData(req.url, '/file'); fs.readFile(file, (err, data) => { if (err) { error(res, 404, 'File not found: ' + JSON.stringify(err)); return; } res.setHeader('Content-Disposition', 'attachment; filename="' + name + '"'); res.writeHead(200, { 'Content-Type': ext in FILE_TYPES ? FILE_TYPES[ext] : 'application/octet-stream' }); res.end(data); }); } routes['upload'] = (req, res) => { if (req.method != 'GET' && req.method != 'POST') { error(res, 405, 'Use this URL with only GET or POST requests'); return; } if (req.method == 'POST') { const form = new formidable.IncomingForm(); form.uploadDir = __dirname + '/NodeStaticFiles'; form.keepExtensions = true; form.parse(req); form.on('fileBegin', (name, file) => { file.path = file.path.substring(0, file.path.lastIndexOf('/') + 1); file.path += file.name; }); form.on('end', () => { res.writeHead(302, { 'Location': '/explore' }); res.end(); }); return; } res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(` Upload

Upload


`); }; function wordMap(string) { const words = string.trim().split(/[^\w]+/); const wordDict = {}; for (const word of words) { if (word.trim() == '') { continue; } if (!wordDict[word]) { wordDict[word] = 1; } else { wordDict[word]++; } } return wordDict; } function getFileStats(req, res, path, callback) { const p = url.parse(req.url, true); const reqFile = p && p.query && '/' + p.query['file']; if (!reqFile) { error(res, 400, 'Search parameter "file" is required'); return; } const { file, name, ext } = fileData(reqFile, path, { isPath: true }); if (ext != 'txt' && ext != 'html') { error(res, 400, `Only txt and html files can be processed by ${path}`); return; } fs.readFile(file, 'utf8', (err, data) => { if (err) { error(res, 404, 'File not found: ' + JSON.stringify(err)); return; } const dict = wordMap(data); callback({ reqFile: reqFile, dict: dict }); }); } routes['stats'] = (req, res) => { if (noSubPaths(req, '/stats')) { return; } getFileStats(req, res, '/stats', ({ reqFile, dict }) => { res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(` Stats for ${reqFile}

Word frequency count for "${reqFile}"

`); for (const word in dict) { res.write(``); } res.end(`
WordFreq.
${word} ${dict[word]}
`); }); }; routes['cloud'] = (req, res) => { if (noSubPaths(req, '/cloud')) { return; } getFileStats(req, res, '/cloud', ({ reqFile, dict }) => { if (req.headers.accept && req.headers.accept == 'application/json') { res.writeHead(200, {'Content-Type': 'application/json' }); res.end(JSON.stringify(dict, null, 2)); } else { res.writeHead(200, { 'Content-Type': 'text/html' }); res.write(` Cloud for ${reqFile}

Tag cloud for "${reqFile}"

`); const max = Math.max(...Object.values(dict)); const min = Math.min(...Object.values(dict)); for (const word in dict) { const size = 0.75 + ((dict[word] - min) / max) * 4 + 'rem'; res.write(` ${word} `); } res.end(`
`); } }); }; // Main server handler function onRequest(req, res) { const pathname = url.parse(req.url).pathname; const uri = pathname.split('/', 3)[1]; if (typeof routes[uri] === 'function') { routes[uri](req, res); } else { error(res, 404, 'Path not found'); } } http.createServer(onRequest).listen(3000); console.log('Server started at localhost:3000');