Tizen 2.0 Release
[platform/framework/web/web-ui-fw.git] / libs / js / jquery-mobile-1.2.0 / node_modules / grunt / node_modules / connect / node_modules / send / lib / send.js
1
2 /**
3  * Module dependencies.
4  */
5
6 var debug = require('debug')('send')
7   , parseRange = require('range-parser')
8   , Stream = require('stream')
9   , mime = require('mime')
10   , fresh = require('fresh')
11   , path = require('path')
12   , http = require('http')
13   , fs = require('fs')
14   , basename = path.basename
15   , normalize = path.normalize
16   , join = path.join
17   , utils = require('./utils');
18
19 /**
20  * Expose `send`.
21  */
22
23 exports = module.exports = send;
24
25 /**
26  * Expose mime module.
27  */
28
29 exports.mime = mime;
30
31 /**
32  * Return a `SendStream` for `req` and `path`.
33  *
34  * @param {Request} req
35  * @param {String} path
36  * @return {SendStream}
37  * @api public
38  */
39
40 function send(req, path) {
41   return new SendStream(req, path);
42 }
43
44 /**
45  * Initialize a `SendStream` with the given `path`.
46  *
47  * Events:
48  *
49  *  - `error` an error occurred
50  *  - `stream` file streaming has started
51  *  - `end` streaming has completed
52  *  - `directory` a directory was requested
53  *
54  * @param {Request} req
55  * @param {String} path
56  * @api private
57  */
58
59 function SendStream(req, path) {
60   var self = this;
61   this.req = req;
62   this.path = path;
63   this.maxage(0);
64   this.hidden(false);
65   this.index('index.html');
66 }
67
68 /**
69  * Inherits from `Stream.prototype`.
70  */
71
72 SendStream.prototype.__proto__ = Stream.prototype;
73
74 /**
75  * Enable or disable "hidden" (dot) files.
76  *
77  * @param {Boolean} path
78  * @return {SendStream}
79  * @api public
80  */
81
82 SendStream.prototype.hidden = function(val){
83   debug('hidden %s', val);
84   this._hidden = val;
85   return this;
86 };
87
88 /**
89  * Set index `path`, set to a falsy
90  * value to disable index support.
91  *
92  * @param {String|Boolean} path
93  * @return {SendStream}
94  * @api public
95  */
96
97 SendStream.prototype.index = function(path){
98   debug('index %s', path);
99   this._index = path;
100   return this;
101 };
102
103 /**
104  * Set root `path`.
105  *
106  * @param {String} path
107  * @return {SendStream}
108  * @api public
109  */
110
111 SendStream.prototype.root = 
112 SendStream.prototype.from = function(path){
113   this._root = normalize(path);
114   return this;
115 };
116
117 /**
118  * Set max-age to `ms`.
119  *
120  * @param {Number} ms
121  * @return {SendStream}
122  * @api public
123  */
124
125 SendStream.prototype.maxage = function(ms){
126   if (Infinity == ms) ms = 60 * 60 * 24 * 365 * 1000;
127   debug('max-age %d', ms);
128   this._maxage = ms;
129   return this;
130 };
131
132 /**
133  * Emit error with `status`.
134  *
135  * @param {Number} status
136  * @api private
137  */
138
139 SendStream.prototype.error = function(status, err){
140   var res = this.res;
141   var msg = http.STATUS_CODES[status];
142   err = err || new Error(msg);
143   err.status = status;
144   if (this.listeners('error').length) return this.emit('error', err);
145   res.statusCode = err.status;
146   res.end(msg);
147 };
148
149 /**
150  * Check if the pathname is potentially malicious.
151  *
152  * @return {Boolean}
153  * @api private
154  */
155
156 SendStream.prototype.isMalicious = function(){
157   return !this._root && ~this.path.indexOf('..');
158 };
159
160 /**
161  * Check if the pathname ends with "/".
162  *
163  * @return {Boolean}
164  * @api private
165  */
166
167 SendStream.prototype.hasTrailingSlash = function(){
168   return '/' == this.path[this.path.length - 1];
169 };
170
171 /**
172  * Check if the basename leads with ".".
173  *
174  * @return {Boolean}
175  * @api private
176  */
177
178 SendStream.prototype.hasLeadingDot = function(){
179   return '.' == basename(this.path)[0];
180 };
181
182 /**
183  * Check if this is a conditional GET request.
184  *
185  * @return {Boolean}
186  * @api private
187  */
188
189 SendStream.prototype.isConditionalGET = function(){
190   return this.req.headers['if-none-match']
191     || this.req.headers['if-modified-since'];
192 };
193
194 /**
195  * Strip content-* header fields.
196  *
197  * @api private
198  */
199
200 SendStream.prototype.removeContentHeaderFields = function(){
201   var res = this.res;
202   Object.keys(res._headers).forEach(function(field){
203     if (0 == field.indexOf('content')) {
204       res.removeHeader(field);
205     }
206   });
207 };
208
209 /**
210  * Respond with 304 not modified.
211  *
212  * @api private
213  */
214
215 SendStream.prototype.notModified = function(){
216   var res = this.res;
217   debug('not modified');
218   this.removeContentHeaderFields();
219   res.statusCode = 304;
220   res.end();
221 };
222
223 /**
224  * Check if the request is cacheable, aka
225  * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
226  *
227  * @return {Boolean}
228  * @api private
229  */
230
231 SendStream.prototype.isCachable = function(){
232   var res = this.res;
233   return (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode;
234 };
235
236 /**
237  * Handle stat() error.
238  *
239  * @param {Error} err
240  * @api private
241  */
242
243 SendStream.prototype.onStatError = function(err){
244   var notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR'];
245   if (~notfound.indexOf(err.code)) return this.error(404, err);
246   this.error(500, err);
247 };
248
249 /**
250  * Check if the cache is fresh.
251  *
252  * @return {Boolean}
253  * @api private
254  */
255
256 SendStream.prototype.isFresh = function(){
257   return fresh(this.req.headers, this.res._headers);
258 };
259
260 /**
261  * Redirect to `path`.
262  *
263  * @param {String} path
264  * @api private
265  */
266
267 SendStream.prototype.redirect = function(path){
268   if (this.listeners('directory').length) return this.emit('directory');
269   var res = this.res;
270   path += '/';
271   res.statusCode = 301;
272   res.setHeader('Location', path);
273   res.end('Redirecting to ' + utils.escape(path));
274 };
275
276 /**
277  * Pipe to `res.
278  *
279  * @param {Stream} res
280  * @return {Stream} res
281  * @api public
282  */
283
284 SendStream.prototype.pipe = function(res){
285   var self = this
286     , args = arguments
287     , path = this.path
288     , root = this._root;
289
290   // references
291   this.res = res;
292
293   // invalid request uri
294   path = utils.decode(path);
295   if (-1 == path) return this.error(400);
296
297   // null byte(s)
298   if (~path.indexOf('\0')) return this.error(400);
299
300   // join / normalize from optional root dir
301   if (root) path = normalize(join(this._root, path));
302
303   // ".." is malicious without "root"
304   if (this.isMalicious()) return this.error(403);
305
306   // malicious path
307   if (root && 0 != path.indexOf(root)) return this.error(403);
308
309   // hidden file support
310   if (!this._hidden && this.hasLeadingDot()) return this.error(404);
311
312   // index file support
313   if (this._index && this.hasTrailingSlash()) path += this._index;
314
315   debug('stat "%s"', path);
316   fs.stat(path, function(err, stat){
317     if (err) return self.onStatError(err);
318     if (stat.isDirectory()) return self.redirect(self.path);
319     self.send(path, stat);
320   });
321
322   return res;
323 };
324
325 /**
326  * Transfer `path`.
327  *
328  * @param {String} path
329  * @api public
330  */
331
332 SendStream.prototype.send = function(path, stat){
333   var options = {};
334   var len = stat.size;
335   var res = this.res;
336   var req = this.req;
337   var ranges = req.headers.range;
338
339   // set header fields
340   this.setHeader(stat);
341
342   // set content-type
343   this.type(path);
344
345   // conditional GET support
346   if (this.isConditionalGET()
347     && this.isCachable()
348     && this.isFresh()) {
349     return this.notModified();
350   }
351
352   // Range support
353   if (ranges) {
354     ranges = parseRange(len, ranges);
355
356     // unsatisfiable
357     if (-1 == ranges) {
358       res.setHeader('Content-Range', 'bytes */' + stat.size);
359       return this.error(416);
360     }
361
362     // valid (syntactically invalid ranges are treated as a regular response)
363     if (-2 != ranges) {
364       options.start = ranges[0].start;
365       options.end = ranges[0].end;
366
367       // Content-Range
368       len = options.end - options.start + 1;
369       res.statusCode = 206;
370       res.setHeader('Content-Range', 'bytes '
371         + options.start
372         + '-'
373         + options.end
374         + '/'
375         + stat.size);
376     }
377   }
378
379   // content-length
380   res.setHeader('Content-Length', len);
381
382   // HEAD support
383   if ('HEAD' == req.method) return res.end();
384
385   this.stream(path, options);
386 };
387
388 /**
389  * Stream `path` to the response.
390  *
391  * @param {String} path
392  * @param {Object} options
393  * @api private
394  */
395
396 SendStream.prototype.stream = function(path, options){
397   // TODO: this is all lame, refactor meeee
398   var self = this;
399   var res = this.res;
400   var req = this.req;
401
402   // pipe
403   var stream = fs.createReadStream(path, options);
404   this.emit('stream', stream);
405   stream.pipe(res);
406
407   // socket closed, done with the fd
408   req.on('close', stream.destroy.bind(stream));
409
410   // error handling code-smell
411   stream.on('error', function(err){
412     // no hope in responding
413     if (res._header) {
414       console.error(err.stack);
415       req.destroy();
416       return;
417     }
418
419     // 500
420     err.status = 500;
421     self.emit('error', err);
422   });
423
424   // end
425   stream.on('end', function(){
426     self.emit('end');
427   });
428 };
429
430 /**
431  * Set content-type based on `path`
432  * if it hasn't been explicitly set.
433  *
434  * @param {String} path
435  * @api private
436  */
437
438 SendStream.prototype.type = function(path){
439   var res = this.res;
440   if (res.getHeader('Content-Type')) return;
441   var type = mime.lookup(path);
442   var charset = mime.charsets.lookup(type);
443   debug('content-type %s', type);
444   res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
445 };
446
447 /**
448  * Set reaponse header fields, most
449  * fields may be pre-defined.
450  *
451  * @param {Object} stat
452  * @api private
453  */
454
455 SendStream.prototype.setHeader = function(stat){
456   var res = this.res;
457   if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes');
458   if (!res.getHeader('ETag')) res.setHeader('ETag', utils.etag(stat));
459   if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString());
460   if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + (this._maxage / 1000));
461   if (!res.getHeader('Last-Modified')) res.setHeader('Last-Modified', stat.mtime.toUTCString());
462 };