Socket
Socket
Sign inDemoInstall

ecstatic

Package Overview
Dependencies
Maintainers
2
Versions
79
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

ecstatic - npm Package Compare versions

Comparing version 2.2.1 to 3.0.0

.eslintignore

11

CHANGELOG.md

@@ -0,1 +1,12 @@

2017/08/28 Version 3.0.0
- Lint ./lib/ ./example and ./test against airbnb modified to support node 4.x
and a few quirky hard-to-fix idioms
- Change gzip behavior to default
- Change weak etags and weak etag comparisons to be on by default
- Remove support for 0.12.0
- Remove union examples and test harnesses (support should have been removed
long ago)
- Fix icon styles in directory listing for small screens
- Update mime to ^v1.4.0 - This changes gzip responses to always have application/gzip as their content-type
2017/06/06 Version 2.2.1

@@ -2,0 +13,0 @@ - Fix version number in CHANGELOG.md

16

CONTRIBUTING.md

@@ -40,15 +40,9 @@ # Contributing Guidelines

Ecstatic's code base follows a relatively consistent style. The closer your
patch blends in with the status quo, the better.
Ecstatic lints using a number of modifications on top of airbnb. If you think
"airbnb except it doesn't need to be transpiled for targeted platforms" you're
pretty close.
A few PROTIPS off the top of my head:
Linting is executed as part of pretest. Your code should pass linting before
being merged.
1. Variables don't need to all be declared at the top, BUT variable *blocks*
should do the whole one-var, tons-of-commas thing.
2. Look at how spacing is done around conditionals and functions. Do it like
that.
3. `else`'s and similar should be on the line *after* the preceding bracket.
We can refine this as the need arises.
## A Few Other Minor Guidelines

@@ -55,0 +49,0 @@

@@ -63,1 +63,3 @@ General format is: contributor, github handle, email. In some cases, the

* @wood1986
* Mahdi Hasheminejad @mahdi-ninja
* Bradley Farias @bmeck <bradley.meck@gmail.com>

@@ -1,6 +0,9 @@

var http = require('http');
var ecstatic = require('../lib/ecstatic')({
root: __dirname + '/public',
'use strict';
const http = require('http');
const ecstatic = require('../lib/ecstatic')({
root: `${__dirname}/public`,
showDir: true,
autoIndex: true
autoIndex: true,
});

@@ -7,0 +10,0 @@

@@ -1,12 +0,16 @@

var express = require('express');
var ecstatic = require('../lib/ecstatic');
var http = require('http');
'use strict';
var app = express();
const express = require('express');
const ecstatic = require('../lib/ecstatic');
const http = require('http');
const app = express();
app.use(ecstatic({
root: __dirname + '/public',
showdir : true
root: `${__dirname}/public`,
showdir: true,
}));
http.createServer(app).listen(8080);
console.log('Listening on :8080');
#! /usr/bin/env node
var path = require('path'),
fs = require('fs'),
url = require('url'),
mime = require('mime'),
urlJoin = require('url-join'),
showDir = require('./ecstatic/show-dir'),
version = JSON.parse(
fs.readFileSync(__dirname + '/../package.json').toString()
).version,
status = require('./ecstatic/status-handlers'),
generateEtag = require('./ecstatic/etag'),
optsParser = require('./ecstatic/opts');
'use strict';
var ecstatic = module.exports = function (dir, options) {
if (typeof dir !== 'string') {
options = dir;
const path = require('path');
const fs = require('fs');
const url = require('url');
const mime = require('mime');
const urlJoin = require('url-join');
const showDir = require('./ecstatic/show-dir');
const version = require('../package.json').version;
const status = require('./ecstatic/status-handlers');
const generateEtag = require('./ecstatic/etag');
const optsParser = require('./ecstatic/opts');
let ecstatic = null;
// See: https://github.com/jesusabdullah/node-ecstatic/issues/109
function decodePathname(pathname) {
const pieces = pathname.replace(/\\/g, '/').split('/');
return pieces.map((rawPiece) => {
const piece = decodeURIComponent(rawPiece);
if (process.platform === 'win32' && /\\/.test(piece)) {
throw new Error('Invalid forward slash character');
}
return piece;
}).join('/');
}
// Check to see if we should try to compress a file with gzip.
function shouldCompress(req) {
const headers = req.headers;
return headers && headers['accept-encoding'] &&
headers['accept-encoding']
.split(',')
.some(el => ['*', 'compress', 'gzip', 'deflate'].indexOf(el) !== -1)
;
}
function hasGzipId12(gzipped, cb) {
const stream = fs.createReadStream(gzipped, { start: 0, end: 1 });
let buffer = Buffer('');
let hasBeenCalled = false;
stream.on('data', (chunk) => {
buffer = Buffer.concat([buffer, chunk], 2);
});
stream.on('error', (err) => {
if (hasBeenCalled) {
throw err;
}
hasBeenCalled = true;
cb(err);
});
stream.on('close', () => {
if (hasBeenCalled) {
return;
}
hasBeenCalled = true;
cb(null, buffer[0] === 31 && buffer[1] === 139);
});
}
module.exports = function createMiddleware(_dir, _options) {
let dir;
let options;
if (typeof _dir === 'string') {
dir = _dir;
options = _options;
} else {
options = _dir;
dir = options.root;
}
var root = path.join(path.resolve(dir), '/'),
opts = optsParser(options),
cache = opts.cache,
autoIndex = opts.autoIndex,
baseDir = opts.baseDir,
defaultExt = opts.defaultExt,
handleError = opts.handleError,
headers = opts.headers,
serverHeader = opts.serverHeader,
weakEtags = opts.weakEtags,
handleOptionsMethod = opts.handleOptionsMethod;
const root = path.join(path.resolve(dir), '/');
const opts = optsParser(options);
const cache = opts.cache;
const autoIndex = opts.autoIndex;
const baseDir = opts.baseDir;
let defaultExt = opts.defaultExt;
const handleError = opts.handleError;
const headers = opts.headers;
const serverHeader = opts.serverHeader;
const weakEtags = opts.weakEtags;
const handleOptionsMethod = opts.handleOptionsMethod;
opts.root = dir;
if (defaultExt && /^\./.test(defaultExt)) defaultExt = defaultExt.replace(/^\./, '');
if (defaultExt && /^\./.test(defaultExt)) {
defaultExt = defaultExt.replace(/^\./, '');
}

@@ -42,7 +108,8 @@ // Support hashes and .types files in mimeTypes @since 0.8

opts.mimeTypes = JSON.parse(opts.mimeTypes);
} catch (e) {}
} catch (e) {
// swallow parse errors, treat this as a string mimetype input
}
if (typeof opts.mimeTypes === 'string') {
mime.load(opts.mimeTypes);
}
else if (typeof opts.mimeTypes === 'object') {
} else if (typeof opts.mimeTypes === 'object') {
mime.define(opts.mimeTypes);

@@ -52,5 +119,55 @@ }

function shouldReturn304(req, serverLastModified, serverEtag) {
if (!req || !req.headers) {
return false;
}
return function middleware (req, res, next) {
const clientModifiedSince = req.headers['if-modified-since'];
const clientEtag = req.headers['if-none-match'];
let clientModifiedDate;
if (!clientModifiedSince && !clientEtag) {
// Client did not provide any conditional caching headers
return false;
}
if (clientModifiedSince) {
// Catch "illegal access" dates that will crash v8
// https://github.com/jfhbrook/node-ecstatic/pull/179
try {
clientModifiedDate = new Date(Date.parse(clientModifiedSince));
} catch (err) {
return false;
}
if (clientModifiedDate.toString() === 'Invalid Date') {
return false;
}
// If the client's copy is older than the server's, don't return 304
if (clientModifiedDate < new Date(serverLastModified)) {
return false;
}
}
if (clientEtag) {
// Do a strong or weak etag comparison based on setting
// https://www.ietf.org/rfc/rfc2616.txt Section 13.3.3
if (opts.weakCompare && clientEtag !== serverEtag
&& clientEtag !== `W/${serverEtag}` && `W/${clientEtag}` !== serverEtag) {
return false;
} else if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) {
return false;
}
}
return true;
}
return function middleware(req, res, next) {
// Figure out the path for the file from the given url
const parsed = url.parse(req.url);
let pathname = null;
let file = null;
let gzipped = null;
// Strip any null bytes from the url

@@ -67,3 +184,3 @@ // This was at one point necessary because of an old bug in url.parse

/*
while(req.url.indexOf('%00') !== -1) {
while (req.url.indexOf('%00') !== -1) {
req.url = req.url.replace(/\%00/g, '');

@@ -73,32 +190,30 @@ }

// Figure out the path for the file from the given url
var parsed = url.parse(req.url);
try {
decodeURIComponent(req.url); // check validity of url
var pathname = decodePathname(parsed.pathname);
pathname = decodePathname(parsed.pathname);
} catch (err) {
status[400](res, next, { error: err });
return;
}
catch (err) {
return status[400](res, next, { error: err });
}
var file = path.normalize(
path.join(root,
path.relative(
path.join('/', baseDir),
pathname
)
)
),
gzipped = file + '.gz';
file = path.normalize(
path.join(
root,
path.relative(path.join('/', baseDir), pathname)
)
);
gzipped = `${file}.gz`;
if(serverHeader !== false) {
if (serverHeader !== false) {
// Set common headers.
res.setHeader('server', 'ecstatic-'+version);
res.setHeader('server', `ecstatic-${version}`);
}
Object.keys(headers).forEach(function (key) {
res.setHeader(key, headers[key])
})
Object.keys(headers).forEach((key) => {
res.setHeader(key, headers[key]);
});
if (req.method === 'OPTIONS' && handleOptionsMethod) {
return res.end();
res.end();
return;
}

@@ -109,105 +224,22 @@

if (file.slice(0, root.length) !== root) {
return status[403](res, next);
status[403](res, next);
return;
}
if (req.method && (req.method !== 'GET' && req.method !== 'HEAD' )) {
return status[405](res, next);
if (req.method && (req.method !== 'GET' && req.method !== 'HEAD')) {
status[405](res, next);
return;
}
function statFile() {
fs.stat(file, function (err, stat) {
if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {
if (req.statusCode == 404) {
// 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).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.
middleware({
url: parsed.pathname + '.' + defaultExt + ((parsed.search) ? parsed.search : ''),
headers: req.headers
}, res, next);
}
else {
// Try to serve default ./404.html
middleware({
url: (handleError ? ('/' + path.join(baseDir, '404.' + defaultExt)) : req.url),
headers: req.headers,
statusCode: 404
}, res, next);
}
}
else if (err) {
status[500](res, next, { error: err });
}
else if (stat.isDirectory()) {
if (!autoIndex && !opts.showDir) {
status[404](res, next);
return;
}
// 302 to / if necessary
if (!parsed.pathname.match(/\/$/)) {
res.statusCode = 302;
res.setHeader('location', parsed.pathname + '/' +
(parsed.query? ('?' + parsed.query):'')
);
return res.end();
}
if (autoIndex) {
return middleware({
url: urlJoin(encodeURIComponent(pathname), '/index.' + defaultExt),
headers: req.headers
}, res, function (err) {
if (err) {
return status[500](res, next, { error: err });
}
if (opts.showDir) {
return showDir(opts, stat)(req, res);
}
return status[403](res, next);
});
}
if (opts.showDir) {
return showDir(opts, stat)(req, res);
}
}
else {
serve(stat);
}
});
}
// Look for a gzipped file if this is turned on
if (opts.gzip && shouldCompress(req)) {
fs.stat(gzipped, function (err, stat) {
if (!err && stat.isFile()) {
hasGzipId12(gzipped, function (err, isGzip) {
if (isGzip) {
file = gzipped;
return serve(stat);
} else {
statFile();
}
});
} else {
statFile();
}
});
} else {
statFile();
}
function serve(stat) {
// Do a MIME lookup, fall back to octet-stream and handle gzip
// special case.
var defaultType = opts.contentType || 'application/octet-stream',
contentType = mime.lookup(file, defaultType),
charSet;
const defaultType = opts.contentType || 'application/octet-stream';
let contentType = mime.lookup(file, defaultType);
let charSet;
const range = (req.headers && req.headers.range);
const lastModified = (new Date(stat.mtime)).toUTCString();
const etag = generateEtag(stat, weakEtags);
let stream = null;

@@ -217,3 +249,3 @@ if (contentType) {

if (charSet) {
contentType += '; charset=' + charSet;
contentType += `; charset=${charSet}`;
}

@@ -226,29 +258,35 @@ }

// strip gz ending and lookup mime type
contentType = mime.lookup(path.basename(file, ".gz"), defaultType);
contentType = mime.lookup(path.basename(file, '.gz'), defaultType);
}
var range = (req.headers && req.headers['range']);
if (range) {
var total = stat.size;
var parts = range.replace(/bytes=/, "").split("-");
var partialstart = parts[0];
var partialend = parts[1];
var start = parseInt(partialstart, 10);
var end = Math.min(total-1, partialend ? parseInt(partialend, 10) : total-1);
var chunksize = (end-start)+1;
const total = stat.size;
const parts = range.replace(/bytes=/, '').split('-');
const partialstart = parts[0];
const partialend = parts[1];
const start = parseInt(partialstart, 10);
const end = Math.min(
total - 1,
partialend ? parseInt(partialend, 10) : total - 1
);
const chunksize = (end - start) + 1;
let fstream = null;
if (start > end || isNaN(start) || isNaN(end)) {
return status['416'](res, next);
status['416'](res, next);
return;
}
var fstream = fs.createReadStream(file, {start: start, end: end});
fstream.on('error', function (err) {
fstream = fs.createReadStream(file, { start, end });
fstream.on('error', (err) => {
status['500'](res, next, { error: err });
});
res.on('close', function () {
fstream.destroy();
res.on('close', () => {
fstream.destroy();
});
res.writeHead(206, {
'Content-Range': 'bytes ' + start + '-' + end + '/' + total,
'Content-Range': `bytes ${start}-${end}/${total}`,
'Accept-Ranges': 'bytes',
'Content-Length': chunksize,
'Content-Type': contentType
'Content-Type': contentType,
});

@@ -260,4 +298,2 @@ fstream.pipe(res);

// TODO: Helper for this, with default headers.
var lastModified = (new Date(stat.mtime)).toUTCString(),
etag = generateEtag(stat, weakEtags);
res.setHeader('last-modified', lastModified);

@@ -267,5 +303,5 @@ res.setHeader('etag', etag);

if (typeof cache === 'function') {
var requestSpecificCache = cache(pathname);
let requestSpecificCache = cache(pathname);
if (typeof requestSpecificCache === 'number') {
requestSpecificCache = 'max-age=' + requestSpecificCache;
requestSpecificCache = `max-age=${requestSpecificCache}`;
}

@@ -279,3 +315,4 @@ res.setHeader('cache-control', requestSpecificCache);

if (shouldReturn304(req, lastModified, etag)) {
return status[304](res, next);
status[304](res, next);
return;
}

@@ -291,10 +328,11 @@

if (req.method === "HEAD") {
return res.end();
if (req.method === 'HEAD') {
res.end();
return;
}
var stream = fs.createReadStream(file);
stream = fs.createReadStream(file);
stream.pipe(res);
stream.on('error', function (err) {
stream.on('error', (err) => {
status['500'](res, next, { error: err });

@@ -304,44 +342,91 @@ });

function shouldReturn304(req, serverLastModified, serverEtag) {
if (!req || !req.headers) {
return false;
}
var clientModifiedSince = req.headers['if-modified-since'],
clientEtag = req.headers['if-none-match'];
function statFile() {
fs.stat(file, (err, stat) => {
if (err && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {
if (req.statusCode === 404) {
// 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).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.
middleware({
url: `${parsed.pathname}.${defaultExt}${(parsed.search) ? parsed.search : ''}`,
headers: req.headers,
}, res, next);
} else {
// Try to serve default ./404.html
middleware({
url: (handleError ? `/${path.join(baseDir, `404.${defaultExt}`)}` : req.url),
headers: req.headers,
statusCode: 404,
}, res, next);
}
} else if (err) {
status[500](res, next, { error: err });
} else if (stat.isDirectory()) {
if (!autoIndex && !opts.showDir) {
status[404](res, next);
return;
}
if (!clientModifiedSince && !clientEtag) {
// Client did not provide any conditional caching headers
return false;
}
// 302 to / if necessary
if (!parsed.pathname.match(/\/$/)) {
res.statusCode = 302;
const q = parsed.query ? `?${parsed.query}` : '';
res.setHeader('location', `${parsed.pathname}/${q}`);
res.end();
return;
}
if (clientModifiedSince) {
// Catch "illegal access" dates that will crash v8
// https://github.com/jfhbrook/node-ecstatic/pull/179
try {
var clientModifiedDate = new Date(Date.parse(clientModifiedSince));
}
catch (err) { return false }
if (autoIndex) {
middleware({
url: urlJoin(
encodeURIComponent(pathname),
`/index.${defaultExt}`
),
headers: req.headers,
}, res, (autoIndexError) => {
if (autoIndexError) {
status[500](res, next, { error: autoIndexError });
return;
}
if (opts.showDir) {
showDir(opts, stat)(req, res);
return;
}
if (clientModifiedDate.toString() === 'Invalid Date') {
return false;
status[403](res, next);
});
return;
}
if (opts.showDir) {
showDir(opts, stat)(req, res);
}
} else {
serve(stat);
}
// If the client's copy is older than the server's, don't return 304
if (clientModifiedDate < new Date(serverLastModified)) {
return false;
}
}
});
}
if (clientEtag) {
// Do a strong or weak etag comparison based on setting
// https://www.ietf.org/rfc/rfc2616.txt Section 13.3.3
if (opts.weakCompare && clientEtag !== serverEtag
&& clientEtag !== ('W/' + serverEtag) && ('W/' + clientEtag) !== serverEtag) {
return false;
} else if (!opts.weakCompare && (clientEtag !== serverEtag || clientEtag.indexOf('W/') === 0)) {
return false;
// Look for a gzipped file if this is turned on
if (opts.gzip && shouldCompress(req)) {
fs.stat(gzipped, (err, stat) => {
if (!err && stat.isFile()) {
hasGzipId12(gzipped, (gzipErr, isGzip) => {
if (!gzipErr && isGzip) {
file = gzipped;
serve(stat);
} else {
statFile();
}
});
} else {
statFile();
}
}
return true;
});
} else {
statFile();
}

@@ -351,86 +436,37 @@ };

ecstatic = module.exports;
ecstatic.version = version;
ecstatic.showDir = showDir;
function hasGzipId12(gzipped, cb) {
var stream = fs.createReadStream(gzipped, { start: 0, end: 1 }),
buffer = Buffer(''),
hasBeenCalled = false;
stream.on("data", function (chunk) {
buffer = Buffer.concat([buffer, chunk], 2);
});
if (!module.parent) {
/* eslint-disable global-require */
/* eslint-disable no-console */
const defaults = require('./ecstatic/defaults.json');
const http = require('http');
const minimist = require('minimist');
const aliases = require('./ecstatic/aliases.json');
stream.on("error", function (err) {
if (hasBeenCalled) {
throw err;
}
hasBeenCalled = true;
cb(error);
const opts = minimist(process.argv.slice(2), {
alias: aliases,
default: defaults,
boolean: Object.keys(defaults).filter(
key => typeof defaults[key] === 'boolean'
),
});
const envPORT = parseInt(process.env.PORT, 10);
const port = envPORT > 1024 && envPORT <= 65536 ? envPORT : opts.port || opts.p || 8000;
const dir = opts.root || opts._[0] || process.cwd();
stream.on("close", function () {
if (hasBeenCalled) {
return;
}
hasBeenCalled = true;
cb(null, buffer[0] == 31 && buffer[1] == 139);
});
}
// Check to see if we should try to compress a file with gzip.
function shouldCompress(req) {
var headers = req.headers;
return headers && headers['accept-encoding'] &&
headers['accept-encoding']
.split(",")
.some(function (el) {
return ['*','compress', 'gzip', 'deflate'].indexOf(el) != -1;
if (opts.help || opts.h) {
console.error('usage: ecstatic [dir] {options} --port PORT');
console.error('see https://npm.im/ecstatic for more docs');
} else {
http.createServer(ecstatic(dir, opts))
.listen(port, () => {
console.log(`ecstatic serving ${dir} at http://0.0.0.0:${port}`);
})
;
}
// See: https://github.com/jesusabdullah/node-ecstatic/issues/109
function decodePathname(pathname) {
var pieces = pathname.replace(/\\/g,"/").split('/');
return pieces.map(function (piece) {
piece = decodeURIComponent(piece);
if (process.platform === 'win32' && /\\/.test(piece)) {
throw new Error('Invalid forward slash character');
}
return piece;
}).join('/');
}
if (!module.parent) {
var defaults = require('./ecstatic/defaults.json')
var http = require('http'),
opts = require('minimist')(process.argv.slice(2), {
alias: require('./ecstatic/aliases.json'),
default: defaults,
boolean: Object.keys(defaults).filter(function (key) {
return typeof defaults[key] === 'boolean'
})
}),
envPORT = parseInt(process.env.PORT, 10),
port = envPORT > 1024 && envPORT <= 65536 ? envPORT : opts.port || opts.p || 8000,
dir = opts.root || opts._[0] || process.cwd();
if (opts.help || opts.h) {
var u = console.error;
u('usage: ecstatic [dir] {options} --port PORT');
u('see https://npm.im/ecstatic for more docs');
return;
}
http.createServer(ecstatic(dir, opts))
.listen(port, function () {
console.log('ecstatic serving ' + dir + ' at http://0.0.0.0:' + port);
});
}

@@ -9,3 +9,3 @@ {

"cors": false,
"gzip": false,
"gzip": true,
"defaultExt": ".html",

@@ -15,5 +15,5 @@ "handleError": true,

"contentType": "application/octet-stream",
"weakEtags": false,
"weakCompare": false,
"weakEtags": true,
"weakCompare": true,
"handleOptionsMethod": false
}

@@ -1,7 +0,9 @@

module.exports = function (stat, weakEtag) {
var etag = '"' + [stat.ino, stat.size, JSON.stringify(stat.mtime)].join('-') + '"';
'use strict';
module.exports = (stat, weakEtag) => {
let etag = `"${[stat.ino, stat.size, JSON.stringify(stat.mtime)].join('-')}"`;
if (weakEtag) {
etag = 'W/' + etag;
etag = `W/${etag}`;
}
return etag;
}
};

@@ -0,23 +1,25 @@

'use strict';
// This is so you can have options aliasing and defaults in one place.
var defaults = require('./defaults.json');
var aliases = require('./aliases.json')
const defaults = require('./defaults.json');
const aliases = require('./aliases.json');
module.exports = function (opts) {
var autoIndex = defaults.autoIndex,
showDir = defaults.showDir,
showDotfiles = defaults.showDotfiles,
humanReadable = defaults.humanReadable,
si = defaults.si,
cache = defaults.cache,
gzip = defaults.gzip,
defaultExt = defaults.defaultExt,
handleError = defaults.handleError,
headers = {},
serverHeader = defaults.serverHeader,
contentType = defaults.contentType,
mimeTypes,
weakEtags = defaults.weakEtags,
weakCompare = defaults.weakCompare,
handleOptionsMethod = defaults.handleOptionsMethod;
module.exports = (opts) => {
let autoIndex = defaults.autoIndex;
let showDir = defaults.showDir;
let showDotfiles = defaults.showDotfiles;
let humanReadable = defaults.humanReadable;
let si = defaults.si;
let cache = defaults.cache;
let gzip = defaults.gzip;
let defaultExt = defaults.defaultExt;
let handleError = defaults.handleError;
const headers = {};
let serverHeader = defaults.serverHeader;
let contentType = defaults.contentType;
let mimeTypes;
let weakEtags = defaults.weakEtags;
let weakCompare = defaults.weakCompare;
let handleOptionsMethod = defaults.handleOptionsMethod;

@@ -28,4 +30,14 @@ function isDeclared(k) {

function setHeader(str) {
const m = /^(.+?)\s*:\s*(.*)$/.exec(str);
if (!m) {
headers[str] = true;
} else {
headers[m[1]] = m[2];
}
}
if (opts) {
aliases.autoIndex.some(function (k) {
aliases.autoIndex.some((k) => {
if (isDeclared(k)) {

@@ -35,5 +47,6 @@ autoIndex = opts[k];

}
return false;
});
aliases.showDir.some(function (k) {
aliases.showDir.some((k) => {
if (isDeclared(k)) {

@@ -43,5 +56,6 @@ showDir = opts[k];

}
return false;
});
aliases.showDotfiles.some(function (k) {
aliases.showDotfiles.some((k) => {
if (isDeclared(k)) {

@@ -51,5 +65,6 @@ showDotfiles = opts[k];

}
return false;
});
aliases.humanReadable.some(function (k) {
aliases.humanReadable.some((k) => {
if (isDeclared(k)) {

@@ -59,5 +74,6 @@ humanReadable = opts[k];

}
return false;
});
aliases.si.some(function (k) {
aliases.si.some((k) => {
if (isDeclared(k)) {

@@ -67,2 +83,3 @@ si = opts[k];

}
return false;
});

@@ -77,9 +94,7 @@

cache = opts.cache;
} else if (typeof opts.cache === 'number') {
cache = `max-age=${opts.cache}`;
} else if (typeof opts.cache === 'function') {
cache = opts.cache;
}
else if (typeof opts.cache === 'number') {
cache = 'max-age=' + opts.cache;
}
else if (typeof opts.cache === 'function') {
cache = opts.cache
}
}

@@ -91,3 +106,3 @@

aliases.handleError.some(function (k) {
aliases.handleError.some((k) => {
if (isDeclared(k)) {

@@ -97,5 +112,6 @@ handleError = opts[k];

}
return false;
});
aliases.cors.forEach(function(k) {
aliases.cors.forEach((k) => {
if (isDeclared(k) && k) {

@@ -108,22 +124,17 @@ handleOptionsMethod = true;

aliases.headers.forEach(function (k) {
if (!isDeclared(k)) return;
if (Array.isArray(opts[k])) {
opts[k].forEach(setHeader);
aliases.headers.forEach((k) => {
if (isDeclared(k)) {
if (Array.isArray(opts[k])) {
opts[k].forEach(setHeader);
} else if (opts[k] && typeof opts[k] === 'object') {
Object.keys(opts[k]).forEach((key) => {
headers[key] = opts[k][key];
});
} else {
setHeader(opts[k]);
}
}
else if (opts[k] && typeof opts[k] === 'object') {
Object.keys(opts[k]).forEach(function (key) {
headers[key] = opts[k][key];
});
}
else setHeader(opts[k]);
function setHeader (str) {
var m = /^(.+?)\s*:\s*(.*)$/.exec(str)
if (!m) headers[str] = true
else headers[m[1]] = m[2]
}
});
aliases.serverHeader.some(function (k) {
aliases.serverHeader.some((k) => {
if (isDeclared(k)) {

@@ -133,5 +144,6 @@ serverHeader = opts[k];

}
return false;
});
aliases.contentType.some(function (k) {
aliases.contentType.some((k) => {
if (isDeclared(k)) {

@@ -141,5 +153,6 @@ contentType = opts[k];

}
return false;
});
aliases.mimeType.some(function (k) {
aliases.mimeType.some((k) => {
if (isDeclared(k)) {

@@ -149,5 +162,6 @@ mimeTypes = opts[k];

}
return false;
});
aliases.weakEtags.some(function (k) {
aliases.weakEtags.some((k) => {
if (isDeclared(k)) {

@@ -157,5 +171,6 @@ weakEtags = opts[k];

}
return false;
});
aliases.weakCompare.some(function (k) {
aliases.weakCompare.some((k) => {
if (isDeclared(k)) {

@@ -165,5 +180,6 @@ weakCompare = opts[k];

}
return false;
});
aliases.handleOptionsMethod.some(function (k) {
aliases.handleOptionsMethod.some((k) => {
if (isDeclared(k)) {

@@ -173,2 +189,3 @@ handleOptionsMethod = handleOptionsMethod || opts[k];

}
return false;
});

@@ -178,20 +195,20 @@ }

return {
cache: cache,
autoIndex: autoIndex,
showDir: showDir,
showDotfiles: showDotfiles,
humanReadable: humanReadable,
si: si,
defaultExt: defaultExt,
cache,
autoIndex,
showDir,
showDotfiles,
humanReadable,
si,
defaultExt,
baseDir: (opts && opts.baseDir) || '/',
gzip: gzip,
handleError: handleError,
headers: headers,
serverHeader: serverHeader,
contentType: contentType,
mimeTypes: mimeTypes,
weakEtags: weakEtags,
weakCompare: weakCompare,
handleOptionsMethod: handleOptionsMethod
gzip,
handleError,
headers,
serverHeader,
contentType,
mimeTypes,
weakEtags,
weakCompare,
handleOptionsMethod,
};
};

@@ -1,48 +0,63 @@

var styles = require('./styles'),
supportedIcons = styles.icons,
css = styles.css,
permsToString = require('./perms-to-string'),
sizeToString = require('./size-to-string'),
sortFiles = require('./sort-files'),
fs = require('fs'),
path = require('path'),
he = require('he'),
etag = require('../etag'),
url = require('url'),
status = require('../status-handlers');
'use strict';
module.exports = function (opts, stat) {
const styles = require('./styles');
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
var cache = opts.cache,
root = path.resolve(opts.root),
baseDir = opts.baseDir,
humanReadable = opts.humanReadable,
handleError = opts.handleError,
showDotfiles = opts.showDotfiles,
si = opts.si,
weakEtags = opts.weakEtags;
const cache = opts.cache;
const root = path.resolve(opts.root);
const baseDir = opts.baseDir;
const humanReadable = opts.humanReadable;
const handleError = opts.handleError;
const showDotfiles = opts.showDotfiles;
const si = opts.si;
const weakEtags = opts.weakEtags;
return function middleware (req, res, next) {
return function middleware(req, res, next) {
// Figure out the path for the file from the given url
var parsed = url.parse(req.url),
pathname = decodeURIComponent(parsed.pathname),
dir = path.normalize(
path.join(root,
path.relative(
path.join('/', baseDir),
pathname
)
)
);
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, function (err, stat) {
if (err) {
return handleError ? status[500](res, next, { error: err }) : next();
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, function (err, files) {
if (err) {
return handleError ? status[500](res, next, { error: err }) : next();
fs.readdir(dir, (readErr, _files) => {
let files = _files;
if (readErr) {
if (handleError) {
status[500](res, next, { error: readErr });
} else {
next();
}
return;
}

@@ -52,5 +67,3 @@

if (!showDotfiles) {
files = files.filter(function(filename){
return filename.slice(0,1) !== '.';
});
files = files.filter(filename => filename.slice(0, 1) !== '.');
}

@@ -63,28 +76,6 @@

sortFiles(dir, files, function (lolwuts, dirs, files) {
// 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) {
return fs.stat(path.join(dir, '..'), function (err, s) {
if (err) {
return handleError ? status[500](res, next, { error: err }) : next();
}
dirs.unshift([ '..', s ]);
render(dirs, files, lolwuts);
});
}
render(dirs, files, lolwuts);
});
function render(dirs, files, lolwuts) {
function render(dirs, renderFiles, lolwuts) {
// each entry in the array is a [name, stat] tuple
var html = [
let html = `${[
'<!doctype html>',

@@ -95,47 +86,46 @@ '<html>',

' <meta name="viewport" content="width=device-width">',
' <title>Index of ' + he.encode(pathname) +'</title>',
' <style type="text/css">' + css + '</style>',
` <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';
`<h1>Index of ${he.encode(pathname)}</h1>`,
].join('\n')}\n`;
html += '<table>';
var failed = false;
var writeRow = function (file, i) {
const failed = false;
const writeRow = (file) => {
// render a row given a [name, stat] tuple
var isDir = file[1].isDirectory && file[1].isDirectory();
var href = parsed.pathname.replace(/\/$/, '') + '/' + encodeURIComponent(file[0]);
const isDir = file[1].isDirectory && file[1].isDirectory();
let href = `${parsed.pathname.replace(/\/$/, '')}/${encodeURIComponent(file[0])}`;
// append trailing slash and query for dir entry
if (isDir) {
href += '/' + he.encode((parsed.search)? parsed.search:'');
href += `/${he.encode((parsed.search) ? parsed.search : '')}`;
}
var displayName = he.encode(file[0]) + ((isDir)? '/':'');
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}`;
var ext = file[0].split('.').pop();
var iconClass = 'icon-' + (isDir ? '_blank' : (supportedIcons[ext] ? ext : '_page'));
// TODO: use stylessheets?
html += '<tr>' +
'<td class="icon-parent"><i class="' + iconClass + '"></i></td>' +
'<td class="perms"><code>(' + permsToString(file[1]) + ')</code></td>' +
'<td class="file-size"><code>' + sizeToString(file[1], humanReadable, si) + '</code></td>' +
'<td class="display-name"><a href="' + href + '">' + displayName + '</a></td>' +
html += `${'<tr>' +
'<td><i class="icon '}${iconClass}"></i></td>` +
`<td class="perms"><code>(${permsToString(file[1])})</code></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(function (a, b) { return a[0].toString().localeCompare(b[0].toString()); }).forEach(writeRow);
files.sort(function (a, b) { return a.toString().localeCompare(b.toString()); }).forEach(writeRow);
lolwuts.sort(function (a, b) { return a[0].toString().localeCompare(b[0].toString()); }).forEach(writeRow);
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/jfhbrook/node-ecstatic">ecstatic</a> ' +
'server running @ ' +
he.encode(req.headers.host || '') + '</address>\n' +
html += `<br><address>Node.js ${
process.version
}/ <a href="https://github.com/jfhbrook/node-ecstatic">ecstatic</a> ` +
`server running @ ${
he.encode(req.headers.host || '')}</address>\n` +
'</body></html>'

@@ -145,6 +135,34 @@ ;

if (!failed) {
res.writeHead(200, { "Content-Type": "text/html" });
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);
}
});
});

@@ -154,31 +172,1 @@ });

};
// given a file's stat, return the size of it in string
// humanReadable: (boolean) whether to result is human readable
// si: (boolean) whether to use si (1k = 1000), otherwise 1k = 1024
// adopted from http://stackoverflow.com/a/14919494/665507
function sizeToString(stat, humanReadable, si) {
if (stat.isDirectory && stat.isDirectory()) {
return '';
}
var sizeString = '';
var bytes = stat.size;
var threshold = si ? 1000 : 1024;
if (!humanReadable || bytes < threshold) {
return bytes + 'B';
}
var units = [ 'k','M','G','T','P','E','Z','Y' ];
var u = -1;
do {
bytes /= threshold;
++u;
} while (bytes >= threshold);
var b = bytes.toFixed(1);
if (isNaN(b)) b = '??';
return b + units[u];
}

@@ -0,3 +1,4 @@

'use strict';
module.exports = function permsToString(stat) {
if (!stat.isDirectory || !stat.mode) {

@@ -7,18 +8,16 @@ return '???!!!???';

var dir = stat.isDirectory() ? 'd' : '-',
mode = stat.mode.toString(8);
const dir = stat.isDirectory() ? 'd' : '-';
const mode = stat.mode.toString(8);
return dir + mode.slice(-3).split('').map(function (n) {
return [
'---',
'--x',
'-w-',
'-wx',
'r--',
'r-x',
'rw-',
'rwx'
][parseInt(n, 10)];
}).join('');
return dir + mode.slice(-3).split('').map(n => [
'---',
'--x',
'-w-',
'-wx',
'r--',
'r-x',
'rw-',
'rwx',
][parseInt(n, 10)]).join('');
};

@@ -0,1 +1,3 @@

'use strict';
// given a file's stat, return the size of it in string

@@ -10,18 +12,17 @@ // humanReadable: (boolean) whether to result is human readable

var sizeString = '';
var bytes = stat.size;
var threshold = si ? 1000 : 1024;
let bytes = stat.size;
const threshold = si ? 1000 : 1024;
if (!humanReadable || bytes < threshold) {
return bytes + 'B';
return `${bytes}B`;
}
var units = [ 'k','M','G','T','P','E','Z','Y' ];
var u = -1;
const units = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y'];
let u = -1;
do {
bytes /= threshold;
++u;
bytes /= threshold;
u += 1;
} while (bytes >= threshold);
var b = bytes.toFixed(1);
let b = bytes.toFixed(1);
if (isNaN(b)) b = '??';

@@ -28,0 +29,0 @@

@@ -1,4 +0,6 @@

var fs = require('fs'),
path = require('path');
'use strict';
const fs = require('fs');
const path = require('path');
module.exports = function sortByIsDirectory(dir, paths, cb) {

@@ -8,24 +10,24 @@ // take the listing file names in `dir`

// of the array a [name, stat] tuple
var pending = paths.length,
errs = [],
dirs = [],
files = [];
let pending = paths.length;
const errs = [];
const dirs = [];
const files = [];
if (!pending) {
return cb(errs, dirs, files);
cb(errs, dirs, files);
return;
}
paths.forEach(function (file) {
fs.stat(path.join(dir, file), function (err, s) {
paths.forEach((file) => {
fs.stat(path.join(dir, file), (err, s) => {
if (err) {
errs.push([file, err]);
}
else if (s.isDirectory()) {
} else if (s.isDirectory()) {
dirs.push([file, s]);
}
else {
} else {
files.push([file, s]);
}
if (--pending === 0) {
pending -= 1;
if (pending === 0) {
cb(errs, dirs, files);

@@ -32,0 +34,0 @@ }

@@ -1,8 +0,8 @@

var fs = require('fs'),
icons = require('./icons.json'),
path = require('path');
'use strict';
var IMG_SIZE = 16;
const icons = require('./icons.json');
var css = 'td.icon-parent { height: ' + IMG_SIZE + 'px; width: ' + IMG_SIZE + 'px; }\n';
const IMG_SIZE = 16;
let css = `i.icon { display: block; height: ${IMG_SIZE}px; width: ${IMG_SIZE}px; }\n`;
css += 'td.perms {}\n';

@@ -12,6 +12,5 @@ css += 'td.file-size { text-align: right; padding-left: 1em; }\n';

Object.keys(icons).forEach(function(key) {
css += 'i.icon-' + key + ' {\n';
css += ' display: block; width: 100%; height: 100%; background-repeat: no-repeat;\n';
css += ' background: url("data:image/png;base64,' + icons[key] + '");\n';
Object.keys(icons).forEach((key) => {
css += `i.icon-${key} {\n`;
css += ` background: url("data:image/png;base64,${icons[key]}");\n`;
css += '}\n\n';

@@ -18,0 +17,0 @@ });

@@ -1,5 +0,7 @@

var he = require('he');
'use strict';
const he = require('he');
// not modified
exports['304'] = function (res, next) {
exports['304'] = (res) => {
res.statusCode = 304;

@@ -10,22 +12,18 @@ res.end();

// access denied
exports['403'] = function (res, next) {
exports['403'] = (res, next) => {
res.statusCode = 403;
if (typeof next === "function") {
if (typeof next === 'function') {
next();
} else if (res.writable) {
res.setHeader('content-type', 'text/plain');
res.end('ACCESS DENIED');
}
else {
if (res.writable) {
res.setHeader('content-type', 'text/plain');
res.end('ACCESS DENIED');
}
}
};
// disallowed method
exports['405'] = function (res, next, opts) {
exports['405'] = (res, next, opts) => {
res.statusCode = 405;
if (typeof next === "function") {
if (typeof next === 'function') {
next();
}
else {
} else {
res.setHeader('allow', (opts && opts.allow) || 'GET, HEAD');

@@ -37,34 +35,28 @@ res.end();

// not found
exports['404'] = function (res, next) {
exports['404'] = (res, next) => {
res.statusCode = 404;
if (typeof next === "function") {
if (typeof next === 'function') {
next();
} else if (res.writable) {
res.setHeader('content-type', 'text/plain');
res.end('File not found. :(');
}
else {
if (res.writable) {
res.setHeader('content-type', 'text/plain');
res.end('File not found. :(');
}
}
};
exports['416'] = function (res, next) {
exports['416'] = (res, next) => {
res.statusCode = 416;
if (typeof next === "function") {
if (typeof next === 'function') {
next();
} else if (res.writable) {
res.setHeader('content-type', 'text/plain');
res.end('Requested range not satisfiable');
}
else {
if (res.writable) {
res.setHeader('content-type', 'text/plain');
res.end('Requested range not satisfiable');
}
}
};
// flagrant error
exports['500'] = function (res, next, opts) {
exports['500'] = (res, next, opts) => {
res.statusCode = 500;
res.setHeader('content-type', 'text/html');
var error = String(opts.error.stack || opts.error || "No specified error"),
html = [
const error = String(opts.error.stack || opts.error || 'No specified error');
const html = `${[
'<!doctype html>',

@@ -78,7 +70,7 @@ '<html>',

' <p>',
' ' + he.encode(error),
` ${he.encode(error)}`,
' </p>',
' </body>',
'</html>'
].join('\n') + '\n';
'</html>',
].join('\n')}\n`;
res.end(html);

@@ -88,7 +80,7 @@ };

// bad request
exports['400'] = function (res, next, opts) {
exports['400'] = (res, next, opts) => {
res.statusCode = 400;
res.setHeader('content-type', 'text/html');
var error = opts && opts.error ? String(opts.error) : 'Malformed request.',
html = [
const error = opts && opts.error ? String(opts.error) : 'Malformed request.';
const html = `${[
'<!doctype html>',

@@ -102,8 +94,8 @@ '<html>',

' <p>',
' ' + he.encode(error),
` ${he.encode(error)}`,
' </p>',
' </body>',
'</html>'
].join('\n') + '\n';
'</html>',
].join('\n')}\n`;
res.end(html);
};

@@ -5,3 +5,3 @@ {

"description": "A simple static file server middleware that works with both Express and Flatiron",
"version": "2.2.1",
"version": "3.0.0",
"homepage": "https://github.com/jfhbrook/node-ecstatic",

@@ -14,2 +14,4 @@ "repository": {

"scripts": {
"fix": "eslint --fix ./lib/ ./example/ ./test",
"pretest": "eslint ./lib/ ./example/ ./test",
"test": "tap --coverage test/*.js",

@@ -29,3 +31,3 @@ "posttest": "tap --coverage-report=text-lcov | codecov"

"he": "^1.1.1",
"mime": "^1.2.11",
"mime": "^1.4.0",
"minimist": "^1.1.0",

@@ -37,2 +39,5 @@ "url-join": "^2.0.2"

"eol": "^0.9.0",
"eslint": "^3.19.0",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-plugin-import": "^2.3.0",
"express": "^4.12.3",

@@ -39,0 +44,0 @@ "mkdirp": "^0.5.0",

@@ -13,8 +13,15 @@ # Ecstatic [![build status](https://secure.travis-ci.org/jfhbrook/node-ecstatic.png)](http://travis-ci.org/jfhbrook/node-ecstatic) [![codecov.io](https://codecov.io/github/jfhbrook/node-ecstatic/coverage.svg?branch=master)](https://codecov.io/github/jfhbrook/node-ecstatic?branch=master)

``` js
var http = require('http');
var express = require('express');
var ecstatic = require('ecstatic');
'use strict';
var app = express();
app.use(ecstatic({ root: __dirname + '/public' }));
const express = require('express');
const ecstatic = require('../lib/ecstatic');
const http = require('http');
const app = express();
app.use(ecstatic({
root: `${__dirname}/public`,
showdir: true,
}));
http.createServer(app).listen(8080);

@@ -28,9 +35,14 @@

``` js
var http = require('http');
var ecstatic = require('ecstatic');
'use strict';
http.createServer(
ecstatic({ root: __dirname + '/public' })
).listen(8080);
const http = require('http');
const ecstatic = require('../lib/ecstatic')({
root: `${__dirname}/public`,
showDir: true,
autoIndex: true,
});
http.createServer(ecstatic).listen(8080);
console.log('Listening on :8080');

@@ -78,20 +90,21 @@ ```

```js
var opts = {
root : __dirname + '/public',
port : 8000,
baseDir : '/',
cache : 3600,
showDir : true,
showDotfiles : true,
autoIndex : false,
humanReadable : true,
headers : {},
si : false,
defaultExt : 'html',
gzip : false,
serverHeader : true,
contentType : 'application/octet-stream',
mimeTypes : undefined,
handleOptionsMethod: false
}
const opts = {
root: path.join(__dirname, 'public'),
baseDir: '/',
autoIndex: true,
showDir: true,
showDotfiles: true,
humanReadable: true,
si: false,
cache: 'max-age=3600',
cors: false,
gzip: true,
defaultExt: 'html',
handleError: true,
serverHeader: true,
contentType: 'application/octet-stream',
weakEtags: true,
weakCompare: true,
handleOptionsMethod: false,
}
```

@@ -188,7 +201,9 @@

### `opts.gzip`
### `--gzip`
### `--no-gzip`
Set `opts.gzip === true` in order to turn on "gzip mode," wherein ecstatic will
serve `./public/some-file.js.gz` in place of `./public/some-file.js` when the
gzipped version exists and ecstatic determines that the behavior is appropriate.
By default, ecstatic will serve `./public/some-file.js.gz` in place of
`./public/some-file.js` when the gzipped version exists and ecstatic determines
that the behavior is appropriate. If `./public/some-file.js.gz` is not valid
gzip, this will fall back to `./public/some-file.js`. You can turn this off
with `opts.gzip === false`.

@@ -198,4 +213,4 @@ ### `opts.serverHeader`

Set `opts.serverHeader` to false in order to turn off setting the `Server` header
on all responses served by ecstatic.
Set `opts.serverHeader` to false in order to turn off setting the `Server`
header on all responses served by ecstatic.

@@ -211,4 +226,6 @@ ### `opts.contentType`

Add new or override one or more mime-types. This affects the HTTP Content-Type header.
Can either be a path to a [`.types`](http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types) file or an object hash of type(s).
Add new or override one or more mime-types. This affects the HTTP Content-Type
header. Can either be a path to a
[`.types`](http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)
file or an object hash of type(s).

@@ -219,14 +236,17 @@ ecstatic({ mimeType: { 'mime-type': ['file_extension', 'file_extension'] } })

Turn **off** handleErrors to allow fall-through with `opts.handleError === false`, Defaults to **true**.
Turn **off** handleErrors to allow fall-through with
`opts.handleError === false`, Defaults to **true**.
### `opts.weakEtags`
### `--weak-etags`
### `--no-weak-etags`
Set `opts.weakEtags` to true in order to generate weak etags instead of strong etags. Defaults to **false**. See `opts.weakCompare` as well.
Set `opts.weakEtags` to false in order to generate strong etags instead of
weak etags. Defaults to **true**. See `opts.weakCompare` as well.
### `opts.weakCompare`
### `--weak-compare`
### `--no-weak-compare`
Turn **on** weakCompare to allow the weak comparison function for etag validation. Defaults to **false**.
See https://www.ietf.org/rfc/rfc2616.txt Section 13.3.3 for more details.
Turn off weakCompare to disable the weak comparison function for etag
validation. Defaults to **true**. See
https://www.ietf.org/rfc/rfc2616.txt Section 13.3.3 for more details.

@@ -233,0 +253,0 @@ ### `opts.handleOptionsMethod`

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc