]> git.cworth.org Git - obsolete/notmuch-web/blob - node_modules/express/node_modules/connect/lib/middleware/static.js
Install the "express" node module via npm
[obsolete/notmuch-web] / node_modules / express / node_modules / connect / lib / middleware / static.js
1
2 /*!
3  * Connect - staticProvider
4  * Copyright(c) 2010 Sencha Inc.
5  * Copyright(c) 2011 TJ Holowaychuk
6  * MIT Licensed
7  */
8
9 /**
10  * Module dependencies.
11  */
12
13 var fs = require('fs')
14   , join = require('path').join
15   , utils = require('../utils')
16   , Buffer = require('buffer').Buffer
17   , parse = require('url').parse
18   , mime = require('mime');
19
20 /**
21  * Static file server with the given `root` path.
22  *
23  * Examples:
24  *
25  *     var oneDay = 86400000;
26  *
27  *     connect(
28  *       connect.static(__dirname + '/public')
29  *     ).listen(3000);
30  *
31  *     connect(
32  *       connect.static(__dirname + '/public', { maxAge: oneDay })
33  *     ).listen(3000);
34  *
35  * Options:
36  *
37  *    - `maxAge`   Browser cache maxAge in milliseconds, defaults to 0
38  *
39  * @param {String} root
40  * @param {Object} options
41  * @return {Function}
42  * @api public
43  */
44
45 exports = module.exports = function static(root, options){
46   options = options || {};
47
48   // root required
49   if (!root) throw new Error('static() root path required');
50   options.root = root;
51
52   return function static(req, res, next) {
53     options.path = req.url;
54     send(req, res, next, options);
55   };
56 };
57
58 /**
59  * Respond with 403 "Forbidden".
60  *
61  * @param {ServerResponse} res
62  * @api private
63  */
64
65 function forbidden(res) {
66   var body = 'Forbidden';
67   res.setHeader('Content-Type', 'text/plain');
68   res.setHeader('Content-Length', body.length);
69   res.statusCode = 403;
70   res.end(body);
71 }
72
73 /**
74  * Respond with 416  "Requested Range Not Satisfiable"
75  *
76  * @param {ServerResponse} res
77  * @api private
78  */
79
80 function invalidRange(res) {
81   var body = 'Requested Range Not Satisfiable';
82   res.setHeader('Content-Type', 'text/plain');
83   res.setHeader('Content-Length', body.length);
84   res.statusCode = 416;
85   res.end(body);
86 }
87
88 /**
89  * Attempt to tranfer the requseted file to `res`.
90  *
91  * @param {ServerRequest}
92  * @param {ServerResponse}
93  * @param {Function} next
94  * @param {Object} options
95  * @api private
96  */
97
98 var send = exports.send = function(req, res, next, options){
99   options = options || {};
100   if (!options.path) throw new Error('path required');
101
102   // setup
103   var maxAge = options.maxAge || 0
104     , ranges = req.headers.range
105     , head = 'HEAD' == req.method
106     , root = options.root
107     , fn = options.callback;
108
109   // replace next() with callback when available
110   if (fn) next = fn;
111
112   // ignore non-GET requests
113   if ('GET' != req.method && !head) return next();
114
115   // parse url
116   var url = parse(options.path)
117     , path = decodeURIComponent(url.pathname)
118     , type;
119
120   // potentially malicious path
121   if (~path.indexOf('..')) return fn
122     ? fn(new Error('Forbidden'))
123     : forbidden(res);
124
125   // join from optional root dir
126   path = join(options.root, path);
127
128   // index.html support
129   if ('/' == path[path.length - 1]) path += 'index.html';
130
131   // mime type
132   type = mime.lookup(path);
133
134   fs.stat(path, function(err, stat){
135     // ignore ENOENT
136     if (err) {
137       if (fn) return fn(err);
138       return 'ENOENT' == err.code
139         ? next()
140         : next(err);
141     // ignore directories
142     } else if (stat.isDirectory()) {
143       return fn
144         ? fn(new Error('Cannot Transfer Directory'))
145         : next();
146     }
147
148     // we have a Range request
149     if (ranges) {
150       ranges = utils.parseRange(stat.size, ranges);
151       // valid
152       if (ranges) {
153         // TODO: stream options
154         // TODO: multiple support
155         var stream = fs.createReadStream(path, ranges[0])
156           , start = ranges[0].start
157           , end = ranges[0].end;
158         res.statusCode = 206;
159         res.setHeader('Content-Range', 'bytes '
160           + start
161           + '-'
162           + end
163           + '/'
164           + stat.size);
165       // invalid
166       } else {
167         return fn
168           ? fn(new Error('Requested Range Not Satisfiable'))
169           : invalidRange(res);
170       }
171     // stream the entire file
172     } else {
173       res.setHeader('Content-Length', stat.size);
174       res.setHeader('Cache-Control', 'public, max-age=' + (maxAge / 1000));
175       res.setHeader('Last-Modified', stat.mtime.toUTCString());
176       res.setHeader('ETag', utils.etag(stat));
177
178       // conditional GET support
179       if (utils.conditionalGET(req)) {
180         if (!utils.modified(req, res)) {
181           return utils.notModified(res);
182         }
183       }
184
185       // read stream
186       var stream = fs.createReadStream(path);
187     }
188
189     // transfer
190     if (!res.getHeader('content-type')) {
191       var charset = mime.charsets.lookup(type);
192       res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
193     }
194     res.setHeader('Accept-Ranges', 'bytes');
195
196     if (head) return res.end();
197     stream.pipe(res);
198     if (fn) {
199       res.connection.on('error', fn);
200       stream.on('end', fn);
201     }
202   });
203 };