[Pkg-javascript-commits] [node-serve-index] 01/02: Imported Upstream version 1.4.0

Leo Iannacone l3on-guest at moszumanska.debian.org
Tue Oct 14 11:40:10 UTC 2014


This is an automated email from the git hooks/post-receive script.

l3on-guest pushed a commit to branch master
in repository node-serve-index.

commit ae7258e9d0ad9b9543bf4a78284499541c13e54b
Author: Leo Iannacone <l3on at ubuntu.com>
Date:   Tue Oct 14 12:11:24 2014 +0200

    Imported Upstream version 1.4.0
---
 .travis.yml                                        |  11 +
 HISTORY.md                                         |  95 ++++
 LICENSE                                            |  25 +
 README.md                                          | 127 +++++
 index.js                                           | 592 ++++++++++++++++++++
 package.json                                       |  35 ++
 public/directory.html                              |  82 +++
 public/icons/application_xp.png                    | Bin 0 -> 426 bytes
 public/icons/application_xp_terminal.png           | Bin 0 -> 507 bytes
 public/icons/box.png                               | Bin 0 -> 555 bytes
 public/icons/cd.png                                | Bin 0 -> 673 bytes
 public/icons/controller.png                        | Bin 0 -> 666 bytes
 public/icons/drive.png                             | Bin 0 -> 346 bytes
 public/icons/film.png                              | Bin 0 -> 653 bytes
 public/icons/folder.png                            | Bin 0 -> 634 bytes
 public/icons/font.png                              | Bin 0 -> 567 bytes
 public/icons/image.png                             | Bin 0 -> 516 bytes
 public/icons/map.png                               | Bin 0 -> 804 bytes
 public/icons/page.png                              | Bin 0 -> 635 bytes
 public/icons/page_add.png                          | Bin 0 -> 739 bytes
 public/icons/page_attach.png                       | Bin 0 -> 794 bytes
 public/icons/page_code.png                         | Bin 0 -> 818 bytes
 public/icons/page_copy.png                         | Bin 0 -> 663 bytes
 public/icons/page_delete.png                       | Bin 0 -> 740 bytes
 public/icons/page_edit.png                         | Bin 0 -> 807 bytes
 public/icons/page_error.png                        | Bin 0 -> 793 bytes
 public/icons/page_excel.png                        | Bin 0 -> 817 bytes
 public/icons/page_find.png                         | Bin 0 -> 879 bytes
 public/icons/page_gear.png                         | Bin 0 -> 833 bytes
 public/icons/page_go.png                           | Bin 0 -> 779 bytes
 public/icons/page_green.png                        | Bin 0 -> 621 bytes
 public/icons/page_key.png                          | Bin 0 -> 801 bytes
 public/icons/page_lightning.png                    | Bin 0 -> 839 bytes
 public/icons/page_link.png                         | Bin 0 -> 830 bytes
 public/icons/page_paintbrush.png                   | Bin 0 -> 813 bytes
 public/icons/page_paste.png                        | Bin 0 -> 703 bytes
 public/icons/page_red.png                          | Bin 0 -> 641 bytes
 public/icons/page_refresh.png                      | Bin 0 -> 858 bytes
 public/icons/page_save.png                         | Bin 0 -> 774 bytes
 public/icons/page_white.png                        | Bin 0 -> 294 bytes
 public/icons/page_white_acrobat.png                | Bin 0 -> 591 bytes
 public/icons/page_white_actionscript.png           | Bin 0 -> 664 bytes
 public/icons/page_white_add.png                    | Bin 0 -> 512 bytes
 public/icons/page_white_c.png                      | Bin 0 -> 587 bytes
 public/icons/page_white_camera.png                 | Bin 0 -> 656 bytes
 public/icons/page_white_cd.png                     | Bin 0 -> 666 bytes
 public/icons/page_white_code.png                   | Bin 0 -> 603 bytes
 public/icons/page_white_code_red.png               | Bin 0 -> 587 bytes
 public/icons/page_white_coldfusion.png             | Bin 0 -> 592 bytes
 public/icons/page_white_compressed.png             | Bin 0 -> 724 bytes
 public/icons/page_white_copy.png                   | Bin 0 -> 309 bytes
 public/icons/page_white_cplusplus.png              | Bin 0 -> 621 bytes
 public/icons/page_white_csharp.png                 | Bin 0 -> 700 bytes
 public/icons/page_white_cup.png                    | Bin 0 -> 639 bytes
 public/icons/page_white_database.png               | Bin 0 -> 579 bytes
 public/icons/page_white_delete.png                 | Bin 0 -> 536 bytes
 public/icons/page_white_dvd.png                    | Bin 0 -> 638 bytes
 public/icons/page_white_edit.png                   | Bin 0 -> 618 bytes
 public/icons/page_white_error.png                  | Bin 0 -> 623 bytes
 public/icons/page_white_excel.png                  | Bin 0 -> 663 bytes
 public/icons/page_white_find.png                   | Bin 0 -> 676 bytes
 public/icons/page_white_flash.png                  | Bin 0 -> 582 bytes
 public/icons/page_white_freehand.png               | Bin 0 -> 639 bytes
 public/icons/page_white_gear.png                   | Bin 0 -> 402 bytes
 public/icons/page_white_get.png                    | Bin 0 -> 516 bytes
 public/icons/page_white_go.png                     | Bin 0 -> 612 bytes
 public/icons/page_white_h.png                      | Bin 0 -> 603 bytes
 public/icons/page_white_horizontal.png             | Bin 0 -> 296 bytes
 public/icons/page_white_key.png                    | Bin 0 -> 616 bytes
 public/icons/page_white_lightning.png              | Bin 0 -> 669 bytes
 public/icons/page_white_link.png                   | Bin 0 -> 614 bytes
 public/icons/page_white_magnify.png                | Bin 0 -> 554 bytes
 public/icons/page_white_medal.png                  | Bin 0 -> 706 bytes
 public/icons/page_white_office.png                 | Bin 0 -> 779 bytes
 public/icons/page_white_paint.png                  | Bin 0 -> 688 bytes
 public/icons/page_white_paintbrush.png             | Bin 0 -> 618 bytes
 public/icons/page_white_paste.png                  | Bin 0 -> 620 bytes
 public/icons/page_white_php.png                    | Bin 0 -> 538 bytes
 public/icons/page_white_picture.png                | Bin 0 -> 650 bytes
 public/icons/page_white_powerpoint.png             | Bin 0 -> 588 bytes
 public/icons/page_white_put.png                    | Bin 0 -> 523 bytes
 public/icons/page_white_ruby.png                   | Bin 0 -> 626 bytes
 public/icons/page_white_stack.png                  | Bin 0 -> 317 bytes
 public/icons/page_white_star.png                   | Bin 0 -> 565 bytes
 public/icons/page_white_swoosh.png                 | Bin 0 -> 634 bytes
 public/icons/page_white_text.png                   | Bin 0 -> 342 bytes
 public/icons/page_white_text_width.png             | Bin 0 -> 315 bytes
 public/icons/page_white_tux.png                    | Bin 0 -> 668 bytes
 public/icons/page_white_vector.png                 | Bin 0 -> 644 bytes
 public/icons/page_white_visualstudio.png           | Bin 0 -> 702 bytes
 public/icons/page_white_width.png                  | Bin 0 -> 309 bytes
 public/icons/page_white_word.png                   | Bin 0 -> 651 bytes
 public/icons/page_white_world.png                  | Bin 0 -> 734 bytes
 public/icons/page_white_wrench.png                 | Bin 0 -> 613 bytes
 public/icons/page_white_zip.png                    | Bin 0 -> 386 bytes
 public/icons/page_word.png                         | Bin 0 -> 777 bytes
 public/icons/page_world.png                        | Bin 0 -> 903 bytes
 public/style.css                                   | 257 +++++++++
 test/fixtures/#directory/index.html                |   1 +
 test/fixtures/.hidden                              |   1 +
 test/fixtures/collect/sample                       |   0
 test/fixtures/collect/sample.jpg                   |   0
 test/fixtures/collect/sample.mp4                   |   0
 test/fixtures/collect/sample.pdf                   |   0
 test/fixtures/collect/sample.qfx                   |   0
 test/fixtures/collect/sample.rdf                   |   0
 test/fixtures/collect/sample.txt                   |   0
 test/fixtures/collect/sample.xlsx                  |   0
 test/fixtures/file #1.txt                          |   1 +
 test/fixtures/foo bar                              |   1 +
 test/fixtures/g# %3 o %2525 %37 dir/empty.txt      |   0
 test/fixtures/nums                                 |   1 +
 test/fixtures/todo.txt                             |   1 +
 test/fixtures/users/index.html                     |   1 +
 test/fixtures/users/tobi.txt                       |   1 +
 .../\343\201\225\343\201\217\343\202\211.txt"      |   0
 test/shared/index.js                               |  26 +
 test/shared/styles.css                             |   3 +
 test/shared/template.html                          |  15 +
 test/test.js                                       | 618 +++++++++++++++++++++
 120 files changed, 1894 insertions(+)

diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..1ff243c
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,11 @@
+language: node_js
+node_js:
+  - "0.8"
+  - "0.10"
+  - "0.11"
+matrix:
+  allow_failures:
+    - node_js: "0.11"
+  fast_finish: true
+script: "npm run-script test-travis"
+after_script: "npm install coveralls at 2.10.0 && cat ./coverage/lcov.info | coveralls"
diff --git a/HISTORY.md b/HISTORY.md
new file mode 100644
index 0000000..2801483
--- /dev/null
+++ b/HISTORY.md
@@ -0,0 +1,95 @@
+1.4.0 / 2014-10-03
+==================
+
+  * Add `dir` argument to `filter` function
+  * Support using tokens multiple times
+
+1.3.1 / 2014-10-01
+==================
+
+  * Fix incorrect 403 on Windows and Node.js 0.11
+  * deps: accepts@~1.1.1
+    - deps: mime-types@~2.0.2
+    - deps: negotiator at 0.4.8
+
+1.3.0 / 2014-09-20
+==================
+
+  * Add icon for mkv files
+  * Lookup icon by mime type for greater icon support
+
+1.2.1 / 2014-09-05
+==================
+
+  * deps: accepts@~1.1.0
+  * deps: debug@~2.0.0
+
+1.2.0 / 2014-08-25
+==================
+
+  * Add `debug` messages
+  * Resolve relative paths at middleware setup
+
+1.1.6 / 2014-08-10
+==================
+
+  * Fix URL parsing
+  * deps: parseurl@~1.3.0
+
+1.1.5 / 2014-07-27
+==================
+
+  * Fix Content-Length calculation for multi-byte file names
+  * deps: accepts@~1.0.7
+    - deps: negotiator at 0.4.7
+
+1.1.4 / 2014-06-20
+==================
+
+  * deps: accepts@~1.0.5
+
+1.1.3 / 2014-06-20
+==================
+
+  * deps: accepts@~1.0.4
+    - use `mime-types`
+
+1.1.2 / 2014-06-19
+==================
+
+  * deps: batch at 0.5.1
+
+1.1.1 / 2014-06-11
+==================
+
+  * deps: accepts at 1.0.3
+
+1.1.0 / 2014-05-29
+==================
+
+  * Fix content negotiation when no `Accept` header
+  * Properly support all HTTP methods
+  * Support vanilla node.js http servers
+  * Treat `ENAMETOOLONG` as code 414
+  * Use accepts for negotiation
+
+1.0.3 / 2014-05-20
+==================
+
+  * Fix error from non-statable files in HTML view
+
+1.0.2 / 2014-04-28
+==================
+
+  * Add `stylesheet` option
+  * deps: negotiator at 0.4.3
+
+1.0.1 / 2014-03-05
+==================
+
+  * deps: negotiator at 0.4.2
+
+1.0.0 / 2014-03-05
+==================
+
+  * Genesis from connect
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..b7bc085
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+(The MIT License)
+
+Copyright (c) 2010 Sencha Inc.
+Copyright (c) 2011 LearnBoost
+Copyright (c) 2011 TJ Holowaychuk
+Copyright (c) 2014 Douglas Christopher Wilson
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+'Software'), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..8874086
--- /dev/null
+++ b/README.md
@@ -0,0 +1,127 @@
+# serve-index
+
+[![NPM Version][npm-image]][npm-url]
+[![NPM Downloads][downloads-image]][downloads-url]
+[![Build Status][travis-image]][travis-url]
+[![Test Coverage][coveralls-image]][coveralls-url]
+[![Gittip][gittip-image]][gittip-url]
+
+  Serves pages that contain directory listings for a given path.
+
+## Install
+
+```sh
+$ npm install serve-index
+```
+
+## API
+
+```js
+var serveIndex = require('serve-index')
+```
+
+### serveIndex(path, options)
+
+Returns middlware that serves an index of the directory in the given `path`.
+
+The `path` is based off the `req.url` value, so a `req.url` of `'/some/dir`
+with a `path` of `'public'` will look at `'public/some/dir'`. If you are using
+something like `express`, you can change the URL "base" with `app.use` (see
+the express example).
+
+#### Options
+
+Serve index accepts these properties in the options object.
+
+##### filter
+
+Apply this filter function to files. Defaults to `false`. The `filter` function
+is called for each file, with the signature `filter(filename, index, files, dir)`
+where `filename` is the name of the file, `index` is the array index, `files` is
+the array of files and `dir` is the absolute path the file is located (and thus,
+the directory the listing is for).
+
+##### hidden
+
+Display hidden (dot) files. Defaults to `false`.
+
+##### icons
+
+Display icons. Defaults to `false`.
+
+##### stylesheet
+
+Optional path to a CSS stylesheet. Defaults to a built-in stylesheet.
+
+##### template
+
+Optional path to an HTML template. Defaults to a built-in template.
+
+The following tokens are replaced in templates:
+
+  * `{directory}` with the name of the directory.
+  * `{files}` with the HTML of an unordered list of file links.
+  * `{linked-path}` with the HTML of a link to the directory.
+  * `{style}` with the specified stylesheet and embedded images.
+
+##### view
+
+Display mode. `tiles` and `details` are available. Defaults to `tiles`.
+
+## Examples
+
+### Serve directory indexes with vanilla node.js http server
+
+```js
+var finalhandler = require('finalhandler')
+var http = require('http')
+var serveIndex = require('serve-index')
+var serveStatic = require('serve-static')
+
+// Serve directory indexes for public/ftp folder (with icons)
+var index = serveIndex('public/ftp', {'icons': true})
+
+// Serve up public/ftp folder files
+var serve = serveStatic('public/ftp')
+
+// Create server
+var server = http.createServer(function onRequest(req, res){
+  var done = finalhandler(req, res)
+  serve(req, res, function onNext(err) {
+    if (err) return done(err)
+    index(req, res, done)
+  })
+})
+
+// Listen
+server.listen(3000)
+```
+
+### Serve directory indexes with express
+
+```js
+var express    = require('express')
+var serveIndex = require('serve-index')
+
+var app = express()
+
+// Serve URLs like /ftp/thing as public/ftp/thing
+app.use('/ftp', serveIndex('public/ftp', {'icons': true}))
+app.listen()
+```
+
+## License
+
+[MIT](LICENSE). The [Silk](http://www.famfamfam.com/lab/icons/silk/) icons
+are created by/copyright of [FAMFAMFAM](http://www.famfamfam.com/).
+
+[npm-image]: https://img.shields.io/npm/v/serve-index.svg?style=flat
+[npm-url]: https://npmjs.org/package/serve-index
+[travis-image]: https://img.shields.io/travis/expressjs/serve-index.svg?style=flat
+[travis-url]: https://travis-ci.org/expressjs/serve-index
+[coveralls-image]: https://img.shields.io/coveralls/expressjs/serve-index.svg?style=flat
+[coveralls-url]: https://coveralls.io/r/expressjs/serve-index?branch=master
+[downloads-image]: https://img.shields.io/npm/dm/serve-index.svg?style=flat
+[downloads-url]: https://npmjs.org/package/serve-index
+[gittip-image]: https://img.shields.io/gittip/dougwilson.svg?style=flat
+[gittip-url]: https://www.gittip.com/dougwilson/
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..7b62fca
--- /dev/null
+++ b/index.js
@@ -0,0 +1,592 @@
+
+/*!
+ * serve-index
+ * Copyright(c) 2011 Sencha Inc.
+ * Copyright(c) 2011 TJ Holowaychuk
+ * Copyright(c) 2014 Douglas Christopher Wilson
+ * MIT Licensed
+ */
+
+// TODO: arrow key navigation
+// TODO: make icons extensible
+
+/**
+ * Module dependencies.
+ */
+
+var accepts = require('accepts');
+var debug = require('debug')('serve-index');
+var http = require('http')
+  , fs = require('fs')
+  , path = require('path')
+  , normalize = path.normalize
+  , sep = path.sep
+  , extname = path.extname
+  , join = path.join;
+var Batch = require('batch');
+var mime = require('mime-types');
+var parseUrl = require('parseurl');
+var resolve = require('path').resolve;
+
+/*!
+ * Icon cache.
+ */
+
+var cache = {};
+
+/*!
+ * Default template.
+ */
+
+var defaultTemplate = join(__dirname, 'public', 'directory.html');
+
+/*!
+ * Stylesheet.
+ */
+
+var defaultStylesheet = join(__dirname, 'public', 'style.css');
+
+/**
+ * Media types and the map for content negotiation.
+ */
+
+var mediaTypes = [
+  'text/html',
+  'text/plain',
+  'application/json'
+];
+
+var mediaType = {
+  'text/html': 'html',
+  'text/plain': 'plain',
+  'application/json': 'json'
+};
+
+/**
+ * Serve directory listings with the given `root` path.
+ *
+ * See Readme.md for documentation of options.
+ *
+ * @param {String} path
+ * @param {Object} options
+ * @return {Function} middleware
+ * @api public
+ */
+
+exports = module.exports = function serveIndex(root, options){
+  options = options || {};
+
+  // root required
+  if (!root) throw new TypeError('serveIndex() root path required');
+
+  // resolve root to absolute and normalize
+  root = resolve(root);
+  root = normalize(root + sep);
+
+  var hidden = options.hidden
+    , icons = options.icons
+    , view = options.view || 'tiles'
+    , filter = options.filter
+    , template = options.template || defaultTemplate
+    , stylesheet = options.stylesheet || defaultStylesheet;
+
+  return function serveIndex(req, res, next) {
+    if (req.method !== 'GET' && req.method !== 'HEAD') {
+      res.statusCode = 'OPTIONS' === req.method
+        ? 200
+        : 405;
+      res.setHeader('Allow', 'GET, HEAD, OPTIONS');
+      res.end();
+      return;
+    }
+
+    // parse URLs
+    var url = parseUrl(req);
+    var originalUrl = parseUrl.original(req);
+    var dir = decodeURIComponent(url.pathname);
+    var originalDir = decodeURIComponent(originalUrl.pathname);
+
+    // join / normalize from root dir
+    var path = normalize(join(root, dir));
+
+    // null byte(s), bad request
+    if (~path.indexOf('\0')) return next(createError(400));
+
+    // malicious path
+    if ((path + sep).substr(0, root.length) !== root) {
+      debug('malicious path "%s"', path);
+      return next(createError(403));
+    }
+
+    // determine ".." display
+    var showUp = normalize(resolve(path) + sep) !== root;
+
+    // check if we have a directory
+    debug('stat "%s"', path);
+    fs.stat(path, function(err, stat){
+      if (err && err.code === 'ENOENT') {
+        return next();
+      }
+
+      if (err) {
+        err.status = err.code === 'ENAMETOOLONG'
+          ? 414
+          : 500;
+        return next(err);
+      }
+
+      if (!stat.isDirectory()) return next();
+
+      // fetch files
+      debug('readdir "%s"', path);
+      fs.readdir(path, function(err, files){
+        if (err) return next(err);
+        if (!hidden) files = removeHidden(files);
+        if (filter) files = files.filter(function(filename, index, list) {
+          return filter(filename, index, list, path);
+        });
+        files.sort();
+
+        // content-negotiation
+        var accept = accepts(req);
+        var type = accept.types(mediaTypes);
+
+        // not acceptable
+        if (!type) return next(createError(406));
+        exports[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
+      });
+    });
+  };
+};
+
+/**
+ * Respond with text/html.
+ */
+
+exports.html = function(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet){
+  fs.readFile(template, 'utf8', function(err, str){
+    if (err) return next(err);
+    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: '..' });
+        str = str
+          .replace(/\{style\}/g, style.concat(iconStyle(files, icons)))
+          .replace(/\{files\}/g, html(files, dir, icons, view))
+          .replace(/\{directory\}/g, dir)
+          .replace(/\{linked-path\}/g, htmlPath(dir));
+
+        var buf = new Buffer(str, 'utf8');
+        res.setHeader('Content-Type', 'text/html; charset=utf-8');
+        res.setHeader('Content-Length', buf.length);
+        res.end(buf);
+      });
+    });
+  });
+};
+
+/**
+ * Respond with application/json.
+ */
+
+exports.json = function(req, res, files){
+  var body = JSON.stringify(files);
+  var buf = new Buffer(body, 'utf8');
+
+  res.setHeader('Content-Type', 'application/json; charset=utf-8');
+  res.setHeader('Content-Length', buf.length);
+  res.end(buf);
+};
+
+/**
+ * Respond with text/plain.
+ */
+
+exports.plain = function(req, res, files){
+  var body = files.join('\n') + '\n';
+  var buf = new Buffer(body, 'utf8');
+
+  res.setHeader('Content-Type', 'text/plain; charset=utf-8');
+  res.setHeader('Content-Length', buf.length);
+  res.end(buf);
+};
+
+/**
+ * Generate an `Error` from the given status `code`
+ * and optional `msg`.
+ *
+ * @param {Number} code
+ * @param {String} msg
+ * @return {Error}
+ * @api private
+ */
+
+function createError(code, msg) {
+  var err = new Error(msg || http.STATUS_CODES[code]);
+  err.status = code;
+  return err;
+};
+
+/**
+ * Sort function for with directories first.
+ */
+
+function fileSort(a, b) {
+  return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
+    String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
+}
+
+/**
+ * Map html `dir`, returning a linked path.
+ */
+
+function htmlPath(dir) {
+  var curr = [];
+  return dir.split('/').map(function(part){
+    curr.push(encodeURIComponent(part));
+    return part ? '<a href="' + curr.join('/') + '">' + part + '</a>' : '';
+  }).join(' / ');
+}
+
+/**
+ * Get the icon data for the file name.
+ */
+
+function iconLookup(filename) {
+  var ext = extname(filename);
+
+  // try by extension
+  if (icons[ext]) {
+    return {
+      className: 'icon-' + ext.substring(1),
+      fileName: icons[ext]
+    };
+  }
+
+  var mimetype = mime.lookup(ext);
+
+  // default if no mime type
+  if (mimetype === false) {
+    return {
+      className: 'icon-default',
+      fileName: icons.default
+    };
+  }
+
+  // try by mime type
+  if (icons[mimetype]) {
+    return {
+      className: 'icon-' + mimetype.replace('/', '-'),
+      fileName: icons[mimetype]
+    };
+  }
+
+  var suffix = mimetype.split('+')[1];
+
+  if (suffix && icons['+' + suffix]) {
+    return {
+      className: 'icon-' + suffix,
+      fileName: icons['+' + suffix]
+    };
+  }
+
+  var type = mimetype.split('/')[0];
+
+  // try by type only
+  if (icons[type]) {
+    return {
+      className: 'icon-' + type,
+      fileName: icons[type]
+    };
+  }
+
+  return {
+    className: 'icon-default',
+    fileName: icons.default
+  };
+}
+
+/**
+ * Load icon images, return css string.
+ */
+
+function iconStyle (files, useIcons) {
+  if (!useIcons) return '';
+  var className;
+  var i;
+  var iconName;
+  var list = [];
+  var rules = {};
+  var selector;
+  var selectors = {};
+  var style = '';
+
+  for (i = 0; i < files.length; i++) {
+    var file = files[i];
+
+    var isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
+    var icon = isDir
+      ? { className: 'icon-directory', fileName: icons.folder }
+      : iconLookup(file.name);
+    var iconName = icon.fileName;
+
+    selector = '#files .' + icon.className + ' .name';
+
+    if (!rules[iconName]) {
+      rules[iconName] = 'background-image: url(data:image/png;base64,' + load(iconName) + ');'
+      selectors[iconName] = [];
+      list.push(iconName);
+    }
+
+    if (selectors[iconName].indexOf(selector) === -1) {
+      selectors[iconName].push(selector);
+    }
+  }
+
+  for (i = 0; i < list.length; i++) {
+    iconName = list[i];
+    style += selectors[iconName].join(',\n') + ' {\n  ' + rules[iconName] + '\n}\n';
+  }
+
+  return style;
+}
+
+/**
+ * Map html `files`, returning an html unordered list.
+ */
+
+function html(files, dir, useIcons, view) {
+  return '<ul id="files" class="view-' + view + '">'
+    + (view == 'details' ? (
+      '<li class="header">'
+      + '<span class="name">Name</span>'
+      + '<span class="size">Size</span>'
+      + '<span class="date">Modified</span>'
+      + '</li>') : '')
+    + files.map(function(file){
+    var isDir = '..' == file.name || (file.stat && file.stat.isDirectory())
+      , classes = []
+      , path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
+
+    if (useIcons) {
+      classes.push('icon');
+
+      if (isDir) {
+        classes.push('icon-directory');
+      } else {
+        var ext = extname(file.name);
+        var icon = iconLookup(file.name);
+
+        classes.push('icon');
+        classes.push('icon-' + ext.substring(1));
+
+        if (classes.indexOf(icon.className) === -1) {
+          classes.push(icon.className);
+        }
+      }
+    }
+
+    path.push(encodeURIComponent(file.name));
+
+    var date = file.stat && file.name !== '..'
+      ? file.stat.mtime.toDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
+      : '';
+    var size = file.stat && !isDir
+      ? file.stat.size
+      : '';
+
+    return '<li><a href="'
+      + normalizeSlashes(normalize(path.join('/')))
+      + '" class="'
+      + classes.join(' ') + '"'
+      + ' title="' + file.name + '">'
+      + '<span class="name">'+file.name+'</span>'
+      + '<span class="size">'+size+'</span>'
+      + '<span class="date">'+date+'</span>'
+      + '</a></li>';
+
+  }).join('\n') + '</ul>';
+}
+
+/**
+ * Load and cache the given `icon`.
+ *
+ * @param {String} icon
+ * @return {String}
+ * @api private
+ */
+
+function load(icon) {
+  if (cache[icon]) return cache[icon];
+  return cache[icon] = fs.readFileSync(__dirname + '/public/icons/' + icon, 'base64');
+}
+
+/**
+ * Normalizes the path separator from system separator
+ * to URL separator, aka `/`.
+ *
+ * @param {String} path
+ * @return {String}
+ * @api private
+ */
+
+function normalizeSlashes(path) {
+  return path.split(sep).join('/');
+};
+
+/**
+ * Filter "hidden" `files`, aka files
+ * beginning with a `.`.
+ *
+ * @param {Array} files
+ * @return {Array}
+ * @api private
+ */
+
+function removeHidden(files) {
+  return files.filter(function(file){
+    return '.' != file[0];
+  });
+}
+
+/**
+ * Stat all files and return array of stat
+ * in same order.
+ */
+
+function stat(dir, files, cb) {
+  var batch = new Batch();
+
+  batch.concurrency(10);
+
+  files.forEach(function(file){
+    batch.push(function(done){
+      fs.stat(join(dir, file), function(err, stat){
+        if (err && err.code !== 'ENOENT') return done(err);
+
+        // pass ENOENT as null stat, not error
+        done(null, stat || null);
+      });
+    });
+  });
+
+  batch.end(cb);
+}
+
+/**
+ * Icon map.
+ */
+
+var icons = {
+  // base icons
+  'default': 'page_white.png',
+  'folder': 'folder.png',
+
+  // generic mime type icons
+  'image': 'image.png',
+  'text': 'page_white_text.png',
+  'video': 'film.png',
+
+  // generic mime suffix icons
+  '+json': 'page_white_code.png',
+  '+xml': 'page_white_code.png',
+  '+zip': 'box.png',
+
+  // specific mime type icons
+  'application/font-woff': 'font.png',
+  'application/javascript': 'page_white_code_red.png',
+  'application/json': 'page_white_code.png',
+  'application/msword': 'page_white_word.png',
+  'application/pdf': 'page_white_acrobat.png',
+  'application/postscript': 'page_white_vector.png',
+  'application/rtf': 'page_white_word.png',
+  'application/vnd.ms-excel': 'page_white_excel.png',
+  'application/vnd.ms-powerpoint': 'page_white_powerpoint.png',
+  'application/vnd.oasis.opendocument.presentation': 'page_white_powerpoint.png',
+  'application/vnd.oasis.opendocument.spreadsheet': 'page_white_excel.png',
+  'application/vnd.oasis.opendocument.text': 'page_white_word.png',
+  'application/x-7z-compressed': 'box.png',
+  'application/x-sh': 'application_xp_terminal.png',
+  'application/x-font-ttf': 'font.png',
+  'application/x-msaccess': 'page_white_database.png',
+  'application/x-shockwave-flash': 'page_white_flash.png',
+  'application/x-sql': 'page_white_database.png',
+  'application/x-tar': 'box.png',
+  'application/x-xz': 'box.png',
+  'application/xml': 'page_white_code.png',
+  'application/zip': 'box.png',
+  'image/svg+xml': 'page_white_vector.png',
+  'text/css': 'page_white_code.png',
+  'text/html': 'page_white_code.png',
+  'text/less': 'page_white_code.png',
+
+  // other, extension-specific icons
+  '.accdb': 'page_white_database.png',
+  '.apk': 'box.png',
+  '.app': 'application_xp.png',
+  '.as': 'page_white_actionscript.png',
+  '.asp': 'page_white_code.png',
+  '.aspx': 'page_white_code.png',
+  '.bat': 'application_xp_terminal.png',
+  '.bz2': 'box.png',
+  '.c': 'page_white_c.png',
+  '.cab': 'box.png',
+  '.cfm': 'page_white_coldfusion.png',
+  '.clj': 'page_white_code.png',
+  '.cc': 'page_white_cplusplus.png',
+  '.cgi': 'application_xp_terminal.png',
+  '.cpp': 'page_white_cplusplus.png',
+  '.cs': 'page_white_csharp.png',
+  '.db': 'page_white_database.png',
+  '.dbf': 'page_white_database.png',
+  '.deb': 'box.png',
+  '.dll': 'page_white_gear.png',
+  '.dmg': 'drive.png',
+  '.docx': 'page_white_word.png',
+  '.erb': 'page_white_ruby.png',
+  '.exe': 'application_xp.png',
+  '.fnt': 'font.png',
+  '.gam': 'controller.png',
+  '.gz': 'box.png',
+  '.h': 'page_white_h.png',
+  '.ini': 'page_white_gear.png',
+  '.iso': 'cd.png',
+  '.jar': 'box.png',
+  '.java': 'page_white_cup.png',
+  '.jsp': 'page_white_cup.png',
+  '.lua': 'page_white_code.png',
+  '.lz': 'box.png',
+  '.lzma': 'box.png',
+  '.m': 'page_white_code.png',
+  '.map': 'map.png',
+  '.msi': 'box.png',
+  '.mv4': 'film.png',
+  '.otf': 'font.png',
+  '.pdb': 'page_white_database.png',
+  '.php': 'page_white_php.png',
+  '.pl': 'page_white_code.png',
+  '.pkg': 'box.png',
+  '.pptx': 'page_white_powerpoint.png',
+  '.psd': 'page_white_picture.png',
+  '.py': 'page_white_code.png',
+  '.rar': 'box.png',
+  '.rb': 'page_white_ruby.png',
+  '.rm': 'film.png',
+  '.rom': 'controller.png',
+  '.rpm': 'box.png',
+  '.sass': 'page_white_code.png',
+  '.sav': 'controller.png',
+  '.scss': 'page_white_code.png',
+  '.srt': 'page_white_text.png',
+  '.tbz2': 'box.png',
+  '.tgz': 'box.png',
+  '.tlz': 'box.png',
+  '.vb': 'page_white_code.png',
+  '.vbs': 'page_white_code.png',
+  '.xcf': 'page_white_picture.png',
+  '.xlsx': 'page_white_excel.png',
+  '.yaws': 'page_white_code.png'
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e2af38b
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "serve-index",
+  "description": "Serve directory listings",
+  "version": "1.4.0",
+  "author": "Douglas Christopher Wilson <doug at somethingdoug.com>",
+  "license": "MIT",
+  "repository": "expressjs/serve-index",
+  "dependencies": {
+    "accepts": "~1.1.1",
+    "batch": "0.5.1",
+    "debug": "~2.0.0",
+    "mime-types": "~2.0.1",
+    "parseurl": "~1.3.0"
+  },
+  "devDependencies": {
+    "istanbul": "0.3.2",
+    "mocha": "~1.21.1",
+    "should": "~4.0.0",
+    "supertest": "~0.14.0"
+  },
+  "files": [
+    "public/",
+    "LICENSE",
+    "HISTORY.md",
+    "index.js"
+  ],
+  "engines": {
+    "node": ">= 0.8.0"
+  },
+  "scripts": {
+    "test": "mocha --reporter spec --bail --check-leaks test/",
+    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/",
+    "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/"
+  }
+}
diff --git a/public/directory.html b/public/directory.html
new file mode 100644
index 0000000..8ed8b4a
--- /dev/null
+++ b/public/directory.html
@@ -0,0 +1,82 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset='utf-8'> 
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
+    <title>listing directory {directory}</title>
+    <style>{style}</style>
+    <script>
+      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);
+      });
+    </script>
+  </head>
+  <body class="directory">
+    <input id="search" type="text" placeholder="Search" autocomplete="off" />
+    <div id="wrapper">
+      <h1>{linked-path}</h1>
+      {files}
+    </div>
+  </body>
+</html>
\ No newline at end of file
diff --git a/public/icons/application_xp.png b/public/icons/application_xp.png
new file mode 100644
index 0000000..d22860a
Binary files /dev/null and b/public/icons/application_xp.png differ
diff --git a/public/icons/application_xp_terminal.png b/public/icons/application_xp_terminal.png
new file mode 100644
index 0000000..c28dd63
Binary files /dev/null and b/public/icons/application_xp_terminal.png differ
diff --git a/public/icons/box.png b/public/icons/box.png
new file mode 100644
index 0000000..8443c23
Binary files /dev/null and b/public/icons/box.png differ
diff --git a/public/icons/cd.png b/public/icons/cd.png
new file mode 100644
index 0000000..ef43223
Binary files /dev/null and b/public/icons/cd.png differ
diff --git a/public/icons/controller.png b/public/icons/controller.png
new file mode 100644
index 0000000..5cf76ed
Binary files /dev/null and b/public/icons/controller.png differ
diff --git a/public/icons/drive.png b/public/icons/drive.png
new file mode 100644
index 0000000..37b7c9b
Binary files /dev/null and b/public/icons/drive.png differ
diff --git a/public/icons/film.png b/public/icons/film.png
new file mode 100644
index 0000000..b0ce7bb
Binary files /dev/null and b/public/icons/film.png differ
diff --git a/public/icons/folder.png b/public/icons/folder.png
new file mode 100644
index 0000000..698f3d3
Binary files /dev/null and b/public/icons/folder.png differ
diff --git a/public/icons/font.png b/public/icons/font.png
new file mode 100644
index 0000000..b7960db
Binary files /dev/null and b/public/icons/font.png differ
diff --git a/public/icons/image.png b/public/icons/image.png
new file mode 100644
index 0000000..fc3c393
Binary files /dev/null and b/public/icons/image.png differ
diff --git a/public/icons/map.png b/public/icons/map.png
new file mode 100644
index 0000000..f90ef25
Binary files /dev/null and b/public/icons/map.png differ
diff --git a/public/icons/page.png b/public/icons/page.png
new file mode 100644
index 0000000..03ddd79
Binary files /dev/null and b/public/icons/page.png differ
diff --git a/public/icons/page_add.png b/public/icons/page_add.png
new file mode 100644
index 0000000..d5bfa07
Binary files /dev/null and b/public/icons/page_add.png differ
diff --git a/public/icons/page_attach.png b/public/icons/page_attach.png
new file mode 100644
index 0000000..89ee2da
Binary files /dev/null and b/public/icons/page_attach.png differ
diff --git a/public/icons/page_code.png b/public/icons/page_code.png
new file mode 100644
index 0000000..f7ea904
Binary files /dev/null and b/public/icons/page_code.png differ
diff --git a/public/icons/page_copy.png b/public/icons/page_copy.png
new file mode 100644
index 0000000..195dc6d
Binary files /dev/null and b/public/icons/page_copy.png differ
diff --git a/public/icons/page_delete.png b/public/icons/page_delete.png
new file mode 100644
index 0000000..3141467
Binary files /dev/null and b/public/icons/page_delete.png differ
diff --git a/public/icons/page_edit.png b/public/icons/page_edit.png
new file mode 100644
index 0000000..046811e
Binary files /dev/null and b/public/icons/page_edit.png differ
diff --git a/public/icons/page_error.png b/public/icons/page_error.png
new file mode 100644
index 0000000..f07f449
Binary files /dev/null and b/public/icons/page_error.png differ
diff --git a/public/icons/page_excel.png b/public/icons/page_excel.png
new file mode 100644
index 0000000..eb6158e
Binary files /dev/null and b/public/icons/page_excel.png differ
diff --git a/public/icons/page_find.png b/public/icons/page_find.png
new file mode 100644
index 0000000..2f19388
Binary files /dev/null and b/public/icons/page_find.png differ
diff --git a/public/icons/page_gear.png b/public/icons/page_gear.png
new file mode 100644
index 0000000..8e83281
Binary files /dev/null and b/public/icons/page_gear.png differ
diff --git a/public/icons/page_go.png b/public/icons/page_go.png
new file mode 100644
index 0000000..80fe1ed
Binary files /dev/null and b/public/icons/page_go.png differ
diff --git a/public/icons/page_green.png b/public/icons/page_green.png
new file mode 100644
index 0000000..de8e003
Binary files /dev/null and b/public/icons/page_green.png differ
diff --git a/public/icons/page_key.png b/public/icons/page_key.png
new file mode 100644
index 0000000..d6626cb
Binary files /dev/null and b/public/icons/page_key.png differ
diff --git a/public/icons/page_lightning.png b/public/icons/page_lightning.png
new file mode 100644
index 0000000..7e56870
Binary files /dev/null and b/public/icons/page_lightning.png differ
diff --git a/public/icons/page_link.png b/public/icons/page_link.png
new file mode 100644
index 0000000..312eab0
Binary files /dev/null and b/public/icons/page_link.png differ
diff --git a/public/icons/page_paintbrush.png b/public/icons/page_paintbrush.png
new file mode 100644
index 0000000..246a2f0
Binary files /dev/null and b/public/icons/page_paintbrush.png differ
diff --git a/public/icons/page_paste.png b/public/icons/page_paste.png
new file mode 100644
index 0000000..968f073
Binary files /dev/null and b/public/icons/page_paste.png differ
diff --git a/public/icons/page_red.png b/public/icons/page_red.png
new file mode 100644
index 0000000..0b18247
Binary files /dev/null and b/public/icons/page_red.png differ
diff --git a/public/icons/page_refresh.png b/public/icons/page_refresh.png
new file mode 100644
index 0000000..cf347c7
Binary files /dev/null and b/public/icons/page_refresh.png differ
diff --git a/public/icons/page_save.png b/public/icons/page_save.png
new file mode 100644
index 0000000..caea546
Binary files /dev/null and b/public/icons/page_save.png differ
diff --git a/public/icons/page_white.png b/public/icons/page_white.png
new file mode 100644
index 0000000..8b8b1ca
Binary files /dev/null and b/public/icons/page_white.png differ
diff --git a/public/icons/page_white_acrobat.png b/public/icons/page_white_acrobat.png
new file mode 100644
index 0000000..8f8095e
Binary files /dev/null and b/public/icons/page_white_acrobat.png differ
diff --git a/public/icons/page_white_actionscript.png b/public/icons/page_white_actionscript.png
new file mode 100644
index 0000000..159b240
Binary files /dev/null and b/public/icons/page_white_actionscript.png differ
diff --git a/public/icons/page_white_add.png b/public/icons/page_white_add.png
new file mode 100644
index 0000000..aa23dde
Binary files /dev/null and b/public/icons/page_white_add.png differ
diff --git a/public/icons/page_white_c.png b/public/icons/page_white_c.png
new file mode 100644
index 0000000..34a05cc
Binary files /dev/null and b/public/icons/page_white_c.png differ
diff --git a/public/icons/page_white_camera.png b/public/icons/page_white_camera.png
new file mode 100644
index 0000000..f501a59
Binary files /dev/null and b/public/icons/page_white_camera.png differ
diff --git a/public/icons/page_white_cd.png b/public/icons/page_white_cd.png
new file mode 100644
index 0000000..848bdaf
Binary files /dev/null and b/public/icons/page_white_cd.png differ
diff --git a/public/icons/page_white_code.png b/public/icons/page_white_code.png
new file mode 100644
index 0000000..0c76bd1
Binary files /dev/null and b/public/icons/page_white_code.png differ
diff --git a/public/icons/page_white_code_red.png b/public/icons/page_white_code_red.png
new file mode 100644
index 0000000..87a6914
Binary files /dev/null and b/public/icons/page_white_code_red.png differ
diff --git a/public/icons/page_white_coldfusion.png b/public/icons/page_white_coldfusion.png
new file mode 100644
index 0000000..c66011f
Binary files /dev/null and b/public/icons/page_white_coldfusion.png differ
diff --git a/public/icons/page_white_compressed.png b/public/icons/page_white_compressed.png
new file mode 100644
index 0000000..2b6b100
Binary files /dev/null and b/public/icons/page_white_compressed.png differ
diff --git a/public/icons/page_white_copy.png b/public/icons/page_white_copy.png
new file mode 100644
index 0000000..a9f31a2
Binary files /dev/null and b/public/icons/page_white_copy.png differ
diff --git a/public/icons/page_white_cplusplus.png b/public/icons/page_white_cplusplus.png
new file mode 100644
index 0000000..a87cf84
Binary files /dev/null and b/public/icons/page_white_cplusplus.png differ
diff --git a/public/icons/page_white_csharp.png b/public/icons/page_white_csharp.png
new file mode 100644
index 0000000..ffb8fc9
Binary files /dev/null and b/public/icons/page_white_csharp.png differ
diff --git a/public/icons/page_white_cup.png b/public/icons/page_white_cup.png
new file mode 100644
index 0000000..0a7d6f4
Binary files /dev/null and b/public/icons/page_white_cup.png differ
diff --git a/public/icons/page_white_database.png b/public/icons/page_white_database.png
new file mode 100644
index 0000000..bddba1f
Binary files /dev/null and b/public/icons/page_white_database.png differ
diff --git a/public/icons/page_white_delete.png b/public/icons/page_white_delete.png
new file mode 100644
index 0000000..af1ecaf
Binary files /dev/null and b/public/icons/page_white_delete.png differ
diff --git a/public/icons/page_white_dvd.png b/public/icons/page_white_dvd.png
new file mode 100644
index 0000000..4cc537a
Binary files /dev/null and b/public/icons/page_white_dvd.png differ
diff --git a/public/icons/page_white_edit.png b/public/icons/page_white_edit.png
new file mode 100644
index 0000000..b93e776
Binary files /dev/null and b/public/icons/page_white_edit.png differ
diff --git a/public/icons/page_white_error.png b/public/icons/page_white_error.png
new file mode 100644
index 0000000..9fc5a0a
Binary files /dev/null and b/public/icons/page_white_error.png differ
diff --git a/public/icons/page_white_excel.png b/public/icons/page_white_excel.png
new file mode 100644
index 0000000..b977d7e
Binary files /dev/null and b/public/icons/page_white_excel.png differ
diff --git a/public/icons/page_white_find.png b/public/icons/page_white_find.png
new file mode 100644
index 0000000..5818436
Binary files /dev/null and b/public/icons/page_white_find.png differ
diff --git a/public/icons/page_white_flash.png b/public/icons/page_white_flash.png
new file mode 100644
index 0000000..5769120
Binary files /dev/null and b/public/icons/page_white_flash.png differ
diff --git a/public/icons/page_white_freehand.png b/public/icons/page_white_freehand.png
new file mode 100644
index 0000000..8d719df
Binary files /dev/null and b/public/icons/page_white_freehand.png differ
diff --git a/public/icons/page_white_gear.png b/public/icons/page_white_gear.png
new file mode 100644
index 0000000..106f5aa
Binary files /dev/null and b/public/icons/page_white_gear.png differ
diff --git a/public/icons/page_white_get.png b/public/icons/page_white_get.png
new file mode 100644
index 0000000..e4a1ecb
Binary files /dev/null and b/public/icons/page_white_get.png differ
diff --git a/public/icons/page_white_go.png b/public/icons/page_white_go.png
new file mode 100644
index 0000000..7e62a92
Binary files /dev/null and b/public/icons/page_white_go.png differ
diff --git a/public/icons/page_white_h.png b/public/icons/page_white_h.png
new file mode 100644
index 0000000..e902abb
Binary files /dev/null and b/public/icons/page_white_h.png differ
diff --git a/public/icons/page_white_horizontal.png b/public/icons/page_white_horizontal.png
new file mode 100644
index 0000000..1d2d0a4
Binary files /dev/null and b/public/icons/page_white_horizontal.png differ
diff --git a/public/icons/page_white_key.png b/public/icons/page_white_key.png
new file mode 100644
index 0000000..d616484
Binary files /dev/null and b/public/icons/page_white_key.png differ
diff --git a/public/icons/page_white_lightning.png b/public/icons/page_white_lightning.png
new file mode 100644
index 0000000..7215d1e
Binary files /dev/null and b/public/icons/page_white_lightning.png differ
diff --git a/public/icons/page_white_link.png b/public/icons/page_white_link.png
new file mode 100644
index 0000000..bf7bd1c
Binary files /dev/null and b/public/icons/page_white_link.png differ
diff --git a/public/icons/page_white_magnify.png b/public/icons/page_white_magnify.png
new file mode 100644
index 0000000..f6b74cc
Binary files /dev/null and b/public/icons/page_white_magnify.png differ
diff --git a/public/icons/page_white_medal.png b/public/icons/page_white_medal.png
new file mode 100644
index 0000000..d3fffb6
Binary files /dev/null and b/public/icons/page_white_medal.png differ
diff --git a/public/icons/page_white_office.png b/public/icons/page_white_office.png
new file mode 100644
index 0000000..a65bcb3
Binary files /dev/null and b/public/icons/page_white_office.png differ
diff --git a/public/icons/page_white_paint.png b/public/icons/page_white_paint.png
new file mode 100644
index 0000000..23a37b8
Binary files /dev/null and b/public/icons/page_white_paint.png differ
diff --git a/public/icons/page_white_paintbrush.png b/public/icons/page_white_paintbrush.png
new file mode 100644
index 0000000..f907e44
Binary files /dev/null and b/public/icons/page_white_paintbrush.png differ
diff --git a/public/icons/page_white_paste.png b/public/icons/page_white_paste.png
new file mode 100644
index 0000000..5b2cbb3
Binary files /dev/null and b/public/icons/page_white_paste.png differ
diff --git a/public/icons/page_white_php.png b/public/icons/page_white_php.png
new file mode 100644
index 0000000..7868a25
Binary files /dev/null and b/public/icons/page_white_php.png differ
diff --git a/public/icons/page_white_picture.png b/public/icons/page_white_picture.png
new file mode 100644
index 0000000..134b669
Binary files /dev/null and b/public/icons/page_white_picture.png differ
diff --git a/public/icons/page_white_powerpoint.png b/public/icons/page_white_powerpoint.png
new file mode 100644
index 0000000..c4eff03
Binary files /dev/null and b/public/icons/page_white_powerpoint.png differ
diff --git a/public/icons/page_white_put.png b/public/icons/page_white_put.png
new file mode 100644
index 0000000..884ffd6
Binary files /dev/null and b/public/icons/page_white_put.png differ
diff --git a/public/icons/page_white_ruby.png b/public/icons/page_white_ruby.png
new file mode 100644
index 0000000..f59b7c4
Binary files /dev/null and b/public/icons/page_white_ruby.png differ
diff --git a/public/icons/page_white_stack.png b/public/icons/page_white_stack.png
new file mode 100644
index 0000000..44084ad
Binary files /dev/null and b/public/icons/page_white_stack.png differ
diff --git a/public/icons/page_white_star.png b/public/icons/page_white_star.png
new file mode 100644
index 0000000..3a1441c
Binary files /dev/null and b/public/icons/page_white_star.png differ
diff --git a/public/icons/page_white_swoosh.png b/public/icons/page_white_swoosh.png
new file mode 100644
index 0000000..e770829
Binary files /dev/null and b/public/icons/page_white_swoosh.png differ
diff --git a/public/icons/page_white_text.png b/public/icons/page_white_text.png
new file mode 100644
index 0000000..813f712
Binary files /dev/null and b/public/icons/page_white_text.png differ
diff --git a/public/icons/page_white_text_width.png b/public/icons/page_white_text_width.png
new file mode 100644
index 0000000..d9cf132
Binary files /dev/null and b/public/icons/page_white_text_width.png differ
diff --git a/public/icons/page_white_tux.png b/public/icons/page_white_tux.png
new file mode 100644
index 0000000..52699bf
Binary files /dev/null and b/public/icons/page_white_tux.png differ
diff --git a/public/icons/page_white_vector.png b/public/icons/page_white_vector.png
new file mode 100644
index 0000000..4a05955
Binary files /dev/null and b/public/icons/page_white_vector.png differ
diff --git a/public/icons/page_white_visualstudio.png b/public/icons/page_white_visualstudio.png
new file mode 100644
index 0000000..a0a433d
Binary files /dev/null and b/public/icons/page_white_visualstudio.png differ
diff --git a/public/icons/page_white_width.png b/public/icons/page_white_width.png
new file mode 100644
index 0000000..1eb8809
Binary files /dev/null and b/public/icons/page_white_width.png differ
diff --git a/public/icons/page_white_word.png b/public/icons/page_white_word.png
new file mode 100644
index 0000000..ae8ecbf
Binary files /dev/null and b/public/icons/page_white_word.png differ
diff --git a/public/icons/page_white_world.png b/public/icons/page_white_world.png
new file mode 100644
index 0000000..6ed2490
Binary files /dev/null and b/public/icons/page_white_world.png differ
diff --git a/public/icons/page_white_wrench.png b/public/icons/page_white_wrench.png
new file mode 100644
index 0000000..fecadd0
Binary files /dev/null and b/public/icons/page_white_wrench.png differ
diff --git a/public/icons/page_white_zip.png b/public/icons/page_white_zip.png
new file mode 100644
index 0000000..fd4bbcc
Binary files /dev/null and b/public/icons/page_white_zip.png differ
diff --git a/public/icons/page_word.png b/public/icons/page_word.png
new file mode 100644
index 0000000..834cdfa
Binary files /dev/null and b/public/icons/page_word.png differ
diff --git a/public/icons/page_world.png b/public/icons/page_world.png
new file mode 100644
index 0000000..b8895dd
Binary files /dev/null and b/public/icons/page_world.png differ
diff --git a/public/style.css b/public/style.css
new file mode 100644
index 0000000..0709908
--- /dev/null
+++ b/public/style.css
@@ -0,0 +1,257 @@
+* {
+  margin: 0;
+  padding: 0;
+  outline: 0;
+}
+
+body {
+  padding: 80px 100px;
+  font: 13px "Helvetica Neue", "Lucida Grande", "Arial";
+  background: #ECE9E9 -webkit-gradient(linear, 0% 0%, 0% 100%, from(#fff), to(#ECE9E9));
+  background: #ECE9E9 -moz-linear-gradient(top, #fff, #ECE9E9);
+  background-repeat: no-repeat;
+  color: #555;
+  -webkit-font-smoothing: antialiased;
+}
+h1, h2, h3 {
+  font-size: 22px;
+  color: #343434;
+}
+h1 em, h2 em {
+  padding: 0 5px;
+  font-weight: normal;
+}
+h1 {
+  font-size: 60px;
+}
+h2 {
+  margin-top: 10px;
+}
+h3 {
+  margin: 5px 0 10px 0;
+  padding-bottom: 5px;
+  border-bottom: 1px solid #eee;
+  font-size: 18px;
+}
+ul li {
+  list-style: none;
+}
+ul li:hover {
+  cursor: pointer;
+  color: #2e2e2e;
+}
+ul li .path {
+  padding-left: 5px;
+  font-weight: bold;
+}
+ul li .line {
+  padding-right: 5px;
+  font-style: italic;
+}
+ul li:first-child .path {
+  padding-left: 0;
+}
+p {
+  line-height: 1.5;
+}
+a {
+  color: #555;
+  text-decoration: none;
+}
+a:hover {
+  color: #303030;
+}
+#stacktrace {
+  margin-top: 15px;
+}
+.directory h1 {
+  margin-bottom: 15px;
+  font-size: 18px;
+}
+ul#files {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+ul#files li {
+  float: left;
+  width: 30%;
+  line-height: 25px;
+  margin: 1px;
+}
+ul#files li a {
+  display: block;
+  height: 25px;
+  border: 1px solid transparent;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+  overflow: hidden;
+  white-space: nowrap;
+}
+ul#files li a:focus,
+ul#files li a:hover {
+  background: rgba(255,255,255,0.65);
+  border: 1px solid #ececec;
+}
+ul#files li a.highlight {
+  -webkit-transition: background .4s ease-in-out;
+  background: #ffff4f;
+  border-color: #E9DC51;
+}
+#search {
+  display: block;
+  position: fixed;
+  top: 20px;
+  right: 20px;
+  width: 90px;
+  -webkit-transition: width ease 0.2s, opacity ease 0.4s;
+  -moz-transition: width ease 0.2s, opacity ease 0.4s;
+  -webkit-border-radius: 32px;
+  -moz-border-radius: 32px;
+  -webkit-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
+  -moz-box-shadow: inset 0px 0px 3px rgba(0, 0, 0, 0.25), inset 0px 1px 3px rgba(0, 0, 0, 0.7), 0px 1px 0px rgba(255, 255, 255, 0.03);
+  -webkit-font-smoothing: antialiased;
+  text-align: left;
+  font: 13px "Helvetica Neue", Arial, sans-serif;
+  padding: 4px 10px;
+  border: none;
+  background: transparent;
+  margin-bottom: 0;
+  outline: none;
+  opacity: 0.7;
+  color: #888;
+}
+#search:focus {
+  width: 120px;
+  opacity: 1.0; 
+}
+
+/*views*/
+#files span {
+  display: inline-block;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  text-indent: 10px;
+}
+#files .name {
+  background-repeat: no-repeat;
+}
+#files .icon .name {
+  text-indent: 28px;
+}
+
+/*tiles*/
+.view-tiles .name {
+  width: 100%;
+  background-position: 8px 5px;
+}
+.view-tiles .size,
+.view-tiles .date {
+  display: none;
+}
+
+/*details*/
+ul#files.view-details li {
+  float: none;
+  display: block;
+  width: 90%;
+}
+ul#files.view-details li.header {
+  height: 25px;
+  background: #000;
+  color: #fff;
+  font-weight: bold;
+}
+.view-details .header {
+  border-radius: 5px;
+}
+.view-details .name {
+  width: 60%;
+  background-position: 8px 5px;
+}
+.view-details .size {
+  width: 10%;
+}
+.view-details .date {
+  width: 30%;
+}
+.view-details .size,
+.view-details .date {
+  text-align: right;
+  direction: rtl;
+}
+
+/*mobile*/
+ at media (max-width: 768px) {
+  body {
+    font-size: 13px;
+    line-height: 16px;
+    padding: 0;
+  }
+  #search {
+    position: static;
+    width: 100%;
+    font-size: 2em;
+    line-height: 1.8em;
+    text-indent: 10px;
+    border: 0;
+    border-radius: 0;
+    padding: 10px 0;
+    margin: 0;
+  }
+  #search:focus {
+    width: 100%;
+    border: 0;
+    opacity: 1;
+  }
+  .directory h1 {
+    font-size: 2em;
+    line-height: 1.5em;
+    color: #fff;
+    background: #000;
+    padding: 15px 10px;
+    margin: 0;
+  }
+  ul#files {
+    border-top: 1px solid #cacaca;
+  }
+  ul#files li {
+    float: none;
+    width: auto !important;
+    display: block;
+    border-bottom: 1px solid #cacaca;
+    font-size: 2em;
+    line-height: 1.2em;
+    text-indent: 0;
+    margin: 0;
+  }
+  ul#files li:nth-child(odd) {
+    background: #e0e0e0;
+  }
+  ul#files li a {
+    height: auto;
+    border: 0;
+    border-radius: 0;
+    padding: 15px 10px;
+  }
+  ul#files li a:focus,
+  ul#files li a:hover {
+    border: 0;
+  }
+  #files .header,
+  #files .size,
+  #files .date {
+    display: none !important;
+  }
+  #files .name {
+    float: none;
+    display: inline-block;
+    width: 100%;
+    text-indent: 0;
+    background-position: 0 0;
+  }
+  #files .icon .name {
+    text-indent: 41px;
+  }
+}
diff --git a/test/fixtures/#directory/index.html b/test/fixtures/#directory/index.html
new file mode 100644
index 0000000..00a2db4
--- /dev/null
+++ b/test/fixtures/#directory/index.html
@@ -0,0 +1 @@
+<p>tobi, loki, jane</p>
\ No newline at end of file
diff --git a/test/fixtures/.hidden b/test/fixtures/.hidden
new file mode 100644
index 0000000..b885243
--- /dev/null
+++ b/test/fixtures/.hidden
@@ -0,0 +1 @@
+I am hidden
\ No newline at end of file
diff --git a/test/fixtures/collect/sample b/test/fixtures/collect/sample
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/collect/sample.jpg b/test/fixtures/collect/sample.jpg
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/collect/sample.mp4 b/test/fixtures/collect/sample.mp4
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/collect/sample.pdf b/test/fixtures/collect/sample.pdf
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/collect/sample.qfx b/test/fixtures/collect/sample.qfx
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/collect/sample.rdf b/test/fixtures/collect/sample.rdf
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/collect/sample.txt b/test/fixtures/collect/sample.txt
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/collect/sample.xlsx b/test/fixtures/collect/sample.xlsx
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/file #1.txt b/test/fixtures/file #1.txt
new file mode 100644
index 0000000..72c0718
--- /dev/null
+++ b/test/fixtures/file #1.txt	
@@ -0,0 +1 @@
+#1 file!
\ No newline at end of file
diff --git a/test/fixtures/foo bar b/test/fixtures/foo bar
new file mode 100644
index 0000000..3f95386
--- /dev/null
+++ b/test/fixtures/foo bar	
@@ -0,0 +1 @@
+baz
\ No newline at end of file
diff --git a/test/fixtures/g# %3 o %2525 %37 dir/empty.txt b/test/fixtures/g# %3 o %2525 %37 dir/empty.txt
new file mode 100644
index 0000000..e69de29
diff --git a/test/fixtures/nums b/test/fixtures/nums
new file mode 100644
index 0000000..e2e107a
--- /dev/null
+++ b/test/fixtures/nums
@@ -0,0 +1 @@
+123456789
\ No newline at end of file
diff --git a/test/fixtures/todo.txt b/test/fixtures/todo.txt
new file mode 100644
index 0000000..8c3539d
--- /dev/null
+++ b/test/fixtures/todo.txt
@@ -0,0 +1 @@
+- groceries
\ No newline at end of file
diff --git a/test/fixtures/users/index.html b/test/fixtures/users/index.html
new file mode 100644
index 0000000..00a2db4
--- /dev/null
+++ b/test/fixtures/users/index.html
@@ -0,0 +1 @@
+<p>tobi, loki, jane</p>
\ No newline at end of file
diff --git a/test/fixtures/users/tobi.txt b/test/fixtures/users/tobi.txt
new file mode 100644
index 0000000..9d9529d
--- /dev/null
+++ b/test/fixtures/users/tobi.txt
@@ -0,0 +1 @@
+ferret
\ No newline at end of file
diff --git "a/test/fixtures/\343\201\225\343\201\217\343\202\211.txt" "b/test/fixtures/\343\201\225\343\201\217\343\202\211.txt"
new file mode 100644
index 0000000..e69de29
diff --git a/test/shared/index.js b/test/shared/index.js
new file mode 100644
index 0000000..3958366
--- /dev/null
+++ b/test/shared/index.js
@@ -0,0 +1,26 @@
+
+var bytes = require('bytes');
+
+exports['default request body'] = function(app){
+  it('should default to {}', function(done){
+    app.request()
+    .post('/')
+    .end(function(res){
+      res.body.should.equal('{}');
+      done();
+    })
+  })
+};
+
+exports['limit body to'] = function(size, type, app){
+  it('should accept a limit option', function(done){
+    app.request()
+    .post('/')
+    .set('Content-Length', bytes(size) + 1)
+    .set('Content-Type', type)
+    .end(function(res){
+      res.should.have.status(413);
+      done();
+    })
+  })
+}
\ No newline at end of file
diff --git a/test/shared/styles.css b/test/shared/styles.css
new file mode 100644
index 0000000..3310f40
--- /dev/null
+++ b/test/shared/styles.css
@@ -0,0 +1,3 @@
+body {
+	color: #00ff00;
+}
\ No newline at end of file
diff --git a/test/shared/template.html b/test/shared/template.html
new file mode 100644
index 0000000..04ed760
--- /dev/null
+++ b/test/shared/template.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>listing directory {directory}</title>
+    <style>{style}</style>
+  </head>
+  <body class="directory">
+    <h1>This is the test template</h1>
+    <h2>directory {directory}</h2>
+    <div id="wrapper">
+      <h1>{linked-path}</h1>
+      {files}
+    </div>
+  </body>
+</html>
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..c595dc6
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,618 @@
+
+var http = require('http');
+var fs = require('fs');
+var path = require('path');
+var request = require('supertest');
+var should = require('should');
+var serveIndex = require('..');
+
+var fixtures = path.join(__dirname, '/fixtures');
+var relative = path.relative(process.cwd(), fixtures);
+
+var skipRelative = ~relative.indexOf('..') || path.resolve(relative) === relative;
+
+describe('serveIndex(root)', function () {
+  it('should require root', function () {
+    serveIndex.should.throw(/root path required/)
+  })
+
+  it('should serve text/html without Accept header', function (done) {
+    var server = createServer()
+
+    request(server)
+    .get('/')
+    .expect('Content-Type', 'text/html; charset=utf-8')
+    .expect(200, done)
+  })
+
+  it('should serve a directory index', function (done) {
+    var server = createServer()
+
+    request(server)
+    .get('/')
+    .expect(200, /todo\.txt/, done)
+  })
+
+  it('should work with HEAD requests', function (done) {
+    var server = createServer()
+
+    request(server)
+    .head('/')
+    .expect(200, '', done)
+  })
+
+  it('should work with OPTIONS requests', function (done) {
+    var server = createServer()
+
+    request(server)
+    .options('/')
+    .expect('Allow', 'GET, HEAD, OPTIONS')
+    .expect(200, done)
+  })
+
+  it('should deny POST requests', function (done) {
+    var server = createServer()
+
+    request(server)
+    .post('/')
+    .expect(405, done)
+  })
+
+  it('should deny path will NULL byte', function (done) {
+    var server = createServer()
+
+    request(server)
+    .get('/%00')
+    .expect(400, done)
+  })
+
+  it('should deny path outside root', function (done) {
+    var server = createServer()
+
+    request(server)
+    .get('/../')
+    .expect(403, done)
+  })
+
+  it('should skip non-existent paths', function (done) {
+    var server = createServer()
+
+    request(server)
+    .get('/bogus')
+    .expect(404, 'Not Found', done)
+  })
+
+  it('should treat an ENAMETOOLONG as a 414', function (done) {
+    var path = Array(11000).join('foobar')
+    var server = createServer()
+
+    request(server)
+    .get('/' + path)
+    .expect(414, done)
+  })
+
+  it('should skip non-directories', function (done) {
+    var server = createServer()
+
+    request(server)
+    .get('/nums')
+    .expect(404, 'Not Found', done)
+  })
+
+  describe('when given Accept: header', function () {
+    describe('when Accept: application/json is given', function () {
+      it('should respond with json', function (done) {
+        var server = createServer()
+
+        request(server)
+        .get('/')
+        .set('Accept', 'application/json')
+        .expect('Content-Type', /json/)
+        .expect(/g# %3 o %2525 %37 dir/)
+        .expect(/users/)
+        .expect(/file #1\.txt/)
+        .expect(/nums/)
+        .expect(/todo\.txt/)
+        .expect(/さくら\.txt/)
+        .expect(200, done)
+      });
+    });
+
+    describe('when Accept: text/html is given', function () {
+      it('should respond with html', function (done) {
+        var server = createServer()
+
+        request(server)
+        .get('/')
+        .set('Accept', 'text/html')
+        .expect(200)
+        .expect('Content-Type', 'text/html; charset=utf-8')
+        .expect(/<a href="\/g%23%20%253%20o%20%252525%20%2537%20dir"/)
+        .expect(/<a href="\/users"/)
+        .expect(/<a href="\/file%20%231.txt"/)
+        .expect(/<a href="\/todo.txt"/)
+        .expect(/<a href="\/%E3%81%95%E3%81%8F%E3%82%89\.txt"/)
+        .end(done);
+      });
+
+      it('should sort folders first', function (done) {
+        var server = createServer()
+
+        request(server)
+        .get('/')
+        .set('Accept', 'text/html')
+        .expect(200)
+        .expect('Content-Type', 'text/html; charset=utf-8')
+        .end(function (err, res) {
+          if (err) throw err;
+          var urls = res.text.split(/<a href="([^"]*)"/).filter(function(s, i){ return i%2; });
+          urls.should.eql([
+            '/%23directory',
+            '/collect',
+            '/g%23%20%253%20o%20%252525%20%2537%20dir',
+            '/users',
+            '/file%20%231.txt',
+            '/foo%20bar',
+            '/nums',
+            '/todo.txt',
+            '/%E3%81%95%E3%81%8F%E3%82%89.txt'
+          ]);
+          done();
+        });
+      });
+    });
+
+    describe('when Accept: text/plain is given', function () {
+      it('should respond with text', function (done) {
+        var server = createServer()
+
+        request(server)
+        .get('/')
+        .set('Accept', 'text/plain')
+        .expect(200)
+        .expect('Content-Type', 'text/plain; charset=utf-8')
+        .expect(/users/)
+        .expect(/g# %3 o %2525 %37 dir/)
+        .expect(/file #1.txt/)
+        .expect(/todo.txt/)
+        .expect(/さくら\.txt/)
+        .end(done);
+      });
+    });
+
+    describe('when Accept: application/x-bogus is given', function () {
+      it('should respond with 406', function (done) {
+        var server = createServer()
+
+        request(server)
+        .get('/')
+        .set('Accept', 'application/x-bogus')
+        .expect(406, done)
+      });
+    });
+  });
+
+  describe('with "hidden" option', function () {
+    it('should filter hidden files by default', function (done) {
+      var server = createServer()
+
+      request(server)
+      .get('/')
+      .expect(200, function (err, res) {
+        if (err) return done(err)
+        res.text.should.not.containEql('.hidden')
+        done()
+      });
+    });
+
+    it('should filter hidden files', function (done) {
+      var server = createServer('test/fixtures', {'hidden': false})
+
+      request(server)
+      .get('/')
+      .expect(200, function (err, res) {
+        if (err) return done(err)
+        res.text.should.not.containEql('.hidden')
+        done()
+      });
+    });
+
+    it('should not filter hidden files', function (done) {
+      var server = createServer('test/fixtures', {'hidden': true})
+
+      request(server)
+      .get('/')
+      .expect(200, /\.hidden/, done)
+    });
+  });
+
+  describe('with "filter" option', function () {
+    it('should custom filter files', function (done) {
+      var seen = false
+      var server = createServer(fixtures, {'filter': filter})
+
+      function filter(name) {
+        if (name.indexOf('foo') === -1) return true
+        seen = true
+        return false
+      }
+
+      request(server)
+      .get('/')
+      .expect(200, function (err, res) {
+        if (err) return done(err)
+        seen.should.be.true
+        res.text.should.not.containEql('foo')
+        done()
+      });
+    });
+
+    it('should filter after hidden filter', function (done) {
+      var seen = false
+      var server = createServer(fixtures, {'filter': filter, 'hidden': false})
+
+      function filter(name) {
+        seen = seen || name.indexOf('.') === 0
+        return true
+      }
+
+      request(server)
+      .get('/')
+      .expect(200, function (err, res) {
+        if (err) return done(err)
+        seen.should.be.false
+        done()
+      });
+    });
+
+    it('should filter directory paths', function (done) {
+      var seen = false
+      var server = createServer(fixtures, {'filter': filter})
+
+      function filter(name, index, list, dir) {
+        if (path.normalize(dir) === path.normalize(path.join(fixtures, '/users'))) {
+          seen = true
+        }
+        return true
+      }
+
+      request(server)
+      .get('/users')
+      .expect(200, function (err, res) {
+        if (err) return done(err)
+        seen.should.be.true
+        done()
+      });
+    });
+  });
+
+  describe('with "icons" option', function () {
+    it('should include icons for html', function (done) {
+      var server = createServer(fixtures, {'icons': true})
+
+      request(server)
+      .get('/collect')
+      .expect(/data:image\/png/)
+      .expect(/icon-default/)
+      .expect(/icon-directory/)
+      .expect(/icon-image/)
+      .expect(/icon-txt/)
+      .expect(/icon-application-pdf/)
+      .expect(/icon-video/)
+      .expect(/icon-xml/)
+      .expect(200, done)
+    });
+  });
+
+  describe('when using custom handler', function () {
+    describe('exports.html', function () {
+      var orig = serveIndex.html
+      after(function () {
+        serveIndex.html = orig
+      })
+
+      it('should get called with Accept: text/html', function (done) {
+        var server = createServer()
+
+        serveIndex.html = function (req, res, files) {
+          res.setHeader('Content-Type', 'text/html');
+          res.end('called');
+        }
+
+        request(server)
+        .get('/')
+        .set('Accept', 'text/html')
+        .expect(200, 'called', done)
+      });
+
+      it('should get file list', function (done) {
+        var server = createServer()
+
+        serveIndex.html = function (req, res, files) {
+          var text = files
+            .filter(function (f) { return /\.txt$/.test(f) })
+            .sort()
+          res.setHeader('Content-Type', 'text/html')
+          res.end('<b>' + text.length + ' text files</b>')
+        }
+
+        request(server)
+        .get('/')
+        .set('Accept', 'text/html')
+        .expect(200, '<b>3 text files</b>', done)
+      });
+
+      it('should get dir name', function (done) {
+        var server = createServer()
+
+        serveIndex.html = function (req, res, files, next, dir) {
+          res.setHeader('Content-Type', 'text/html')
+          res.end('<b>' + dir + '</b>')
+        }
+
+        request(server)
+        .get('/users/')
+        .set('Accept', 'text/html')
+        .expect(200, '<b>/users/</b>', done)
+      });
+
+      it('should get template path', function (done) {
+        var server = createServer()
+
+        serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
+          res.setHeader('Content-Type', 'text/html')
+          res.end(String(fs.existsSync(template)))
+        }
+
+        request(server)
+        .get('/users/')
+        .set('Accept', 'text/html')
+        .expect(200, 'true', done)
+      });
+
+      it('should get template with tokens', function (done) {
+        var server = createServer()
+
+        serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template) {
+          res.setHeader('Content-Type', 'text/html')
+          res.end(fs.readFileSync(template, 'utf8'))
+        }
+
+        request(server)
+        .get('/users/')
+        .set('Accept', 'text/html')
+        .expect(/{directory}/)
+        .expect(/{files}/)
+        .expect(/{linked-path}/)
+        .expect(/{style}/)
+        .expect(200, done)
+      });
+
+      it('should get stylesheet path', function (done) {
+        var server = createServer()
+
+        serveIndex.html = function (req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
+          res.setHeader('Content-Type', 'text/html')
+          res.end(String(fs.existsSync(stylesheet)))
+        }
+
+        request(server)
+        .get('/users/')
+        .set('Accept', 'text/html')
+        .expect(200, 'true', done)
+      });
+    });
+
+    describe('exports.plain', function () {
+      var orig = serveIndex.plain
+      after(function () {
+        serveIndex.plain = orig
+      })
+
+      it('should get called with Accept: text/plain', function (done) {
+        var server = createServer()
+
+        serveIndex.plain = function (req, res, files) {
+          res.setHeader('Content-Type', 'text/plain');
+          res.end('called');
+        }
+
+        request(server)
+        .get('/')
+        .set('Accept', 'text/plain')
+        .expect(200, 'called', done)
+      });
+    });
+
+    describe('exports.json', function () {
+      var orig = serveIndex.json
+      after(function () {
+        serveIndex.json = orig
+      })
+
+      it('should get called with Accept: application/json', function (done) {
+        var server = createServer()
+
+        serveIndex.json = function (req, res, files) {
+          res.setHeader('Content-Type', 'application/json');
+          res.end('"called"');
+        }
+
+        request(server)
+        .get('/')
+        .set('Accept', 'application/json')
+        .expect(200, '"called"', done)
+      });
+    });
+  });
+
+  describe('when navigating to other directory', function () {
+    it('should respond with correct listing', function (done) {
+      var server = createServer()
+
+      request(server)
+      .get('/users/')
+      .set('Accept', 'text/html')
+      .expect(200)
+      .expect('Content-Type', 'text/html; charset=utf-8')
+      .expect(/<a href="\/users\/index.html"/)
+      .expect(/<a href="\/users\/tobi.txt"/)
+      .end(done);
+    });
+
+    it('should work for directory with #', function (done) {
+      var server = createServer()
+
+      request(server)
+      .get('/%23directory/')
+      .set('Accept', 'text/html')
+      .expect(200)
+      .expect('Content-Type', 'text/html; charset=utf-8')
+      .expect(/<a href="\/%23directory"/)
+      .expect(/<a href="\/%23directory\/index.html"/)
+      .end(done);
+    });
+
+    it('should work for directory with special chars', function (done) {
+      var server = createServer()
+
+      request(server)
+      .get('/g%23%20%253%20o%20%252525%20%2537%20dir/')
+      .set('Accept', 'text/html')
+      .expect(200)
+      .expect('Content-Type', 'text/html; charset=utf-8')
+      .expect(/<a href="\/g%23%20%253%20o%20%252525%20%2537%20dir"/)
+      .expect(/<a href="\/g%23%20%253%20o%20%252525%20%2537%20dir\/empty.txt"/)
+      .end(done);
+    });
+
+    it('should not work for outside root', function (done) {
+      var server = createServer()
+
+      request(server)
+      .get('/../support/')
+      .set('Accept', 'text/html')
+      .expect(403, done);
+    });
+  });
+
+  describe('when setting a custom template', function () {
+    var server;
+    before(function () {
+      server = createServer(fixtures, {'template': __dirname + '/shared/template.html'});
+    });
+
+    it('should respond with file list', function (done) {
+      request(server)
+      .get('/')
+      .set('Accept', 'text/html')
+      .expect(/<a href="\/g%23%20%253%20o%20%252525%20%2537%20dir"/)
+      .expect(/<a href="\/users"/)
+      .expect(/<a href="\/file%20%231.txt"/)
+      .expect(/<a href="\/todo.txt"/)
+      .expect(200, done)
+    });
+
+    it('should respond with testing template sentence', function (done) {
+      request(server)
+      .get('/')
+      .set('Accept', 'text/html')
+      .expect(200, /This is the test template/, done)
+    });
+
+    it('should have default styles', function (done) {
+      request(server)
+      .get('/')
+      .set('Accept', 'text/html')
+      .expect(200, /ul#files/, done)
+    });
+
+    it('should list directory twice', function (done) {
+      request(server)
+      .get('/users/')
+      .set('Accept', 'text/html')
+      .expect(function (res) {
+        var occurances = res.text.match(/directory \/users\//g)
+        if (occurances && occurances.length === 2) return
+        throw new Error('directory not listed twice')
+      })
+      .expect(200, done)
+    });
+  });
+
+  describe('when setting a custom stylesheet', function () {
+    var server;
+    before(function () {
+      server = createServer(fixtures, {'stylesheet': __dirname + '/shared/styles.css'});
+    });
+
+    it('should respond with appropriate embedded styles', function (done) {
+      request(server)
+      .get('/')
+      .set('Accept', 'text/html')
+      .expect(200)
+      .expect('Content-Type', 'text/html; charset=utf-8')
+      .expect(/color: #00ff00;/)
+      .end(done);
+    });
+  });
+
+  describe('when set with trailing slash', function () {
+    var server;
+    before(function () {
+      server = createServer(fixtures + '/');
+    });
+
+    it('should respond with file list', function (done) {
+      request(server)
+      .get('/')
+      .set('Accept', 'application/json')
+      .expect('Content-Type', /json/)
+      .expect(/users/)
+      .expect(/file #1\.txt/)
+      .expect(/nums/)
+      .expect(/todo\.txt/)
+      .expect(200, done)
+    });
+  });
+
+  (skipRelative ? describe.skip : describe)('when set to \'.\'', function () {
+    var server;
+    before(function () {
+      server = createServer('.');
+    });
+
+    it('should respond with file list', function (done) {
+      var dest = relative.split(path.sep).join('/');
+      request(server)
+      .get('/' + dest + '/')
+      .set('Accept', 'application/json')
+      .expect('Content-Type', /json/)
+      .expect(/users/)
+      .expect(/file #1\.txt/)
+      .expect(/nums/)
+      .expect(/todo\.txt/)
+      .expect(200, done)
+    });
+
+    it('should not allow serving outside root', function (done) {
+      request(server)
+      .get('/../')
+      .set('Accept', 'text/html')
+      .expect(403, done);
+    });
+  });
+});
+
+function createServer(dir, opts) {
+  dir = dir || fixtures
+
+  var _serveIndex = serveIndex(dir, opts)
+
+  return http.createServer(function (req, res) {
+    _serveIndex(req, res, function (err) {
+      res.statusCode = err ? (err.status || 500) : 404
+      res.end(err ? err.message : 'Not Found')
+    })
+  })
+}

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/node-serve-index.git



More information about the Pkg-javascript-commits mailing list