From ae2db1640beae00ad636bc2caac50eae1cdea5a6 Mon Sep 17 00:00:00 2001 From: Jan Nicklas Date: Wed, 28 May 2014 18:26:36 +0200 Subject: [PATCH] Fixes #8 - Separate javascript and css from directory.html to be compliant with content security policy --- index.js | 55 ++++++++++++++++++++++++++++++++-- public/directory.html | 69 ++----------------------------------------- public/script.js | 64 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 119 insertions(+), 69 deletions(-) create mode 100644 public/script.js diff --git a/index.js b/index.js index 1f4b51a8..6c64e70f 100644 --- a/index.js +++ b/index.js @@ -43,18 +43,28 @@ var defaultTemplate = join(__dirname, 'public', 'directory.html'); var defaultStylesheet = join(__dirname, 'public', 'style.css'); +/*! + * Stylesheet. + */ + +var defaultScript = join(__dirname, 'public', 'script.js'); + /** * Media types and the map for content negotiation. */ var mediaTypes = [ + 'text/css', 'text/html', + 'text/javascript', 'text/plain', 'application/json' ]; var mediaType = { + 'text/css': 'css', 'text/html': 'html', + 'text/javascript': 'javascript', 'text/plain': 'plain', 'application/json': 'json' }; @@ -81,7 +91,8 @@ exports = module.exports = function directory(root, options){ , filter = options.filter , root = normalize(root + sep) , template = options.template || defaultTemplate - , stylesheet = options.stylesheet || defaultStylesheet; + , stylesheet = options.stylesheet || defaultStylesheet + , script = options.script || defaultScript; return function directory(req, res, next) { if ('GET' != req.method && 'HEAD' != req.method) return next(); @@ -117,9 +128,14 @@ exports = module.exports = function directory(root, options){ // content-negotiation var type = new Negotiator(req).preferredMediaType(mediaTypes); + // take custom types from the url + // e.g /?text/css + var mediaTypeFromUrl = req.url.replace(/^.+\?/, ''); + if (mediaTypes.indexOf(mediaTypeFromUrl) !== -1) type = mediaTypeFromUrl; + // not acceptable if (!type) return next(createError(406)); - exports[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet); + exports[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet, script); }); }); }; @@ -152,6 +168,41 @@ exports.html = function(req, res, files, next, dir, showUp, icons, path, view, t }); }; +/** + * Respond with text/css. + */ + +exports.css = function (req, res, files, next, dir, showUp, icons, path, view, template, stylesheet){ + fs.readFile(stylesheet, 'utf8', function (err, style) { + if (err) return next(err); + stat(path, files, function (err, stats){ + if (err) return next(err); + files = files.map(function (file, i){ + return { name: file, stat: stats[i] }; + }); + files.sort(fileSort); + if (showUp) files.unshift({ name: '..' }); + var str = style.concat(iconStyle(files, icons)); + res.setHeader('Content-Type', 'text/css'); + res.setHeader('Content-Length', str.length); + res.end(str); + }); + }); +}; + +/** + * Respond with text/javascript. + */ + +exports.javascript = function (req, res, files, next, dir, showUp, icons, path, view, template, stylesheet, script){ + fs.readFile(script, 'utf8', function (err, script){ + if (err) return next(err); + res.setHeader('Content-Type', 'text/javascript'); + res.setHeader('Content-Length', script.length); + res.end(script); + }); +}; + /** * Respond with application/json. */ diff --git a/public/directory.html b/public/directory.html index 8ed8b4ae..28e1cdc7 100644 --- a/public/directory.html +++ b/public/directory.html @@ -4,73 +4,8 @@ listing directory {directory} - - + + diff --git a/public/script.js b/public/script.js new file mode 100644 index 00000000..9bf03208 --- /dev/null +++ b/public/script.js @@ -0,0 +1,64 @@ +function $(id){ + var el = 'string' == typeof id + ? document.getElementById(id) + : id; + + el.on = function(event, fn){ + if ('content loaded' == event) { + event = window.attachEvent ? "load" : "DOMContentLoaded"; + } + el.addEventListener + ? el.addEventListener(event, fn, false) + : el.attachEvent("on" + event, fn); + }; + + el.all = function(selector){ + return $(el.querySelectorAll(selector)); + }; + + el.each = function(fn){ + for (var i = 0, len = el.length; i < len; ++i) { + fn($(el[i]), i); + } + }; + + el.getClasses = function(){ + return this.getAttribute('class').split(/\s+/); + }; + + el.addClass = function(name){ + var classes = this.getAttribute('class'); + el.setAttribute('class', classes + ? classes + ' ' + name + : name); + }; + + el.removeClass = function(name){ + var classes = this.getClasses().filter(function(curr){ + return curr != name; + }); + this.setAttribute('class', classes.join(' ')); + }; + + return el; +} + +function search() { + var str = $('search').value + , links = $('files').all('a'); + + links.each(function(link){ + var text = link.textContent; + + if ('..' == text) return; + if (str.length && ~text.indexOf(str)) { + link.addClass('highlight'); + } else { + link.removeClass('highlight'); + } + }); +} + +$(window).on('content loaded', function(){ + $('search').on('keyup', search); +}); \ No newline at end of file