[Pkg-javascript-commits] [node-connect] 01/23: Imported Upstream version 3.0.0

Leo Iannacone l3on-guest at moszumanska.debian.org
Sun Jun 15 16:36:17 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-connect.

commit 6134e9255f085b14f653f8f51e2f6960d0798ef5
Author: Leo Iannacone <l3on at ubuntu.com>
Date:   Sun Jun 15 17:18:29 2014 +0200

    Imported Upstream version 3.0.0
---
 .npmignore                                   |   12 +-
 .travis.yml                                  |   10 +
 History.md                                   | 1251 ++++++++++++++++++++++++++
 LICENSE                                      |    4 +-
 Readme.md                                    |   96 ++
 index.js                                     |    4 +-
 lib/cache.js                                 |   81 --
 lib/connect.js                               |   68 +-
 lib/index.js                                 |   54 --
 lib/middleware/basicAuth.js                  |   98 --
 lib/middleware/bodyParser.js                 |   61 --
 lib/middleware/compress.js                   |  142 ---
 lib/middleware/cookieParser.js               |   62 --
 lib/middleware/cookieSession.js              |   91 --
 lib/middleware/csrf.js                       |   75 --
 lib/middleware/directory.js                  |  227 -----
 lib/middleware/errorHandler.js               |   87 --
 lib/middleware/favicon.js                    |   86 --
 lib/middleware/json.js                       |   56 --
 lib/middleware/limit.js                      |   77 --
 lib/middleware/logger.js                     |  331 -------
 lib/middleware/methodOverride.js             |   40 -
 lib/middleware/multipart.js                  |   99 --
 lib/middleware/query.js                      |   44 -
 lib/middleware/responseTime.js               |   32 -
 lib/middleware/session.js                    |  302 -------
 lib/middleware/session/cookie.js             |  129 ---
 lib/middleware/session/memory.js             |  131 ---
 lib/middleware/session/session.js            |  137 ---
 lib/middleware/session/store.js              |   87 --
 lib/middleware/static.js                     |  246 -----
 lib/middleware/staticCache.js                |  185 ----
 lib/middleware/urlencoded.js                 |   57 --
 lib/middleware/vhost.js                      |   40 -
 lib/patch.js                                 |   79 --
 lib/proto.js                                 |   64 +-
 lib/public/directory.html                    |   75 --
 lib/public/error.html                        |   13 -
 lib/public/favicon.ico                       |  Bin 1406 -> 0 bytes
 lib/public/icons/page.png                    |  Bin 635 -> 0 bytes
 lib/public/icons/page_add.png                |  Bin 739 -> 0 bytes
 lib/public/icons/page_attach.png             |  Bin 794 -> 0 bytes
 lib/public/icons/page_code.png               |  Bin 818 -> 0 bytes
 lib/public/icons/page_copy.png               |  Bin 663 -> 0 bytes
 lib/public/icons/page_delete.png             |  Bin 740 -> 0 bytes
 lib/public/icons/page_edit.png               |  Bin 807 -> 0 bytes
 lib/public/icons/page_error.png              |  Bin 793 -> 0 bytes
 lib/public/icons/page_excel.png              |  Bin 817 -> 0 bytes
 lib/public/icons/page_find.png               |  Bin 879 -> 0 bytes
 lib/public/icons/page_gear.png               |  Bin 833 -> 0 bytes
 lib/public/icons/page_go.png                 |  Bin 779 -> 0 bytes
 lib/public/icons/page_green.png              |  Bin 621 -> 0 bytes
 lib/public/icons/page_key.png                |  Bin 801 -> 0 bytes
 lib/public/icons/page_lightning.png          |  Bin 839 -> 0 bytes
 lib/public/icons/page_link.png               |  Bin 830 -> 0 bytes
 lib/public/icons/page_paintbrush.png         |  Bin 813 -> 0 bytes
 lib/public/icons/page_paste.png              |  Bin 703 -> 0 bytes
 lib/public/icons/page_red.png                |  Bin 641 -> 0 bytes
 lib/public/icons/page_refresh.png            |  Bin 858 -> 0 bytes
 lib/public/icons/page_save.png               |  Bin 774 -> 0 bytes
 lib/public/icons/page_white.png              |  Bin 294 -> 0 bytes
 lib/public/icons/page_white_acrobat.png      |  Bin 591 -> 0 bytes
 lib/public/icons/page_white_actionscript.png |  Bin 664 -> 0 bytes
 lib/public/icons/page_white_add.png          |  Bin 512 -> 0 bytes
 lib/public/icons/page_white_c.png            |  Bin 587 -> 0 bytes
 lib/public/icons/page_white_camera.png       |  Bin 656 -> 0 bytes
 lib/public/icons/page_white_cd.png           |  Bin 666 -> 0 bytes
 lib/public/icons/page_white_code.png         |  Bin 603 -> 0 bytes
 lib/public/icons/page_white_code_red.png     |  Bin 587 -> 0 bytes
 lib/public/icons/page_white_coldfusion.png   |  Bin 592 -> 0 bytes
 lib/public/icons/page_white_compressed.png   |  Bin 724 -> 0 bytes
 lib/public/icons/page_white_copy.png         |  Bin 309 -> 0 bytes
 lib/public/icons/page_white_cplusplus.png    |  Bin 621 -> 0 bytes
 lib/public/icons/page_white_csharp.png       |  Bin 700 -> 0 bytes
 lib/public/icons/page_white_cup.png          |  Bin 639 -> 0 bytes
 lib/public/icons/page_white_database.png     |  Bin 579 -> 0 bytes
 lib/public/icons/page_white_delete.png       |  Bin 536 -> 0 bytes
 lib/public/icons/page_white_dvd.png          |  Bin 638 -> 0 bytes
 lib/public/icons/page_white_edit.png         |  Bin 618 -> 0 bytes
 lib/public/icons/page_white_error.png        |  Bin 623 -> 0 bytes
 lib/public/icons/page_white_excel.png        |  Bin 663 -> 0 bytes
 lib/public/icons/page_white_find.png         |  Bin 676 -> 0 bytes
 lib/public/icons/page_white_flash.png        |  Bin 582 -> 0 bytes
 lib/public/icons/page_white_freehand.png     |  Bin 639 -> 0 bytes
 lib/public/icons/page_white_gear.png         |  Bin 402 -> 0 bytes
 lib/public/icons/page_white_get.png          |  Bin 516 -> 0 bytes
 lib/public/icons/page_white_go.png           |  Bin 612 -> 0 bytes
 lib/public/icons/page_white_h.png            |  Bin 603 -> 0 bytes
 lib/public/icons/page_white_horizontal.png   |  Bin 296 -> 0 bytes
 lib/public/icons/page_white_key.png          |  Bin 616 -> 0 bytes
 lib/public/icons/page_white_lightning.png    |  Bin 669 -> 0 bytes
 lib/public/icons/page_white_link.png         |  Bin 614 -> 0 bytes
 lib/public/icons/page_white_magnify.png      |  Bin 554 -> 0 bytes
 lib/public/icons/page_white_medal.png        |  Bin 706 -> 0 bytes
 lib/public/icons/page_white_office.png       |  Bin 779 -> 0 bytes
 lib/public/icons/page_white_paint.png        |  Bin 688 -> 0 bytes
 lib/public/icons/page_white_paintbrush.png   |  Bin 618 -> 0 bytes
 lib/public/icons/page_white_paste.png        |  Bin 620 -> 0 bytes
 lib/public/icons/page_white_php.png          |  Bin 538 -> 0 bytes
 lib/public/icons/page_white_picture.png      |  Bin 650 -> 0 bytes
 lib/public/icons/page_white_powerpoint.png   |  Bin 588 -> 0 bytes
 lib/public/icons/page_white_put.png          |  Bin 523 -> 0 bytes
 lib/public/icons/page_white_ruby.png         |  Bin 626 -> 0 bytes
 lib/public/icons/page_white_stack.png        |  Bin 317 -> 0 bytes
 lib/public/icons/page_white_star.png         |  Bin 565 -> 0 bytes
 lib/public/icons/page_white_swoosh.png       |  Bin 634 -> 0 bytes
 lib/public/icons/page_white_text.png         |  Bin 342 -> 0 bytes
 lib/public/icons/page_white_text_width.png   |  Bin 315 -> 0 bytes
 lib/public/icons/page_white_tux.png          |  Bin 668 -> 0 bytes
 lib/public/icons/page_white_vector.png       |  Bin 644 -> 0 bytes
 lib/public/icons/page_white_visualstudio.png |  Bin 702 -> 0 bytes
 lib/public/icons/page_white_width.png        |  Bin 309 -> 0 bytes
 lib/public/icons/page_white_word.png         |  Bin 651 -> 0 bytes
 lib/public/icons/page_white_world.png        |  Bin 734 -> 0 bytes
 lib/public/icons/page_white_wrench.png       |  Bin 613 -> 0 bytes
 lib/public/icons/page_white_zip.png          |  Bin 386 -> 0 bytes
 lib/public/icons/page_word.png               |  Bin 777 -> 0 bytes
 lib/public/icons/page_world.png              |  Bin 903 -> 0 bytes
 lib/public/style.css                         |  141 ---
 lib/utils.js                                 |  488 ----------
 package.json                                 |   57 +-
 test/app.listen.js                           |   19 +
 test/fixtures/.hidden                        |    1 -
 test/fqdn.js                                 |   86 ++
 test/mounting.js                             |  232 +++++
 test/server.js                               |  170 ++++
 test/support/env.js                          |    2 +
 127 files changed, 1960 insertions(+), 3969 deletions(-)

diff --git a/.npmignore b/.npmignore
index 9046dde..078c11e 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,12 +1,4 @@
-*.markdown
-*.md
-.git*
-Makefile
-benchmarks/
+coverage/
 docs/
-examples/
-install.sh
-support/
 test/
-.DS_Store
-coverage.html
+.travis.yml
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..73568f2
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+node_js:
+  - "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..aa4c933
--- /dev/null
+++ b/History.md
@@ -0,0 +1,1251 @@
+3.0.0 / 2014-05-29
+==================
+
+  * No changes
+
+3.0.0-rc.2 / 2014-05-04
+=======================
+
+  * Call error stack even when response has been sent
+  * Prevent default 404 handler after response sent
+  * dep: debug at 0.8.1
+  * encode stack in HTML for default error handler
+  * remove `proto` export
+
+3.0.0-rc.1 / 2014-03-06
+=======================
+
+  * move middleware to separate repos
+  * remove docs
+  * remove node patches
+  * remove connect(middleware...)
+  * remove the old `connect.createServer()` method
+  * remove various private `connect.utils` functions
+  * drop node.js 0.8 support
+
+2.18.0 / 2014-05-29
+===================
+
+  * deps: compression at 1.0.3
+  * deps: serve-static at 1.1.0
+    - 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
+  * deps: serve-static at 1.2.0
+    - Calculate ETag with md5 for reduced collisions
+    - Fix wrong behavior when index file matches directory
+    - Ignore stream errors after request ends
+    - Skip directories in index file search
+    - deps: send at 0.4.0
+
+2.17.3 / 2014-05-27
+===================
+
+  * deps: express-session at 1.2.1
+    - Fix `resave` such that `resave: true` works
+
+2.17.2 / 2014-05-27
+===================
+
+  * deps: body-parser at 1.2.2
+    - invoke `next(err)` after request fully read
+    - deps: raw-body at 1.1.6
+  * deps: method-override at 1.0.2
+    - Handle `req.body` key referencing array or object
+    - Handle multiple HTTP headers
+
+2.17.1 / 2014-05-21
+===================
+
+  * fix `res.charset` appending charset when `content-type` has one
+
+2.17.0 / 2014-05-20
+===================
+
+  * deps: express-session at 1.2.0
+    - Add `resave` option to control saving unmodified sessions
+  * deps: morgan at 1.1.1
+    - "dev" format will use same tokens as other formats
+    - `:response-time` token is now empty when immediate used
+    - `:response-time` token is now monotonic
+    - `:response-time` token has precision to 1 μs
+    - fix `:status` + immediate output in node.js 0.8
+    - improve `buffer` option to prevent indefinite event loop holding
+    - simplify method to get remote address
+    - deps: bytes at 1.0.0
+  * deps: serve-index at 1.0.3
+    - Fix error from non-statable files in HTML view
+
+2.16.2 / 2014-05-18
+===================
+
+  * fix edge-case in `res.appendHeader` that would append in wrong order
+  * deps: method-override at 1.0.1
+
+2.16.1 / 2014-05-17
+===================
+
+  * remove usages of `res.headerSent` from core
+
+2.16.0 / 2014-05-17
+===================
+
+  * deprecate `res.headerSent` -- use `res.headersSent`
+  * deprecate `res.on("header")` -- use on-headers module instead
+  * fix `connect.version` to reflect the actual version
+  * json: use body-parser
+    - add `type` option
+    - fix repeated limit parsing with every request
+    - improve parser speed
+  * urlencoded: use body-parser
+    - add `type` option
+    - fix repeated limit parsing with every request
+  * dep: bytes at 1.0.0
+    * add negative support
+  * dep: cookie-parser at 1.1.0
+    - deps: cookie at 0.1.2
+  * dep: csurf at 1.2.0
+    - add support for double-submit cookie
+  * dep: express-session at 1.1.0
+    - Add `name` option; replacement for `key` option
+    - Use `setImmediate` in MemoryStore for node.js >= 0.10
+
+2.15.0 / 2014-05-04
+===================
+
+  * Add simple `res.cookie` support
+  * Add `res.appendHeader`
+  * Call error stack even when response has been sent
+  * Patch `res.headerSent` to return Boolean
+  * Patch `res.headersSent` for node.js 0.8
+  * Prevent default 404 handler after response sent
+  * dep: compression at 1.0.2
+    * support headers given to `res.writeHead`
+    * deps: bytes at 0.3.0
+    * deps: negotiator at 0.4.3
+  * dep: connect-timeout at 1.1.0
+    * Add `req.timedout` property
+    * Add `respond` option to constructor
+    * Clear timer on socket destroy
+    * deps: debug at 0.8.1
+  * dep: debug@^0.8.0
+    * add `enable()` method
+    * change from stderr to stdout
+  * dep: errorhandler at 1.0.1
+    * Clean up error CSS
+    * Do not respond after headers sent
+  * dep: express-session at 1.0.4
+    * Remove import of `setImmediate`
+    * Use `res.cookie()` instead of `res.setHeader()`
+    * deps: cookie at 0.1.2
+    * deps: debug at 0.8.1
+  * dep: morgan at 1.0.1
+    * Make buffer unique per morgan instance
+    * deps: bytes at 0.3.0
+  * dep: serve-favicon at 2.0.0
+    * Accept `Buffer` of icon as first argument
+    * Non-GET and HEAD requests are denied
+    * Send valid max-age value
+    * Support conditional requests
+    * Support max-age=0
+    * Support OPTIONS method
+    * Throw if `path` argument is directory
+  * dep: serve-index at 1.0.2
+    * Add stylesheet option
+    * deps: negotiator at 0.4.3
+
+2.14.5 / 2014-04-24
+===================
+
+  * dep: raw-body at 1.1.4
+    * allow true as an option
+    * deps: bytes at 0.3.0
+  * dep: serve-static at 1.1.0
+    * Accept options directly to `send` module
+    * deps: send at 0.3.0
+
+2.14.4 / 2014-04-07
+===================
+
+  * dep: bytes at 0.3.0
+    * added terabyte support
+  * dep: csurf at 1.1.0
+    * add constant-time string compare
+  * dep: serve-static at 1.0.4
+    * Resolve relative paths at middleware setup
+    * Use parseurl to parse the URL from request
+  * fix node.js 0.8 compatibility with memory session
+
+2.14.3 / 2014-03-18
+===================
+
+  * dep: static-favicon at 1.0.2
+    * Fixed content of default icon
+
+2.14.2 / 2014-03-11
+===================
+
+  * dep: static-favicon at 1.0.1
+    * Fixed path to default icon
+
+2.14.1 / 2014-03-06
+===================
+
+  * dep: fresh at 0.2.2
+    * no real changes
+  * dep: serve-index at 1.0.1
+    * deps: negotiator at 0.4.2
+  * dep: serve-static at 1.0.2
+    * deps: send at 0.2.0
+
+2.14.0 / 2014-03-05
+===================
+
+ * basicAuth: use basic-auth-connect
+ * cookieParser: use cookie-parser
+ * compress: use compression
+ * csrf: use csurf
+ * dep: cookie-signature at 1.0.3
+ * directory: use serve-index
+ * errorHandler: use errorhandler
+ * favicon: use static-favicon
+ * logger: use morgan
+ * methodOverride: use method-override
+ * responseTime: use response-time
+ * session: use express-session
+ * static: use serve-static
+ * timeout: use connect-timeout
+ * vhost: use vhost
+
+2.13.1 / 2014-03-05
+===================
+
+ * cookieSession: compare full value rather than crc32
+ * deps: raw-body at 1.1.3
+
+2.13.0 / 2014-02-14
+===================
+
+ * fix typo in memory store warning #974 @rvagg
+ * compress: use compressible
+ * directory: add template option #990 @gottaloveit @Earl-Brown
+ * csrf: prevent deprecated warning with old sessions
+
+2.12.0 / 2013-12-10
+===================
+
+ * bump qs
+ * directory: sort folders before files
+ * directory: add folder icons
+ * directory: de-duplicate icons, details/mobile views #968 @simov
+ * errorHandler: end default 404 handler with a newline #972 @rlidwka
+ * session: remove long cookie expire check #870 @undoZen
+
+2.11.2 / 2013-12-01
+===================
+
+ * bump raw-body
+
+2.11.1 / 2013-11-27
+===================
+
+ * bump raw-body
+ * errorHandler: use `res.setHeader()` instead of `res.writeHead()` #949 @lo1tuma
+
+2.11.0 / 2013-10-29
+===================
+
+ * update bytes
+ * update uid2
+ * update negotiator
+ * sessions: add rolling session option #944 @ilmeo
+ * sessions: property set cookies when given FQDN
+ * cookieSessions: properly set cookies when given FQDN #948 @bmancini55
+ * proto: fix FQDN mounting when multiple handlers #945 @bmancini55
+
+2.10.1 / 2013-10-23
+===================
+
+ * fixed; fixed a bug with static middleware at root and trailing slashes #942 (@dougwilson)
+
+2.10.0 / 2013-10-22
+===================
+
+ * fixed: set headers written by writeHead before emitting 'header'
+ * fixed: mounted path should ignore querystrings on FQDNs #940 (@dougwilson)
+ * fixed: parsing protocol-relative URLs with @ as pathnames #938 (@dougwilson)
+ * fixed: fix static directory redirect for mount's root #937 (@dougwilson)
+ * fixed: setting set-cookie header when mixing arrays and strings #893 (@anuj123)
+ * bodyParser: optional verify function for urlencoded and json parsers for signing request bodies
+ * compress: compress checks content-length to check threshold
+ * compress: expose `res.flush()` for flushing responses
+ * cookieParser: pass options into node-cookie #803 (@cauldrath)
+ * errorHandler: replace `\n`s with `<br/>`s in error handler
+
+2.9.2 / 2013-10-18
+==================
+
+ * warn about multiparty and limit middleware deprecation for v3
+ * fix fully qualified domain name mounting. #920 (@dougwilson)
+ * directory: Fix potential security issue with serving files outside the root. #929 (@dougwilson)
+ * logger: store IP at beginning in case socket prematurely closes #930 (@dougwilson)
+
+2.9.1 / 2013-10-15
+==================
+
+ * update multiparty
+ * compress: Set vary header only if Content-Type passes filter #904
+ * directory: Fix directory middleware URI escaping #917 (@dougwilson)
+ * directory: Fix directory seperators for Windows #914 (@dougwilson)
+ * directory: Keep query string intact during directory redirect #913 (@dougwilson)
+ * directory: Fix paths in links #730 (@JacksonTian)
+ * errorHandler: Don't escape text/plain as HTML #875 (@johan)
+ * logger: Write '0' instead of '-' when response time is zero #910 (@dougwilson)
+ * logger: Log even when connections are aborted #760 (@dylanahsmith)
+ * methodOverride: Check req.body is an object #907 (@kbjr)
+ * multipart: Add .type back to file parts for backwards compatibility #912 (@dougwilson)
+ * multipart: Allow passing options to the Multiparty constructor #902 (@niftylettuce)
+
+2.9.0 / 2013-09-07
+==================
+
+ * multipart: add docs regarding tmpfiles
+ * multipart: add .name back to file parts
+ * multipart: use multiparty instead of formidable
+
+2.8.8 / 2013-09-02
+==================
+
+ * csrf: change to math.random() salt and remove csrfToken() callback
+
+2.8.7 / 2013-08-28
+==================
+
+ * csrf: prevent salt generation on every request, and add async req.csrfToken(fn)
+
+2.8.6 / 2013-08-28
+==================
+
+ * csrf: refactor to use HMAC tokens (BREACH attack)
+ * compress: add compression of SVG and common font files by default.
+
+2.8.5 / 2013-08-11
+==================
+
+ * add: compress Dart source files by default
+ * update fresh
+
+2.8.4 / 2013-07-08
+==================
+
+ * update send
+
+2.8.3 / 2013-07-04
+==================
+
+ * add a name back to static middleware ("staticMiddleware")
+ * fix .hasBody() utility to require transfer-encoding or content-length
+
+2.8.2 / 2013-07-03
+==================
+
+ * update send
+ * update cookie dep.
+ * add better debug() for middleware
+ * add whitelisting of supported methods to methodOverride()
+
+2.8.1 / 2013-06-27
+==================
+
+ * fix: escape req.method in 404 response
+
+2.8.0 / 2013-06-26
+==================
+
+ * add `threshold` option to `compress()` to prevent compression of small responses
+ * add support for vendor JSON mime types in json()
+ * add X-Forwarded-Proto initial https proxy support
+ * change static redirect to 303
+ * change octal escape sequences for strict mode
+ * change: replace utils.uid() with uid2 lib
+ * remove other "static" function name. Fixes #794
+ * fix: hasBody() should return false if Content-Length: 0
+
+2.7.11 / 2013-06-02
+==================
+
+ * update send
+
+2.7.10 / 2013-05-21
+==================
+
+ * update qs
+ * update formidable
+ * fix: write/end to noop() when request aborted
+
+2.7.9 / 2013-05-07
+==================
+
+  * update qs
+  * drop support for node < v0.8
+
+2.7.8 / 2013-05-03
+==================
+
+  * update qs
+
+2.7.7 / 2013-04-29
+==================
+
+  * update qs dependency
+  * remove "static" function name. Closes #794
+  * update node-formidable
+  * update buffer-crc32
+
+2.7.6 / 2013-04-15
+==================
+
+  * revert cookie signature which was creating session race conditions
+
+2.7.5 / 2013-04-12
+==================
+
+  * update cookie-signature
+  * limit: do not consume request in node 0.10.x
+
+2.7.4 / 2013-04-01
+==================
+
+  * session: add long expires check and prevent excess set-cookie
+  * session: add console.error() of session#save() errors
+
+2.7.3 / 2013-02-19
+==================
+
+  * add name to compress middleware
+  * add appending Accept-Encoding to Vary when set but missing
+  * add tests for csrf middleware
+  * add 'next' support for connect() server handler
+  * change utils.uid() to return url-safe chars. Closes #753
+  * fix treating '.' as a regexp in vhost()
+  * fix duplicate bytes dep in package.json. Closes #743
+  * fix #733 - parse x-forwarded-proto in a more generally compatibly way
+  * revert "add support for `next(status[, msg])`"; makes composition hard
+
+2.7.2 / 2013-01-04
+==================
+
+  * add support for `next(status[, msg])` back
+  * add utf-8 meta tag to support foreign characters in filenames/directories
+  * change `timeout()` 408 to 503
+  * replace 'node-crc' with 'buffer-crc32', fixes licensing
+  * fix directory.html IE support
+
+2.7.1 / 2012-12-05
+==================
+
+  * add directory() tests
+  * add support for bodyParser to ignore Content-Type if no body is present (jquery primarily does this poorely)
+  * fix errorHandler signature
+
+2.7.0 / 2012-11-13
+==================
+
+  * add support for leading JSON whitespace
+  * add logging of `req.ip` when present
+  * add basicAuth support for `:`-delimited string
+  * update cookie module. Closes #688
+
+2.6.2 / 2012-11-01
+==================
+
+  * add `debug()` for disconnected session store
+  * fix session regeneration bug. Closes #681
+
+2.6.1 / 2012-10-25
+==================
+
+  * add passing of `connect.timeout()` errors to `next()`
+  * replace signature utils with cookie-signature module
+
+2.6.0 / 2012-10-09
+==================
+
+  * add `defer` option to `multipart()` [Blake Miner]
+  * fix mount path case sensitivity. Closes #663
+  * fix default of ascii encoding from `logger()`, now utf8. Closes #293
+
+2.5.0 / 2012-09-27
+==================
+
+  * add `err.status = 400` to multipart() errors
+  * add double-encoding protection to `compress()`. Closes #659
+  * add graceful handling cookie parsing errors [shtylman]
+  * fix typo X-Response-time to X-Response-Time
+
+2.4.6 / 2012-09-18
+==================
+
+  * update qs
+
+2.4.5 / 2012-09-03
+==================
+
+  * add session store "connect" / "disconnect" support [louischatriot]
+  * fix `:url` log token
+
+2.4.4 / 2012-08-21
+==================
+
+  * fix `static()` pause regression from "send" integration
+
+2.4.3 / 2012-08-07
+==================
+
+  * fix `.write()` encoding for zlib inconstancy. Closes #561
+
+2.4.2 / 2012-07-25
+==================
+
+  * remove limit default from `urlencoded()`
+  * remove limit default from `json()`
+  * remove limit default from `multipart()`
+  * fix `cookieSession()` clear cookie path / domain bug. Closes #636
+
+2.4.1 / 2012-07-24
+==================
+
+  * fix `options` mutation in `static()`
+
+2.4.0 / 2012-07-23
+==================
+
+  * add `connect.timeout()`
+  * add __GET__ / __HEAD__ check to `directory()`. Closes #634
+  * add "pause" util dep
+  * update send dep for normalization bug
+
+2.3.9 / 2012-07-16
+==================
+
+  * add more descriptive invalid json error message
+  * update send dep for root normalization regression
+  * fix staticCache fresh dep
+
+2.3.8 / 2012-07-12
+==================
+
+  * fix `connect.static()` 404 regression, pass `next()`. Closes #629
+
+2.3.7 / 2012-07-05
+==================
+
+  * add `json()` utf-8 illustration test. Closes #621
+  * add "send" dependency
+  * change `connect.static()` internals to use "send"
+  * fix `session()` req.session generation with pathname mismatch
+  * fix `cookieSession()` req.session generation with pathname mismatch
+  * fix mime export. Closes #618
+
+2.3.6 / 2012-07-03
+==================
+
+  * Fixed cookieSession() with cookieParser() secret regression. Closes #602
+  * Fixed set-cookie header fields on cookie.path mismatch. Closes #615
+
+2.3.5 / 2012-06-28
+==================
+
+  * Remove `logger()` mount check
+  * Fixed `staticCache()` dont cache responses with set-cookie. Closes #607
+  * Fixed `staticCache()` when Cookie is present
+
+2.3.4 / 2012-06-22
+==================
+
+  * Added `err.buf` to urlencoded() and json()
+  * Update cookie to 0.0.4. Closes #604
+  * Fixed: only send 304 if original response in 2xx or 304 [timkuijsten]
+
+2.3.3 / 2012-06-11
+==================
+
+  * Added ETags back to `static()` [timkuijsten]
+  * Replaced `utils.parseRange()` with `range-parser` module
+  * Replaced `utils.parseBytes()` with `bytes` module
+  * Replaced `utils.modified()` with `fresh` module
+  * Fixed `cookieSession()` regression with invalid cookie signing [shtylman]
+
+2.3.2 / 2012-06-08
+==================
+
+  * expose mime module
+  * Update crc dep (which bundled nodeunit)
+
+2.3.1 / 2012-06-06
+==================
+
+  * Added `secret` option to `cookieSession` middleware [shtylman]
+  * Added `secret` option to `session` middleware [shtylman]
+  * Added `req.remoteUser` back to `basicAuth()` as alias of `req.user`
+  * Performance: improve signed cookie parsing
+  * Update `cookie` dependency [shtylman]
+
+2.3.0 / 2012-05-20
+==================
+
+  * Added limit option to `json()`
+  * Added limit option to `urlencoded()`
+  * Added limit option to `multipart()`
+  * Fixed: remove socket error event listener on callback
+  * Fixed __ENOTDIR__ error on `static` middleware
+
+2.2.2 / 2012-05-07
+==================
+
+  * Added support to csrf middle for pre-flight CORS requests
+  * Updated `engines` to allow newer version of node
+  * Removed duplicate repo prop. Closes #560
+
+2.2.1 / 2012-04-28
+==================
+
+  * Fixed `static()` redirect when mounted. Closes #554
+
+2.2.0 / 2012-04-25
+==================
+
+  * Added `make benchmark`
+  * Perf: memoize url parsing (~20% increase)
+  * Fixed `connect(fn, fn2, ...)`. Closes #549
+
+2.1.3 / 2012-04-20
+==================
+
+  * Added optional json() `reviver` function to be passed to JSON.parse [jed]
+  * Fixed: emit drain in compress middleware [nsabovic]
+
+2.1.2 / 2012-04-11
+==================
+
+  * Fixed cookieParser() `req.cookies` regression
+
+2.1.1 / 2012-04-11
+==================
+
+  * Fixed `session()` browser-session length cookies & examples
+  * Fixed: make `query()` "self-aware" [jed]
+
+2.1.0 / 2012-04-05
+==================
+
+  * Added `debug()` calls to `.use()` (`DEBUG=connect:displatcher`)
+  * Added `urlencoded()` support for GET
+  * Added `json()` support for GET. Closes #497
+  * Added `strict` option to `json()`
+  * Changed: `session()` only set-cookie when modified
+  * Removed `Session#lastAccess` property. Closes #399
+
+2.0.3 / 2012-03-20
+==================
+
+  * Added: `cookieSession()` only sets cookie on change. Closes #442
+  * Added `connect:dispatcher` debug() probes
+
+2.0.2 / 2012-03-04
+==================
+
+  * Added test for __ENAMETOOLONG__ now that node is fixed
+  * Fixed static() index "/" check on windows. Closes #498
+  * Fixed Content-Range behaviour to match RFC2616 [matthiasdg / visionmedia]
+
+2.0.1 / 2012-02-29
+==================
+
+  * Added test coverage for `vhost()` middleware
+  * Changed `cookieParser()` signed cookie support to use SHA-2 [senotrusov]
+  * Fixed `static()` Range: respond with 416 when unsatisfiable
+  * Fixed `vhost()` middleware. Closes #494
+
+2.0.0 / 2011-10-05
+==================
+
+  * Added `cookieSession()` middleware for cookie-only sessions
+  * Added `compress()` middleware for gzip / deflate support
+  * Added `session()` "proxy" setting to trust `X-Forwarded-Proto`
+  * Added `json()` middleware to parse "application/json"
+  * Added `urlencoded()` middleware to parse "application/x-www-form-urlencoded"
+  * Added `multipart()` middleware to parse "multipart/form-data"
+  * Added `cookieParser(secret)` support so anything using this middleware may access signed cookies
+  * Added signed cookie support to `cookieParser()`
+  * Added support for JSON-serialized cookies to `cookieParser()`
+  * Added `err.status` support in Connect's default end-point
+  * Added X-Cache MISS / HIT to `staticCache()`
+  * Added public `res.headerSent` checking nodes `res._headerSent` until node does
+  * Changed `basicAuth()` req.remoteUser to req.user
+  * Changed: default `session()` to a browser-session cookie. Closes #475
+  * Changed: no longer lowercase cookie names
+  * Changed `bodyParser()` to use `json()`, `urlencoded()`, and `multipart()`
+  * Changed: `errorHandler()` is now a development-only middleware
+  * Changed middleware to `next()` errors when possible so applications can unify logging / handling
+  * Removed `http[s].Server` inheritance, now just a function, making it easy to have an app providing both http and https
+  * Removed `.createServer()` (use `connect()`)
+  * Removed `secret` option from `session()`, use `cookieParser(secret)`
+  * Removed `connect.session.ignore` array support
+  * Removed `router()` middleware. Closes #262
+  * Fixed: set-cookie only once for browser-session cookies
+  * Fixed FQDN support. dont add leading "/"
+  * Fixed 404 XSS attack vector. Closes #473
+  * Fixed __HEAD__ support for 404s and 500s generated by Connect's end-point
+
+1.8.5 / 2011-12-22
+==================
+
+  * Fixed: actually allow empty body for json
+
+1.8.4 / 2011-12-22
+==================
+
+  * Changed: allow empty body for json/urlencoded requests. Backport for #443
+
+1.8.3 / 2011-12-16
+==================
+
+  * Fixed `static()` _index.html_ support on windows
+
+1.8.2 / 2011-12-03
+==================
+
+  * Fixed potential security issue, store files in req.files. Closes #431 [reported by dobesv]
+
+1.8.1 / 2011-11-21
+==================
+
+  * Added nesting support for _multipart/form-data_ [jackyz]
+
+1.8.0 / 2011-11-17
+==================
+
+  * Added _multipart/form-data_ support to `bodyParser()` using formidable
+
+1.7.3 / 2011-11-11
+==================
+
+  * Fixed `req.body`, always default to {}
+  * Fixed HEAD support for 404s and 500s
+
+1.7.2 / 2011-10-24
+==================
+
+  * "node": ">= 0.4.1 < 0.7.0"
+  * Added `static()` redirect option. Closes #398
+  * Changed `limit()`: respond with 413 when content-length exceeds the limit
+  * Removed socket error listener in static(). Closes #389
+  * Fixed `staticCache()` Age header field
+  * Fixed race condition causing errors reported in #329.
+
+1.7.1 / 2011-09-12
+==================
+
+  * Added: make `Store` inherit from `EventEmitter`
+  * Added session `Store#load(sess, fn)` to fetch a `Session` instance
+  * Added backpressure support to `staticCache()`
+  * Changed `res.socket.destroy()` to `req.socket.destroy()`
+
+1.7.0 / 2011-08-31
+==================
+
+  * Added `staticCache()` middleware, a memory cache for `static()`
+  * Added public `res.headerSent` checking nodes `res._headerSent` (remove when node adds this)
+  * Changed: ignore error handling middleware when header is sent
+  * Changed: dispatcher errors after header is sent destroy the sock
+
+1.6.4 / 2011-08-26
+==================
+
+  * Revert "Added double-next reporting"
+
+1.6.3 / 2011-08-26
+==================
+
+  * Added double-`next()` reporting
+  * Added `immediate` option to `logger()`. Closes #321
+  * Dependency `qs >= 0.3.1`
+
+1.6.2 / 2011-08-11
+==================
+
+  * Fixed `connect.static()` null byte vulnerability
+  * Fixed `connect.directory()` null byte vulnerability
+  * Changed: 301 redirect in `static()` to postfix "/" on directory. Closes #289
+
+1.6.1 / 2011-08-03
+==================
+
+  * Added: allow retval `== null` from logger callback to ignore line
+  * Added `getOnly` option to `connect.static.send()`
+  * Added response "header" event allowing augmentation
+  * Added `X-CSRF-Token` header field check
+  * Changed dep `qs >= 0.3.0`
+  * Changed: persist csrf token. Closes #322
+  * Changed: sort directory middleware files alphabetically
+
+1.6.0 / 2011-07-10
+==================
+
+  * Added :response-time to "dev" logger format
+  * Added simple `csrf()` middleware. Closes #315
+  * Fixed `res._headers` logger regression. Closes #318
+  * Removed support for multiple middleware being passed to `.use()`
+
+1.5.2 / 2011-07-06
+==================
+
+  * Added `filter` function option to `directory()` [David Rio Deiros]
+  * Changed: re-write of the `logger()` middleware, with extensible tokens and formats
+  * Changed: `static.send()` ".." in path without root considered malicious
+  * Fixed quotes in docs. Closes #312
+  * Fixed urls when mounting `directory()`, use `originalUrl` [Daniel Dickison]
+
+
+1.5.1 / 2011-06-20
+==================
+
+  * Added malicious path check to `directory()` middleware
+  * Added `utils.forbidden(res)`
+  * Added `connect.query()` middleware
+
+1.5.0 / 2011-06-20
+==================
+
+  * Added `connect.directory()` middleware for serving directory listings
+
+1.4.6 / 2011-06-18
+==================
+
+  * Fixed `connect.static()` root with `..`
+  * Fixed `connect.static()` __EBADF__
+
+1.4.5 / 2011-06-17
+==================
+
+  * Fixed EBADF in `connect.static()`. Closes #297
+
+1.4.4 / 2011-06-16
+==================
+
+  * Changed `connect.static()` to check resolved dirname. Closes #294
+
+1.4.3 / 2011-06-06
+==================
+
+  * Fixed fd leak in `connect.static()` when the socket is closed
+  * Fixed; `bodyParser()` ignoring __GET/HEAD__. Closes #285
+
+1.4.2 / 2011-05-27
+==================
+
+  * Changed to `devDependencies`
+  * Fixed stream creation on `static()` __HEAD__ request. [Andreas Lind Petersen]
+  * Fixed Win32 support for `static()`
+  * Fixed monkey-patch issue. Closes #261
+
+1.4.1 / 2011-05-08
+==================
+
+  * Added "hidden" option to `static()`. ignores hidden files by default. Closes   * Added; expose `connect.static.mime.define()`. Closes #251
+  * Fixed `errorHandler` middleware for missing stack traces. [aseemk]
+#274
+
+1.4.0 / 2011-04-25
+==================
+
+  * Added route-middleware `next('route')` support to jump passed the route itself
+  * Added Content-Length support to `limit()`
+  * Added route-specific middleware support (used to be in express)
+  * Changed; refactored duplicate session logic
+  * Changed; prevent redefining `store.generate` per request
+  * Fixed; `static()` does not set Content-Type when explicitly set [nateps]
+  * Fixed escape `errorHandler()` {error} contents
+  * NOTE: `router` will be removed in 2.0
+
+
+1.3.0 / 2011-04-06
+==================
+
+  * Added `router.remove(path[, method])` to remove a route
+
+1.2.3 / 2011-04-05
+==================
+
+  * Fixed basicAuth realm issue when passing strings. Closes #253
+
+1.2.2 / 2011-04-05
+==================
+
+  * Added `basicAuth(username, password)` support
+  * Added `errorHandler.title` defaulting to "Connect"
+  * Changed `errorHandler` css
+
+1.2.1 / 2011-03-30
+==================
+
+  * Fixed `logger()` https `remoteAddress` logging [Alexander Simmerl]
+
+1.2.0 / 2011-03-30
+==================
+
+  * Added `router.lookup(path[, method])`
+  * Added `router.match(url[, method])`
+  * Added basicAuth async support. Closes #223
+
+1.1.5 / 2011-03-27
+==================
+
+  * Added; allow `logger()` callback function to return an empty string to ignore logging
+  * Fixed; utilizing `mime.charsets.lookup()` for `static()`. Closes 245
+
+1.1.4 / 2011-03-23
+==================
+
+  * Added `logger()` support for format function
+  * Fixed `logger()` to support mess of writeHead()/progressive api for node 0.4.x
+
+1.1.3 / 2011-03-21
+==================
+
+  * Changed; `limit()` now calls `req.destroy()`
+
+1.1.2 / 2011-03-21
+==================
+
+  * Added request "limit" event to `limit()` middleware
+  * Changed; `limit()` middleware will `next(err)` on failure
+
+1.1.1 / 2011-03-18
+==================
+
+  * Fixed session middleware for HTTPS. Closes #241 [reported by mt502]
+
+1.1.0 / 2011-03-17
+==================
+
+  * Added `Session#reload(fn)`
+
+1.0.6 / 2011-03-09
+==================
+
+  * Fixed `res.setHeader()` patch, preserve casing
+
+1.0.5 / 2011-03-09
+==================
+
+  * Fixed; `logger()` using `req.originalUrl` instead of `req.url`
+
+1.0.4 / 2011-03-09
+==================
+
+  * Added `res.charset`
+  * Added conditional sessions example
+  * Added support for `session.ignore` to be replaced. Closes #227
+  * Fixed `Cache-Control` delimiters. Closes #228
+
+1.0.3 / 2011-03-03
+==================
+
+  * Fixed; `static.send()` invokes callback with connection error
+
+1.0.2 / 2011-03-02
+==================
+
+  * Fixed exported connect function
+  * Fixed package.json; node ">= 0.4.1 < 0.5.0"
+
+1.0.1 / 2011-03-02
+==================
+
+  * Added `Session#save(fn)`. Closes #213
+  * Added callback support to `connect.static.send()` for express
+  * Added `connect.static.send()` "path" option
+  * Fixed content-type in `static()` for _index.html_
+
+1.0.0 / 2011-03-01
+==================
+
+  * Added `stack`, `message`, and `dump` errorHandler option aliases
+  * Added `req.originalMethod` to methodOverride
+  * Added `favicon()` maxAge option support
+  * Added `connect()` alternative to `connect.createServer()`
+  * Added new [documentation](http://senchalabs.github.com/connect)
+  * Added Range support to `static()`
+  * Added HTTPS support
+  * Rewrote session middleware. The session API now allows for
+    session-specific cookies, so you may alter each individually.
+    Click to view the new [session api](http://senchalabs.github.com/connect/middleware-session.html).
+  * Added middleware self-awareness. This helps prevent
+    middleware breakage when used within mounted servers.
+    For example `cookieParser()` will not parse cookies more
+    than once even when within a mounted server.
+  * Added new examples in the `./examples` directory
+  * Added [limit()](http://senchalabs.github.com/connect/middleware-limit.html) middleware
+  * Added [profiler()](http://senchalabs.github.com/connect/middleware-profiler.html) middleware
+  * Added [responseTime()](http://senchalabs.github.com/connect/middleware-responseTime.html) middleware
+  * Renamed `staticProvider` to `static`
+  * Renamed `bodyDecoder` to `bodyParser`
+  * Renamed `cookieDecoder` to `cookieParser`
+  * Fixed ETag quotes. [reported by papandreou]
+  * Fixed If-None-Match comma-delimited ETag support. [reported by papandreou]
+  * Fixed; only set req.originalUrl once. Closes #124
+  * Fixed symlink support for `static()`. Closes #123
+
+0.5.10 / 2011-02-14
+==================
+
+  * Fixed SID space issue. Closes #196
+  * Fixed; proxy `res.end()` to commit session data
+  * Fixed directory traversal attack in `staticProvider`. Closes #198
+
+0.5.9 / 2011-02-09
+==================
+
+  * qs >= 0.0.4
+
+0.5.8 / 2011-02-04
+==================
+
+  * Added `qs` dependency
+  * Fixed router race-condition causing possible failure
+    when `next()`ing to one or more routes with parallel
+    requests
+
+0.5.7 / 2011-02-01
+==================
+
+  * Added `onvhost()` call so Express (and others) can know when they are
+  * Revert "Added stylus support" (use the middleware which ships with stylus)
+  * Removed custom `Server#listen()` to allow regular `http.Server#listen()` args to work properly
+  * Fixed long standing router issue (#83) that causes '.' to be disallowed within named placeholders in routes [Andreas Lind Petersen]
+  * Fixed `utils.uid()` length error [Jxck]
+mounted
+
+0.5.6 / 2011-01-23
+==================
+
+  * Added stylus support to `compiler`
+  * _favicon.js_ cleanup
+  * _compiler.js_ cleanup
+  * _bodyDecoder.js_ cleanup
+
+0.5.5 / 2011-01-13
+==================
+
+  * Changed; using sha256 HMAC instead of md5. [Paul Querna]
+  * Changed; generated a longer random UID, without time influence. [Paul Querna]
+  * Fixed; session middleware throws when secret is not present. [Paul Querna]
+
+0.5.4 / 2011-01-07
+==================
+
+  * Added; throw when router path or callback is missing
+  * Fixed; `next(err)` on cookie parse exception instead of ignoring
+  * Revert "Added utils.pathname(), memoized url.parse(str).pathname"
+
+0.5.3 / 2011-01-05
+==================
+
+  * Added _docs/api.html_
+  * Added `utils.pathname()`, memoized url.parse(str).pathname
+  * Fixed `session.id` issue. Closes #183
+  * Changed; Defaulting `staticProvider` maxAge to 0 not 1 year. Closes #179
+  * Removed bad outdated docs, we need something new / automated eventually
+
+0.5.2 / 2010-12-28
+==================
+
+  * Added default __OPTIONS__ support to _router_ middleware
+
+0.5.1 / 2010-12-28
+==================
+
+  * Added `req.session.id` mirroring `req.sessionID`
+  * Refactored router, exposing `connect.router.methods`
+  * Exclude non-lib files from npm
+  * Removed imposed headers `X-Powered-By`, `Server`, etc
+
+0.5.0 / 2010-12-06
+==================
+
+  * Added _./index.js_
+  * Added route segment precondition support and example
+  * Added named capture group support to router
+
+0.4.0 / 2010-11-29
+==================
+
+  * Added `basicAuth` middleware
+  * Added more HTTP methods to the `router` middleware
+
+0.3.0 / 2010-07-21
+==================
+
+  * Added _staticGzip_ middleware
+  * Added `connect.utils` to expose utils
+  * Added `connect.session.Session`
+  * Added `connect.session.Store`
+  * Added `connect.session.MemoryStore`
+  * Added `connect.middleware` to expose the middleware getters
+  * Added `buffer` option to _logger_ for performance increase
+  * Added _favicon_ middleware for serving your own favicon or the connect default
+  * Added option support to _staticProvider_, can now pass _root_ and _lifetime_.
+  * Added; mounted `Server` instances now have the `route` property exposed for reflection
+  * Added support for callback as first arg to `Server#use()`
+  * Added support for `next(true)` in _router_ to bypass match attempts
+  * Added `Server#listen()` _host_ support
+  * Added `Server#route` when `Server#use()` is called with a route on a `Server` instance
+  * Added _methodOverride_ X-HTTP-Method-Override support
+  * Refactored session internals, adds _secret_ option
+  * Renamed `lifetime` option to `maxAge` in _staticProvider_
+  * Removed connect(1), it is now [spark(1)](http://github.com/senchalabs/spark)
+  * Removed connect(1) dependency on examples, they can all now run with node(1)
+  * Remove a typo that was leaking a global.
+  * Removed `Object.prototype` forEach() and map() methods
+  * Removed a few utils not used
+  * Removed `connect.createApp()`
+  * Removed `res.simpleBody()`
+  * Removed _format_ middleware
+  * Removed _flash_ middleware
+  * Removed _redirect_ middleware
+  * Removed _jsonrpc_ middleware, use [visionmedia/connect-jsonrpc](http://github.com/visionmedia/connect-jsonrpc)
+  * Removed _pubsub_ middleware
+  * Removed need for `params.{captures,splat}` in _router_ middleware, `params` is an array
+  * Changed; _compiler_ no longer 404s
+  * Changed; _router_ signature now matches connect middleware signature
+  * Fixed a require in _session_ for default `MemoryStore`
+  * Fixed nasty request body bug in _router_. Closes #54
+  * Fixed _less_ support in _compiler_
+  * Fixed bug preventing proper bubbling of exceptions in mounted servers
+  * Fixed bug in `Server#use()` preventing `Server` instances as the first arg
+  * Fixed **ENOENT** special case, is now treated as any other exception
+  * Fixed spark env support
+
+0.2.1 / 2010-07-09
+==================
+
+  * Added support for _router_ `next()` to continue calling matched routes
+  * Added mime type for _cache.manifest_ files.
+  * Changed _compiler_ middleware to use async require
+  * Changed session api, stores now only require `#get()`, and `#set()`
+  * Fixed _cacheManifest_ by adding `utils.find()` back
+
+0.2.0 / 2010-07-01
+==================
+
+  * Added calls to `Session()` casts the given object as a `Session` instance
+  * Added passing of `next()` to _router_ callbacks. Closes #46
+  * Changed; `MemoryStore#destroy()` removes `req.session`
+  * Changed `res.redirect("back")` to default to "/" when Referr?er is not present
+  * Fixed _staticProvider_ urlencoded paths issue. Closes #47
+  * Fixed _staticProvider_ middleware responding to **GET** requests
+  * Fixed _jsonrpc_ middleware `Accept` header check. Closes #43
+  * Fixed _logger_ format option
+  * Fixed typo in _compiler_ middleware preventing the _dest_ option from working
+
+0.1.0 / 2010-06-25
+==================
+
+  * Revamped the api, view the [Connect documentation](http://extjs.github.com/Connect/index.html#Middleware-Authoring) for more info (hover on the right for menu)
+  * Added [extended api docs](http://extjs.github.com/Connect/api.html)
+  * Added docs for several more middleware layers
+  * Added `connect.Server#use()`
+  * Added _compiler_ middleware which provides arbitrary static compilation
+  * Added `req.originalUrl`
+  * Removed _blog_ example
+  * Removed _sass_ middleware (use _compiler_)
+  * Removed _less_ middleware (use _compiler_)
+  * Renamed middleware to be camelcase, _body-decoder_ is now _bodyDecoder_ etc.
+  * Fixed `req.url` mutation bug when matching `connect.Server#use()` routes
+  * Fixed `mkdir -p` implementation used in _bin/connect_. Closes #39
+  * Fixed bug in _bodyDecoder_ throwing exceptions on request empty bodies
+  * `make install` installing lib to $LIB_PREFIX aka $HOME/.node_libraries
+
+0.0.6 / 2010-06-22
+==================
+
+  * Added _static_ middleware usage example
+  * Added support for regular expressions as paths for _router_
+  * Added `util.merge()`
+  * Increased performance of _static_ by ~ 200 rps
+  * Renamed the _rest_ middleware to _router_
+  * Changed _rest_ api to accept a callback function
+  * Removed _router_ middleware
+  * Removed _proto.js_, only `Object#forEach()` remains
+
+0.0.5 / 2010-06-21
+==================
+
+  * Added Server#use() which contains the Layer normalization logic
+  * Added documentation for several middleware
+  * Added several new examples
+  * Added _less_ middleware
+  * Added _repl_ middleware
+  * Added _vhost_ middleware
+  * Added _flash_ middleware
+  * Added _cookie_ middleware
+  * Added _session_ middleware
+  * Added `utils.htmlEscape()`
+  * Added `utils.base64Decode()`
+  * Added `utils.base64Encode()`
+  * Added `utils.uid()`
+  * Added bin/connect app path and --config path support for .js suffix, although optional. Closes #26
+  * Moved mime code to `utils.mime`, ex `utils.mime.types`, and `utils.mime.type()`
+  * Renamed req.redirect() to res.redirect(). Closes #29
+  * Fixed _sass_ 404 on **ENOENT**
+  * Fixed +new Date duplication. Closes #24
+
+0.0.4 / 2010-06-16
+==================
+
+  * Added workerPidfile() to bin/connect
+  * Added --workers support to bin/connect stop and status commands
+  * Added _redirect_ middleware
+  * Added better --config support to bin/connect. All flags can be utilized
+  * Added auto-detection of _./config.js_
+  * Added config example
+  * Added `net.Server` support to bin/connect
+  * Writing worker pids relative to `env.pidfile`
+  * s/parseQuery/parse/g
+  * Fixed npm support
+
+0.0.3 / 2010-06-16
+==================
+
+  * Fixed node dependency in package.json, now _">= 0.1.98-0"_ to support __HEAD__
+
+0.0.2 / 2010-06-15
+==================
+
+  * Added `-V, --version` to bin/connect
+  * Added `utils.parseCookie()`
+  * Added `utils.serializeCookie()`
+  * Added `utils.toBoolean()`
+  * Added _sass_ middleware
+  * Added _cookie_ middleware
+  * Added _format_ middleware
+  * Added _lint_ middleware
+  * Added _rest_ middleware
+  * Added _./package.json_ (npm install connect)
+  * Added `handleError()` support
+  * Added `process.connectEnv`
+  * Added custom log format support to _log_ middleware
+  * Added arbitrary env variable support to bin/connect (ext: --logFormat ":method :url")
+  * Added -w, --workers to bin/connect
+  * Added bin/connect support for --user NAME and --group NAME
+  * Fixed url re-writing support
+
+0.0.1 / 2010-06-03
+==================
+
+  * Initial release
+
diff --git a/LICENSE b/LICENSE
index 0c5d22d..6dd4c1c 100644
--- a/LICENSE
+++ b/LICENSE
@@ -2,7 +2,7 @@
 
 Copyright (c) 2010 Sencha Inc.
 Copyright (c) 2011 LearnBoost
-Copyright (c) 2011 TJ Holowaychuk
+Copyright (c) 2011-2014 TJ Holowaychuk
 
 Permission is hereby granted, free of charge, to any person obtaining
 a copy of this software and associated documentation files (the
@@ -21,4 +21,4 @@ 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.
\ No newline at end of file
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..5131186
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,96 @@
+# Connect [![NPM version](https://badge.fury.io/js/connect.svg)](http://badge.fury.io/js/connect) [![Build Status](https://travis-ci.org/senchalabs/connect.svg?branch=master)](https://travis-ci.org/senchalabs/connect) [![Coverage Status](https://img.shields.io/coveralls/senchalabs/connect.svg)](https://coveralls.io/r/senchalabs/connect)
+
+  Connect is an extensible HTTP server framework for [node](http://nodejs.org) using "plugins" known as _middleware_.
+
+```js
+var connect = require('connect')
+  , http = require('http');
+
+var app = connect()
+  .use(require('compression')())
+  .use(require('cookie-session')({
+    keys: ['secret1', 'secret2']
+  }))
+  .use(require('body-parser')())
+  .use(function(req, res){
+    res.end('Hello from Connect!\n');
+  });
+
+http.createServer(app).listen(3000);
+```
+
+## Connect 3.0
+
+Connect 3.0 is in progress in the `master` branch. The main changes in Connect are:
+
+- Middleware will be moved to their own repositories in the [expressjs](http://github.com/expressjs) organization
+- All node patches will be removed - all middleware _should_ work without Connect and with similar frameworks like [restify](https://github.com/mcavage/node-restify)
+- Node `0.8` is no longer supported
+- The website documentation has been removed - view the markdown readmes instead
+
+If you would like to help maintain these middleware, please contact a [member of the expressjs team](https://github.com/orgs/expressjs/members).
+
+## Middleware
+
+These middleware and libraries are officially supported by the Connect/Express team:
+
+  - [body-parser](https://github.com/expressjs/body-parser) - previous `bodyParser`, `json`, and `urlencoded`. You may also be interested in:
+    - [body](https://github.com/raynos/body)
+    - [co-body](https://github.com/visionmedia/co-body)
+    - [raw-body](https://github.com/stream-utils/raw-body)
+  - [compression](https://github.com/expressjs/compression) - previously `compress`
+  - [connect-timeout](https://github.com/expressjs/timeout) - previously `timeout`
+  - [cookie-parser](https://github.com/expressjs/cookie-parser) - previously `cookieParser`
+  - [cookie-session](https://github.com/expressjs/cookie-session) - previously `cookieSession`
+  - [csurf](https://github.com/expressjs/csurf) - previousy `csrf`
+  - [errorhandler](https://github.com/expressjs/errorhandler) - previously `error-handler`
+  - [express-session](https://github.com/expressjs/session) - previously `session`
+  - [method-override](https://github.com/expressjs/method-override) - previously `method-override`
+  - [morgan](https://github.com/expressjs/morgan) - previously `logger`
+  - [response-time](https://github.com/expressjs/response-time) - previously `response-time`
+  - [serve-favicon](https://github.com/expressjs/serve-favicon) - previously `favicon`
+  - [serve-index](https://github.com/expressjs/serve-index) - previousy `directory`
+  - [serve-static](https://github.com/expressjs/serve-static) - previously `static`
+  - [vhost](https://github.com/expressjs/vhost) - previously `vhost`
+
+Most of these are exact ports of their Connect 2.x equivalents. The primary exception is `cookie-session`.
+
+Some middleware previously included with Connect are no longer supported by the Connect/Express team, are replaced by an alternative module, or should be superseded by a better module. Use one of these alternatives instead:
+
+  - `cookieParser`
+    - [cookies](https://github.com/jed/cookies) and [keygrip](https://github.com/jed/keygrip)
+  - `limit`
+    - [raw-body](https://github.com/stream-utils/raw-body)
+  - `multipart`
+    - [connect-multiparty](https://github.com/superjoe30/connect-multiparty)
+    - [connect-busboy](https://github.com/mscdex/connect-busboy)
+  - `query`
+    - [qs](https://github.com/visionmedia/node-querystring)
+  - `staticCache`
+    - [st](https://github.com/isaacs/st)
+    - [connect-static](https://github.com/andrewrk/connect-static)
+
+Checkout [http-framework](https://github.com/Raynos/http-framework/wiki/Modules) for many other compatible middleware! 
+
+## Running Tests
+
+```bash
+npm install
+npm test
+```
+
+## Contributors
+
+ https://github.com/senchalabs/connect/graphs/contributors
+
+## Node Compatibility
+
+  - Connect `< 1.x` - node `0.2`
+  - Connect `1.x` - node `0.4`
+  - Connect `< 2.8` - node `0.6`
+  - Connect `>= 2.8 < 3` - node `0.8`
+  - Connect `>= 3` - node `0.10`
+
+## License
+
+View the [LICENSE](https://github.com/senchalabs/connect/blob/master/LICENSE) file.
diff --git a/index.js b/index.js
index 23240ee..02befb4 100644
--- a/index.js
+++ b/index.js
@@ -1,4 +1,2 @@
 
-module.exports = process.env.CONNECT_COV
-  ? require('./lib-cov/connect')
-  : require('./lib/connect');
\ No newline at end of file
+module.exports = require('./lib/connect');
diff --git a/lib/cache.js b/lib/cache.js
deleted file mode 100644
index 052fcdb..0000000
--- a/lib/cache.js
+++ /dev/null
@@ -1,81 +0,0 @@
-
-/*!
- * Connect - Cache
- * Copyright(c) 2011 Sencha Inc.
- * MIT Licensed
- */
-
-/**
- * Expose `Cache`.
- */
-
-module.exports = Cache;
-
-/**
- * LRU cache store.
- *
- * @param {Number} limit
- * @api private
- */
-
-function Cache(limit) {
-  this.store = {};
-  this.keys = [];
-  this.limit = limit;
-}
-
-/**
- * Touch `key`, promoting the object.
- *
- * @param {String} key
- * @param {Number} i
- * @api private
- */
-
-Cache.prototype.touch = function(key, i){
-  this.keys.splice(i,1);
-  this.keys.push(key);
-};
-
-/**
- * Remove `key`.
- *
- * @param {String} key
- * @api private
- */
-
-Cache.prototype.remove = function(key){
-  delete this.store[key];
-};
-
-/**
- * Get the object stored for `key`.
- *
- * @param {String} key
- * @return {Array}
- * @api private
- */
-
-Cache.prototype.get = function(key){
-  return this.store[key];
-};
-
-/**
- * Add a cache `key`.
- *
- * @param {String} key
- * @return {Array}
- * @api private
- */
-
-Cache.prototype.add = function(key){
-  // initialize store
-  var len = this.keys.push(key);
-
-  // limit reached, invalidate LRU
-  if (len > this.limit) this.remove(this.keys.shift());
-
-  var arr = this.store[key] = [];
-  arr.createdAt = new Date;
-  return arr;
-};
diff --git a/lib/connect.js b/lib/connect.js
index c4821e6..cc4cf35 100644
--- a/lib/connect.js
+++ b/lib/connect.js
@@ -1,4 +1,3 @@
-
 /*!
  * Connect
  * Copyright(c) 2010 Sencha Inc.
@@ -10,44 +9,13 @@
  * Module dependencies.
  */
 
-var EventEmitter = require('events').EventEmitter
-  , proto = require('./proto')
-  , utils = require('./utils')
-  , path = require('path')
-  , basename = path.basename
-  , fs = require('fs');
-
-// node patches
-
-require('./patch');
+var EventEmitter = require('events').EventEmitter;
+var merge = require('utils-merge');
+var proto = require('./proto');
 
 // expose createServer() as the module
 
-exports = module.exports = createServer;
-
-/**
- * Framework version.
- */
-
-exports.version = '2.0.3';
-
-/**
- * Expose the prototype.
- */
-
-exports.proto = proto;
-
-/**
- * Auto-load middleware getters.
- */
-
-exports.middleware = {};
-
-/**
- * Expose utilities.
- */
-
-exports.utils = utils;
+module.exports = createServer;
 
 /**
  * Create a new connect server.
@@ -57,28 +25,10 @@ exports.utils = utils;
  */
 
 function createServer() {
-  function app(req, res){ app.handle(req, res); }
-  utils.merge(app, proto);
-  utils.merge(app, EventEmitter.prototype);
+  function app(req, res, next){ app.handle(req, res, next); }
+  merge(app, proto);
+  merge(app, EventEmitter.prototype);
   app.route = '/';
-  app.stack = [].slice.apply(arguments);
+  app.stack = [];
   return app;
-};
-
-/**
- * Support old `.createServer()` method.
- */
-
-createServer.createServer = createServer;
-
-/**
- * Auto-load bundled middleware with getters.
- */
-
-fs.readdirSync(__dirname + '/middleware').forEach(function(filename){
-  if (!/\.js$/.test(filename)) return;
-  var name = basename(filename, '.js');
-  function load(){ return require('./middleware/' + name); }
-  exports.middleware.__defineGetter__(name, load);
-  exports.__defineGetter__(name, load);
-});
+}
diff --git a/lib/index.js b/lib/index.js
deleted file mode 100644
index cca8479..0000000
--- a/lib/index.js
+++ /dev/null
@@ -1,54 +0,0 @@
-
-/**
- * Connect is a middleware framework for node,
- * shipping with over 18 bundled middleware and a rich selection of
- * 3rd-party middleware.
- *
- *     var app = connect()
- *       .use(connect.logger('dev'))
- *       .use(connect.static('public'))
- *       .use(function(req, res){
- *         res.end('hello world\n');
- *       })
- *      .listen(3000);
- *     
- * Installation:
- * 
- *     $ npm install connect
- *
- * Middleware:
- *
- *  - [logger](logger.html) request logger with custom format support
- *  - [csrf](csrf.html) Cross-site request forgery protection
- *  - [compress](compress.html) Gzip compression middleware
- *  - [basicAuth](basicAuth.html) basic http authentication
- *  - [bodyParser](bodyParser.html) extensible request body parser
- *  - [json](json.html) application/json parser
- *  - [urlencoded](urlencoded.html) application/x-www-form-urlencoded parser
- *  - [multipart](multipart.html) multipart/form-data parser
- *  - [cookieParser](cookieParser.html) cookie parser
- *  - [session](session.html) session management support with bundled MemoryStore
- *  - [cookieSession](cookieSession.html) cookie-based session support
- *  - [methodOverride](methodOverride.html) faux HTTP method support
- *  - [responseTime](responseTime.html) calculates response-time and exposes via X-Response-Time
- *  - [staticCache](staticCache.html) memory cache layer for the static() middleware
- *  - [static](static.html) streaming static file server supporting `Range` and more
- *  - [directory](directory.html) directory listing middleware
- *  - [vhost](vhost.html) virtual host sub-domain mapping middleware
- *  - [favicon](favicon.html) efficient favicon server (with default icon)
- *  - [limit](limit.html) limit the bytesize of request bodies
- *  - [query](query.html) automatic querystring parser, populating `req.query`
- *  - [errorHandler](errorHandler.html) flexible error handler
- *
- * Internals:
- *
- *  - server [prototype](proto.html)
- *  - connect [utilities](utils.html)
- *  - node monkey [patches](patch.html)
- *
- * Links:
- * 
- *   - list of [3rd-party](https://github.com/senchalabs/connect/wiki) middleware
- *   - GitHub [repository](http://github.com/senchalabs/connect)
- * 
- */
\ No newline at end of file
diff --git a/lib/middleware/basicAuth.js b/lib/middleware/basicAuth.js
deleted file mode 100644
index ce7ed41..0000000
--- a/lib/middleware/basicAuth.js
+++ /dev/null
@@ -1,98 +0,0 @@
-
-/*!
- * Connect - basicAuth
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../utils')
-  , unauthorized = utils.unauthorized;
-
-/**
- * Basic Auth:
- *
- * Enfore basic authentication by providing a `callback(user, pass)`,
- * which must return `true` in order to gain access. Alternatively an async
- * method is provided as well, invoking `callback(user, pass, callback)`. Populates
- * `req.user`. The final alternative is simply passing username / password
- * strings.
- *
- *  Simple username and password
- *
- *     connect(connect.basicAuth('username', 'password'));
- *
- *  Callback verification
- *
- *     connect()
- *       .use(connect.basicAuth(function(user, pass){
- *         return 'tj' == user & 'wahoo' == pass;
- *       }))
- *
- *  Async callback verification, accepting `fn(err, user)`.
- *
- *     connect()
- *       .use(connect.basicAuth(function(user, pass, fn){
- *         User.authenticate({ user: user, pass: pass }, fn);
- *       }))
- *
- * @param {Function|String} callback or username
- * @param {String} realm
- * @api public
- */
-
-module.exports = function basicAuth(callback, realm) {
-  var username, password;
-
-  // user / pass strings
-  if ('string' == typeof callback) {
-    username = callback;
-    password = realm;
-    if ('string' != typeof password) throw new Error('password argument required');
-    realm = arguments[2];
-    callback = function(user, pass){
-      return user == username && pass == password;
-    }
-  }
-
-  realm = realm || 'Authorization Required';
-
-  return function(req, res, next) {
-    var authorization = req.headers.authorization;
-
-    if (req.user) return next();
-    if (!authorization) return unauthorized(res, realm);
-
-    var parts = authorization.split(' ')
-      , scheme = parts[0]
-      , credentials = new Buffer(parts[1], 'base64').toString().split(':')
-      , user = credentials[0]
-      , pass = credentials[1];
-
-    if ('Basic' != scheme) return next(utils.error(400));
-
-    // async
-    if (callback.length >= 3) {
-      var pause = utils.pause(req);
-      callback(user, pass, function(err, user){
-        if (err || !user)  return unauthorized(res, realm);
-        req.user = user;
-        next();
-        pause.resume();
-      });
-    // sync
-    } else {
-      if (callback(user, pass)) {
-        req.user = user;
-        next();
-      } else {
-        unauthorized(res, realm);
-      }
-    }
-  }
-};
-
diff --git a/lib/middleware/bodyParser.js b/lib/middleware/bodyParser.js
deleted file mode 100644
index 9f692cd..0000000
--- a/lib/middleware/bodyParser.js
+++ /dev/null
@@ -1,61 +0,0 @@
-
-/*!
- * Connect - bodyParser
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var multipart = require('./multipart')
-  , urlencoded = require('./urlencoded')
-  , json = require('./json');
-
-/**
- * Body parser:
- * 
- *   Parse request bodies, supports _application/json_,
- *   _application/x-www-form-urlencoded_, and _multipart/form-data_.
- *
- *   This is equivalent to: 
- *
- *     app.use(connect.json());
- *     app.use(connect.urlencoded());
- *     app.use(connect.multipart());
- *
- * Examples:
- *
- *      connect()
- *        .use(connect.bodyParser())
- *        .use(function(req, res) {
- *          res.end('viewing user ' + req.body.user.name);
- *        });
- *
- *      $ curl -d 'user[name]=tj' http://local/
- *      $ curl -d '{"user":{"name":"tj"}}' -H "Content-Type: application/json" http://local/
- *
- *  View [json](json.html), [urlencoded](urlencoded.html), and [multipart](multipart.html) for more info.
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function bodyParser(options){
-  var _urlencoded = urlencoded(options)
-    , _multipart = multipart(options)
-    , _json = json(options);
-
-  return function bodyParser(req, res, next) {
-    _json(req, res, function(err){
-      if (err) return next(err);
-      _urlencoded(req, res, function(err){
-        if (err) return next(err);
-        _multipart(req, res, next);
-      });
-    });
-  }
-};
\ No newline at end of file
diff --git a/lib/middleware/compress.js b/lib/middleware/compress.js
deleted file mode 100644
index feda4b6..0000000
--- a/lib/middleware/compress.js
+++ /dev/null
@@ -1,142 +0,0 @@
-
-/*!
- * Connect - compress
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var zlib = require('zlib');
-
-/**
- * Supported content-encoding methods.
- */
-
-exports.methods = {
-    gzip: zlib.createGzip
-  , deflate: zlib.createDeflate
-};
-
-/**
- * Default filter function.
- */
-
-exports.filter = function(req, res){
-  var type = res.getHeader('Content-Type') || '';
-  return type.match(/json|text|javascript/);
-};
-
-/**
- * Compress:
- *
- * Compress response data with gzip/deflate.
- *
- * Filter:
- *
- *  A `filter` callback function may be passed to
- *  replace the default logic of:
- *
- *     exports.filter = function(req, res){
- *       var type = res.getHeader('Content-Type') || '';
- *       return type.match(/json|text|javascript/);
- *     };
- *
- * Options:
- *
- *  All remaining options are passed to the gzip/deflate
- *  creation functions. Consult node's docs for additional details.
- *
- *   - `chunkSize` (default: 16*1024)
- *   - `windowBits`
- *   - `level`: 0-9 where 0 is no compression, and 9 is slow but best compression
- *   - `memLevel`: 1-9 low is slower but uses less memory, high is fast but uses more
- *   - `strategy`: compression strategy
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-module.exports = function compress(options) {
-  var options = options || {}
-    , names = Object.keys(exports.methods)
-    , filter = options.filter || exports.filter;
-
-  return function(req, res, next){
-    var accept = req.headers['accept-encoding']
-      , write = res.write
-      , end = res.end
-      , stream
-      , method;
-
-    // vary
-    res.setHeader('Vary', 'Accept-Encoding');
-
-    // proxy
-
-    res.write = function(chunk, encoding){
-      if (!this.headerSent) this._implicitHeader();
-      return stream
-        ? stream.write(chunk, encoding)
-        : write.call(res, chunk, encoding);
-    };
-
-    res.end = function(chunk, encoding){
-      if (chunk) this.write(chunk, encoding);
-      return stream
-        ? stream.end()
-        : end.call(res);
-    };
-
-    res.on('header', function(){
-      // default request filter
-      if (!filter(req, res)) return;
-
-      // SHOULD use identity
-      if (!accept) return;
-
-      // head
-      if ('HEAD' == req.method) return;
-
-      // default to gzip
-      if ('*' == accept.trim()) method = 'gzip';
-
-      // compression method
-      if (!method) {
-        for (var i = 0, len = names.length; i < len; ++i) {
-          if (~accept.indexOf(names[i])) {
-            method = names[i];
-            break;
-          }
-        }
-      }
-
-      // compression method
-      if (!method) return;
-
-      // compression stream
-      stream = exports.methods[method](options);
-
-      // header fields
-      res.setHeader('Content-Encoding', method);
-      res.removeHeader('Content-Length');
-
-      // compression
-
-      stream.on('data', function(chunk){
-        write.call(res, chunk);
-      });
-
-      stream.on('end', function(){
-        end.call(res);
-      });
-      
-    });
-
-    next();
-  };
-}
diff --git a/lib/middleware/cookieParser.js b/lib/middleware/cookieParser.js
deleted file mode 100644
index ee01c23..0000000
--- a/lib/middleware/cookieParser.js
+++ /dev/null
@@ -1,62 +0,0 @@
-
-/*!
- * Connect - cookieParser
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('./../utils');
-
-/**
- * Cookie parser:
- *
- * Parse _Cookie_ header and populate `req.cookies`
- * with an object keyed by the cookie names. Optionally
- * you may enabled signed cookie support by passing
- * a `secret` string, which assigns `req.secret` so
- * it may be used by other middleware such as `session()`.
- *
- * Examples:
- *
- *     connect()
- *       .use(connect.cookieParser('keyboard cat'))
- *       .use(function(req, res, next){
- *         res.end(JSON.stringify(req.cookies));
- *       })
- *
- * @param {String} secret
- * @return {Function}
- * @api public
- */
-
-module.exports = function cookieParser(secret){
-  return function cookieParser(req, res, next) {
-    var cookie = req.headers.cookie;
-    if (req.cookies) return next();
-
-    req.secret = secret;
-    req.cookies = {};
-    req.signedCookies = {};
-    
-    if (cookie) {
-      try {
-        req.cookies = utils.parseCookie(cookie);
-        if (secret) {
-          req.signedCookies = utils.parseSignedCookies(req.cookies, secret);
-          var obj = utils.parseJSONCookies(req.signedCookies);
-          req.signedCookies = obj.cookies;
-          req.cookieHashes = obj.hashes;
-        }
-        req.cookies = utils.parseJSONCookies(req.cookies);
-      } catch (err) {
-        return next(err);
-      }
-    }
-    next();
-  };
-};
\ No newline at end of file
diff --git a/lib/middleware/cookieSession.js b/lib/middleware/cookieSession.js
deleted file mode 100644
index 3573287..0000000
--- a/lib/middleware/cookieSession.js
+++ /dev/null
@@ -1,91 +0,0 @@
-
-/*!
- * Connect - cookieSession
- * Copyright(c) 2011 Sencha Inc.
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('./../utils')
-  , Cookie = require('./session/cookie')
-  , debug = require('debug')('connect:cookieSession')
-  , crc16 = require('crc').crc16;
-
-// environment
-
-var env = process.env.NODE_ENV;
-
-/**
- * Cookie Session:
- *
- *   Cookie session middleware.
- *
- *      var app = connect();
- *      app.use(connect.cookieParser('tobo!'));
- *      app.use(connect.cookieSession({ cookie: { maxAge: 60 * 60 * 1000 }}));
- *
- * Options:
- *
- *   - `key` cookie name defaulting to `connect.sess`
- *   - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
- *   - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-module.exports = function cookieSession(options){
-  // TODO: utilize Session/Cookie to unify API
-  // TODO: only set-cookie on changes to the session data
-
-  var options = options || {}
-    , key = options.key || 'connect.sess'
-    , cookie = options.cookie
-    , trustProxy = options.proxy;
-
-  return function cookieSession(req, res, next) {
-    req.session = req.signedCookies[key] || {};
-    req.session.cookie = new Cookie(req, cookie);
-
-    res.on('header', function(){
-      // removed
-      if (!req.session) {
-        debug('clear session');
-        res.setHeader('Set-Cookie', key + '=; expires=' + new Date(0).toUTCString());
-        return;
-      }
-
-      var cookie = req.session.cookie;
-      delete req.session.cookie;
-
-      // check security
-      var proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
-        , tls = req.connection.encrypted || (trustProxy && 'https' == proto)
-        , secured = cookie.secure && tls;
-
-      // only send secure cookies via https
-      if (cookie.secure && !secured) return debug('not secured');
-
-      // serialize
-      debug('serializing %j', req.session);
-      var val = 'j:' + JSON.stringify(req.session);
-
-      // compare hashes
-      var originalHash = req.cookieHashes && req.cookieHashes[key];
-      var hash = crc16(val);
-      if (originalHash == hash) return debug('unmodified session');
-
-      // set-cookie
-      val = utils.sign(val, req.secret);
-      val = utils.serializeCookie(key, val, cookie);
-      debug('set-cookie %j', cookie);
-      res.setHeader('Set-Cookie', val);
-    });
-
-    next();
-  };
-};
diff --git a/lib/middleware/csrf.js b/lib/middleware/csrf.js
deleted file mode 100644
index d6ff98f..0000000
--- a/lib/middleware/csrf.js
+++ /dev/null
@@ -1,75 +0,0 @@
-
-/*!
- * Connect - csrf
- * Copyright(c) 2011 Sencha Inc.
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../utils')
-  , crypto = require('crypto');
-
-/**
- * Anti CSRF:
- *
- * CRSF protection middleware.
- *
- * By default this middleware generates a token named "_csrf"
- * which should be added to requests which mutate
- * state, within a hidden form field, query-string etc. This
- * token is validated against the visitor's `req.session._csrf`
- * property.
- *
- * The default `value` function checks `req.body` generated
- * by the `bodyParser()` middleware, `req.query` generated
- * by `query()`, and the "X-CSRF-Token" header field.
- *
- * This middleware requires session support, thus should be added
- * somewhere _below_ `session()` and `cookieParser()`.
- *
- * Options:
- *
- *    - `value` a function accepting the request, returning the token 
- *
- * @param {Object} options
- * @api public
- */
-
-module.exports = function csrf(options) {
-  var options = options || {}
-    , value = options.value || defaultValue;
-
-  return function(req, res, next){
-    // generate CSRF token
-    var token = req.session._csrf || (req.session._csrf = utils.uid(24));
-
-    // ignore GET & HEAD (for now)
-    if ('GET' == req.method || 'HEAD' == req.method) return next();
-
-    // determine value
-    var val = value(req);
-
-    // check
-    if (val != token) return next(utils.error(403));
-    
-    next();
-  }
-};
-
-/**
- * Default value function, checking the `req.body`
- * and `req.query` for the CSRF token.
- *
- * @param {IncomingMessage} req
- * @return {String}
- * @api private
- */
-
-function defaultValue(req) {
-  return (req.body && req.body._csrf)
-    || (req.query && req.query._csrf)
-    || (req.headers['x-csrf-token']);
-}
\ No newline at end of file
diff --git a/lib/middleware/directory.js b/lib/middleware/directory.js
deleted file mode 100644
index 7dcfb3c..0000000
--- a/lib/middleware/directory.js
+++ /dev/null
@@ -1,227 +0,0 @@
-
-/*!
- * Connect - directory
- * Copyright(c) 2011 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-// TODO: icon / style for directories
-// TODO: arrow key navigation
-// TODO: make icons extensible
-
-/**
- * Module dependencies.
- */
-
-var fs = require('fs')
-  , parse = require('url').parse
-  , utils = require('../utils')
-  , path = require('path')
-  , normalize = path.normalize
-  , extname = path.extname
-  , join = path.join;
-
-/*!
- * Icon cache.
- */
-
-var cache = {};
-
-/**
- * Directory:
- *
- * Serve directory listings with the given `root` path.
- *
- * Options:
- *
- *  - `hidden` display hidden (dot) files. Defaults to false.
- *  - `icons`  display icons. Defaults to false.
- *  - `filter` Apply this filter function to files. Defaults to false.
- *
- * @param {String} root
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function directory(root, options){
-  options = options || {};
-
-  // root required
-  if (!root) throw new Error('directory() root path required');
-  var hidden = options.hidden
-    , icons = options.icons
-    , filter = options.filter
-    , root = normalize(root);
-
-  return function directory(req, res, next) {
-    var accept = req.headers.accept || 'text/plain'
-      , url = parse(req.url)
-      , dir = decodeURIComponent(url.pathname)
-      , path = normalize(join(root, dir))
-      , originalUrl = parse(req.originalUrl)
-      , originalDir = decodeURIComponent(originalUrl.pathname)
-      , showUp = path != root && path != root + '/';
-
-    // null byte(s), bad request
-    if (~path.indexOf('\0')) return next(utils.error(400));
-
-    // malicious path, forbidden
-    if (0 != path.indexOf(root)) return next(utils.error(403));
-
-    // check if we have a directory
-    fs.stat(path, function(err, stat){
-      if (err) return 'ENOENT' == err.code
-        ? next()
-        : next(err);
-
-      if (!stat.isDirectory()) return next();
-
-      // fetch files
-      fs.readdir(path, function(err, files){
-        if (err) return next(err);
-        if (!hidden) files = removeHidden(files);
-        if (filter) files = files.filter(filter);
-        files.sort();
-
-        // content-negotiation
-        for (var key in exports) {
-          if (~accept.indexOf(key) || ~accept.indexOf('*/*')) {
-            exports[key](req, res, files, next, originalDir, showUp, icons);
-            return;
-          }
-        }
-
-        // not acceptable
-        next(utils.error(406));
-      });
-    });
-  };
-};
-
-/**
- * Respond with text/html.
- */
-
-exports.html = function(req, res, files, next, dir, showUp, icons){
-  fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){
-    if (err) return next(err);
-    fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){
-      if (err) return next(err);
-      if (showUp) files.unshift('..');
-      str = str
-        .replace('{style}', style)
-        .replace('{files}', html(files, dir, icons))
-        .replace('{directory}', dir)
-        .replace('{linked-path}', htmlPath(dir));
-      res.setHeader('Content-Type', 'text/html');
-      res.setHeader('Content-Length', str.length);
-      res.end(str);
-    });
-  });
-};
-
-/**
- * Respond with application/json.
- */
-
-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);
-};
-
-/**
- * Respond with text/plain.
- */
-
-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);
-};
-
-/**
- * Map html `dir`, returning a linked path.
- */
-
-function htmlPath(dir) {
-  var curr = [];
-  return dir.split('/').map(function(part){
-    curr.push(part);
-    return '<a href="' + curr.join('/') + '">' + part + '</a>';
-  }).join(' / ');
-}
-
-/**
- * Map html `files`, returning an html unordered list.
- */
-
-function html(files, dir, useIcons) {
-  return '<ul id="files">' + files.map(function(file){
-    var icon = ''
-      , classes = [];
-
-    if (useIcons && '..' != file) {
-      icon = icons[extname(file)] || icons.default;
-      icon = '<img src="data:image/png;base64,' + load(icon) + '" />';
-      classes.push('icon');
-    }
-
-    return '<li><a href="'
-      + join(dir, file)
-      + '" class="'
-      + classes.join(' ') + '"'
-      + ' title="' + file + '">'
-      + icon + file + '</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');
-}
-
-/**
- * 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];
-  });
-}
-
-/**
- * Icon map.
- */
-
-var icons = {
-    '.js': 'page_white_code_red.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'
-  , '.cpp': 'page_white_cplusplus.png'
-  , '.swf': 'page_white_flash.png'
-  , '.pdf': 'page_white_acrobat.png'
-  , 'default': 'page_white.png'
-};
diff --git a/lib/middleware/errorHandler.js b/lib/middleware/errorHandler.js
deleted file mode 100644
index b62aab7..0000000
--- a/lib/middleware/errorHandler.js
+++ /dev/null
@@ -1,87 +0,0 @@
-/*!
- * Connect - errorHandler
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../utils')
-  , url = require('url')
-  , fs = require('fs');
-
-// environment
-
-var env = process.env.NODE_ENV || 'development';
-
-/**
- * Error handler:
- *
- * Development error handler, providing stack traces
- * and error message responses for requests accepting text, html,
- * or json.
- *
- * Text:
- *
- *   By default, and when _text/plain_ is accepted a simple stack trace
- *   or error message will be returned.
- *
- * JSON:
- *
- *   When _application/json_ is accepted, connect will respond with
- *   an object in the form of `{ "error": error }`.
- *
- * HTML:
- *
- *   When accepted connect will output a nice html stack trace.
- *
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function errorHandler(){
-  return function errorHandler(err, req, res, next){
-    if (err.status) res.statusCode = err.status;
-    if (res.statusCode < 400) res.statusCode = 500;
-    if ('test' != env) console.error(err.stack);
-    var accept = req.headers.accept || '';
-    // html
-    if (~accept.indexOf('html')) {
-      fs.readFile(__dirname + '/../public/style.css', 'utf8', function(e, style){
-        fs.readFile(__dirname + '/../public/error.html', 'utf8', function(e, html){
-          var stack = (err.stack || '')
-            .split('\n').slice(1)
-            .map(function(v){ return '<li>' + v + '</li>'; }).join('');
-            html = html
-              .replace('{style}', style)
-              .replace('{stack}', stack)
-              .replace('{title}', exports.title)
-              .replace('{statusCode}', res.statusCode)
-              .replace(/\{error\}/g, utils.escape(err.toString()));
-            res.setHeader('Content-Type', 'text/html; charset=utf-8');
-            res.end(html);
-        });
-      });
-    // json
-    } else if (~accept.indexOf('json')) {
-      var error = { message: err.message, stack: err.stack };
-      for (var prop in err) error[prop] = err[prop];
-      var json = JSON.stringify({ error: error });
-      res.setHeader('Content-Type', 'application/json');
-      res.end(json);
-    // plain text
-    } else {
-      res.writeHead(res.statusCode, { 'Content-Type': 'text/plain' });
-      res.end(err.stack);
-    }
-  };
-};
-
-/**
- * Template title, framework authors may override this value.
- */
-
-exports.title = 'Connect';
diff --git a/lib/middleware/favicon.js b/lib/middleware/favicon.js
deleted file mode 100644
index b344218..0000000
--- a/lib/middleware/favicon.js
+++ /dev/null
@@ -1,86 +0,0 @@
-
-/*!
- * Connect - favicon
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var fs = require('fs')
-  , utils = require('../utils');
-
-/*!
- * Favicon cache.
- */
-
-var icon;
-
-/**
- * Favicon:
- *
- * By default serves the connect favicon, or the favicon
- * located by the given `path`.
- *
- * Options:
- *
- *   - `maxAge`  cache-control max-age directive, defaulting to 1 day
- *
- * Examples:
- *
- *   Serve default favicon:
- *
- *     connect()
- *       .use(connect.favicon())
- *
- *   Serve favicon before logging for brevity:
- *
- *     connect()
- *       .use(connect.favicon())
- *       .use(connect.logger('dev'))
- *
- *   Serve custom favicon:
- *
- *     connect()
- *       .use(connect.favicon('public/favicon.ico))
- *
- * @param {String} path
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-module.exports = function favicon(path, options){
-  var options = options || {}
-    , path = path || __dirname + '/../public/favicon.ico'
-    , maxAge = options.maxAge || 86400000;
-
-  return function favicon(req, res, next){
-    if ('/favicon.ico' == req.url) {
-      if (icon) {
-        res.writeHead(200, icon.headers);
-        res.end(icon.body);
-      } else {
-        fs.readFile(path, function(err, buf){
-          if (err) return next(err);
-          icon = {
-            headers: {
-                'Content-Type': 'image/x-icon'
-              , 'Content-Length': buf.length
-              , 'ETag': '"' + utils.md5(buf) + '"'
-              , 'Cache-Control': 'public, max-age=' + (maxAge / 1000)
-            },
-            body: buf
-          };
-          res.writeHead(200, icon.headers);
-          res.end(icon.body);
-        });
-      }
-    } else {
-      next();
-    }
-  };
-};
\ No newline at end of file
diff --git a/lib/middleware/json.js b/lib/middleware/json.js
deleted file mode 100644
index 1d30244..0000000
--- a/lib/middleware/json.js
+++ /dev/null
@@ -1,56 +0,0 @@
-
-/*!
- * Connect - json
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../utils');
-
-/**
- * JSON:
- *
- * Parse JSON request bodies, providing the
- * parsed object as `req.body`.
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function(options){
-  options = options || {};
-  return function json(req, res, next) {
-    if (req._body) return next();
-    req.body = req.body || {};
-
-    // ignore GET
-    if ('GET' == req.method || 'HEAD' == req.method) return next();
-
-    // check Content-Type
-    if ('application/json' != utils.mime(req)) return next();
-
-    // flag as parsed
-    req._body = true;
-
-    // parse
-    var buf = '';
-    req.setEncoding('utf8');
-    req.on('data', function(chunk){ buf += chunk });
-    req.on('end', function(){
-      if ('{' != buf[0] && '[' != buf[0]) return next(utils.error(400));
-      try {
-        req.body = JSON.parse(buf);
-        next();
-      } catch (err){
-        err.status = 400;
-        next(err);
-      }
-    });
-  }
-};
\ No newline at end of file
diff --git a/lib/middleware/limit.js b/lib/middleware/limit.js
deleted file mode 100644
index d0b9d9b..0000000
--- a/lib/middleware/limit.js
+++ /dev/null
@@ -1,77 +0,0 @@
-
-/*!
- * Connect - limit
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../utils');
-
-/**
- * Limit:
- *
- *   Limit request bodies to the given size in `bytes`.
- *
- *   A string representation of the bytesize may also be passed,
- *   for example "5mb", "200kb", "1gb", etc.
- *
- *     connect()
- *       .use(connect.limit('5.5mb'))
- *       .use(handleImageUpload)
- *
- * @param {Number|String} bytes
- * @return {Function}
- * @api public
- */
-
-module.exports = function limit(bytes){
-  if ('string' == typeof bytes) bytes = parse(bytes);
-  if ('number' != typeof bytes) throw new Error('limit() bytes required');
-  return function limit(req, res, next){
-    var received = 0
-      , len = req.headers['content-length']
-        ? parseInt(req.headers['content-length'], 10)
-        : null;
-
-    // self-awareness
-    if (req._limit) return next();
-    req._limit = true;
-
-    // limit by content-length
-    if (len && len > bytes) return next(utils.error(413));
-
-    // limit
-    req.on('data', function(chunk){
-      received += chunk.length;
-      if (received > bytes) req.destroy();
-    });
-
-    next();
-  };
-};
-
-/**
- * Parse byte `size` string.
- *
- * @param {String} size
- * @return {Number}
- * @api private
- */
-
-function parse(size) {
-  var parts = size.match(/^(\d+(?:\.\d+)?) *(kb|mb|gb)$/)
-    , n = parseFloat(parts[1])
-    , type = parts[2];
-
-  var map = {
-      kb: 1024
-    , mb: 1024 * 1024
-    , gb: 1024 * 1024 * 1024
-  };
-
-  return map[type] * n;
-}
\ No newline at end of file
diff --git a/lib/middleware/logger.js b/lib/middleware/logger.js
deleted file mode 100644
index 816f637..0000000
--- a/lib/middleware/logger.js
+++ /dev/null
@@ -1,331 +0,0 @@
-
-/*!
- * Connect - logger
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/*!
- * Log buffer.
- */
-
-var buf = [];
-
-/*!
- * Default log buffer duration.
- */
-
-var defaultBufferDuration = 1000;
-
-/**
- * Logger:
- *
- * Log requests with the given `options` or a `format` string.
- *
- * Options:
- *
- *   - `format`  Format string, see below for tokens
- *   - `stream`  Output stream, defaults to _stdout_
- *   - `buffer`  Buffer duration, defaults to 1000ms when _true_
- *   - `immediate`  Write log line on request instead of response (for response times)
- *
- * Tokens:
- *
- *   - `:req[header]` ex: `:req[Accept]`
- *   - `:res[header]` ex: `:res[Content-Length]`
- *   - `:http-version`
- *   - `:response-time`
- *   - `:remote-addr`
- *   - `:date`
- *   - `:method`
- *   - `:url`
- *   - `:referrer`
- *   - `:user-agent`
- *   - `:status`
- *
- * Formats:
- *
- *   Pre-defined formats that ship with connect:
- *
- *    - `default` ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"'
- *    - `short` ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms'
- *    - `tiny`  ':method :url :status :res[content-length] - :response-time ms'
- *    - `dev` concise output colored by response status for development use
- *
- * Examples:
- *
- *      connect.logger() // default
- *      connect.logger('short')
- *      connect.logger('tiny')
- *      connect.logger({ immediate: true, format: 'dev' })
- *      connect.logger(':method :url - :referrer')
- *      connect.logger(':req[content-type] -> :res[content-type]')
- *      connect.logger(function(req, res){ return 'some format string' })
- *
- * Defining Tokens:
- *
- *   To define a token, simply invoke `connect.logger.token()` with the
- *   name and a callback function. The value returned is then available
- *   as ":type" in this case.
- *
- *      connect.logger.token('type', function(req, res){ return req.headers['content-type']; })
- *
- * Defining Formats:
- *
- *   All default formats are defined this way, however it's public API as well:
- *
- *       connect.logger.format('name', 'string or function')
- *
- * @param {String|Function|Object} format or options
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function logger(options) {
-  if ('object' == typeof options) {
-    options = options || {};
-  } else if (options) {
-    options = { format: options };
-  } else {
-    options = {};
-  }
-
-  // output on request instead of response
-  var immediate = options.immediate;
-
-  // format name
-  var fmt = exports[options.format] || options.format || exports.default;
-
-  // compile format
-  if ('function' != typeof fmt) fmt = compile(fmt);
-
-  // options
-  var stream = options.stream || process.stdout
-    , buffer = options.buffer;
-
-  // buffering support
-  if (buffer) {
-    var realStream = stream
-      , interval = 'number' == typeof buffer
-        ? buffer
-        : defaultBufferDuration;
-
-    // flush interval
-    setInterval(function(){
-      if (buf.length) {
-        realStream.write(buf.join(''), 'ascii');
-        buf.length = 0;
-      }
-    }, interval); 
-
-    // swap the stream
-    stream = {
-      write: function(str){
-        buf.push(str);
-      }
-    };
-  }
-
-  return function logger(req, res, next) {
-    req._startTime = new Date;
-
-    // mount safety
-    if (req._logging) return next();
-
-    // flag as logging
-    req._logging = true;
-
-    // immediate
-    if (immediate) {
-      var line = fmt(exports, req, res);
-      if (null == line) return;
-      stream.write(line + '\n', 'ascii');
-    // proxy end to output logging
-    } else {
-      var end = res.end;
-      res.end = function(chunk, encoding){
-        res.end = end;
-        res.end(chunk, encoding);
-        var line = fmt(exports, req, res);
-        if (null == line) return;
-        stream.write(line + '\n', 'ascii');
-      };
-    }
-
-
-    next();
-  };
-};
-
-/**
- * Compile `fmt` into a function.
- *
- * @param {String} fmt
- * @return {Function}
- * @api private
- */
-
-function compile(fmt) {
-  fmt = fmt.replace(/"/g, '\\"');
-  var js = '  return "' + fmt.replace(/:([-\w]{2,})(?:\[([^\]]+)\])?/g, function(_, name, arg){
-    return '"\n    + (tokens["' + name + '"](req, res, "' + arg + '") || "-") + "';
-  }) + '";'
-  return new Function('tokens, req, res', js);
-};
-
-/**
- * Define a token function with the given `name`,
- * and callback `fn(req, res)`.
- *
- * @param {String} name
- * @param {Function} fn
- * @return {Object} exports for chaining
- * @api public
- */
-
-exports.token = function(name, fn) {
-  exports[name] = fn;
-  return this;
-};
-
-/**
- * Define a `fmt` with the given `name`.
- *
- * @param {String} name
- * @param {String|Function} fmt
- * @return {Object} exports for chaining
- * @api public
- */
-
-exports.format = function(name, str){
-  exports[name] = str;
-  return this;
-};
-
-/**
- * Default format.
- */
-
-exports.format('default', ':remote-addr - - [:date] ":method :url HTTP/:http-version" :status :res[content-length] ":referrer" ":user-agent"');
-
-/**
- * Short format.
- */
-
-exports.format('short', ':remote-addr - :method :url HTTP/:http-version :status :res[content-length] - :response-time ms');
-
-/**
- * Tiny format.
- */
-
-exports.format('tiny', ':method :url :status :res[content-length] - :response-time ms');
-
-/**
- * dev (colored)
- */
-
-exports.format('dev', function(tokens, req, res){
-  var status = res.statusCode
-    , color = 32;
-
-  if (status >= 500) color = 31
-  else if (status >= 400) color = 33
-  else if (status >= 300) color = 36;
-
-  return '\033[90m' + req.method
-    + ' ' + req.originalUrl + ' '
-    + '\033[' + color + 'm' + res.statusCode
-    + ' \033[90m'
-    + (new Date - req._startTime)
-    + 'ms\033[0m';
-});
-
-/**
- * request url
- */
-
-exports.token('url', function(req){
-  return req.originalUrl;
-});
-
-/**
- * request method
- */
-
-exports.token('method', function(req){
-  return req.method;
-});
-
-/**
- * response time in milliseconds
- */
-
-exports.token('response-time', function(req){
-  return new Date - req._startTime;
-});
-
-/**
- * UTC date
- */
-
-exports.token('date', function(){
-  return new Date().toUTCString();
-});
-
-/**
- * response status code
- */
-
-exports.token('status', function(req, res){
-  return res.statusCode;
-});
-
-/**
- * normalized referrer
- */
-
-exports.token('referrer', function(req){
-  return req.headers['referer'] || req.headers['referrer'];
-});
-
-/**
- * remote address
- */
-
-exports.token('remote-addr', function(req){
-  return req.socket && (req.socket.remoteAddress || (req.socket.socket && req.socket.socket.remoteAddress));
-});
-
-/**
- * HTTP version
- */
-
-exports.token('http-version', function(req){
-  return req.httpVersionMajor + '.' + req.httpVersionMinor;
-});
-
-/**
- * UA string
- */
-
-exports.token('user-agent', function(req){
-  return req.headers['user-agent'];
-});
-
-/**
- * request header
- */
-
-exports.token('req', function(req, res, field){
-  return req.headers[field.toLowerCase()];
-});
-
-/**
- * response header
- */
-
-exports.token('res', function(req, res, field){
-  return (res._headers || {})[field.toLowerCase()];
-});
-
diff --git a/lib/middleware/methodOverride.js b/lib/middleware/methodOverride.js
deleted file mode 100644
index aaf4014..0000000
--- a/lib/middleware/methodOverride.js
+++ /dev/null
@@ -1,40 +0,0 @@
-
-/*!
- * Connect - methodOverride
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Method Override:
- * 
- * Provides faux HTTP method support.
- * 
- * Pass an optional `key` to use when checking for
- * a method override, othewise defaults to _\_method_.
- * The original method is available via `req.originalMethod`.
- *
- * @param {String} key
- * @return {Function}
- * @api public
- */
-
-module.exports = function methodOverride(key){
-  key = key || "_method";
-  return function methodOverride(req, res, next) {
-    req.originalMethod = req.originalMethod || req.method;
-
-    // req.body
-    if (req.body && key in req.body) {
-      req.method = req.body[key].toUpperCase();
-      delete req.body[key];
-    // check X-HTTP-Method-Override
-    } else if (req.headers['x-http-method-override']) {
-      req.method = req.headers['x-http-method-override'].toUpperCase();
-    }
-    
-    next();
-  };
-};
-
diff --git a/lib/middleware/multipart.js b/lib/middleware/multipart.js
deleted file mode 100644
index f48e4da..0000000
--- a/lib/middleware/multipart.js
+++ /dev/null
@@ -1,99 +0,0 @@
-
-/*!
- * Connect - multipart
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var formidable = require('formidable')
-  , utils = require('../utils')
-  , qs = require('qs');
-
-/**
- * Multipart:
- * 
- * Parse multipart/form-data request bodies,
- * providing the parsed object as `req.body`
- * and `req.files`.
- *
- * Configuration:
- *
- *  The options passed are merged with [formidable](https://github.com/felixge/node-formidable)'s
- *  `IncomingForm` object, allowing you to configure the upload directory,
- *  size limits, etc. For example if you wish to change the upload dir do the following.
- *
- *     app.use(connect.multipart({ uploadDir: path }));
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function(options){
-  options = options || {};
-  return function multipart(req, res, next) {
-    if (req._body) return next();
-    req.body = req.body || {};
-    req.files = req.files || {};
-
-    // ignore GET
-    if ('GET' == req.method || 'HEAD' == req.method) return next();
-
-    // check Content-Type
-    if ('multipart/form-data' != utils.mime(req)) return next();
-
-    // flag as parsed
-    req._body = true;
-
-    // parse
-    var form = new formidable.IncomingForm
-      , data = {}
-      , files = {}
-      , done;
-
-    Object.keys(options).forEach(function(key){
-      form[key] = options[key];
-    });
-
-    function ondata(name, val, data){
-      if (Array.isArray(data[name])) {
-        data[name].push(val);
-      } else if (data[name]) {
-        data[name] = [data[name], val];
-      } else {
-        data[name] = val;
-      }
-    }
-
-    form.on('field', function(name, val){
-      ondata(name, val, data);
-    });
-
-    form.on('file', function(name, val){
-      ondata(name, val, files);
-    });
-
-    form.on('error', function(err){
-      next(err);
-      done = true;
-    });
-
-    form.on('end', function(){
-      if (done) return;
-      try {
-        req.body = qs.parse(data);
-        req.files = qs.parse(files);
-        next();
-      } catch (err) {
-        next(err);
-      }
-    });
-
-    form.parse(req);
-  }
-};
diff --git a/lib/middleware/query.js b/lib/middleware/query.js
deleted file mode 100644
index 604b768..0000000
--- a/lib/middleware/query.js
+++ /dev/null
@@ -1,44 +0,0 @@
-
-/*!
- * Connect - query
- * Copyright(c) 2011 TJ Holowaychuk
- * Copyright(c) 2011 Sencha Inc.
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var qs = require('qs')
-  , parse = require('url').parse;
-
-/**
- * Query:
- *
- * Automatically parse the query-string when available,
- * populating the `req.query` object.
- *
- * Examples:
- *
- *     connect()
- *       .use(connect.query())
- *       .use(function(req, res){
- *         res.end(JSON.stringify(req.query));
- *       });
- *
- *  The `options` passed are provided to qs.parse function.
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-module.exports = function query(options){
-  return function query(req, res, next){
-    req.query = ~req.url.indexOf('?')
-      ? qs.parse(parse(req.url).query, options)
-      : {};
-    next();
-  };
-};
diff --git a/lib/middleware/responseTime.js b/lib/middleware/responseTime.js
deleted file mode 100644
index 57858f6..0000000
--- a/lib/middleware/responseTime.js
+++ /dev/null
@@ -1,32 +0,0 @@
-
-/*!
- * Connect - responseTime
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Reponse time:
- *
- * Adds the `X-Response-Time` header displaying the response
- * duration in milliseconds.
- *
- * @return {Function}
- * @api public
- */
-
-module.exports = function responseTime(){
-  return function(req, res, next){
-    var start = new Date;
-
-    if (res._responseTime) return next();
-    res._responseTime = true;
-
-    res.on('header', function(header){
-      var duration = new Date - start;
-      res.setHeader('X-Response-time', duration + 'ms');
-    });
-
-    next();
-  };
-};
diff --git a/lib/middleware/session.js b/lib/middleware/session.js
deleted file mode 100644
index 0d18126..0000000
--- a/lib/middleware/session.js
+++ /dev/null
@@ -1,302 +0,0 @@
-
-/*!
- * Connect - session
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var Session = require('./session/session')
-  , debug = require('debug')('connect:session')
-  , MemoryStore = require('./session/memory')
-  , Cookie = require('./session/cookie')
-  , Store = require('./session/store')
-  , utils = require('./../utils')
-  , parse = require('url').parse
-  , crypto = require('crypto');
-
-// environment
-
-var env = process.env.NODE_ENV;
-
-/**
- * Expose the middleware.
- */
-
-exports = module.exports = session;
-
-/**
- * Expose constructors.
- */
-
-exports.Store = Store;
-exports.Cookie = Cookie;
-exports.Session = Session;
-exports.MemoryStore = MemoryStore;
-
-/**
- * Warning message for `MemoryStore` usage in production.
- */
-
-var warning = 'Warning: connection.session() MemoryStore is not\n'
-  + 'designed for a production environment, as it will leak\n'
-  + 'memory, and obviously only work within a single process.';
-
-/**
- * Session:
- * 
- *   Setup session store with the given `options`.
- *
- *   Session data is _not_ saved in the cookie itself, however
- *   cookies are used, so we must use the [cookieParser()](cookieParser.html)
- *   middleware _before_ `session()`.
- *
- * Examples:
- *
- *     connect()
- *       .use(connect.cookieParser('keyboard cat'))
- *       .use(connect.session({ key: 'sid', cookie: { secure: true }}))
- *
- * Options:
- *
- *   - `key` cookie name defaulting to `connect.sid`
- *   - `store` session store instance
- *   - `cookie` session cookie settings, defaulting to `{ path: '/', httpOnly: true, maxAge: null }`
- *   - `proxy` trust the reverse proxy when setting secure cookies (via "x-forwarded-proto")
- *
- * Cookie option:
- *
- *  By default `cookie.maxAge` is `null`, meaning no "expires" parameter is set
- *  so the cookie becomes a browser-session cookie. When the user closes the 
- *  browser the cookie (and session) will be removed.
- *
- * ## req.session
- *
- *  To store or access session data, simply use the request property `req.session`,
- *  which is (generally) serialized as JSON by the store, so nested objects 
- *  are typically fine. For example below is a user-specific view counter:
- *
- *       connect()
- *         .use(connect.favicon())
- *         .use(connect.cookieParser('keyboard cat'))
- *         .use(connect.session({ cookie: { maxAge: 60000 }}))
- *         .use(function(req, res, next){
- *           var sess = req.session;
- *           if (sess.views) {
- *             res.setHeader('Content-Type', 'text/html');
- *             res.write('<p>views: ' + sess.views + '</p>');
- *             res.write('<p>expires in: ' + (sess.cookie.maxAge / 1000) + 's</p>');
- *             res.end();
- *             sess.views++;
- *           } else {
- *             sess.views = 1;
- *             res.end('welcome to the session demo. refresh!');
- *           }
- *         }
- *       )).listen(3000);
- *
- * ## Session#regenerate()
- *
- *  To regenerate the session simply invoke the method, once complete
- *  a new SID and `Session` instance will be initialized at `req.session`.
- *
- *      req.session.regenerate(function(err){
- *        // will have a new session here
- *      });
- *
- * ## Session#destroy()
- *
- *  Destroys the session, removing `req.session`, will be re-generated next request.
- *
- *      req.session.destroy(function(err){
- *        // cannot access session here
- *      });
- * 
- * ## Session#reload()
- *
- *  Reloads the session data.
- *
- *      req.session.reload(function(err){
- *        // session updated
- *      });
- *
- * ## Session#save()
- *
- *  Save the session.
- *
- *      req.session.save(function(err){
- *        // session saved
- *      });
- *
- * ## Session#touch()
- *
- *   Updates the `.maxAge`, and `.lastAccess` properties. Typically this is
- *   not necessary to call, as the session middleware does this for you.
- *
- * ## Session#cookie
- *
- *  Each session has a unique cookie object accompany it. This allows
- *  you to alter the session cookie per visitor. For example we can
- *  set `req.session.cookie.expires` to `false` to enable the cookie
- *  to remain for only the duration of the user-agent.
- *
- * ## Session#maxAge
- *
- *  Alternatively `req.session.cookie.maxAge` will return the time
- *  remaining in milliseconds, which we may also re-assign a new value
- *  to adjust the `.expires` property appropriately. The following
- *  are essentially equivalent
- *
- *     var hour = 3600000;
- *     req.session.cookie.expires = new Date(Date.now() + hour);
- *     req.session.cookie.maxAge = hour;
- *
- * For example when `maxAge` is set to `60000` (one minute), and 30 seconds
- * has elapsed it will return `30000` until the current request has completed,
- * at which time `req.session.touch()` is called to update `req.session.lastAccess`,
- * and reset `req.session.maxAge` to its original value.
- *
- *     req.session.cookie.maxAge;
- *     // => 30000
- *
- * Session Store Implementation:
- *
- * Every session store _must_ implement the following methods
- *
- *    - `.get(sid, callback)`
- *    - `.set(sid, session, callback)`
- *    - `.destroy(sid, callback)`
- *
- * Recommended methods include, but are not limited to:
- *
- *    - `.length(callback)`
- *    - `.clear(callback)`
- *
- * For an example implementation view the [connect-redis](http://github.com/visionmedia/connect-redis) repo.
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-function session(options){
-  var options = options || {}
-    , key = options.key || 'connect.sid'
-    , store = options.store || new MemoryStore
-    , cookie = options.cookie
-    , trustProxy = options.proxy;
-
-  // notify user that this store is not
-  // meant for a production environment
-  if ('production' == env && store instanceof MemoryStore) {
-    console.warn(warning);
-  }
-
-  // generates the new session
-  store.generate = function(req){
-    req.sessionID = utils.uid(24);
-    req.session = new Session(req);
-    req.session.cookie = new Cookie(req, cookie);
-  };
-
-  return function session(req, res, next) {
-    // self-awareness
-    if (req.session) return next();
-
-    // ensure secret is available or bail
-    if (!req.secret) throw new Error('connect.cookieParser("secret") required for security when using sessions');
-
-    // parse url
-    var url = parse(req.url)
-      , path = url.pathname
-      , sessionIsNew;
-
-    // expose store
-    req.sessionStore = store;
-
-    // set-cookie
-    res.on('header', function(){
-      if (!req.session) return;
-      var cookie = req.session.cookie
-        , proto = (req.headers['x-forwarded-proto'] || '').toLowerCase()
-        , tls = req.connection.encrypted || (trustProxy && 'https' == proto)
-        , secured = cookie.secure && tls;
-
-      // browser-session cookies only set-cookie once
-      if (null == cookie.expires && !sessionIsNew) return;
-
-      // only send secure cookies via https
-      if (cookie.secure && !secured) return debug('not secured');
-
-      debug('set %s to %s', key, req.sessionID);
-      res.setHeader('Set-Cookie', cookie.serialize(key, req.sessionID));
-    });
-
-    // proxy end() to commit the session
-    var end = res.end;
-    res.end = function(data, encoding){
-      res.end = end;
-      if (!req.session) return res.end(data, encoding);
-      debug('saving');
-      req.session.resetMaxAge();
-      req.session.save(function(){
-        debug('saved');
-        res.end(data, encoding);
-      });
-    };
-
-    // generate the session
-    function generate() {
-      sessionIsNew = true;
-      store.generate(req);
-    }
-
-    // get the sessionID from the cookie
-    req.sessionID = req.signedCookies[key];
-
-    // generate a session if the browser doesn't send a sessionID
-    if (!req.sessionID) {
-      debug('no SID sent, generating session');
-      generate();
-      next();
-      return;
-    }
-
-    // generate the session object
-    var pause = utils.pause(req);
-    debug('fetching %s', req.sessionID);
-    store.get(req.sessionID, function(err, sess){
-      // proxy to resume() events
-      var _next = next;
-      next = function(err){
-        _next(err);
-        pause.resume();
-      }
-
-      // error handling
-      if (err) {
-        debug('error');
-        if ('ENOENT' == err.code) {
-          generate();
-          next();
-        } else {
-          next(err);
-        }
-      // no session
-      } else if (!sess) {
-        debug('no session found');
-        generate();
-        next();
-      // populate req.session
-      } else {
-        debug('session found');
-        store.createSession(req, sess);
-        next();
-      }
-    });
-  };
-};
diff --git a/lib/middleware/session/cookie.js b/lib/middleware/session/cookie.js
deleted file mode 100644
index 5e7f4c3..0000000
--- a/lib/middleware/session/cookie.js
+++ /dev/null
@@ -1,129 +0,0 @@
-
-/*!
- * Connect - session - Cookie
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../../utils');
-
-/**
- * Initialize a new `Cookie` with the given `options`.
- *
- * @param {IncomingMessage} req
- * @param {Object} options
- * @api private
- */
-
-var Cookie = module.exports = function Cookie(req, options) {
-  this.path = '/';
-  this.maxAge = null;
-  this.httpOnly = true;
-  if (options) utils.merge(this, options);
-  Object.defineProperty(this, 'req', { value: req });
-  this.originalMaxAge = undefined == this.originalMaxAge
-    ? this.maxAge
-    : this.originalMaxAge;
-};
-
-/*!
- * Prototype.
- */
-
-Cookie.prototype = {
-
-  /**
-   * Set expires `date`.
-   *
-   * @param {Date} date
-   * @api public
-   */
-  
-  set expires(date) {
-    this._expires = date;
-    this.originalMaxAge = this.maxAge;
-  },
-
-  /**
-   * Get expires `date`.
-   *
-   * @return {Date}
-   * @api public
-   */
-
-  get expires() {
-    return this._expires;
-  },
-  
-  /**
-   * Set expires via max-age in `ms`.
-   *
-   * @param {Number} ms
-   * @api public
-   */
-  
-  set maxAge(ms) {
-    this.expires = 'number' == typeof ms
-      ? new Date(Date.now() + ms)
-      : ms;
-  },
-
-  /**
-   * Get expires max-age in `ms`.
-   *
-   * @return {Number}
-   * @api public
-   */
-
-  get maxAge() {
-    return this.expires instanceof Date
-      ? this.expires.valueOf() - Date.now()
-      : this.expires;
-  },
-
-  /**
-   * Return cookie data object.
-   *
-   * @return {Object}
-   * @api private
-   */
-
-  get data() {
-    return {
-        originalMaxAge: this.originalMaxAge
-      , expires: this._expires
-      , secure: this.secure
-      , httpOnly: this.httpOnly
-      , domain: this.domain
-      , path: this.path
-    }
-  },
-
-  /**
-   * Return a serialized cookie string.
-   *
-   * @return {String}
-   * @api public
-   */
-
-  serialize: function(name, val){
-    val = utils.sign(val, this.req.secret);
-    return utils.serializeCookie(name, val, this.data);
-  },
-
-  /**
-   * Return JSON representation of this cookie.
-   *
-   * @return {Object}
-   * @api private
-   */
-  
-  toJSON: function(){
-    return this.data;
-  }
-};
diff --git a/lib/middleware/session/memory.js b/lib/middleware/session/memory.js
deleted file mode 100644
index ec569f5..0000000
--- a/lib/middleware/session/memory.js
+++ /dev/null
@@ -1,131 +0,0 @@
-
-/*!
- * Connect - session - MemoryStore
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var Store = require('./store')
-  , utils = require('../../utils')
-  , Session = require('./session');
-
-/**
- * Initialize a new `MemoryStore`.
- *
- * @api public
- */
-
-var MemoryStore = module.exports = function MemoryStore() {
-  this.sessions = {};
-};
-
-/**
- * Inherit from `Store.prototype`.
- */
-
-MemoryStore.prototype.__proto__ = Store.prototype;
-
-/**
- * Attempt to fetch session by the given `sid`.
- *
- * @param {String} sid
- * @param {Function} fn
- * @api public
- */
-
-MemoryStore.prototype.get = function(sid, fn){
-  var self = this;
-  process.nextTick(function(){
-    var expires
-      , sess = self.sessions[sid];
-    if (sess) {
-      sess = JSON.parse(sess);
-      expires = 'string' == typeof sess.cookie.expires
-        ? new Date(sess.cookie.expires)
-        : sess.cookie.expires;
-      if (!expires || new Date < expires) {
-        fn(null, sess);
-      } else {
-        self.destroy(sid, fn);
-      }
-    } else {
-      fn();
-    }
-  });
-};
-
-/**
- * Commit the given `sess` object associated with the given `sid`.
- *
- * @param {String} sid
- * @param {Session} sess
- * @param {Function} fn
- * @api public
- */
-
-MemoryStore.prototype.set = function(sid, sess, fn){
-  var self = this;
-  process.nextTick(function(){
-    self.sessions[sid] = JSON.stringify(sess);
-    fn && fn();
-  });
-};
-
-/**
- * Destroy the session associated with the given `sid`.
- *
- * @param {String} sid
- * @api public
- */
-
-MemoryStore.prototype.destroy = function(sid, fn){
-  var self = this;
-  process.nextTick(function(){
-    delete self.sessions[sid];
-    fn && fn();
-  });
-};
-
-/**
- * Invoke the given callback `fn` with all active sessions.
- *
- * @param {Function} fn
- * @api public
- */
-
-MemoryStore.prototype.all = function(fn){
-  var arr = []
-    , keys = Object.keys(this.sessions);
-  for (var i = 0, len = keys.length; i < len; ++i) {
-    arr.push(this.sessions[keys[i]]);
-  }
-  fn(null, arr);
-};
-
-/**
- * Clear all sessions.
- *
- * @param {Function} fn
- * @api public
- */
-
-MemoryStore.prototype.clear = function(fn){
-  this.sessions = {};
-  fn && fn();
-};
-
-/**
- * Fetch number of sessions.
- *
- * @param {Function} fn
- * @api public
- */
-
-MemoryStore.prototype.length = function(fn){
-  fn(null, Object.keys(this.sessions).length);
-};
diff --git a/lib/middleware/session/session.js b/lib/middleware/session/session.js
deleted file mode 100644
index 4e7e1a6..0000000
--- a/lib/middleware/session/session.js
+++ /dev/null
@@ -1,137 +0,0 @@
-
-/*!
- * Connect - session - Session
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../../utils')
-  , Cookie = require('./cookie');
-
-/**
- * Create a new `Session` with the given request and `data`.
- *
- * @param {IncomingRequest} req
- * @param {Object} data
- * @api private
- */
-
-var Session = module.exports = function Session(req, data) {
-  Object.defineProperty(this, 'req', { value: req });
-  Object.defineProperty(this, 'id', { value: req.sessionID });
-  if ('object' == typeof data) {
-    utils.merge(this, data);
-  } else {
-    this.lastAccess = Date.now();
-  }
-};
-
-/**
- * Update `.lastAccess` timestamp,
- * and reset `.cookie.maxAge` to prevent
- * the cookie from expiring when the
- * session is still active.
- *
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.touch = function(){
-  return this
-    .resetLastAccess()
-    .resetMaxAge();
-};
-
-/**
- * Update `.lastAccess` timestamp.
- *
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.resetLastAccess = function(){
-  this.lastAccess = Date.now();
-  return this;
-};
-
-/**
- * Reset `.maxAge` to `.originalMaxAge`.
- *
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.resetMaxAge = function(){
-  this.cookie.maxAge = this.cookie.originalMaxAge;
-  return this;
-};
-
-/**
- * Save the session data with optional callback `fn(err)`.
- *
- * @param {Function} fn
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.save = function(fn){
-  this.req.sessionStore.set(this.id, this, fn || function(){});
-  return this;
-};
-
-/**
- * Re-loads the session data _without_ altering
- * the maxAge or lastAccess properties. Invokes the
- * callback `fn(err)`, after which time if no exception
- * has occurred the `req.session` property will be
- * a new `Session` object, although representing the
- * same session.
- *
- * @param {Function} fn
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.reload = function(fn){
-  var req = this.req
-    , store = this.req.sessionStore;
-  store.get(this.id, function(err, sess){
-    if (err) return fn(err);
-    if (!sess) return fn(new Error('failed to load session'));
-    store.createSession(req, sess);
-    fn();
-  });
-  return this;
-};
-
-/**
- * Destroy `this` session.
- *
- * @param {Function} fn
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.destroy = function(fn){
-  delete this.req.session;
-  this.req.sessionStore.destroy(this.id, fn);
-  return this;
-};
-
-/**
- * Regenerate this request's session.
- *
- * @param {Function} fn
- * @return {Session} for chaining
- * @api public
- */
-
-Session.prototype.regenerate = function(fn){
-  this.req.sessionStore.regenerate(this.req, fn);
-  return this;
-};
diff --git a/lib/middleware/session/store.js b/lib/middleware/session/store.js
deleted file mode 100644
index 5dd8c64..0000000
--- a/lib/middleware/session/store.js
+++ /dev/null
@@ -1,87 +0,0 @@
-
-/*!
- * Connect - session - Store
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var EventEmitter = require('events').EventEmitter
-  , Session = require('./session')
-  , Cookie = require('./cookie')
-  , utils = require('../../utils');
-
-/**
- * Initialize abstract `Store`.
- *
- * @api private
- */
-
-var Store = module.exports = function Store(options){};
-
-/**
- * Inherit from `EventEmitter.prototype`.
- */
-
-Store.prototype.__proto__ = EventEmitter.prototype;
-
-/**
- * Re-generate the given requests's session.
- *
- * @param {IncomingRequest} req
- * @return {Function} fn
- * @api public
- */
-
-Store.prototype.regenerate = function(req, fn){
-  var self = this;
-  this.destroy(req.sessionID, function(err){
-    self.generate(req);
-    fn(err);
-  });
-};
-
-/**
- * Load a `Session` instance via the given `sid`
- * and invoke the callback `fn(err, sess)`.
- *
- * @param {String} sid
- * @param {Function} fn
- * @api public
- */
-
-Store.prototype.load = function(sid, fn){
-  var self = this;
-  this.get(sid, function(err, sess){
-    if (err) return fn(err);
-    if (!sess) return fn();
-    var req = { sessionID: sid, sessionStore: self };
-    sess = self.createSession(req, sess, false);
-    fn(null, sess);
-  });
-};
-
-/**
- * Create session from JSON `sess` data.
- *
- * @param {IncomingRequest} req
- * @param {Object} sess
- * @return {Session}
- * @api private
- */
-
-Store.prototype.createSession = function(req, sess, update){
-  var expires = sess.cookie.expires
-    , orig = sess.cookie.originalMaxAge
-    , update = null == update ? true : false;
-  sess.cookie = new Cookie(req, sess.cookie);
-  if ('string' == typeof expires) sess.cookie.expires = new Date(expires);
-  sess.cookie.originalMaxAge = orig;
-  req.session = new Session(req, sess);
-  if (update) req.session.resetLastAccess();
-  return req.session;
-};
\ No newline at end of file
diff --git a/lib/middleware/static.js b/lib/middleware/static.js
deleted file mode 100644
index 7c5e2f3..0000000
--- a/lib/middleware/static.js
+++ /dev/null
@@ -1,246 +0,0 @@
-/*!
- * Connect - staticProvider
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var fs = require('fs')
-  , path = require('path')
-  , join = path.join
-  , basename = path.basename
-  , normalize = path.normalize
-  , utils = require('../utils')
-  , Buffer = require('buffer').Buffer
-  , parse = require('url').parse
-  , mime = require('mime');
-
-/**
- * Static:
- *
- *   Static file server with the given `root` path.
- *
- * Examples:
- *
- *     var oneDay = 86400000;
- *
- *     connect()
- *       .use(connect.static(__dirname + '/public'))
- *
- *     connect()
- *       .use(connect.static(__dirname + '/public', { maxAge: oneDay }))
- *
- * Options:
- *
- *    - `maxAge`   Browser cache maxAge in milliseconds. defaults to 0
- *    - `hidden`   Allow transfer of hidden files. defaults to false
- *    - `redirect`   Redirect to trailing "/" when the pathname is a dir
- *
- * @param {String} root
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function static(root, options){
-  options = options || {};
-
-  // root required
-  if (!root) throw new Error('static() root path required');
-  options.root = root;
-
-  return function static(req, res, next) {
-    options.path = req.url;
-    options.getOnly = true;
-    send(req, res, next, options);
-  };
-};
-
-/**
- * Expose mime module.
- * 
- * If you wish to extend the mime table use this
- * reference to the "mime" module in the npm registry.
- */
-
-exports.mime = mime;
-
-/**
- * decodeURIComponent.
- *
- * Allows V8 to only deoptimize this fn instead of all
- * of send().
- *
- * @param {String} path
- * @api private
- */
-
-function decode(path){
-  try {
-    return decodeURIComponent(path);
-  } catch (err) {
-    return err;
-  }
-}
-
-/**
- * Attempt to tranfer the requested file to `res`.
- *
- * @param {ServerRequest}
- * @param {ServerResponse}
- * @param {Function} next
- * @param {Object} options
- * @api private
- */
-
-var send = exports.send = function(req, res, next, options){
-  options = options || {};
-  if (!options.path) throw new Error('path required');
-
-  // setup
-  var maxAge = options.maxAge || 0
-    , ranges = req.headers.range
-    , head = 'HEAD' == req.method
-    , get = 'GET' == req.method
-    , root = options.root ? normalize(options.root) : null
-    , redirect = false === options.redirect ? false : true
-    , getOnly = options.getOnly
-    , fn = options.callback
-    , hidden = options.hidden
-    , done;
-
-  // replace next() with callback when available
-  if (fn) next = fn;
-
-  // ignore non-GET requests
-  if (getOnly && !get && !head) return next();
-
-  // parse url
-  var url = parse(options.path)
-    , path = decode(url.pathname)
-    , type;
-
-  if (path instanceof URIError) return next(utils.error(400));
-
-  // null byte(s)
-  if (~path.indexOf('\0')) return next(utils.error(400));
-
-  // when root is not given, consider .. malicious
-  if (!root && ~path.indexOf('..')) return next(utils.error(403));
-  
-  // index.html support
-  if ('/' == path[path.length - 1]) path += 'index.html';
-  
-  // join / normalize from optional root dir
-  path = normalize(join(root, path));
-
-  // malicious path
-  if (root && 0 != path.indexOf(root)) return next(utils.error(403));
-
-  // "hidden" file
-  if (!hidden && '.' == basename(path)[0]) return next();
-
-  fs.stat(path, function(err, stat){
-    // mime type
-    type = mime.lookup(path);
-
-    // ignore ENOENT
-    if (err) {
-      if (fn) return fn(err);
-      return ('ENOENT' == err.code || 'ENAMETOOLONG' == err.code)
-        ? next()
-        : next(err);
-    // redirect directory in case index.html is present
-    } else if (stat.isDirectory()) {
-      if (!redirect) return next();
-      res.statusCode = 301;
-      res.setHeader('Location', url.pathname + '/');
-      res.end('Redirecting to ' + url.pathname + '/');
-      return;
-    }
-
-    // header fields
-    if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString());
-    if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000));
-    if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString());
-    if (!res.getHeader('Content-Type')) {
-      var charset = mime.charsets.lookup(type);
-      res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
-    }
-    res.setHeader('Accept-Ranges', 'bytes');
-
-    // conditional GET support
-    if (utils.conditionalGET(req)) {
-      if (!utils.modified(req, res)) {
-        req.emit('static');
-        return utils.notModified(res);
-      }
-    }
-
-    var opts = {}
-      , len = stat.size;
-
-    // we have a Range request
-    if (ranges) {
-      ranges = utils.parseRange(len, ranges);
-
-      // valid
-      if (ranges) {
-        opts.start = ranges[0].start;
-        opts.end = ranges[0].end;
-
-        // unsatisfiable range
-        if (opts.start > len - 1) {
-          res.setHeader('Content-Range', 'bytes */' + stat.size);
-          return next(utils.error(416));
-        }
-
-        // limit last-byte-pos to current length
-        if (opts.end > len - 1) opts.end= len - 1;
-
-        // Content-Range
-        len = opts.end - opts.start + 1;
-        res.statusCode = 206;
-        res.setHeader('Content-Range', 'bytes '
-          + opts.start
-          + '-'
-          + opts.end
-          + '/'
-          + stat.size);
-      }
-    }
-
-    res.setHeader('Content-Length', len);
-
-    // transfer
-    if (head) return res.end();
-
-    // stream
-    var stream = fs.createReadStream(path, opts);
-    req.emit('static', stream);
-    req.on('close', stream.destroy.bind(stream));
-    stream.pipe(res);
-
-    // callback
-    if (fn) {
-      function callback(err) { done || fn(err); done = true }
-      req.on('close', callback);
-      req.socket.on('error', callback);
-      stream.on('error', callback);
-      stream.on('end', callback);
-    } else {
-      stream.on('error', function(err){
-        if (res.headerSent) {
-          console.error(err.stack);
-          req.destroy();
-        } else {
-          next(err);
-        }
-      });
-    }
-  });
-};
diff --git a/lib/middleware/staticCache.js b/lib/middleware/staticCache.js
deleted file mode 100644
index 3ec5e42..0000000
--- a/lib/middleware/staticCache.js
+++ /dev/null
@@ -1,185 +0,0 @@
-
-/*!
- * Connect - staticCache
- * Copyright(c) 2011 Sencha Inc.
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var http = require('http')
-  , utils = require('../utils')
-  , Cache = require('../cache')
-  , url = require('url')
-  , fs = require('fs');
-
-/**
- * Static cache:
- *
- * Enables a memory cache layer on top of
- * the `static()` middleware, serving popular
- * static files.
- *
- * By default a maximum of 128 objects are
- * held in cache, with a max of 256k each,
- * totalling ~32mb.
- *
- * A Least-Recently-Used (LRU) cache algo
- * is implemented through the `Cache` object,
- * simply rotating cache objects as they are
- * hit. This means that increasingly popular
- * objects maintain their positions while
- * others get shoved out of the stack and
- * garbage collected.
- *
- * Benchmarks:
- *
- *     static(): 2700 rps
- *     node-static: 5300 rps
- *     static() + staticCache(): 7500 rps
- *
- * Options:
- *
- *   - `maxObjects`  max cache objects [128]
- *   - `maxLength`  max cache object length 256kb
- *
- * @param {Type} name
- * @return {Type}
- * @api public
- */
-
-module.exports = function staticCache(options){
-  var options = options || {}
-    , cache = new Cache(options.maxObjects || 128)
-    , maxlen = options.maxLength || 1024 * 256;
-
-  return function staticCache(req, res, next){
-    var path = url.parse(req.url).pathname
-      , ranges = req.headers.range
-      , hit = cache.get(path)
-      , hitCC
-      , uaCC
-      , header
-      , age;
-
-    function miss() {
-      res.setHeader('X-Cache', 'MISS');
-      next();
-    }
-
-    // cache static
-    // TODO: change from staticCache() -> static()
-    // and make this work for any request
-    req.on('static', function(stream){
-      var headers = res._headers
-        , cc = utils.parseCacheControl(headers['cache-control'] || '')
-        , contentLength = headers['content-length']
-        , hit;
-
-      // ignore larger files
-      if (!contentLength || contentLength > maxlen) return;
-
-      // dont cache items we shouldn't be
-      // TODO: real support for must-revalidate / no-cache
-      if ( cc['no-cache']
-        || cc['no-store']
-        || cc['private']
-        || cc['must-revalidate']) return;
-
-      // if already in cache then validate
-      if (hit = cache.get(path)){
-        if (headers.etag == hit[0].etag) {
-          hit[0].date = new Date;
-          return;
-        } else {
-          cache.remove(path);
-        }
-      }
-
-      // validation notifiactions don't contain a steam
-      if (null == stream) return;
-
-      // add the cache object
-      var arr = cache.add(path);
-      arr.push(headers);
-
-      // store the chunks
-      stream.on('data', function(chunk){
-        arr.push(chunk);
-      });
-
-      // flag it as complete
-      stream.on('end', function(){
-        arr.complete = true;
-      });
-    });
-
-    // cache hit, doesnt support range requests
-    if (hit && hit.complete && !ranges) {
-      header = utils.merge({}, hit[0]);
-      header.Age = age = (new Date - new Date(header.date)) / 1000 | 0;
-      header.date = new Date().toUTCString();
-
-      // parse cache-controls
-      hitCC = utils.parseCacheControl(header['cache-control'] || '');
-      uaCC = utils.parseCacheControl(req.headers['cache-control'] || '');
-
-      // check if we must revalidate(bypass)
-      if (hitCC['no-cache'] || uaCC['no-cache']) return miss();
-
-      // check freshness of entity
-      if (isStale(hitCC, age) || isStale(uaCC, age)) return miss();
-
-      // conditional GET support
-      if (utils.conditionalGET(req)) {
-        if (!utils.modified(req, res, header)) {
-          header['content-length'] = 0;
-          res.writeHead(304, header);
-          return res.end();
-        }
-      }
-
-      // HEAD support
-      if ('HEAD' == req.method) {
-        res.writeHead(200, header);
-        return res.end();
-      }
-
-      // respond with cache
-      header['x-cache'] = 'HIT';
-      res.writeHead(200, header);
-
-      // backpressure
-      function write(i) {
-        var buf = hit[i];
-        if (!buf) return res.end();
-        if (false === res.write(buf)) {
-          res.once('drain', function(){
-            write(++i);
-          });
-        } else {
-          write(++i);
-        }
-      }
-
-      return write(1);
-    }
-
-    miss();
-  }
-};
-
-/**
- * Check if cache item is stale
- *
- * @param {Object} cc
- * @param {Number} age
- * @return {Boolean}
- * @api private
- */
-
-function isStale(cc, age) {
-  return cc['max-age'] && cc['max-age'] <= age;
-}
\ No newline at end of file
diff --git a/lib/middleware/urlencoded.js b/lib/middleware/urlencoded.js
deleted file mode 100644
index ae1f25f..0000000
--- a/lib/middleware/urlencoded.js
+++ /dev/null
@@ -1,57 +0,0 @@
-
-/*!
- * Connect - urlencoded
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var utils = require('../utils')
-  , qs = require('qs');
-
-/**
- * Urlencoded:
- * 
- *  Parse x-ww-form-urlencoded request bodies,
- *  providing the parsed object as `req.body`.
- *
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-exports = module.exports = function(options){
-  options = options || {};
-  return function urlencoded(req, res, next) {
-    if (req._body) return next();
-    req.body = req.body || {};
-
-    // ignore GET
-    if ('GET' == req.method || 'HEAD' == req.method) return next();
-
-    // check Content-Type
-    if ('application/x-www-form-urlencoded' != utils.mime(req)) return next();
-
-    // flag as parsed
-    req._body = true;
-
-    // parse
-    var buf = '';
-    req.setEncoding('utf8');
-    req.on('data', function(chunk){ buf += chunk });
-    req.on('end', function(){
-      try {
-        req.body = buf.length
-          ? qs.parse(buf, options)
-          : {};
-        next();
-      } catch (err){
-        next(err);
-      }
-    });
-  }
-};
\ No newline at end of file
diff --git a/lib/middleware/vhost.js b/lib/middleware/vhost.js
deleted file mode 100644
index 897a9d8..0000000
--- a/lib/middleware/vhost.js
+++ /dev/null
@@ -1,40 +0,0 @@
-
-/*!
- * Connect - vhost
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Vhost:
- * 
- *   Setup vhost for the given `hostname` and `server`.
- *
- *     connect()
- *       .use(connect.vhost('foo.com', fooApp))
- *       .use(connect.vhost('bar.com', barApp))
- *       .use(connect.vhost('*.com', mainApp))
- *
- *  The `server` may be a Connect server or
- *  a regular Node `http.Server`. 
- *
- * @param {String} hostname
- * @param {Server} server
- * @return {Function}
- * @api public
- */
-
-module.exports = function vhost(hostname, server){
-  if (!hostname) throw new Error('vhost hostname required');
-  if (!server) throw new Error('vhost server required');
-  var regexp = new RegExp('^' + hostname.replace(/[*]/g, '(.*?)') + '$', 'i');
-  if (server.onvhost) server.onvhost(hostname);
-  return function vhost(req, res, next){
-    if (!req.headers.host) return next();
-    var host = req.headers.host.split(':')[0];
-    if (!regexp.test(host)) return next();
-    if ('function' == typeof server) return server(req, res, next);
-    server.emit('request', req, res);
-  };
-};
diff --git a/lib/patch.js b/lib/patch.js
deleted file mode 100644
index 7cf0012..0000000
--- a/lib/patch.js
+++ /dev/null
@@ -1,79 +0,0 @@
-
-/*!
- * Connect
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var http = require('http')
-  , res = http.ServerResponse.prototype
-  , setHeader = res.setHeader
-  , _renderHeaders = res._renderHeaders
-  , writeHead = res.writeHead;
-
-// apply only once
-
-if (!res._hasConnectPatch) {
-
-  /**
-   * Provide a public "header sent" flag
-   * until node does.
-   *
-   * @return {Boolean}
-   * @api public
-   */
-
-  res.__defineGetter__('headerSent', function(){
-    return this._header;
-  });
-
-  /**
-   * Set header `field` to `val`, special-casing
-   * the `Set-Cookie` field for multiple support.
-   *
-   * @param {String} field
-   * @param {String} val
-   * @api public
-   */
-
-  res.setHeader = function(field, val){
-    var key = field.toLowerCase()
-      , prev;
-
-    // special-case Set-Cookie
-    if (this._headers && 'set-cookie' == key) {
-      if (prev = this.getHeader(field)) {
-        val = Array.isArray(prev)
-          ? prev.concat(val)
-          : [prev, val];
-      }
-    // charset
-    } else if ('content-type' == key && this.charset) {
-      val += '; charset=' + this.charset;
-    }
-
-    return setHeader.call(this, field, val);
-  };
-
-  /**
-   * Proxy to emit "header" event.
-   */
-
-  res._renderHeaders = function(){
-    if (!this._emittedHeader) this.emit('header');
-    this._emittedHeader = true;
-    return _renderHeaders.call(this);
-  };
-
-  res.writeHead = function(){
-    if (!this._emittedHeader) this.emit('header');
-    this._emittedHeader = true;
-    return writeHead.apply(this, arguments);
-  };
-
-  res._hasConnectPatch = true;
-}
diff --git a/lib/proto.js b/lib/proto.js
index 46baec8..d645c7e 100644
--- a/lib/proto.js
+++ b/lib/proto.js
@@ -1,4 +1,3 @@
-
 /*!
  * Connect - HTTPServer
  * Copyright(c) 2010 Sencha Inc.
@@ -10,10 +9,10 @@
  * Module dependencies.
  */
 
-var http = require('http')
-  , parse = require('url').parse
-  , utils = require('./utils')
-  , debug = require('debug')('connect:dispatcher');
+var http = require('http');
+var escapehtml = require('escape-html');
+var debug = require('debug')('connect:dispatcher');
+var parseUrl = require('parseurl');
 
 // prototype
 
@@ -48,7 +47,7 @@ var env = process.env.NODE_ENV || 'development';
  *
  * This api is chainable, so the following is valid:
  *
- *      connect
+ *      connect()
  *        .use(connect.favicon())
  *        .use(connect.logger())
  *        .use(connect.static(__dirname + '/public'))
@@ -70,7 +69,7 @@ app.use = function(route, fn){
   // wrap sub-apps
   if ('function' == typeof fn.handle) {
     var server = fn;
-    fn.route = route;
+    server.route = route;
     fn = function(req, res, next){
       server.handle(req, res, next);
     };
@@ -87,6 +86,7 @@ app.use = function(route, fn){
   }
 
   // add the middleware
+  debug('use %s %s', route || '/', fn.name || 'anonymous');
   this.stack.push({ route: route, handle: fn });
 
   return this;
@@ -101,13 +101,23 @@ app.use = function(route, fn){
 
 app.handle = function(req, res, out) {
   var stack = this.stack
-    , fqdn = ~req.url.indexOf('://')
+    , search = 1 + req.url.indexOf('?')
+    , pathlength = search ? search - 1 : req.url.length
+    , fqdn = 1 + req.url.substr(0, pathlength).indexOf('://')
+    , protohost = fqdn ? req.url.substr(0, req.url.indexOf('/', 2 + fqdn)) : ''
     , removed = ''
+    , slashAdded = false
     , index = 0;
 
   function next(err) {
-    var layer, path, status, c;
-    req.url = removed + req.url;
+    var layer, path, c;
+
+    if (slashAdded) {
+      req.url = req.url.substr(1);
+      slashAdded = false;
+    }
+
+    req.url = protohost + removed + req.url.substr(protohost.length);
     req.originalUrl = req.originalUrl || req.url;
     removed = '';
 
@@ -115,7 +125,7 @@ app.handle = function(req, res, out) {
     layer = stack[index++];
 
     // all done
-    if (!layer || res.headerSent) {
+    if (!layer) {
       // delegate to parent
       if (out) return out(err);
 
@@ -132,30 +142,31 @@ app.handle = function(req, res, out) {
         var msg = 'production' == env
           ? http.STATUS_CODES[res.statusCode]
           : err.stack || err.toString();
+        msg = escapehtml(msg).replace(/\n/g, '<br>').replace(/  /g, '  ');
 
         // log to stderr in a non-test env
         if ('test' != env) console.error(err.stack || err.toString());
-        if (res.headerSent) return req.socket.destroy();
-        res.setHeader('Content-Type', 'text/plain');
+        if (res.headersSent) return req.socket.destroy();
+        res.setHeader('Content-Type', 'text/html');
         res.setHeader('Content-Length', Buffer.byteLength(msg));
         if ('HEAD' == req.method) return res.end();
-        res.end(msg);
-      } else {
+        res.end(msg + '\n');
+      } else if (!res.headersSent) {
         debug('default 404');
         res.statusCode = 404;
-        res.setHeader('Content-Type', 'text/plain');
+        res.setHeader('Content-Type', 'text/html');
         if ('HEAD' == req.method) return res.end();
-        res.end('Cannot ' + req.method + ' ' + utils.escape(req.originalUrl));
+        res.end('Cannot ' + escapehtml(req.method) + ' ' + escapehtml(req.originalUrl) + '\n');
       }
       return;
     }
 
     try {
-      path = parse(req.url).pathname;
+      path = parseUrl(req).pathname;
       if (undefined == path) path = '/';
 
       // skip this layer if the route doesn't match.
-      if (0 != path.indexOf(layer.route)) return next(err);
+      if (0 != path.toLowerCase().indexOf(layer.route.toLowerCase())) return next(err);
 
       c = path[layer.route.length];
       if (c && '/' != c && '.' != c) return next(err);
@@ -163,12 +174,15 @@ app.handle = function(req, res, out) {
       // Call the layer handler
       // Trim off the part of the url that matches the route
       removed = layer.route;
-      req.url = req.url.substr(removed.length);
+      req.url = protohost + req.url.substr(protohost.length + removed.length);
 
       // Ensure leading slash
-      if (!fqdn && '/' != req.url[0]) req.url = '/' + req.url;
+      if (!fqdn && '/' != req.url[0]) {
+        req.url = '/' + req.url;
+        slashAdded = true;
+      }
 
-      debug('%s', layer.handle.name || 'anonymous');
+      debug('%s %s : %s', layer.handle.name || 'anonymous', layer.route, req.originalUrl);
       var arity = layer.handle.length;
       if (err) {
         if (arity === 4) {
@@ -192,7 +206,7 @@ app.handle = function(req, res, out) {
  * Listen for connections.
  *
  * This method takes the same arguments
- * as node's `http.Server#listen()`.  
+ * as node's `http.Server#listen()`.
  *
  * HTTP and HTTPS:
  *
@@ -204,9 +218,9 @@ app.handle = function(req, res, out) {
  *      var connect = require('connect')
  *        , http = require('http')
  *        , https = require('https');
- *      
+ *
  *      var app = connect();
- *      
+ *
  *      http.createServer(app).listen(80);
  *      https.createServer(options, app).listen(443);
  *
diff --git a/lib/public/directory.html b/lib/public/directory.html
deleted file mode 100644
index 15164bb..0000000
--- a/lib/public/directory.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<html>
-  <head>
-    <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 = 'DOMContentLoaded';
-          el.addEventListener(event, fn, false);
-        };
-
-        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);
-        };
-
-        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/lib/public/error.html b/lib/public/error.html
deleted file mode 100644
index c5ae73a..0000000
--- a/lib/public/error.html
+++ /dev/null
@@ -1,13 +0,0 @@
-<html>
-  <head>
-    <title>{error}</title>
-    <style>{style}</style>
-  </head>
-  <body>
-    <div id="wrapper">
-      <h1>{title}</h1>
-      <h2><em>{statusCode}</em> {error}</h2>
-      <ul id="stacktrace">{stack}</ul>
-    </div>
-  </body>
-</html>
diff --git a/lib/public/favicon.ico b/lib/public/favicon.ico
deleted file mode 100644
index 895fc96..0000000
Binary files a/lib/public/favicon.ico and /dev/null differ
diff --git a/lib/public/icons/page.png b/lib/public/icons/page.png
deleted file mode 100644
index 03ddd79..0000000
Binary files a/lib/public/icons/page.png and /dev/null differ
diff --git a/lib/public/icons/page_add.png b/lib/public/icons/page_add.png
deleted file mode 100644
index d5bfa07..0000000
Binary files a/lib/public/icons/page_add.png and /dev/null differ
diff --git a/lib/public/icons/page_attach.png b/lib/public/icons/page_attach.png
deleted file mode 100644
index 89ee2da..0000000
Binary files a/lib/public/icons/page_attach.png and /dev/null differ
diff --git a/lib/public/icons/page_code.png b/lib/public/icons/page_code.png
deleted file mode 100644
index f7ea904..0000000
Binary files a/lib/public/icons/page_code.png and /dev/null differ
diff --git a/lib/public/icons/page_copy.png b/lib/public/icons/page_copy.png
deleted file mode 100644
index 195dc6d..0000000
Binary files a/lib/public/icons/page_copy.png and /dev/null differ
diff --git a/lib/public/icons/page_delete.png b/lib/public/icons/page_delete.png
deleted file mode 100644
index 3141467..0000000
Binary files a/lib/public/icons/page_delete.png and /dev/null differ
diff --git a/lib/public/icons/page_edit.png b/lib/public/icons/page_edit.png
deleted file mode 100644
index 046811e..0000000
Binary files a/lib/public/icons/page_edit.png and /dev/null differ
diff --git a/lib/public/icons/page_error.png b/lib/public/icons/page_error.png
deleted file mode 100644
index f07f449..0000000
Binary files a/lib/public/icons/page_error.png and /dev/null differ
diff --git a/lib/public/icons/page_excel.png b/lib/public/icons/page_excel.png
deleted file mode 100644
index eb6158e..0000000
Binary files a/lib/public/icons/page_excel.png and /dev/null differ
diff --git a/lib/public/icons/page_find.png b/lib/public/icons/page_find.png
deleted file mode 100644
index 2f19388..0000000
Binary files a/lib/public/icons/page_find.png and /dev/null differ
diff --git a/lib/public/icons/page_gear.png b/lib/public/icons/page_gear.png
deleted file mode 100644
index 8e83281..0000000
Binary files a/lib/public/icons/page_gear.png and /dev/null differ
diff --git a/lib/public/icons/page_go.png b/lib/public/icons/page_go.png
deleted file mode 100644
index 80fe1ed..0000000
Binary files a/lib/public/icons/page_go.png and /dev/null differ
diff --git a/lib/public/icons/page_green.png b/lib/public/icons/page_green.png
deleted file mode 100644
index de8e003..0000000
Binary files a/lib/public/icons/page_green.png and /dev/null differ
diff --git a/lib/public/icons/page_key.png b/lib/public/icons/page_key.png
deleted file mode 100644
index d6626cb..0000000
Binary files a/lib/public/icons/page_key.png and /dev/null differ
diff --git a/lib/public/icons/page_lightning.png b/lib/public/icons/page_lightning.png
deleted file mode 100644
index 7e56870..0000000
Binary files a/lib/public/icons/page_lightning.png and /dev/null differ
diff --git a/lib/public/icons/page_link.png b/lib/public/icons/page_link.png
deleted file mode 100644
index 312eab0..0000000
Binary files a/lib/public/icons/page_link.png and /dev/null differ
diff --git a/lib/public/icons/page_paintbrush.png b/lib/public/icons/page_paintbrush.png
deleted file mode 100644
index 246a2f0..0000000
Binary files a/lib/public/icons/page_paintbrush.png and /dev/null differ
diff --git a/lib/public/icons/page_paste.png b/lib/public/icons/page_paste.png
deleted file mode 100644
index 968f073..0000000
Binary files a/lib/public/icons/page_paste.png and /dev/null differ
diff --git a/lib/public/icons/page_red.png b/lib/public/icons/page_red.png
deleted file mode 100644
index 0b18247..0000000
Binary files a/lib/public/icons/page_red.png and /dev/null differ
diff --git a/lib/public/icons/page_refresh.png b/lib/public/icons/page_refresh.png
deleted file mode 100644
index cf347c7..0000000
Binary files a/lib/public/icons/page_refresh.png and /dev/null differ
diff --git a/lib/public/icons/page_save.png b/lib/public/icons/page_save.png
deleted file mode 100644
index caea546..0000000
Binary files a/lib/public/icons/page_save.png and /dev/null differ
diff --git a/lib/public/icons/page_white.png b/lib/public/icons/page_white.png
deleted file mode 100644
index 8b8b1ca..0000000
Binary files a/lib/public/icons/page_white.png and /dev/null differ
diff --git a/lib/public/icons/page_white_acrobat.png b/lib/public/icons/page_white_acrobat.png
deleted file mode 100644
index 8f8095e..0000000
Binary files a/lib/public/icons/page_white_acrobat.png and /dev/null differ
diff --git a/lib/public/icons/page_white_actionscript.png b/lib/public/icons/page_white_actionscript.png
deleted file mode 100644
index 159b240..0000000
Binary files a/lib/public/icons/page_white_actionscript.png and /dev/null differ
diff --git a/lib/public/icons/page_white_add.png b/lib/public/icons/page_white_add.png
deleted file mode 100644
index aa23dde..0000000
Binary files a/lib/public/icons/page_white_add.png and /dev/null differ
diff --git a/lib/public/icons/page_white_c.png b/lib/public/icons/page_white_c.png
deleted file mode 100644
index 34a05cc..0000000
Binary files a/lib/public/icons/page_white_c.png and /dev/null differ
diff --git a/lib/public/icons/page_white_camera.png b/lib/public/icons/page_white_camera.png
deleted file mode 100644
index f501a59..0000000
Binary files a/lib/public/icons/page_white_camera.png and /dev/null differ
diff --git a/lib/public/icons/page_white_cd.png b/lib/public/icons/page_white_cd.png
deleted file mode 100644
index 848bdaf..0000000
Binary files a/lib/public/icons/page_white_cd.png and /dev/null differ
diff --git a/lib/public/icons/page_white_code.png b/lib/public/icons/page_white_code.png
deleted file mode 100644
index 0c76bd1..0000000
Binary files a/lib/public/icons/page_white_code.png and /dev/null differ
diff --git a/lib/public/icons/page_white_code_red.png b/lib/public/icons/page_white_code_red.png
deleted file mode 100644
index 87a6914..0000000
Binary files a/lib/public/icons/page_white_code_red.png and /dev/null differ
diff --git a/lib/public/icons/page_white_coldfusion.png b/lib/public/icons/page_white_coldfusion.png
deleted file mode 100644
index c66011f..0000000
Binary files a/lib/public/icons/page_white_coldfusion.png and /dev/null differ
diff --git a/lib/public/icons/page_white_compressed.png b/lib/public/icons/page_white_compressed.png
deleted file mode 100644
index 2b6b100..0000000
Binary files a/lib/public/icons/page_white_compressed.png and /dev/null differ
diff --git a/lib/public/icons/page_white_copy.png b/lib/public/icons/page_white_copy.png
deleted file mode 100644
index a9f31a2..0000000
Binary files a/lib/public/icons/page_white_copy.png and /dev/null differ
diff --git a/lib/public/icons/page_white_cplusplus.png b/lib/public/icons/page_white_cplusplus.png
deleted file mode 100644
index a87cf84..0000000
Binary files a/lib/public/icons/page_white_cplusplus.png and /dev/null differ
diff --git a/lib/public/icons/page_white_csharp.png b/lib/public/icons/page_white_csharp.png
deleted file mode 100644
index ffb8fc9..0000000
Binary files a/lib/public/icons/page_white_csharp.png and /dev/null differ
diff --git a/lib/public/icons/page_white_cup.png b/lib/public/icons/page_white_cup.png
deleted file mode 100644
index 0a7d6f4..0000000
Binary files a/lib/public/icons/page_white_cup.png and /dev/null differ
diff --git a/lib/public/icons/page_white_database.png b/lib/public/icons/page_white_database.png
deleted file mode 100644
index bddba1f..0000000
Binary files a/lib/public/icons/page_white_database.png and /dev/null differ
diff --git a/lib/public/icons/page_white_delete.png b/lib/public/icons/page_white_delete.png
deleted file mode 100644
index af1ecaf..0000000
Binary files a/lib/public/icons/page_white_delete.png and /dev/null differ
diff --git a/lib/public/icons/page_white_dvd.png b/lib/public/icons/page_white_dvd.png
deleted file mode 100644
index 4cc537a..0000000
Binary files a/lib/public/icons/page_white_dvd.png and /dev/null differ
diff --git a/lib/public/icons/page_white_edit.png b/lib/public/icons/page_white_edit.png
deleted file mode 100644
index b93e776..0000000
Binary files a/lib/public/icons/page_white_edit.png and /dev/null differ
diff --git a/lib/public/icons/page_white_error.png b/lib/public/icons/page_white_error.png
deleted file mode 100644
index 9fc5a0a..0000000
Binary files a/lib/public/icons/page_white_error.png and /dev/null differ
diff --git a/lib/public/icons/page_white_excel.png b/lib/public/icons/page_white_excel.png
deleted file mode 100644
index b977d7e..0000000
Binary files a/lib/public/icons/page_white_excel.png and /dev/null differ
diff --git a/lib/public/icons/page_white_find.png b/lib/public/icons/page_white_find.png
deleted file mode 100644
index 5818436..0000000
Binary files a/lib/public/icons/page_white_find.png and /dev/null differ
diff --git a/lib/public/icons/page_white_flash.png b/lib/public/icons/page_white_flash.png
deleted file mode 100644
index 5769120..0000000
Binary files a/lib/public/icons/page_white_flash.png and /dev/null differ
diff --git a/lib/public/icons/page_white_freehand.png b/lib/public/icons/page_white_freehand.png
deleted file mode 100644
index 8d719df..0000000
Binary files a/lib/public/icons/page_white_freehand.png and /dev/null differ
diff --git a/lib/public/icons/page_white_gear.png b/lib/public/icons/page_white_gear.png
deleted file mode 100644
index 106f5aa..0000000
Binary files a/lib/public/icons/page_white_gear.png and /dev/null differ
diff --git a/lib/public/icons/page_white_get.png b/lib/public/icons/page_white_get.png
deleted file mode 100644
index e4a1ecb..0000000
Binary files a/lib/public/icons/page_white_get.png and /dev/null differ
diff --git a/lib/public/icons/page_white_go.png b/lib/public/icons/page_white_go.png
deleted file mode 100644
index 7e62a92..0000000
Binary files a/lib/public/icons/page_white_go.png and /dev/null differ
diff --git a/lib/public/icons/page_white_h.png b/lib/public/icons/page_white_h.png
deleted file mode 100644
index e902abb..0000000
Binary files a/lib/public/icons/page_white_h.png and /dev/null differ
diff --git a/lib/public/icons/page_white_horizontal.png b/lib/public/icons/page_white_horizontal.png
deleted file mode 100644
index 1d2d0a4..0000000
Binary files a/lib/public/icons/page_white_horizontal.png and /dev/null differ
diff --git a/lib/public/icons/page_white_key.png b/lib/public/icons/page_white_key.png
deleted file mode 100644
index d616484..0000000
Binary files a/lib/public/icons/page_white_key.png and /dev/null differ
diff --git a/lib/public/icons/page_white_lightning.png b/lib/public/icons/page_white_lightning.png
deleted file mode 100644
index 7215d1e..0000000
Binary files a/lib/public/icons/page_white_lightning.png and /dev/null differ
diff --git a/lib/public/icons/page_white_link.png b/lib/public/icons/page_white_link.png
deleted file mode 100644
index bf7bd1c..0000000
Binary files a/lib/public/icons/page_white_link.png and /dev/null differ
diff --git a/lib/public/icons/page_white_magnify.png b/lib/public/icons/page_white_magnify.png
deleted file mode 100644
index f6b74cc..0000000
Binary files a/lib/public/icons/page_white_magnify.png and /dev/null differ
diff --git a/lib/public/icons/page_white_medal.png b/lib/public/icons/page_white_medal.png
deleted file mode 100644
index d3fffb6..0000000
Binary files a/lib/public/icons/page_white_medal.png and /dev/null differ
diff --git a/lib/public/icons/page_white_office.png b/lib/public/icons/page_white_office.png
deleted file mode 100644
index a65bcb3..0000000
Binary files a/lib/public/icons/page_white_office.png and /dev/null differ
diff --git a/lib/public/icons/page_white_paint.png b/lib/public/icons/page_white_paint.png
deleted file mode 100644
index 23a37b8..0000000
Binary files a/lib/public/icons/page_white_paint.png and /dev/null differ
diff --git a/lib/public/icons/page_white_paintbrush.png b/lib/public/icons/page_white_paintbrush.png
deleted file mode 100644
index f907e44..0000000
Binary files a/lib/public/icons/page_white_paintbrush.png and /dev/null differ
diff --git a/lib/public/icons/page_white_paste.png b/lib/public/icons/page_white_paste.png
deleted file mode 100644
index 5b2cbb3..0000000
Binary files a/lib/public/icons/page_white_paste.png and /dev/null differ
diff --git a/lib/public/icons/page_white_php.png b/lib/public/icons/page_white_php.png
deleted file mode 100644
index 7868a25..0000000
Binary files a/lib/public/icons/page_white_php.png and /dev/null differ
diff --git a/lib/public/icons/page_white_picture.png b/lib/public/icons/page_white_picture.png
deleted file mode 100644
index 134b669..0000000
Binary files a/lib/public/icons/page_white_picture.png and /dev/null differ
diff --git a/lib/public/icons/page_white_powerpoint.png b/lib/public/icons/page_white_powerpoint.png
deleted file mode 100644
index c4eff03..0000000
Binary files a/lib/public/icons/page_white_powerpoint.png and /dev/null differ
diff --git a/lib/public/icons/page_white_put.png b/lib/public/icons/page_white_put.png
deleted file mode 100644
index 884ffd6..0000000
Binary files a/lib/public/icons/page_white_put.png and /dev/null differ
diff --git a/lib/public/icons/page_white_ruby.png b/lib/public/icons/page_white_ruby.png
deleted file mode 100644
index f59b7c4..0000000
Binary files a/lib/public/icons/page_white_ruby.png and /dev/null differ
diff --git a/lib/public/icons/page_white_stack.png b/lib/public/icons/page_white_stack.png
deleted file mode 100644
index 44084ad..0000000
Binary files a/lib/public/icons/page_white_stack.png and /dev/null differ
diff --git a/lib/public/icons/page_white_star.png b/lib/public/icons/page_white_star.png
deleted file mode 100644
index 3a1441c..0000000
Binary files a/lib/public/icons/page_white_star.png and /dev/null differ
diff --git a/lib/public/icons/page_white_swoosh.png b/lib/public/icons/page_white_swoosh.png
deleted file mode 100644
index e770829..0000000
Binary files a/lib/public/icons/page_white_swoosh.png and /dev/null differ
diff --git a/lib/public/icons/page_white_text.png b/lib/public/icons/page_white_text.png
deleted file mode 100644
index 813f712..0000000
Binary files a/lib/public/icons/page_white_text.png and /dev/null differ
diff --git a/lib/public/icons/page_white_text_width.png b/lib/public/icons/page_white_text_width.png
deleted file mode 100644
index d9cf132..0000000
Binary files a/lib/public/icons/page_white_text_width.png and /dev/null differ
diff --git a/lib/public/icons/page_white_tux.png b/lib/public/icons/page_white_tux.png
deleted file mode 100644
index 52699bf..0000000
Binary files a/lib/public/icons/page_white_tux.png and /dev/null differ
diff --git a/lib/public/icons/page_white_vector.png b/lib/public/icons/page_white_vector.png
deleted file mode 100644
index 4a05955..0000000
Binary files a/lib/public/icons/page_white_vector.png and /dev/null differ
diff --git a/lib/public/icons/page_white_visualstudio.png b/lib/public/icons/page_white_visualstudio.png
deleted file mode 100644
index a0a433d..0000000
Binary files a/lib/public/icons/page_white_visualstudio.png and /dev/null differ
diff --git a/lib/public/icons/page_white_width.png b/lib/public/icons/page_white_width.png
deleted file mode 100644
index 1eb8809..0000000
Binary files a/lib/public/icons/page_white_width.png and /dev/null differ
diff --git a/lib/public/icons/page_white_word.png b/lib/public/icons/page_white_word.png
deleted file mode 100644
index ae8ecbf..0000000
Binary files a/lib/public/icons/page_white_word.png and /dev/null differ
diff --git a/lib/public/icons/page_white_world.png b/lib/public/icons/page_white_world.png
deleted file mode 100644
index 6ed2490..0000000
Binary files a/lib/public/icons/page_white_world.png and /dev/null differ
diff --git a/lib/public/icons/page_white_wrench.png b/lib/public/icons/page_white_wrench.png
deleted file mode 100644
index fecadd0..0000000
Binary files a/lib/public/icons/page_white_wrench.png and /dev/null differ
diff --git a/lib/public/icons/page_white_zip.png b/lib/public/icons/page_white_zip.png
deleted file mode 100644
index fd4bbcc..0000000
Binary files a/lib/public/icons/page_white_zip.png and /dev/null differ
diff --git a/lib/public/icons/page_word.png b/lib/public/icons/page_word.png
deleted file mode 100644
index 834cdfa..0000000
Binary files a/lib/public/icons/page_word.png and /dev/null differ
diff --git a/lib/public/icons/page_world.png b/lib/public/icons/page_world.png
deleted file mode 100644
index b8895dd..0000000
Binary files a/lib/public/icons/page_world.png and /dev/null differ
diff --git a/lib/public/style.css b/lib/public/style.css
deleted file mode 100644
index 32b6507..0000000
--- a/lib/public/style.css
+++ /dev/null
@@ -1,141 +0,0 @@
-body {
-  margin: 0;
-  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 {
-  margin: 0;
-  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 {
-  margin: 0;
-  padding: 0;
-}
-ul li {
-  margin: 5px 0;
-  padding: 3px 8px;
-  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: 500px;
-}
-ul#files li {
-  padding: 0;
-}
-ul#files li img {
-  position: absolute;
-  top: 5px;
-  left: 5px;
-}
-ul#files li a {
-   position: relative;
-  display: block;
-  margin: 1px;
-  width: 30%;
-  height: 25px;
-  line-height: 25px;
-  text-indent: 8px;
-  float: left;
-  border: 1px solid transparent;
-  -webkit-border-radius: 5px;
-  -moz-border-radius: 5px;
-  border-radius: 5px;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-ul#files li a.icon {
-  text-indent: 25px;
-}
-ul#files li a:focus,
-ul#files li a:hover {
-  outline: none;
-  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; 
-}
diff --git a/lib/utils.js b/lib/utils.js
deleted file mode 100644
index cfa647d..0000000
--- a/lib/utils.js
+++ /dev/null
@@ -1,488 +0,0 @@
-
-/*!
- * Connect - utils
- * Copyright(c) 2010 Sencha Inc.
- * Copyright(c) 2011 TJ Holowaychuk
- * MIT Licensed
- */
-
-/**
- * Module dependencies.
- */
-
-var http = require('http')
-  , crypto = require('crypto')
-  , crc16 = require('crc').crc16
-  , Path = require('path')
-  , fs = require('fs');
-
-/**
- * Extract the mime type from the given request's
- * _Content-Type_ header.
- *
- * @param  {IncomingMessage} req
- * @return {String}
- * @api private
- */
-
-exports.mime = function(req) {
-  var str = req.headers['content-type'] || '';
-  return str.split(';')[0];
-};
-
-/**
- * Generate an `Error` from the given status `code`.
- *
- * @param {Number} code
- * @return {Error}
- * @api private
- */
-
-exports.error = function(code){
-  var err = new Error(http.STATUS_CODES[code]);
-  err.status = code;
-  return err;
-};
-
-/**
- * Return md5 hash of the given string and optional encoding,
- * defaulting to hex.
- *
- *     utils.md5('wahoo');
- *     // => "e493298061761236c96b02ea6aa8a2ad"
- *
- * @param {String} str
- * @param {String} encoding
- * @return {String}
- * @api public
- */
-
-exports.md5 = function(str, encoding){
-  return crypto
-    .createHash('md5')
-    .update(str)
-    .digest(encoding || 'hex');
-};
-
-/**
- * Merge object b with object a.
- *
- *     var a = { foo: 'bar' }
- *       , b = { bar: 'baz' };
- *     
- *     utils.merge(a, b);
- *     // => { foo: 'bar', bar: 'baz' }
- *
- * @param {Object} a
- * @param {Object} b
- * @return {Object}
- * @api private
- */
-
-exports.merge = function(a, b){
-  if (a && b) {
-    for (var key in b) {
-      a[key] = b[key];
-    }
-  }
-  return a;
-};
-
-/**
- * Escape the given string of `html`.
- *
- * @param {String} html
- * @return {String}
- * @api private
- */
-
-exports.escape = function(html){
-  return String(html)
-    .replace(/&(?!\w+;)/g, '&')
-    .replace(/</g, '<')
-    .replace(/>/g, '>')
-    .replace(/"/g, '"');
-};
-
-
-/**
- * Return a unique identifier with the given `len`.
- *
- *     utils.uid(10);
- *     // => "FDaS435D2z"
- *
- * @param {Number} len
- * @return {String}
- * @api private
- */
-
-exports.uid = function(len) {
-  return crypto.randomBytes(Math.ceil(len * 3 / 4))
-    .toString('base64')
-    .slice(0, len);
-};
-
-/**
- * Sign the given `val` with `secret`.
- *
- * @param {String} val
- * @param {String} secret
- * @return {String}
- * @api private
- */
-
-exports.sign = function(val, secret){
-  return val + '.' + crypto
-    .createHmac('sha256', secret)
-    .update(val)
-    .digest('base64')
-    .replace(/=+$/, '');
-};
-
-/**
- * Unsign and decode the given `val` with `secret`,
- * returning `false` if the signature is invalid.
- *
- * @param {String} val
- * @param {String} secret
- * @return {String|Boolean}
- * @api private
- */
-
-exports.unsign = function(val, secret){
-  var str = val.slice(0,val.lastIndexOf('.'));
-  return exports.sign(str, secret) == val
-    ? str
-    : false;
-};
-
-/**
- * Parse signed cookies, returning an object
- * containing the decoded key/value pairs,
- * while removing the signed key from `obj`.
- *
- * @param {Object} obj
- * @return {Object}
- * @api private
- */
-
-exports.parseSignedCookies = function(obj, secret){
-  var ret = {};
-  Object.keys(obj).forEach(function(key){
-    var val = obj[key]
-      , signed = exports.unsign(val, secret);
-
-    if (signed) {
-      ret[key] = signed;
-      delete obj[key];
-    }
-  });
-  return ret;
-};
-
-/**
- * Parse JSON cookies.
- *
- * @param {Object} obj
- * @return {Object}
- * @api private
- */
-
-exports.parseJSONCookies = function(obj){
-  var hashes = {};
-
-  Object.keys(obj).forEach(function(key){
-    var val = obj[key];
-    if (0 == val.indexOf('j:')) {
-      try {
-        hashes[key] = crc16(val); // only crc json cookies for now
-        obj[key] = JSON.parse(val.slice(2));
-      } catch (err) {
-        // nothing
-      }
-    }
-  });
-
-  return {
-    cookies: obj,
-    hashes: hashes
-  };
-};
-
-/**
- * Parse the given cookie string into an object.
- *
- * @param {String} str
- * @return {Object}
- * @api private
- */
-
-exports.parseCookie = function(str){
-  var obj = {}
-    , pairs = str.split(/[;,] */);
-  for (var i = 0, len = pairs.length; i < len; ++i) {
-    var pair = pairs[i]
-      , eqlIndex = pair.indexOf('=')
-      , key = pair.substr(0, eqlIndex).trim()
-      , val = pair.substr(++eqlIndex, pair.length).trim();
-
-    // quoted values
-    if ('"' == val[0]) val = val.slice(1, -1);
-
-    // only assign once
-    if (undefined == obj[key]) {
-      val = val.replace(/\+/g, ' ');
-      try {
-        obj[key] = decodeURIComponent(val);
-      } catch (err) {
-        if (err instanceof URIError) {
-          obj[key] = val;
-        } else {
-          throw err;
-        }
-      }
-    }
-  }
-  return obj;
-};
-
-/**
- * Serialize the given object into a cookie string.
- *
- *      utils.serializeCookie('name', 'tj', { httpOnly: true })
- *      // => "name=tj; httpOnly"
- *
- * @param {String} name
- * @param {String} val
- * @param {Object} obj
- * @return {String}
- * @api private
- */
-
-exports.serializeCookie = function(name, val, obj){
-  var pairs = [name + '=' + encodeURIComponent(val)]
-    , obj = obj || {};
-
-  if (obj.domain) pairs.push('domain=' + obj.domain);
-  if (obj.path) pairs.push('path=' + obj.path);
-  if (obj.expires) pairs.push('expires=' + obj.expires.toUTCString());
-  if (obj.httpOnly) pairs.push('httpOnly');
-  if (obj.secure) pairs.push('secure');
-
-  return pairs.join('; ');
-};
-
-/**
- * Pause `data` and `end` events on the given `obj`.
- * Middleware performing async tasks _should_ utilize
- * this utility (or similar), to re-emit data once
- * the async operation has completed, otherwise these
- * events may be lost.
- *
- *      var pause = utils.pause(req);
- *      fs.readFile(path, function(){
- *         next();
- *         pause.resume();
- *      });
- *
- * @param {Object} obj
- * @return {Object}
- * @api private
- */
-
-exports.pause = function(obj){
-  var onData
-    , onEnd
-    , events = [];
-
-  // buffer data
-  obj.on('data', onData = function(data, encoding){
-    events.push(['data', data, encoding]);
-  });
-
-  // buffer end
-  obj.on('end', onEnd = function(data, encoding){
-    events.push(['end', data, encoding]);
-  });
-
-  return {
-    end: function(){
-      obj.removeListener('data', onData);
-      obj.removeListener('end', onEnd);
-    },
-    resume: function(){
-      this.end();
-      for (var i = 0, len = events.length; i < len; ++i) {
-        obj.emit.apply(obj, events[i]);
-      }
-    }
-  };
-};
-
-/**
- * Check `req` and `res` to see if it has been modified.
- *
- * @param {IncomingMessage} req
- * @param {ServerResponse} res
- * @return {Boolean}
- * @api private
- */
-
-exports.modified = function(req, res, headers) {
-  var headers = headers || res._headers || {}
-    , modifiedSince = req.headers['if-modified-since']
-    , lastModified = headers['last-modified']
-    , noneMatch = req.headers['if-none-match']
-    , etag = headers['etag'];
-
-  if (noneMatch) noneMatch = noneMatch.split(/ *, */);
-
-  // check If-None-Match
-  if (noneMatch && etag && ~noneMatch.indexOf(etag)) {
-    return false;
-  }
-
-  // check If-Modified-Since
-  if (modifiedSince && lastModified) {
-    modifiedSince = new Date(modifiedSince);
-    lastModified = new Date(lastModified);
-    // Ignore invalid dates
-    if (!isNaN(modifiedSince.getTime())) {
-      if (lastModified <= modifiedSince) return false;
-    }
-  }
-  
-  return true;
-};
-
-/**
- * Strip `Content-*` headers from `res`.
- *
- * @param {ServerResponse} res
- * @api private
- */
-
-exports.removeContentHeaders = function(res){
-  Object.keys(res._headers).forEach(function(field){
-    if (0 == field.indexOf('content')) {
-      res.removeHeader(field);
-    }
-  });
-};
-
-/**
- * Check if `req` is a conditional GET request.
- *
- * @param {IncomingMessage} req
- * @return {Boolean}
- * @api private
- */
-
-exports.conditionalGET = function(req) {
-  return req.headers['if-modified-since']
-    || req.headers['if-none-match'];
-};
-
-/**
- * Respond with 401 "Unauthorized".
- *
- * @param {ServerResponse} res
- * @param {String} realm
- * @api private
- */
-
-exports.unauthorized = function(res, realm) {
-  res.statusCode = 401;
-  res.setHeader('WWW-Authenticate', 'Basic realm="' + realm + '"');
-  res.end('Unauthorized');
-};
-
-/**
- * Respond with 304 "Not Modified".
- *
- * @param {ServerResponse} res
- * @param {Object} headers
- * @api private
- */
-
-exports.notModified = function(res) {
-  exports.removeContentHeaders(res);
-  res.statusCode = 304;
-  res.end();
-};
-
-/**
- * Return an ETag in the form of `"<size>-<mtime>"`
- * from the given `stat`.
- *
- * @param {Object} stat
- * @return {String}
- * @api private
- */
-
-exports.etag = function(stat) {
-  return '"' + stat.size + '-' + Number(stat.mtime) + '"';
-};
-
-/**
- * Parse "Range" header `str` relative to the given file `size`.
- *
- * @param {Number} size
- * @param {String} str
- * @return {Array}
- * @api private
- */
-
-exports.parseRange = function(size, str){
-  var valid = true;
-  var arr = str.substr(6).split(',').map(function(range){
-    var range = range.split('-')
-      , start = parseInt(range[0], 10)
-      , end = parseInt(range[1], 10);
-
-    // -500
-    if (isNaN(start)) {
-      start = size - end;
-      end = size - 1;
-    // 500-
-    } else if (isNaN(end)) {
-      end = size - 1;
-    }
-
-    // Invalid
-    if (isNaN(start)
-      || isNaN(end)
-      || start > end
-      || start < 0) valid = false;
-
-    return {
-      start: start,
-      end: end
-    };
-  });
-
-  return valid ? arr : null;
-};
-
-/**
- * Parse the given Cache-Control `str`.
- *
- * @param {String} str
- * @return {Object}
- * @api private
- */
-
-exports.parseCacheControl = function(str){
-  var directives = str.split(',')
-    , obj = {};
-
-  for(var i = 0, len = directives.length; i < len; i++) {
-    var parts = directives[i].split('=')
-      , key = parts.shift().trim()
-      , val = parseInt(parts.shift(), 10);
-
-    obj[key] = isNaN(val) ? true : val;
-  }
-
-  return obj;
-};
diff --git a/package.json b/package.json
index 4ad8037..e63482d 100644
--- a/package.json
+++ b/package.json
@@ -1,24 +1,49 @@
 {
   "name": "connect",
-  "version": "2.0.3",
+  "version": "3.0.0",
   "description": "High performance middleware framework",
-  "keywords": ["framework", "web", "middleware", "connect", "rack"],
-  "repository": "git://github.com/senchalabs/connect.git",
   "author": "TJ Holowaychuk <tj at vision-media.ca> (http://tjholowaychuk.com)",
-  "repository": "git://github.com/senchalabs/connect",
+  "contributors": [
+    {
+      "name": "Douglas Christopher Wilson",
+      "email": "doug at somethingdoug.com"
+    },
+    {
+      "name": "Jonathan Ong",
+      "email": "me at jongleberry.com"
+    },
+    {
+      "name": "Tim Caswell",
+      "email": "tim at creationix.com"
+    }
+  ],
+  "keywords": [
+    "framework",
+    "web",
+    "middleware",
+    "connect",
+    "rack"
+  ],
+  "repository": "git://github.com/senchalabs/connect.git",
   "dependencies": {
-    "qs": "0.4.2",
-    "mime": "1.2.4",
-    "formidable": "1.0.9",
-    "crc": "0.1.0",
-    "debug": "*"
+    "debug": "0.8.1",
+    "parseurl": "1.0.1",
+    "escape-html": "1.0.1",
+    "utils-merge": "1.0.0"
   },
   "devDependencies": {
-    "should": "*",
-    "mocha": "*",
-    "jade": "*",
-    "dox": "*"
+    "istanbul": "0.2.10",
+    "mocha": "~1.19.0",
+    "should": "~3.3.1",
+    "supertest": "~0.12.0"
+  },
+  "license": "MIT",
+  "engines": {
+    "node": ">= 0.10.0"
   },
-  "main": "index",
-  "engines": { "node": ">= 0.5.0 < 0.7.0" }
-}
\ No newline at end of file
+  "scripts": {
+    "test": "mocha --require test/support/env --reporter dot",
+    "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot",
+    "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec"
+  }
+}
diff --git a/test/app.listen.js b/test/app.listen.js
new file mode 100644
index 0000000..8a109bf
--- /dev/null
+++ b/test/app.listen.js
@@ -0,0 +1,19 @@
+
+var connect = require('..');
+var request = require('supertest');
+
+describe('app.listen()', function(){
+  it('should wrap in an http.Server', function(done){
+    var app = connect();
+
+    app.use(function(req, res){
+      res.end();
+    });
+
+    app.listen(0, function(){
+      request(app)
+      .get('/')
+      .expect(200, done);
+    });
+  });
+});
diff --git a/test/fixtures/.hidden b/test/fixtures/.hidden
deleted file mode 100644
index b885243..0000000
--- a/test/fixtures/.hidden
+++ /dev/null
@@ -1 +0,0 @@
-I am hidden
\ No newline at end of file
diff --git a/test/fqdn.js b/test/fqdn.js
new file mode 100644
index 0000000..ad3d1f4
--- /dev/null
+++ b/test/fqdn.js
@@ -0,0 +1,86 @@
+
+var connect = require('..');
+var http = require('http');
+var request = require('supertest');
+
+describe('app.use()', function(){
+  var app;
+
+  beforeEach(function(){
+    app = connect();
+  });
+
+  it('should not obscure FQDNs', function(done){
+    app.use(function(req, res){
+      res.end(req.url);
+    });
+
+    app.handle({ method: 'GET', url: 'http://example.com/foo' }, {
+      end: function(str){
+        str.should.equal('http://example.com/foo');
+        done();
+      }
+    });
+  });
+
+  describe('with a connect app', function(){
+    it('should ignore FQDN in search', function (done) {
+      app.use('/proxy', function (req, res) {
+        res.end(req.url);
+      });
+
+      app.handle({ method: 'GET', url: '/proxy?url=http://example.com/blog/post/1' }, {
+        end: function(str){
+          str.should.equal('/?url=http://example.com/blog/post/1');
+          done();
+        }
+      });
+    });
+
+    it('should adjust FQDN req.url', function(done){
+      app.use('/blog', function(req, res){
+        res.end(req.url);
+      });
+
+      app.handle({ method: 'GET', url: 'http://example.com/blog/post/1' }, {
+        end: function(str){
+          str.should.equal('http://example.com/post/1');
+          done();
+        }
+      });
+    });
+
+    it('should adjust FQDN req.url with multiple handlers', function(done){
+      app.use(function(req,res,next) {
+        next();
+      });
+
+      app.use('/blog', function(req, res){
+        res.end(req.url);
+      });
+
+      app.handle({ method: 'GET', url: 'http://example.com/blog/post/1' }, {
+        end: function(str){
+          str.should.equal('http://example.com/post/1');
+          done();
+        }
+      });
+    });
+
+    it('should adjust FQDN req.url with multiple routed handlers', function(done) {
+      app.use('/blog', function(req,res,next) {
+        next();
+      });
+      app.use('/blog', function(req, res) {
+        res.end(req.url);
+      });
+
+      app.handle({ method: 'GET', url: 'http://example.com/blog/post/1' }, {
+        end: function(str){
+          str.should.equal('http://example.com/post/1');
+          done();
+        }
+      });
+    });
+  });
+});
diff --git a/test/mounting.js b/test/mounting.js
new file mode 100644
index 0000000..8da6184
--- /dev/null
+++ b/test/mounting.js
@@ -0,0 +1,232 @@
+
+var connect = require('..');
+var http = require('http');
+var request = require('supertest');
+var should = require('should');
+
+describe('app.use()', function(){
+  var app;
+
+  beforeEach(function(){
+    app = connect();
+  });
+
+  describe('with a connect app', function(){
+    it('should mount', function(done){
+      var blog = connect();
+
+      blog.use(function(req, res){
+        req.url.should.equal('/');
+        res.end('blog');
+      });
+
+      app.use('/blog', blog);
+
+      request(app)
+      .get('/blog')
+      .expect(200, 'blog', done);
+    });
+
+    it('should retain req.originalUrl', function(done){
+      var app = connect();
+
+      app.use('/blog', function(req, res){
+        res.end(req.originalUrl);
+      });
+
+      request(app)
+      .get('/blog/post/1')
+      .expect(200, '/blog/post/1', done);
+    });
+
+    it('should adjust req.url', function(done){
+      app.use('/blog', function(req, res){
+        res.end(req.url);
+      });
+
+      request(app)
+      .get('/blog/post/1')
+      .expect(200, '/post/1', done);
+    });
+
+    it('should strip trailing slash', function(done){
+      var blog = connect();
+    
+      blog.use(function(req, res){
+        req.url.should.equal('/');
+        res.end('blog');
+      });
+    
+      app.use('/blog/', blog);
+
+      request(app)
+      .get('/blog')
+      .expect('blog', done);
+    });
+
+    it('should set .route', function(){
+      var blog = connect();
+      var admin = connect();
+      app.use('/blog', blog);
+      blog.use('/admin', admin);
+      app.route.should.equal('/');
+      blog.route.should.equal('/blog');
+      admin.route.should.equal('/admin');
+    });
+
+    it('should not add trailing slash to req.url', function(done) {
+      app.use('/admin', function(req, res, next) {
+        next();
+      });
+
+      app.use(function(req, res, next) {
+        res.end(req.url);
+      });
+
+      request(app)
+      .get('/admin')
+      .expect('/admin', done);
+    })
+  })
+
+  describe('with a node app', function(){
+    it('should mount', function(done){
+      var blog = http.createServer(function(req, res){
+        req.url.should.equal('/');
+        res.end('blog');
+      });
+
+      app.use('/blog', blog);
+
+      request(app)
+      .get('/blog')
+      .expect('blog', done);
+    });
+  });
+
+  describe('error handling', function(){
+    it('should send errors to airty 4 fns', function(done){
+      app.use(function(req, res, next){
+        next(new Error('msg'));
+      })
+      app.use(function(err, req, res, next){
+        res.end('got error ' + err.message);
+      });
+
+      request(app)
+      .get('/')
+      .expect('got error msg', done);
+    })
+
+    it('should skip to non-error middleware', function(done){
+      var invoked = false;
+
+      app.use(function(req, res, next){
+        next(new Error('msg'));
+      })
+      app.use(function(req, res, next){
+        invoked = true;
+        next();
+      });
+      app.use(function(err, req, res, next){
+        res.end(invoked ? 'invoked' : err.message);
+      });
+
+      request(app)
+      .get('/')
+      .expect(200, 'msg', done);
+    })
+
+    it('should stack error fns', function(done){
+      app.use(function(req, res, next){
+        next(new Error('msg'));
+      })
+      app.use(function(err, req, res, next){
+        res.setHeader('X-Error', err.message);
+        next(err);
+      });
+      app.use(function(err, req, res, next){
+        res.end('got error ' + err.message);
+      });
+
+      request(app)
+      .get('/')
+      .expect('X-Error', 'msg')
+      .expect(200, 'got error msg', done);
+    })
+
+    it('should invoke error stack even when headers sent', function(done){
+      app.use(function(req, res, next){
+        res.end('0');
+        next(new Error('msg'));
+      });
+      app.use(function(err, req, res, next){
+        done();
+      });
+
+      request(app)
+      .get('/')
+      .end(function(){});
+    })
+  })
+
+  it('should be case insensitive (lower-case route, mixed-case request)', function(done){
+    var blog = http.createServer(function(req, res){
+      req.url.should.equal('/');
+      res.end('blog');
+    });
+
+    app.use('/blog', blog);
+
+    request(app)
+    .get('/BLog')
+    .expect('blog', done);
+  });
+
+  it('should be case insensitive (mixed-case route, lower-case request)', function(done){
+    var blog = http.createServer(function(req, res){
+      req.url.should.equal('/');
+      res.end('blog');
+    });
+
+    app.use('/BLog', blog);
+
+    request(app)
+    .get('/blog')
+    .expect('blog', done);
+  });
+
+  it('should be case insensitive (mixed-case route, mixed-case request)', function(done){
+    var blog = http.createServer(function(req, res){
+      req.url.should.equal('/');
+      res.end('blog');
+    });
+
+    app.use('/BLog', blog);
+
+    request(app)
+    .get('/blOG')
+    .expect('blog', done);
+  });
+
+  it('should ignore fn.arity > 4', function(done){
+    var invoked = [];
+
+    app.use(function(req, res, next, _a, _b){
+      invoked.push(0)
+      next();
+    });
+    app.use(function(req, res, next){
+      invoked.push(1)
+      next(new Error('err'));
+    });
+    app.use(function(err, req, res, next){
+      invoked.push(2);
+      res.end(invoked.join(','));
+    });
+
+    request(app)
+    .get('/')
+    .expect(200, '1,2', done);
+  });
+});
diff --git a/test/server.js b/test/server.js
new file mode 100644
index 0000000..2b31fca
--- /dev/null
+++ b/test/server.js
@@ -0,0 +1,170 @@
+
+var connect = require('..');
+var request = require('supertest');
+var should = require('should');
+
+describe('app', function(){
+  var app;
+
+  beforeEach(function(){
+    app = connect();
+  });
+
+  it('should inherit from event emitter', function(done){
+    app.on('foo', done);
+    app.emit('foo');
+  });
+
+  it('should work as middleware', function(done){
+    var http = require('http');
+
+    // custom server handler array
+    var handlers = [connect(), function(req, res, next){
+      res.writeHead(200, {'Content-Type': 'text/plain'});
+      res.end('Ok');
+    }];
+
+    // execute callbacks in sequence
+    var n = 0;
+    function run(req, res){
+      if (handlers[n]) {
+        handlers[n++](req, res, function(){
+          run(req, res);
+        });
+      }
+    }
+
+    // create a non-connect server
+    var server = http.createServer(run).listen(5556, function(){
+      http.get({
+        host: 'localhost',
+        port: 5556,
+        path: '/'
+      }, function(res){
+        var buf = '';
+        res.setEncoding('utf8');
+        res.on('data', function(s){ buf += s });
+        res.on('end', function(){
+          buf.should.eql('Ok');
+          server.close();
+          done();
+        });
+      });
+    });
+  });
+
+  it('should escape the 500 response body', function(done){
+    app.use(function(req, res, next){
+      next(new Error('error!'));
+    });
+    request(app)
+    .get('/')
+    .expect(/Error: error!<br>/)
+    .expect(/<br>    at/)
+    .expect(500, done);
+  })
+
+  describe('404 handler', function(){
+    it('should escape the 404 response body', function(done){
+      app.handle({ method: 'GET', url: '/foo/<script>stuff</script>' }, {
+        setHeader: function(){},
+        end: function(str){
+          this.statusCode.should.equal(404);
+          str.should.equal('Cannot GET /foo/<script>stuff</script>\n');
+          done();
+        }
+      });
+    });
+
+    it('shoud not fire after headers sent', function(done){
+      var app = connect();
+
+      app.use(function(req, res, next){
+        res.write('body');
+        res.end();
+        process.nextTick(next);
+      })
+
+      request(app)
+      .get('/')
+      .expect(200, done);
+    })
+
+    it('shoud have no body for HEAD', function(done){
+      var app = connect();
+
+      request(app)
+      .head('/')
+      .expect(404, '', done);
+    })
+  })
+
+  describe('error handler', function(){
+    it('should have escaped response body', function(done){
+      var app = connect();
+
+      app.use(function(req, res, next){
+        throw new Error('<script>alert()</script>');
+      })
+
+      request(app)
+      .get('/')
+      .expect(500, /<script>alert\(\)<\/script>/, done);
+    })
+
+    it('should use custom error code', function(done){
+      var app = connect();
+
+      app.use(function(req, res, next){
+        var err = new Error('ack!');
+        err.status = 503;
+        throw err;
+      })
+
+      request(app)
+      .get('/')
+      .expect(503, done);
+    })
+
+    it('should keep error statusCode', function(done){
+      var app = connect();
+
+      app.use(function(req, res, next){
+        res.statusCode = 503;
+        throw new Error('ack!');
+      })
+
+      request(app)
+      .get('/')
+      .expect(503, done);
+    })
+
+    it('shoud not fire after headers sent', function(done){
+      var app = connect();
+
+      app.use(function(req, res, next){
+        res.write('body');
+        res.end();
+        process.nextTick(function() {
+          next(new Error('ack!'));
+        });
+      })
+
+      request(app)
+      .get('/')
+      .expect(200, done);
+    })
+
+    it('shoud have no body for HEAD', function(done){
+      var app = connect();
+
+      app.use(function(req, res, next){
+        throw new Error('ack!');
+      });
+
+      request(app)
+      .head('/')
+      .expect(500, '', done);
+    });
+  });
+});
diff --git a/test/support/env.js b/test/support/env.js
new file mode 100644
index 0000000..eb938f3
--- /dev/null
+++ b/test/support/env.js
@@ -0,0 +1,2 @@
+
+process.env.NODE_ENV = 'test';

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



More information about the Pkg-javascript-commits mailing list