UNPKG

http-server

Version:

A simple zero-configuration command-line http server

174 lines (152 loc) 5.97 kB
'use strict'; const styles = require('./styles'); const lastModifiedToString = require('./last-modified-to-string'); const permsToString = require('./perms-to-string'); const sizeToString = require('./size-to-string'); const sortFiles = require('./sort-files'); const fs = require('fs'); const path = require('path'); const he = require('he'); const etag = require('../etag'); const url = require('url'); const status = require('../status-handlers'); const supportedIcons = styles.icons; const css = styles.css; module.exports = (opts) => { // opts are parsed by opts.js, defaults already applied const cache = opts.cache; const root = path.resolve(opts.root); const baseDir = opts.baseDir; const humanReadable = opts.humanReadable; const hidePermissions = opts.hidePermissions; const handleError = opts.handleError; const showDotfiles = opts.showDotfiles; const si = opts.si; const weakEtags = opts.weakEtags; return function middleware(req, res, next) { // Figure out the path for the file from the given url const parsed = url.parse(req.url); const pathname = decodeURIComponent(parsed.pathname); const dir = path.normalize( path.join( root, path.relative( path.join('/', baseDir), pathname ) ) ); fs.stat(dir, (statErr, stat) => { if (statErr) { if (handleError) { status[500](res, next, { error: statErr }); } else { next(); } return; } // files are the listing of dir fs.readdir(dir, (readErr, _files) => { let files = _files; if (readErr) { if (handleError) { status[500](res, next, { error: readErr }); } else { next(); } return; } // Optionally exclude dotfiles from directory listing. if (!showDotfiles) { files = files.filter(filename => filename.slice(0, 1) !== '.'); } res.setHeader('content-type', 'text/html'); res.setHeader('etag', etag(stat, weakEtags)); res.setHeader('last-modified', (new Date(stat.mtime)).toUTCString()); res.setHeader('cache-control', cache); function render(dirs, renderFiles, lolwuts) { // each entry in the array is a [name, stat] tuple let html = `${[ '<!doctype html>', '<html>', ' <head>', ' <meta charset="utf-8">', ' <meta name="viewport" content="width=device-width">', ` <title>Index of ${he.encode(pathname)}</title>`, ` <style type="text/css">${css}</style>`, ' </head>', ' <body>', `<h1>Index of ${he.encode(pathname)}</h1>`, ].join('\n')}\n`; html += '<table>'; const failed = false; const writeRow = (file) => { // render a row given a [name, stat] tuple const isDir = file[1].isDirectory && file[1].isDirectory(); let href = `./${encodeURIComponent(file[0])}`; // append trailing slash and query for dir entry if (isDir) { href += `/${he.encode((parsed.search) ? parsed.search : '')}`; } const displayName = he.encode(file[0]) + ((isDir) ? '/' : ''); const ext = file[0].split('.').pop(); const classForNonDir = supportedIcons[ext] ? ext : '_page'; const iconClass = `icon-${isDir ? '_blank' : classForNonDir}`; // TODO: use stylessheets? html += `${'<tr>' + '<td><i class="icon '}${iconClass}"></i></td>`; if (!hidePermissions) { html += `<td class="perms"><code>(${permsToString(file[1])})</code></td>`; } html += `<td class="last-modified">${lastModifiedToString(file[1])}</td>` + `<td class="file-size"><code>${sizeToString(file[1], humanReadable, si)}</code></td>` + `<td class="display-name"><a href="${href}">${displayName}</a></td>` + '</tr>\n'; }; dirs.sort((a, b) => a[0].toString().localeCompare(b[0].toString())).forEach(writeRow); renderFiles.sort((a, b) => a.toString().localeCompare(b.toString())).forEach(writeRow); lolwuts.sort((a, b) => a[0].toString().localeCompare(b[0].toString())).forEach(writeRow); html += '</table>\n'; html += `<br><address>Node.js ${ process.version }/ <a href="https://github.com/http-party/http-server">http-server</a> ` + `server running @ ${ he.encode(req.headers.host || '')}</address>\n` + '</body></html>' ; if (!failed) { res.writeHead(200, { 'Content-Type': 'text/html' }); res.end(html); } } sortFiles(dir, files, (lolwuts, dirs, sortedFiles) => { // It's possible to get stat errors for all sorts of reasons here. // Unfortunately, our two choices are to either bail completely, // or just truck along as though everything's cool. In this case, // I decided to just tack them on as "??!?" items along with dirs // and files. // // Whatever. // if it makes sense to, add a .. link if (path.resolve(dir, '..').slice(0, root.length) === root) { fs.stat(path.join(dir, '..'), (err, s) => { if (err) { if (handleError) { status[500](res, next, { error: err }); } else { next(); } return; } dirs.unshift(['..', s]); render(dirs, sortedFiles, lolwuts); }); } else { render(dirs, sortedFiles, lolwuts); } }); }); }); }; };