123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- '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);
- }
- });
- });
- });
- };
- };
|