mirror of
https://github.com/arnaucube/socketioMEANseed.git
synced 2026-02-07 11:46:48 +01:00
init
This commit is contained in:
371
www/node_modules/ecstatic/lib/ecstatic.js
generated
vendored
Executable file
371
www/node_modules/ecstatic/lib/ecstatic.js
generated
vendored
Executable file
@@ -0,0 +1,371 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
var path = require('path'),
|
||||
fs = require('fs'),
|
||||
url = require('url'),
|
||||
mime = require('mime'),
|
||||
urlJoin = require('url-join'),
|
||||
showDir = require('./ecstatic/showdir'),
|
||||
version = JSON.parse(
|
||||
fs.readFileSync(__dirname + '/../package.json').toString()
|
||||
).version,
|
||||
status = require('./ecstatic/status-handlers'),
|
||||
generateEtag = require('./ecstatic/etag'),
|
||||
optsParser = require('./ecstatic/opts');
|
||||
|
||||
var ecstatic = module.exports = function (dir, options) {
|
||||
if (typeof dir !== 'string') {
|
||||
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;
|
||||
|
||||
opts.root = dir;
|
||||
if (defaultExt && /^\./.test(defaultExt)) defaultExt = defaultExt.replace(/^\./, '');
|
||||
|
||||
// Support hashes and .types files in mimeTypes @since 0.8
|
||||
if (opts.mimeTypes) {
|
||||
try {
|
||||
// You can pass a JSON blob here---useful for CLI use
|
||||
opts.mimeTypes = JSON.parse(opts.mimeTypes);
|
||||
} catch (e) {}
|
||||
if (typeof opts.mimeTypes === 'string') {
|
||||
mime.load(opts.mimeTypes);
|
||||
}
|
||||
else if (typeof opts.mimeTypes === 'object') {
|
||||
mime.define(opts.mimeTypes);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return function middleware (req, res, next) {
|
||||
|
||||
// Strip any null bytes from the url
|
||||
while(req.url.indexOf('%00') !== -1) {
|
||||
req.url = req.url.replace(/\%00/g, '');
|
||||
}
|
||||
// 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);
|
||||
}
|
||||
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';
|
||||
|
||||
if(serverHeader !== false) {
|
||||
// Set common headers.
|
||||
res.setHeader('server', 'ecstatic-'+version);
|
||||
}
|
||||
Object.keys(headers).forEach(function (key) {
|
||||
res.setHeader(key, headers[key])
|
||||
})
|
||||
|
||||
if (req.method === 'OPTIONS' && handleOptionsMethod) {
|
||||
return res.end();
|
||||
}
|
||||
|
||||
// TODO: This check is broken, which causes the 403 on the
|
||||
// expected 404.
|
||||
if (file.slice(0, root.length) !== root) {
|
||||
return status[403](res, next);
|
||||
}
|
||||
|
||||
if (req.method && (req.method !== 'GET' && req.method !== 'HEAD' )) {
|
||||
return status[405](res, next);
|
||||
}
|
||||
|
||||
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:'')
|
||||
}, res, next);
|
||||
}
|
||||
else {
|
||||
// Try to serve default ./404.html
|
||||
middleware({
|
||||
url: (handleError ? ('/' + path.join(baseDir, '404.' + defaultExt)) : req.url),
|
||||
statusCode: 404
|
||||
}, res, next);
|
||||
}
|
||||
}
|
||||
else if (err) {
|
||||
status[500](res, next, { error: err });
|
||||
}
|
||||
else if (stat.isDirectory()) {
|
||||
// 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)
|
||||
}, 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);
|
||||
}
|
||||
|
||||
status[404](res, next);
|
||||
|
||||
}
|
||||
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()) {
|
||||
file = gzipped;
|
||||
return serve(stat);
|
||||
} 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;
|
||||
|
||||
if (contentType) {
|
||||
charSet = mime.charsets.lookup(contentType, 'utf-8');
|
||||
if (charSet) {
|
||||
contentType += '; charset=' + charSet;
|
||||
}
|
||||
}
|
||||
|
||||
if (path.extname(file) === '.gz') {
|
||||
res.setHeader('Content-Encoding', 'gzip');
|
||||
|
||||
// strip gz ending and lookup mime type
|
||||
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;
|
||||
if (start > end || isNaN(start) || isNaN(end)) {
|
||||
return status['416'](res, next);
|
||||
}
|
||||
var fstream = fs.createReadStream(file, {start: start, end: end});
|
||||
fstream.on('error', function (err) {
|
||||
status['500'](res, next, { error: err });
|
||||
});
|
||||
res.on('close', function () {
|
||||
fstream.destroy();
|
||||
});
|
||||
res.writeHead(206, {
|
||||
'Content-Range': 'bytes ' + start + '-' + end + '/' + total,
|
||||
'Accept-Ranges': 'bytes',
|
||||
'Content-Length': chunksize,
|
||||
'Content-Type': contentType
|
||||
});
|
||||
fstream.pipe(res);
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Helper for this, with default headers.
|
||||
var lastModified = (new Date(stat.mtime)).toUTCString(),
|
||||
etag = generateEtag(stat, weakEtags);
|
||||
res.setHeader('last-modified', lastModified);
|
||||
res.setHeader('etag', etag);
|
||||
|
||||
if (typeof cache === 'function') {
|
||||
var requestSpecificCache = cache(pathname);
|
||||
if (typeof requestSpecificCache === 'number') {
|
||||
requestSpecificCache = 'max-age=' + requestSpecificCache;
|
||||
}
|
||||
res.setHeader('cache-control', requestSpecificCache);
|
||||
} else {
|
||||
res.setHeader('cache-control', cache);
|
||||
}
|
||||
|
||||
// Return a 304 if necessary
|
||||
if (shouldReturn304(req, lastModified, etag)) {
|
||||
return status[304](res, next);
|
||||
}
|
||||
|
||||
res.setHeader('content-length', stat.size);
|
||||
res.setHeader('content-type', contentType);
|
||||
|
||||
// set the response statusCode if we have a request statusCode.
|
||||
// This only can happen if we have a 404 with some kind of 404.html
|
||||
// In all other cases where we have a file we serve the 200
|
||||
res.statusCode = req.statusCode || 200;
|
||||
|
||||
if (req.method === "HEAD") {
|
||||
return res.end();
|
||||
}
|
||||
|
||||
var stream = fs.createReadStream(file);
|
||||
|
||||
stream.pipe(res);
|
||||
stream.on('error', function (err) {
|
||||
status['500'](res, next, { error: err });
|
||||
});
|
||||
}
|
||||
|
||||
function shouldReturn304(req, serverLastModified, serverEtag) {
|
||||
if (!req || !req.headers) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var clientModifiedSince = req.headers['if-modified-since'],
|
||||
clientEtag = req.headers['if-none-match'];
|
||||
|
||||
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 {
|
||||
var 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;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
ecstatic.version = version;
|
||||
ecstatic.showDir = showDir;
|
||||
|
||||
// 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;
|
||||
})
|
||||
;
|
||||
}
|
||||
|
||||
// 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);
|
||||
});
|
||||
}
|
||||
34
www/node_modules/ecstatic/lib/ecstatic/aliases.json
generated
vendored
Normal file
34
www/node_modules/ecstatic/lib/ecstatic/aliases.json
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"autoIndex": [ "autoIndex", "autoindex" ],
|
||||
"showDir": [ "showDir", "showdir" ],
|
||||
"showDotfiles": ["showDotfiles", "showdotfiles"],
|
||||
"humanReadable": [ "humanReadable", "humanreadable", "human-readable" ],
|
||||
"si": [ "si", "index" ],
|
||||
"handleError": [ "handleError", "handleerror" ],
|
||||
"cors": [ "cors", "CORS" ],
|
||||
"headers": [ "H", "header", "headers" ],
|
||||
"serverHeader": [ "serverHeader", "serverheader", "server-header" ],
|
||||
"contentType": [ "contentType", "contenttype", "content-type" ],
|
||||
"mimeType": [
|
||||
"mimetype",
|
||||
"mimetypes",
|
||||
"mimeType",
|
||||
"mimeTypes",
|
||||
"mime-type",
|
||||
"mime-types",
|
||||
"mime-Type",
|
||||
"mime-Types"
|
||||
],
|
||||
"weakEtags": [ "weakEtags", "weaketags", "weak-etags" ],
|
||||
"weakCompare": [
|
||||
"weakcompare",
|
||||
"weakCompare",
|
||||
"weak-compare",
|
||||
"weak-Compare"
|
||||
],
|
||||
"handleOptionsMethod": [
|
||||
"handleOptionsMethod",
|
||||
"handleoptionsmethod",
|
||||
"handle-options-method"
|
||||
]
|
||||
}
|
||||
17
www/node_modules/ecstatic/lib/ecstatic/defaults.json
generated
vendored
Normal file
17
www/node_modules/ecstatic/lib/ecstatic/defaults.json
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"autoIndex": true,
|
||||
"showDir": true,
|
||||
"showDotfiles": true,
|
||||
"humanReadable": true,
|
||||
"si": false,
|
||||
"cache": "max-age=3600",
|
||||
"cors": false,
|
||||
"gzip": false,
|
||||
"defaultExt": ".html",
|
||||
"handleError": true,
|
||||
"serverHeader": true,
|
||||
"contentType": "application/octet-stream",
|
||||
"weakEtags": false,
|
||||
"weakCompare": false,
|
||||
"handleOptionsMethod": false
|
||||
}
|
||||
7
www/node_modules/ecstatic/lib/ecstatic/etag.js
generated
vendored
Normal file
7
www/node_modules/ecstatic/lib/ecstatic/etag.js
generated
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
module.exports = function (stat, weakEtag) {
|
||||
var etag = '"' + [stat.ino, stat.size, JSON.stringify(stat.mtime)].join('-') + '"';
|
||||
if (weakEtag) {
|
||||
etag = 'W/' + etag;
|
||||
}
|
||||
return etag;
|
||||
}
|
||||
180
www/node_modules/ecstatic/lib/ecstatic/opts.js
generated
vendored
Normal file
180
www/node_modules/ecstatic/lib/ecstatic/opts.js
generated
vendored
Normal file
@@ -0,0 +1,180 @@
|
||||
// This is so you can have options aliasing and defaults in one place.
|
||||
|
||||
var defaults = require('./defaults.json');
|
||||
var 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;
|
||||
|
||||
function isDeclared(k) {
|
||||
return typeof opts[k] !== 'undefined' && opts[k] !== null;
|
||||
}
|
||||
|
||||
if (opts) {
|
||||
aliases.autoIndex.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
autoIndex = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.showDir.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
showDir = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.showDotfiles.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
showDotfiles = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.humanReadable.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
humanReadable = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.si.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
si = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
if (opts.defaultExt && typeof opts.defaultExt === 'string') {
|
||||
defaultExt = opts.defaultExt;
|
||||
}
|
||||
|
||||
if (typeof opts.cache !== 'undefined' && opts.cache !== null) {
|
||||
if (typeof opts.cache === 'string') {
|
||||
cache = opts.cache;
|
||||
}
|
||||
else if (typeof opts.cache === 'number') {
|
||||
cache = 'max-age=' + opts.cache;
|
||||
}
|
||||
else if (typeof opts.cache === 'function') {
|
||||
cache = opts.cache
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof opts.gzip !== 'undefined' && opts.gzip !== null) {
|
||||
gzip = opts.gzip;
|
||||
}
|
||||
|
||||
aliases.handleError.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
handleError = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.cors.forEach(function(k) {
|
||||
if (isDeclared(k) && k) {
|
||||
handleOptionsMethod = true;
|
||||
headers['Access-Control-Allow-Origin'] = '*';
|
||||
headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since';
|
||||
}
|
||||
});
|
||||
|
||||
aliases.headers.forEach(function (k) {
|
||||
if (!isDeclared(k)) return;
|
||||
if (Array.isArray(opts[k])) {
|
||||
opts[k].forEach(setHeader);
|
||||
}
|
||||
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) {
|
||||
if (isDeclared(k)) {
|
||||
serverHeader = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.contentType.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
contentType = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.mimeType.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
mimeTypes = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.weakEtags.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
weakEtags = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.weakCompare.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
weakCompare = opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
aliases.handleOptionsMethod.some(function (k) {
|
||||
if (isDeclared(k)) {
|
||||
handleOptionsMethod = handleOptionsMethod || opts[k];
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
cache: cache,
|
||||
autoIndex: autoIndex,
|
||||
showDir: showDir,
|
||||
showDotfiles: showDotfiles,
|
||||
humanReadable: humanReadable,
|
||||
si: si,
|
||||
defaultExt: defaultExt,
|
||||
baseDir: (opts && opts.baseDir) || '/',
|
||||
gzip: gzip,
|
||||
handleError: handleError,
|
||||
headers: headers,
|
||||
serverHeader: serverHeader,
|
||||
contentType: contentType,
|
||||
mimeTypes: mimeTypes,
|
||||
weakEtags: weakEtags,
|
||||
weakCompare: weakCompare,
|
||||
handleOptionsMethod: handleOptionsMethod
|
||||
};
|
||||
};
|
||||
224
www/node_modules/ecstatic/lib/ecstatic/showdir.js
generated
vendored
Normal file
224
www/node_modules/ecstatic/lib/ecstatic/showdir.js
generated
vendored
Normal file
@@ -0,0 +1,224 @@
|
||||
var ecstatic = require('../ecstatic'),
|
||||
fs = require('fs'),
|
||||
path = require('path'),
|
||||
he = require('he'),
|
||||
etag = require('./etag'),
|
||||
url = require('url'),
|
||||
status = require('./status-handlers');
|
||||
|
||||
module.exports = function (opts, stat) {
|
||||
// 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;
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
fs.stat(dir, function (err, stat) {
|
||||
if (err) {
|
||||
return handleError ? status[500](res, next, { error: err }) : next();
|
||||
}
|
||||
|
||||
// files are the listing of dir
|
||||
fs.readdir(dir, function (err, files) {
|
||||
if (err) {
|
||||
return handleError ? status[500](res, next, { error: err }) : next();
|
||||
}
|
||||
|
||||
// Optionally exclude dotfiles from directory listing.
|
||||
if (!showDotfiles) {
|
||||
files = files.filter(function(filename){
|
||||
return 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);
|
||||
|
||||
sortByIsDirectory(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 sortByIsDirectory(paths, cb) {
|
||||
// take the listing file names in `dir`
|
||||
// returns directory and file array, each entry is
|
||||
// of the array a [name, stat] tuple
|
||||
var pending = paths.length,
|
||||
errs = [],
|
||||
dirs = [],
|
||||
files = [];
|
||||
|
||||
if (!pending) {
|
||||
return cb(errs, dirs, files);
|
||||
}
|
||||
|
||||
paths.forEach(function (file) {
|
||||
fs.stat(path.join(dir, file), function (err, s) {
|
||||
if (err) {
|
||||
errs.push([file, err]);
|
||||
}
|
||||
else if (s.isDirectory()) {
|
||||
dirs.push([file, s]);
|
||||
}
|
||||
else {
|
||||
files.push([file, s]);
|
||||
}
|
||||
|
||||
if (--pending === 0) {
|
||||
cb(errs, dirs, files);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function render(dirs, files, lolwuts) {
|
||||
// each entry in the array is a [name, stat] tuple
|
||||
|
||||
// TODO: use stylessheets?
|
||||
var html = [
|
||||
'<!doctype html>',
|
||||
'<html>',
|
||||
' <head>',
|
||||
' <meta charset="utf-8">',
|
||||
' <meta name="viewport" content="width=device-width">',
|
||||
' <title>Index of ' + he.encode(pathname) +'</title>',
|
||||
' </head>',
|
||||
' <body>',
|
||||
'<h1>Index of ' + he.encode(pathname) + '</h1>'
|
||||
].join('\n') + '\n';
|
||||
|
||||
html += '<table>';
|
||||
|
||||
var failed = false;
|
||||
var writeRow = function (file, i) {
|
||||
// render a row given a [name, stat] tuple
|
||||
var isDir = file[1].isDirectory && file[1].isDirectory();
|
||||
var href = parsed.pathname.replace(/\/$/, '') + '/' + encodeURIComponent(file[0]);
|
||||
|
||||
// append trailing slash and query for dir entry
|
||||
if (isDir) {
|
||||
href += '/' + he.encode((parsed.search)? parsed.search:'');
|
||||
}
|
||||
|
||||
var displayName = he.encode(file[0]) + ((isDir)? '/':'');
|
||||
|
||||
// TODO: use stylessheets?
|
||||
html += '<tr>' +
|
||||
'<td><code>(' + permsToString(file[1]) + ')</code></td>' +
|
||||
'<td style="text-align: right; padding-left: 1em"><code>' + sizeToString(file[1], humanReadable, si) + '</code></td>' +
|
||||
'<td style="padding-left: 1em"><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);
|
||||
|
||||
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' +
|
||||
'</body></html>'
|
||||
;
|
||||
|
||||
if (!failed) {
|
||||
res.writeHead(200, { "Content-Type": "text/html" });
|
||||
res.end(html);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
function permsToString(stat) {
|
||||
|
||||
if (!stat.isDirectory || !stat.mode) {
|
||||
return '???!!!???';
|
||||
}
|
||||
|
||||
var dir = stat.isDirectory() ? 'd' : '-',
|
||||
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('');
|
||||
}
|
||||
|
||||
// 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];
|
||||
}
|
||||
104
www/node_modules/ecstatic/lib/ecstatic/status-handlers.js
generated
vendored
Normal file
104
www/node_modules/ecstatic/lib/ecstatic/status-handlers.js
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
var he = require('he');
|
||||
|
||||
// not modified
|
||||
exports['304'] = function (res, next) {
|
||||
res.statusCode = 304;
|
||||
res.end();
|
||||
};
|
||||
|
||||
// access denied
|
||||
exports['403'] = function (res, next) {
|
||||
res.statusCode = 403;
|
||||
if (typeof next === "function") {
|
||||
next();
|
||||
}
|
||||
else {
|
||||
if (res.writable) {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.end('ACCESS DENIED');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// disallowed method
|
||||
exports['405'] = function (res, next, opts) {
|
||||
res.statusCode = 405;
|
||||
if (typeof next === "function") {
|
||||
next();
|
||||
}
|
||||
else {
|
||||
res.setHeader('allow', (opts && opts.allow) || 'GET, HEAD');
|
||||
res.end();
|
||||
}
|
||||
};
|
||||
|
||||
// not found
|
||||
exports['404'] = function (res, next) {
|
||||
res.statusCode = 404;
|
||||
if (typeof next === "function") {
|
||||
next();
|
||||
}
|
||||
else {
|
||||
if (res.writable) {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.end('File not found. :(');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports['416'] = function (res, next) {
|
||||
res.statusCode = 416;
|
||||
if (typeof next === "function") {
|
||||
next();
|
||||
}
|
||||
else {
|
||||
if (res.writable) {
|
||||
res.setHeader('content-type', 'text/plain');
|
||||
res.end('Requested range not satisfiable');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// flagrant error
|
||||
exports['500'] = function (res, next, opts) {
|
||||
res.statusCode = 500;
|
||||
res.setHeader('content-type', 'text/html');
|
||||
var error = String(opts.error.stack || opts.error || "No specified error"),
|
||||
html = [
|
||||
'<!doctype html>',
|
||||
'<html>',
|
||||
' <head>',
|
||||
' <meta charset="utf-8">',
|
||||
' <title>500 Internal Server Error</title>',
|
||||
' </head>',
|
||||
' <body>',
|
||||
' <p>',
|
||||
' ' + he.encode(error),
|
||||
' </p>',
|
||||
' </body>',
|
||||
'</html>'
|
||||
].join('\n') + '\n';
|
||||
res.end(html);
|
||||
};
|
||||
|
||||
// bad request
|
||||
exports['400'] = function (res, next, opts) {
|
||||
res.statusCode = 400;
|
||||
res.setHeader('content-type', 'text/html');
|
||||
var error = opts && opts.error ? String(opts.error) : 'Malformed request.',
|
||||
html = [
|
||||
'<!doctype html>',
|
||||
'<html>',
|
||||
' <head>',
|
||||
' <meta charset="utf-8">',
|
||||
' <title>400 Bad Request</title>',
|
||||
' </head>',
|
||||
' <body>',
|
||||
' <p>',
|
||||
' ' + he.encode(error),
|
||||
' </p>',
|
||||
' </body>',
|
||||
'</html>'
|
||||
].join('\n') + '\n';
|
||||
res.end(html);
|
||||
};
|
||||
Reference in New Issue
Block a user