Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simple implementation of serving directories as zip files #836

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 4 additions & 2 deletions bin/http-server
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ if (argv.h || argv.help) {
' --no-dotfiles Do not show dotfiles',
' --mimetypes Path to a .types file for custom mimetype definition',
' -h --help Print this list and exit.',
' -v --version Print the version and exit.'
' -v --version Print the version and exit.',
' --serve-dir-zip Serve zip file of directory contents [true]'
].join('\n'));
process.exit();
}
Expand Down Expand Up @@ -153,7 +154,8 @@ function listen(port) {
showDotfiles: argv.dotfiles,
mimetypes: argv.mimetypes,
username: argv.username || process.env.NODE_HTTP_SERVER_USERNAME,
password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD
password: argv.password || process.env.NODE_HTTP_SERVER_PASSWORD,
serveDirZip: argv['serve-dir-zip'],
};

if (argv.cors) {
Expand Down
5 changes: 5 additions & 0 deletions lib/core/aliases.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,10 @@
"handleOptionsMethod",
"handleoptionsmethod",
"handle-options-method"
],
"serveDirZip": [
"serveDirZip",
"servedirzip",
"serve-dir-zip"
]
}
3 changes: 2 additions & 1 deletion lib/core/defaults.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@
"contentType": "application/octet-stream",
"weakEtags": true,
"weakCompare": true,
"handleOptionsMethod": false
"handleOptionsMethod": false,
"serveDirZip": true
}
4 changes: 4 additions & 0 deletions lib/core/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const status = require('./status-handlers');
const generateEtag = require('./etag');
const optsParser = require('./opts');
const htmlEncodingSniffer = require('html-encoding-sniffer');
const serveDirZip = require('./serve-dir-zip');

let httpServerCore = null;

Expand Down Expand Up @@ -352,6 +353,9 @@ module.exports = function createMiddleware(_dir, _options) {
// This means we're already trying ./404.html and can not find it.
// So send plain text response with 404 status code
status[404](res, next);
} else if (path.extname(parsed.pathname) === '.zip' && opts.serveDirZip) {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be case insensitive.

// This is a request for a zip file that doesn't exist. Try to serve the directory as a zip file instead.
serveDirZip(opts, stat)(req, res);
} else if (!path.extname(parsed.pathname).length && defaultExt) {
// If there is no file extension in the path and we have a default
// extension try filename and default extension combination before rendering 404.html.
Expand Down
10 changes: 10 additions & 0 deletions lib/core/opts.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = (opts) => {
let weakEtags = defaults.weakEtags;
let weakCompare = defaults.weakCompare;
let handleOptionsMethod = defaults.handleOptionsMethod;
let serveDirZip = defaults.serveDirZip;

function isDeclared(k) {
return typeof opts[k] !== 'undefined' && opts[k] !== null;
Expand Down Expand Up @@ -178,6 +179,14 @@ module.exports = (opts) => {
}
return false;
});

aliases.serveDirZip.some((k) => {
if (isDeclared(k)) {
serveDirZip = opts[k];
return true;
}
return false;
});
}

return {
Expand All @@ -199,5 +208,6 @@ module.exports = (opts) => {
weakEtags,
weakCompare,
handleOptionsMethod,
serveDirZip,
};
};
77 changes: 77 additions & 0 deletions lib/core/serve-dir-zip/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
'use strict';

const fs = require('fs');
const path = require('path');
const etag = require('../etag');
const url = require('url');
const status = require('../status-handlers');
const archiver = require('archiver');

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 handleError = opts.handleError;
const weakEtags = opts.weakEtags;

return function middleware(req, res, next) {
// Figure out the path for the directory from the given url
const parsed = url.parse(req.url);
const pathname = decodeURIComponent(parsed.pathname);

// Remove the .zip extension and append a trailing slash
const parsedPath = path.parse(pathname);
const dirPath = `${path.join(parsedPath.dir, parsedPath.name)}/`;

const dir = path.normalize(
path.join(
root,
path.relative(
path.join('/', baseDir),
dirPath
)
)
);

fs.stat(dir, (statErr, stat) => {
if (statErr) {
if (handleError) {
status[500](res, next, { error: statErr });
} else {
next();
}
return;
}

res.setHeader('content-type', 'application/zip');
res.setHeader('etag', etag(stat, weakEtags));
res.setHeader('last-modified', (new Date(stat.mtime)).toUTCString());
res.setHeader('cache-control', cache);

// Also supports .tar and .tar.gz archives
const archive = archiver('zip', {
zlib: { level: 9 } // Sets the compression level.
});

archive.on('warning', function(err) {
if (err.code === 'ENOENT') {
// Stat error or other potentially non-blocking error. For now, we just ignore it.
} else {
status['500'](res, next, { error: err });
}
});

archive.on('error', function(err) {
status['500'](res, next, { error: err });
});

archive.pipe(res);

// This always excludes dotfiles
archive.glob('**', { cwd: dir });

archive.finalize();
});
};
};
4 changes: 3 additions & 1 deletion lib/http-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ function HttpServer(options) {
this.showDotfiles = options.showDotfiles;
this.gzip = options.gzip === true;
this.brotli = options.brotli === true;
this.serveDirZip = options.serveDirZip !== 'false';
if (options.ext) {
this.ext = options.ext === true
? 'html'
Expand Down Expand Up @@ -136,7 +137,8 @@ function HttpServer(options) {
brotli: this.brotli,
contentType: this.contentType,
mimetypes: options.mimetypes,
handleError: typeof options.proxy !== 'string'
handleError: typeof options.proxy !== 'string',
serveDirZip: this.serveDirZip
}));

if (typeof options.proxy === 'string') {
Expand Down