Compare commits

...

60 Commits

Author SHA1 Message Date
Douglas Christopher Wilson
ac94303941 1.4.0 2014-10-03 12:02:53 -04:00
Evgenus
cececc8c2e Add dir argument to filter function
fixes #11
closes #18
2014-10-03 11:45:59 -04:00
Evgenus
5e050a7d23 Support using tokens multiple times
closes #19
2014-10-03 11:17:30 -04:00
Douglas Christopher Wilson
5731ebee6b 1.3.1 2014-10-01 11:36:15 -04:00
Douglas Christopher Wilson
c6b9d3bdbf docs: fix non-https badge 2014-10-01 11:29:54 -04:00
Douglas Christopher Wilson
4686c18e1d deps: supertest@~0.14.0 2014-10-01 11:07:38 -04:00
Douglas Christopher Wilson
8a06bb7e19 deps: accepts@~1.1.1 2014-10-01 11:05:24 -04:00
Douglas Christopher Wilson
effbe1a4b0 Fix incorrect 403 on Windows and Node.js 0.11
fixes #17
2014-10-01 11:05:11 -04:00
Douglas Christopher Wilson
351c226736 1.3.0 2014-09-21 00:19:40 -04:00
Douglas Christopher Wilson
1b098fb723 build: minor code tweaks 2014-09-21 00:12:05 -04:00
Douglas Christopher Wilson
8880a8b80a Lookup icon by mime type for greater icon support 2014-09-21 00:10:45 -04:00
Van Nguyen
276b52782a Add icon for mkv files
closes #16
2014-09-20 22:53:11 -04:00
Douglas Christopher Wilson
591fd9ca13 1.2.1 2014-09-06 00:02:42 -04:00
Douglas Christopher Wilson
857b37aad4 deps: accepts@~1.1.0 2014-09-05 23:32:41 -04:00
Douglas Christopher Wilson
40a38e688d deps: istanbul@0.3.2 2014-09-05 23:31:51 -04:00
Douglas Christopher Wilson
874d32748b deps: debug@~2.0.0 2014-09-05 23:31:13 -04:00
Douglas Christopher Wilson
63456cccf2 1.2.0 2014-08-25 19:13:55 -04:00
Douglas Christopher Wilson
6ce2fd216b Add debug messages 2014-08-25 18:56:27 -04:00
Douglas Christopher Wilson
83de752968 Resolve relative paths at middleware setup
fixes #15
2014-08-25 18:51:14 -04:00
Douglas Christopher Wilson
86db218bdb docs: readme tweaks 2014-08-25 18:36:10 -04:00
Douglas Christopher Wilson
0375dd3638 tests: check for global leaks 2014-08-25 18:33:22 -04:00
Douglas Christopher Wilson
005dbad307 build: change readme/history capitalization 2014-08-25 18:32:20 -04:00
Douglas Christopher Wilson
a189d6b5e5 docs: link to license from readme 2014-08-25 18:30:59 -04:00
Douglas Christopher Wilson
a01c0286df docs: update badges 2014-08-25 18:29:49 -04:00
Douglas Christopher Wilson
6e4da1e8d6 build: remove npmignore file 2014-08-25 18:27:54 -04:00
Douglas Christopher Wilson
d88117fad9 docs: expand examples
closes #14
2014-08-17 13:38:00 -04:00
Douglas Christopher Wilson
194663a06f 1.1.6 2014-08-10 17:45:04 -04:00
Douglas Christopher Wilson
fd277c695c Fix URL parsing 2014-08-10 17:40:45 -04:00
Douglas Christopher Wilson
ea7b37f19b 1.1.5 2014-07-27 18:16:38 -04:00
Douglas Christopher Wilson
d613373f78 docs: add Gittip badge 2014-07-27 18:16:28 -04:00
Douglas Christopher Wilson
afb181df84 deps: istanbul@0.3.0 2014-07-27 18:08:51 -04:00
Douglas Christopher Wilson
a70edf46cd deps: mocha@~1.21.1 2014-07-27 18:07:51 -04:00
Douglas Christopher Wilson
9ccd58738b deps: accepts@~1.0.7 2014-07-27 18:07:19 -04:00
Yad Smood
db905a6d8a Fix Content-Length calculation for multi-byte file names
closes #12
2014-07-27 17:42:18 -04:00
Douglas Christopher Wilson
eeda184dbd deps: istanbul@0.2.11 2014-06-24 21:12:07 -04:00
Douglas Christopher Wilson
a879aee5e5 1.1.4 2014-06-20 15:38:46 -04:00
Douglas Christopher Wilson
7ba4f7cf44 deps: accepts@~1.0.5 2014-06-20 15:38:04 -04:00
Douglas Christopher Wilson
a73d752247 1.1.3 2014-06-20 13:49:12 -04:00
Douglas Christopher Wilson
4e8cf53ce5 deps: accepts@~1.0.4 2014-06-20 13:48:16 -04:00
Douglas Christopher Wilson
4e8726c41c 1.1.2 2014-06-19 11:09:23 -04:00
Douglas Christopher Wilson
d7d661245e deps: batch@0.5.1 2014-06-19 11:02:13 -04:00
Douglas Christopher Wilson
5037f808c0 1.1.1 2014-06-11 23:59:59 -04:00
Douglas Christopher Wilson
910923b920 deps: accepts@1.0.3 2014-06-11 23:49:07 -04:00
Douglas Christopher Wilson
70c35f62b2 build: use compact formats in package 2014-06-11 22:46:53 -04:00
Basarat Ali Syed
73ceabeb00 docs: use module name as export name
closes #10
2014-06-08 23:14:33 -04:00
Douglas Christopher Wilson
76daee7400 1.1.0 2014-05-29 15:29:13 -04:00
Douglas Christopher Wilson
f75c172d72 deps: should@~4.0.0 2014-05-29 15:03:58 -04:00
Douglas Christopher Wilson
7a2a8d2ecc Use accepts for negotiation 2014-05-29 14:37:29 -04:00
Douglas Christopher Wilson
cb38e459ca tests: add more tests 2014-05-29 10:55:48 -04:00
Douglas Christopher Wilson
2edcc09345 Treat ENAMETOOLONG as code 414 2014-05-29 09:05:25 -04:00
Douglas Christopher Wilson
a0415f5df2 Remove res.writeHead 2014-05-29 08:38:31 -04:00
Douglas Christopher Wilson
711194e063 Fix content negotiation when no Accept header 2014-05-29 08:35:04 -04:00
Douglas Christopher Wilson
4d19115d6b tests: add more basic tests 2014-05-28 17:22:13 -04:00
Douglas Christopher Wilson
c00618fa62 Properly support all HTTP methods 2014-05-28 17:13:22 -04:00
Douglas Christopher Wilson
db91ccac66 Throw TypeError for missing root argument 2014-05-28 17:01:53 -04:00
Douglas Christopher Wilson
926ee987fa build: add coverage reporting 2014-05-28 16:56:31 -04:00
Douglas Christopher Wilson
6277105d11 build: add test coverage 2014-05-28 16:36:56 -04:00
Douglas Christopher Wilson
199f5d0205 tests: remove use of connect 2014-05-28 16:31:41 -04:00
Douglas Christopher Wilson
871f4d679e Support vanilla node.js http servers 2014-05-28 16:27:28 -04:00
Douglas Christopher Wilson
6e3f352894 tests: add custom handler tests 2014-05-24 23:15:03 -04:00
20 changed files with 970 additions and 414 deletions

64
.gitignore vendored
View File

@@ -1,65 +1,3 @@
# Compiled source #
###################
*.com
*.class
*.dll
*.exe
*.o
*.so
# Packages #
############
# it's better to unpack these files and commit the raw source
# git has its own built in compression methods
*.7z
*.dmg
*.gz
*.iso
*.jar
*.rar
*.tar
*.zip
# Logs and databases #
######################
*.log
*.sql
*.sqlite
# OS generated files #
######################
.DS_Store*
ehthumbs.db
Thumbs.db
# Node.js #
###########
lib-cov
*.seed
*.log
*.csv
*.dat
*.out
*.pid
*.gz
pids
logs
results
coverage
node_modules
npm-debug.log
# Git #
#######
*.orig
*.BASE.*
*.BACKUP.*
*.LOCAL.*
*.REMOTE.*
# Components #
##############
/build
/components

View File

@@ -1 +0,0 @@
test

View File

@@ -3,3 +3,9 @@ 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@2.10.0 && cat ./coverage/lcov.info | coveralls"

95
HISTORY.md Normal file
View File

@@ -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@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@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@0.5.1
1.1.1 / 2014-06-11
==================
* deps: accepts@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@0.4.3
1.0.1 / 2014-03-05
==================
* deps: negotiator@0.4.2
1.0.0 / 2014-03-05
==================
* Genesis from connect

View File

@@ -1,20 +0,0 @@
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@0.4.3
1.0.1 / 2014-03-05
==================
* deps: negotiator@0.4.2
1.0.0 / 2014-03-05
==================
* Genesis from connect

127
README.md Normal file
View File

@@ -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/

View File

@@ -1,60 +0,0 @@
# serve-index [![Build Status](https://travis-ci.org/expressjs/serve-index.svg?branch=master)](https://travis-ci.org/expressjs/serve-index) [![NPM version](https://badge.fury.io/js/serve-index.svg)](http://badge.fury.io/js/serve-index)
Serves pages that contain directory listings for a given path.
## API
```js
var express = require('express')
var directory = require('serve-index')
var app = express()
app.use(directory('public/ftp', {'icons': true}))
app.listen()
```
### directory(path, options)
Returns middlware that serves an index of the directory in the given `path`.
#### Options
- `hidden` - display hidden (dot) files. Defaults to `false`.
- `view` - display mode. `tiles` and `details` are available. Defaults to `tiles`.
- `icons` - display icons. Defaults to `false`.
- `filter` - Apply this filter function to files. 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.
## License
The MIT License (MIT)
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.
The [Silk](http://www.famfamfam.com/lab/icons/silk/) icons are created
by/copyright of [FAMFAMFAM](http://www.famfamfam.com/).

443
index.js
View File

@@ -1,6 +1,6 @@
/*!
* Expressjs | Connect - directory
* serve-index
* Copyright(c) 2011 Sencha Inc.
* Copyright(c) 2011 TJ Holowaychuk
* Copyright(c) 2014 Douglas Christopher Wilson
@@ -14,16 +14,19 @@
* Module dependencies.
*/
var accepts = require('accepts');
var debug = require('debug')('serve-index');
var http = require('http')
, fs = require('fs')
, parse = require('url').parse
, path = require('path')
, normalize = path.normalize
, sep = path.sep
, extname = path.extname
, join = path.join;
var Batch = require('batch');
var Negotiator = require('negotiator');
var mime = require('mime-types');
var parseUrl = require('parseurl');
var resolve = require('path').resolve;
/*!
* Icon cache.
@@ -70,52 +73,83 @@ var mediaType = {
* @api public
*/
exports = module.exports = function directory(root, options){
exports = module.exports = function serveIndex(root, options){
options = options || {};
// root required
if (!root) throw new Error('directory() root path 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
, root = normalize(root + sep)
, template = options.template || defaultTemplate
, stylesheet = options.stylesheet || defaultStylesheet;
return function directory(req, res, next) {
if ('GET' != req.method && 'HEAD' != req.method) return next();
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;
}
var url = parse(req.url)
, dir = decodeURIComponent(url.pathname)
, path = normalize(join(root, dir))
, originalUrl = parse(req.originalUrl)
, originalDir = decodeURIComponent(originalUrl.pathname)
, showUp = path != root;
// 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, forbidden
if (0 != path.indexOf(root)) return next(createError(403));
// 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) return 'ENOENT' == err.code
? next()
: next(err);
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(filter);
if (filter) files = files.filter(function(filename, index, list) {
return filter(filename, index, list, path);
});
files.sort();
// content-negotiation
var type = new Negotiator(req).preferredMediaType(mediaTypes);
var accept = accepts(req);
var type = accept.types(mediaTypes);
// not acceptable
if (!type) return next(createError(406));
@@ -140,13 +174,15 @@ exports.html = function(req, res, files, next, dir, showUp, icons, path, view, t
files.sort(fileSort);
if (showUp) files.unshift({ name: '..' });
str = str
.replace('{style}', style.concat(iconStyle(files, icons)))
.replace('{files}', html(files, dir, icons, view))
.replace('{directory}', dir)
.replace('{linked-path}', htmlPath(dir));
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', str.length);
res.end(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);
});
});
});
@@ -157,10 +193,12 @@ exports.html = function(req, res, files, next, dir, showUp, icons, path, view, t
*/
exports.json = function(req, res, files){
files = JSON.stringify(files);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Length', files.length);
res.end(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);
};
/**
@@ -168,10 +206,12 @@ exports.json = function(req, res, files){
*/
exports.plain = function(req, res, files){
files = files.join('\n') + '\n';
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', files.length);
res.end(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);
};
/**
@@ -211,6 +251,64 @@ function htmlPath(dir) {
}).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.
*/
@@ -219,7 +317,7 @@ function iconStyle (files, useIcons) {
if (!useIcons) return '';
var className;
var i;
var icon;
var iconName;
var list = [];
var rules = {};
var selector;
@@ -230,26 +328,27 @@ function iconStyle (files, useIcons) {
var file = files[i];
var isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
icon = isDir ? icons.folder : icons[extname(file.name)] || icons.default;
var icon = isDir
? { className: 'icon-directory', fileName: icons.folder }
: iconLookup(file.name);
var iconName = icon.fileName;
var ext = extname(file.name);
className = 'icon-' + (isDir ? 'directory' : (icons[ext] ? ext.substring(1) : 'default'));
selector = '#files .' + className + ' .name';
selector = '#files .' + icon.className + ' .name';
if (!rules[icon]) {
rules[icon] = 'background-image: url(data:image/png;base64,' + load(icon) + ');'
selectors[icon] = [];
list.push(icon);
if (!rules[iconName]) {
rules[iconName] = 'background-image: url(data:image/png;base64,' + load(iconName) + ');'
selectors[iconName] = [];
list.push(iconName);
}
if (!~selectors[icon].indexOf(selector)) {
selectors[icon].push(selector);
if (selectors[iconName].indexOf(selector) === -1) {
selectors[iconName].push(selector);
}
}
for (i = 0; i < list.length; i++) {
icon = list[i];
style += selectors[icon].join(',\n') + ' {\n ' + rules[icon] + '\n}\n';
iconName = list[i];
style += selectors[iconName].join(',\n') + ' {\n ' + rules[iconName] + '\n}\n';
}
return style;
@@ -260,7 +359,7 @@ function iconStyle (files, useIcons) {
*/
function html(files, dir, useIcons, view) {
return '<ul id="files" class="view-'+view+'">'
return '<ul id="files" class="view-' + view + '">'
+ (view == 'details' ? (
'<li class="header">'
+ '<span class="name">Name</span>'
@@ -273,10 +372,21 @@ function html(files, dir, useIcons, view) {
, path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
if (useIcons) {
var ext = extname(file.name);
ext = isDir ? '.directory' : (icons[ext] ? ext : '.default');
classes.push('icon');
classes.push('icon-' + ext.substring(1));
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));
@@ -355,11 +465,9 @@ function stat(dir, files, cb) {
files.forEach(function(file){
batch.push(function(done){
fs.stat(join(dir, file), function(err, stat){
if (err && err.code !== 'ENOENT') {
// pass ENOENT as null stat, not error
return done(err);
}
if (err && err.code !== 'ENOENT') return done(err);
// pass ENOENT as null stat, not error
done(null, stat || null);
});
});
@@ -373,125 +481,112 @@ function stat(dir, files, cb) {
*/
var icons = {
'.js': 'page_white_code_red.png'
, '.json': 'page_white_code.png'
, '.c': 'page_white_c.png'
, '.h': 'page_white_h.png'
, '.cc': 'page_white_cplusplus.png'
, '.php': 'page_white_php.png'
, '.rb': 'page_white_ruby.png'
, '.erb': 'page_white_ruby.png'
, '.cpp': 'page_white_cplusplus.png'
, '.as': 'page_white_actionscript.png'
, '.cfm': 'page_white_coldfusion.png'
, '.cs': 'page_white_csharp.png'
, '.java': 'page_white_cup.png'
, '.jsp': 'page_white_cup.png'
, '.dll': 'page_white_gear.png'
, '.ini': 'page_white_gear.png'
, '.asp': 'page_white_code.png'
, '.aspx': 'page_white_code.png'
, '.clj': 'page_white_code.png'
, '.css': 'page_white_code.png'
, '.sass': 'page_white_code.png'
, '.scss': 'page_white_code.png'
, '.less': 'page_white_code.png'
, '.htm': 'page_white_code.png'
, '.html': 'page_white_code.png'
, '.xhtml': 'page_white_code.png'
, '.lua': 'page_white_code.png'
, '.m': 'page_white_code.png'
, '.pl': 'page_white_code.png'
, '.py': 'page_white_code.png'
, '.vb': 'page_white_code.png'
, '.vbs': 'page_white_code.png'
, '.xml': 'page_white_code.png'
, '.yaws': 'page_white_code.png'
, '.map': 'map.png'
// base icons
'default': 'page_white.png',
'folder': 'folder.png',
, '.app': 'application_xp.png'
, '.exe': 'application_xp.png'
, '.bat': 'application_xp_terminal.png'
, '.cgi': 'application_xp_terminal.png'
, '.sh': 'application_xp_terminal.png'
// generic mime type icons
'image': 'image.png',
'text': 'page_white_text.png',
'video': 'film.png',
, '.avi': 'film.png'
, '.flv': 'film.png'
, '.mv4': 'film.png'
, '.mov': 'film.png'
, '.mp4': 'film.png'
, '.mpeg': 'film.png'
, '.mpg': 'film.png'
, '.ogv': 'film.png'
, '.rm': 'film.png'
, '.webm': 'film.png'
, '.wmv': 'film.png'
, '.fnt': 'font.png'
, '.otf': 'font.png'
, '.ttf': 'font.png'
, '.woff': 'font.png'
, '.bmp': 'image.png'
, '.gif': 'image.png'
, '.ico': 'image.png'
, '.jpeg': 'image.png'
, '.jpg': 'image.png'
, '.png': 'image.png'
, '.psd': 'page_white_picture.png'
, '.xcf': 'page_white_picture.png'
, '.pdf': 'page_white_acrobat.png'
, '.swf': 'page_white_flash.png'
, '.ai': 'page_white_vector.png'
, '.eps': 'page_white_vector.png'
, '.ps': 'page_white_vector.png'
, '.svg': 'page_white_vector.png'
// generic mime suffix icons
'+json': 'page_white_code.png',
'+xml': 'page_white_code.png',
'+zip': 'box.png',
, '.ods': 'page_white_excel.png'
, '.xls': 'page_white_excel.png'
, '.xlsx': 'page_white_excel.png'
, '.odp': 'page_white_powerpoint.png'
, '.ppt': 'page_white_powerpoint.png'
, '.pptx': 'page_white_powerpoint.png'
, '.md': 'page_white_text.png'
, '.srt': 'page_white_text.png'
, '.txt': 'page_white_text.png'
, '.doc': 'page_white_word.png'
, '.docx': 'page_white_word.png'
, '.odt': 'page_white_word.png'
, '.rtf': 'page_white_word.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',
, '.dmg': 'drive.png'
, '.iso': 'cd.png'
, '.7z': 'box.png'
, '.apk': 'box.png'
, '.bz2': 'box.png'
, '.cab': 'box.png'
, '.deb': 'box.png'
, '.gz': 'box.png'
, '.jar': 'box.png'
, '.lz': 'box.png'
, '.lzma': 'box.png'
, '.msi': 'box.png'
, '.pkg': 'box.png'
, '.rar': 'box.png'
, '.rpm': 'box.png'
, '.tar': 'box.png'
, '.tbz2': 'box.png'
, '.tgz': 'box.png'
, '.tlz': 'box.png'
, '.xz': 'box.png'
, '.zip': 'box.png'
, '.accdb': 'page_white_database.png'
, '.db': 'page_white_database.png'
, '.dbf': 'page_white_database.png'
, '.mdb': 'page_white_database.png'
, '.pdb': 'page_white_database.png'
, '.sql': 'page_white_database.png'
, '.gam': 'controller.png'
, '.rom': 'controller.png'
, '.sav': 'controller.png'
, 'folder': 'folder.png'
, 'default': 'page_white.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'
};

View File

@@ -1,30 +1,35 @@
{
"name": "serve-index",
"description": "Serve directory listings",
"version": "1.0.3",
"version": "1.4.0",
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/expressjs/serve-index.git"
},
"bugs": {
"url": "https://github.com/expressjs/serve-index/issues"
},
"repository": "expressjs/serve-index",
"dependencies": {
"batch": "0.5.0",
"negotiator": "0.4.3"
"accepts": "~1.1.1",
"batch": "0.5.1",
"debug": "~2.0.0",
"mime-types": "~2.0.1",
"parseurl": "~1.3.0"
},
"devDependencies": {
"connect": "~2.14.1",
"mocha": "~1.17.1",
"should": "~3.1.3",
"supertest": "~0.9.0"
"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 --require should"
"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/"
}
}

0
test/fixtures/collect/sample vendored Normal file
View File

0
test/fixtures/collect/sample.jpg vendored Normal file
View File

0
test/fixtures/collect/sample.mp4 vendored Normal file
View File

0
test/fixtures/collect/sample.pdf vendored Normal file
View File

0
test/fixtures/collect/sample.qfx vendored Normal file
View File

0
test/fixtures/collect/sample.rdf vendored Normal file
View File

0
test/fixtures/collect/sample.txt vendored Normal file
View File

0
test/fixtures/collect/sample.xlsx vendored Normal file
View File

0
test/fixtures/さくら.txt vendored Normal file
View File

View File

@@ -6,6 +6,7 @@
</head>
<body class="directory">
<h1>This is the test template</h1>
<h2>directory {directory}</h2>
<div id="wrapper">
<h1>{linked-path}</h1>
{files}

View File

@@ -1,70 +1,161 @@
process.env.NODE_ENV = 'test';
var connect = require('connect');
var http = require('http');
var fs = require('fs');
var path = require('path');
var request = require('supertest');
var should = require('should');
var serveIndex = require('..');
describe('directory()', function(){
describe('when given Accept: header', function () {
var server;
before(function () {
server = createServer();
});
after(function (done) {
server.close(done);
});
var fixtures = path.join(__dirname, '/fixtures');
var relative = path.relative(process.cwd(), fixtures);
describe('of application/json', function () {
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(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) throw err;
res.body.should.include('g# %3 o %2525 %37 dir');
res.body.should.include('users');
res.body.should.include('file #1.txt');
res.body.should.include('nums');
res.body.should.include('todo.txt');
done();
});
.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', /html/)
.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', /html/)
.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();
});
@@ -73,63 +164,331 @@ describe('directory()', function(){
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', /plain/)
.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 () {
var server;
before(function () {
server = createServer();
});
after(function (done) {
server.close(done);
});
it('should respond with correct listing', function (done) {
var server = createServer()
request(server)
.get('/users/')
.set('Accept', 'text/html')
.expect(200)
.expect('Content-Type', /html/)
.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', /html/)
.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', /html/)
.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')
@@ -140,34 +499,51 @@ describe('directory()', function(){
describe('when setting a custom template', function () {
var server;
before(function () {
server = createServer('test/fixtures', {'template': __dirname + '/shared/template.html'});
});
after(function (done) {
server.close(done);
server = createServer(fixtures, {'template': __dirname + '/shared/template.html'});
});
it('should respond with file list and testing template sentence', function (done) {
it('should respond with file list', function (done) {
request(server)
.get('/')
.set('Accept', 'text/html')
.expect(200)
.expect('Content-Type', /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(/This is the test template/)
.end(done);
.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('test/fixtures', {'stylesheet': __dirname + '/shared/styles.css'});
});
after(function (done) {
server.close(done);
server = createServer(fixtures, {'stylesheet': __dirname + '/shared/styles.css'});
});
it('should respond with appropriate embedded styles', function (done) {
@@ -175,7 +551,7 @@ describe('directory()', function(){
.get('/')
.set('Accept', 'text/html')
.expect(200)
.expect('Content-Type', /html/)
.expect('Content-Type', 'text/html; charset=utf-8')
.expect(/color: #00ff00;/)
.end(done);
});
@@ -184,51 +560,39 @@ describe('directory()', function(){
describe('when set with trailing slash', function () {
var server;
before(function () {
server = createServer('test/fixtures/');
});
after(function (done) {
server.close(done);
server = createServer(fixtures + '/');
});
it('should respond with file list', function (done) {
request(server)
.get('/')
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) throw err;
res.body.should.include('users');
res.body.should.include('file #1.txt');
res.body.should.include('nums');
res.body.should.include('todo.txt');
done();
});
.expect(/users/)
.expect(/file #1\.txt/)
.expect(/nums/)
.expect(/todo\.txt/)
.expect(200, done)
});
});
describe('when set to \'.\'', function () {
(skipRelative ? describe.skip : describe)('when set to \'.\'', function () {
var server;
before(function () {
server = createServer('.');
});
after(function (done) {
server.close(done);
});
it('should respond with file list', function (done) {
var dest = relative.split(path.sep).join('/');
request(server)
.get('/')
.get('/' + dest + '/')
.set('Accept', 'application/json')
.expect(200)
.expect('Content-Type', /json/)
.end(function (err, res) {
if (err) throw err;
res.body.should.include('LICENSE');
res.body.should.include('public');
res.body.should.include('test');
done();
});
.expect(/users/)
.expect(/file #1\.txt/)
.expect(/nums/)
.expect(/todo\.txt/)
.expect(200, done)
});
it('should not allow serving outside root', function (done) {
@@ -241,8 +605,14 @@ describe('directory()', function(){
});
function createServer(dir, opts) {
var app = connect();
dir = dir || 'test/fixtures';
app.use(serveIndex(dir, opts));
return app.listen();
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')
})
})
}