From ae2db1640beae00ad636bc2caac50eae1cdea5a6 Mon Sep 17 00:00:00 2001 From: Jan Nicklas Date: Wed, 28 May 2014 18:26:36 +0200 Subject: [PATCH 1/3] 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 From 58f335685a8f23baba41274fa14ff7a5f87a095f Mon Sep 17 00:00:00 2001 From: Jan Nicklas Date: Wed, 28 May 2014 19:06:02 +0200 Subject: [PATCH 2/3] Fix comment and add test for generated stylesheet --- index.js | 2 +- public/directory.html | 6 +++--- test/test.js | 15 ++++++++++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/index.js b/index.js index 6c64e70f..3d50d34f 100644 --- a/index.js +++ b/index.js @@ -44,7 +44,7 @@ var defaultTemplate = join(__dirname, 'public', 'directory.html'); var defaultStylesheet = join(__dirname, 'public', 'style.css'); /*! - * Stylesheet. + * Javascript. */ var defaultScript = join(__dirname, 'public', 'script.js'); diff --git a/public/directory.html b/public/directory.html index 28e1cdc7..6874b8e2 100644 --- a/public/directory.html +++ b/public/directory.html @@ -1,11 +1,11 @@ - + listing directory {directory} - - + + diff --git a/test/test.js b/test/test.js index eb7a0100..807b4a38 100644 --- a/test/test.js +++ b/test/test.js @@ -221,7 +221,10 @@ describe('directory()', function(){ describe('when setting a custom stylesheet', function () { var server; before(function () { - server = createServer('test/fixtures', {'stylesheet': __dirname + '/shared/styles.css'}); + server = createServer('test/fixtures', { + 'stylesheet': __dirname + '/shared/styles.css', + 'template': __dirname + '/shared/template.html' + }); }); after(function (done) { server.close(done); @@ -236,6 +239,16 @@ describe('directory()', function(){ .expect(/color: #00ff00;/) .end(done); }); + + it('should respond with appropriate style sheet', function (done) { + request(server) + .get('/?css%2Fcss') + .set('Accept', 'text/html') + .expect(200) + .expect('Content-Type', /html/) + .expect(/color: #00ff00;/) + .end(done); + }); }); describe('when set with trailing slash', function () { From ed94ee2e0f5a2c73cf527c3519029818e4f5cd84 Mon Sep 17 00:00:00 2001 From: Jan Nicklas Date: Wed, 28 May 2014 19:40:01 +0200 Subject: [PATCH 3/3] Change request and separate tests --- index.js | 4 ++-- public/directory.html | 4 ++-- test/test.js | 25 +++++++++++++++++-------- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/index.js b/index.js index 3d50d34f..f93abae5 100644 --- a/index.js +++ b/index.js @@ -129,8 +129,8 @@ exports = module.exports = function directory(root, options){ var type = new Negotiator(req).preferredMediaType(mediaTypes); // take custom types from the url - // e.g /?text/css - var mediaTypeFromUrl = req.url.replace(/^.+\?/, ''); + // e.g /?get=text/css + var mediaTypeFromUrl = req.url.replace(/^.+\?get=/, '').replace(/%2F/g, '/'); if (mediaTypes.indexOf(mediaTypeFromUrl) !== -1) type = mediaTypeFromUrl; // not acceptable diff --git a/public/directory.html b/public/directory.html index 6874b8e2..46a6a31d 100644 --- a/public/directory.html +++ b/public/directory.html @@ -4,8 +4,8 @@ listing directory {directory} - - + + diff --git a/test/test.js b/test/test.js index 807b4a38..c5cf17fb 100644 --- a/test/test.js +++ b/test/test.js @@ -218,7 +218,7 @@ describe('directory()', function(){ }); }); - describe('when setting a custom stylesheet', function () { + describe('when setting a custom stylesheet and a custom template', function () { var server; before(function () { server = createServer('test/fixtures', { @@ -229,7 +229,6 @@ describe('directory()', function(){ after(function (done) { server.close(done); }); - it('should respond with appropriate embedded styles', function (done) { request(server) .get('/') @@ -239,15 +238,25 @@ describe('directory()', function(){ .expect(/color: #00ff00;/) .end(done); }); + }); + describe('when setting a custom stylesheet', function () { + var server; + before(function () { + server = createServer('test/fixtures', { + 'stylesheet': __dirname + '/shared/styles.css' + }); + }); + after(function (done) { + server.close(done); + }); it('should respond with appropriate style sheet', function (done) { request(server) - .get('/?css%2Fcss') - .set('Accept', 'text/html') - .expect(200) - .expect('Content-Type', /html/) - .expect(/color: #00ff00;/) - .end(done); + .get('/?get=text%2Fcss') + .set('Accept', 'text/html') + .expect(200) + .expect(/color: #00ff00;/) + .end(done); }); });