Fix license issues
[sdnc/oam.git] / dgbuilder / dgeflows / node_modules / serve-static / node_modules / send / index.js
1 /*!
2  * send
3  * Copyright(c) 2012 TJ Holowaychuk
4  * Copyright(c) 2014-2015 Douglas Christopher Wilson
5  * MIT Licensed
6  */
7
8 /**
9  * Module dependencies.
10  */
11
12 var debug = require('debug')('send')
13 var deprecate = require('depd')('send')
14 var destroy = require('destroy')
15 var escapeHtml = require('escape-html')
16   , parseRange = require('range-parser')
17   , Stream = require('stream')
18   , mime = require('mime')
19   , fresh = require('fresh')
20   , path = require('path')
21   , http = require('http')
22   , fs = require('fs')
23   , normalize = path.normalize
24   , join = path.join
25 var etag = require('etag')
26 var EventEmitter = require('events').EventEmitter;
27 var ms = require('ms');
28 var onFinished = require('on-finished')
29
30 /**
31  * Variables.
32  */
33 var extname = path.extname
34 var maxMaxAge = 60 * 60 * 24 * 365 * 1000; // 1 year
35 var resolve = path.resolve
36 var sep = path.sep
37 var toString = Object.prototype.toString
38 var upPathRegexp = /(?:^|[\\\/])\.\.(?:[\\\/]|$)/
39
40 /**
41  * Expose `send`.
42  */
43
44 exports = module.exports = send;
45
46 /**
47  * Expose mime module.
48  */
49
50 exports.mime = mime;
51
52 /**
53  * Shim EventEmitter.listenerCount for node.js < 0.10
54  */
55
56 /* istanbul ignore next */
57 var listenerCount = EventEmitter.listenerCount
58   || function(emitter, type){ return emitter.listeners(type).length; };
59
60 /**
61  * Return a `SendStream` for `req` and `path`.
62  *
63  * @param {Request} req
64  * @param {String} path
65  * @param {object} [options]
66  * @return {SendStream}
67  * @api public
68  */
69
70 function send(req, path, options) {
71   return new SendStream(req, path, options);
72 }
73
74 /**
75  * Initialize a `SendStream` with the given `path`.
76  *
77  * @param {Request} req
78  * @param {String} path
79  * @param {object} [options]
80  * @api private
81  */
82
83 function SendStream(req, path, options) {
84   var opts = options || {}
85
86   this.options = opts
87   this.path = path
88   this.req = req
89
90   this._etag = opts.etag !== undefined
91     ? Boolean(opts.etag)
92     : true
93
94   this._dotfiles = opts.dotfiles !== undefined
95     ? opts.dotfiles
96     : 'ignore'
97
98   if (['allow', 'deny', 'ignore'].indexOf(this._dotfiles) === -1) {
99     throw new TypeError('dotfiles option must be "allow", "deny", or "ignore"')
100   }
101
102   this._hidden = Boolean(opts.hidden)
103
104   if (opts.hidden !== undefined) {
105     deprecate('hidden: use dotfiles: \'' + (this._hidden ? 'allow' : 'ignore') + '\' instead')
106   }
107
108   // legacy support
109   if (opts.dotfiles === undefined) {
110     this._dotfiles = undefined
111   }
112
113   this._extensions = opts.extensions !== undefined
114     ? normalizeList(opts.extensions)
115     : []
116
117   this._index = opts.index !== undefined
118     ? normalizeList(opts.index)
119     : ['index.html']
120
121   this._lastModified = opts.lastModified !== undefined
122     ? Boolean(opts.lastModified)
123     : true
124
125   this._maxage = opts.maxAge || opts.maxage
126   this._maxage = typeof this._maxage === 'string'
127     ? ms(this._maxage)
128     : Number(this._maxage)
129   this._maxage = !isNaN(this._maxage)
130     ? Math.min(Math.max(0, this._maxage), maxMaxAge)
131     : 0
132
133   this._root = opts.root
134     ? resolve(opts.root)
135     : null
136
137   if (!this._root && opts.from) {
138     this.from(opts.from)
139   }
140 }
141
142 /**
143  * Inherits from `Stream.prototype`.
144  */
145
146 SendStream.prototype.__proto__ = Stream.prototype;
147
148 /**
149  * Enable or disable etag generation.
150  *
151  * @param {Boolean} val
152  * @return {SendStream}
153  * @api public
154  */
155
156 SendStream.prototype.etag = deprecate.function(function etag(val) {
157   val = Boolean(val);
158   debug('etag %s', val);
159   this._etag = val;
160   return this;
161 }, 'send.etag: pass etag as option');
162
163 /**
164  * Enable or disable "hidden" (dot) files.
165  *
166  * @param {Boolean} path
167  * @return {SendStream}
168  * @api public
169  */
170
171 SendStream.prototype.hidden = deprecate.function(function hidden(val) {
172   val = Boolean(val);
173   debug('hidden %s', val);
174   this._hidden = val;
175   this._dotfiles = undefined
176   return this;
177 }, 'send.hidden: use dotfiles option');
178
179 /**
180  * Set index `paths`, set to a falsy
181  * value to disable index support.
182  *
183  * @param {String|Boolean|Array} paths
184  * @return {SendStream}
185  * @api public
186  */
187
188 SendStream.prototype.index = deprecate.function(function index(paths) {
189   var index = !paths ? [] : normalizeList(paths);
190   debug('index %o', paths);
191   this._index = index;
192   return this;
193 }, 'send.index: pass index as option');
194
195 /**
196  * Set root `path`.
197  *
198  * @param {String} path
199  * @return {SendStream}
200  * @api public
201  */
202
203 SendStream.prototype.root = function(path){
204   path = String(path);
205   this._root = resolve(path)
206   return this;
207 };
208
209 SendStream.prototype.from = deprecate.function(SendStream.prototype.root,
210   'send.from: pass root as option');
211
212 SendStream.prototype.root = deprecate.function(SendStream.prototype.root,
213   'send.root: pass root as option');
214
215 /**
216  * Set max-age to `maxAge`.
217  *
218  * @param {Number} maxAge
219  * @return {SendStream}
220  * @api public
221  */
222
223 SendStream.prototype.maxage = deprecate.function(function maxage(maxAge) {
224   maxAge = typeof maxAge === 'string'
225     ? ms(maxAge)
226     : Number(maxAge);
227   if (isNaN(maxAge)) maxAge = 0;
228   if (Infinity == maxAge) maxAge = 60 * 60 * 24 * 365 * 1000;
229   debug('max-age %d', maxAge);
230   this._maxage = maxAge;
231   return this;
232 }, 'send.maxage: pass maxAge as option');
233
234 /**
235  * Emit error with `status`.
236  *
237  * @param {Number} status
238  * @api private
239  */
240
241 SendStream.prototype.error = function(status, err){
242   var res = this.res;
243   var msg = http.STATUS_CODES[status];
244
245   err = err || new Error(msg);
246   err.status = status;
247
248   // emit if listeners instead of responding
249   if (listenerCount(this, 'error') !== 0) {
250     return this.emit('error', err);
251   }
252
253   // wipe all existing headers
254   res._headers = undefined;
255
256   res.statusCode = err.status;
257   res.end(msg);
258 };
259
260 /**
261  * Check if the pathname ends with "/".
262  *
263  * @return {Boolean}
264  * @api private
265  */
266
267 SendStream.prototype.hasTrailingSlash = function(){
268   return '/' == this.path[this.path.length - 1];
269 };
270
271 /**
272  * Check if this is a conditional GET request.
273  *
274  * @return {Boolean}
275  * @api private
276  */
277
278 SendStream.prototype.isConditionalGET = function(){
279   return this.req.headers['if-none-match']
280     || this.req.headers['if-modified-since'];
281 };
282
283 /**
284  * Strip content-* header fields.
285  *
286  * @api private
287  */
288
289 SendStream.prototype.removeContentHeaderFields = function(){
290   var res = this.res;
291   Object.keys(res._headers).forEach(function(field){
292     if (0 == field.indexOf('content')) {
293       res.removeHeader(field);
294     }
295   });
296 };
297
298 /**
299  * Respond with 304 not modified.
300  *
301  * @api private
302  */
303
304 SendStream.prototype.notModified = function(){
305   var res = this.res;
306   debug('not modified');
307   this.removeContentHeaderFields();
308   res.statusCode = 304;
309   res.end();
310 };
311
312 /**
313  * Raise error that headers already sent.
314  *
315  * @api private
316  */
317
318 SendStream.prototype.headersAlreadySent = function headersAlreadySent(){
319   var err = new Error('Can\'t set headers after they are sent.');
320   debug('headers already sent');
321   this.error(500, err);
322 };
323
324 /**
325  * Check if the request is cacheable, aka
326  * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
327  *
328  * @return {Boolean}
329  * @api private
330  */
331
332 SendStream.prototype.isCachable = function(){
333   var res = this.res;
334   return (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode;
335 };
336
337 /**
338  * Handle stat() error.
339  *
340  * @param {Error} err
341  * @api private
342  */
343
344 SendStream.prototype.onStatError = function(err){
345   var notfound = ['ENOENT', 'ENAMETOOLONG', 'ENOTDIR'];
346   if (~notfound.indexOf(err.code)) return this.error(404, err);
347   this.error(500, err);
348 };
349
350 /**
351  * Check if the cache is fresh.
352  *
353  * @return {Boolean}
354  * @api private
355  */
356
357 SendStream.prototype.isFresh = function(){
358   return fresh(this.req.headers, this.res._headers);
359 };
360
361 /**
362  * Check if the range is fresh.
363  *
364  * @return {Boolean}
365  * @api private
366  */
367
368 SendStream.prototype.isRangeFresh = function isRangeFresh(){
369   var ifRange = this.req.headers['if-range'];
370
371   if (!ifRange) return true;
372
373   return ~ifRange.indexOf('"')
374     ? ~ifRange.indexOf(this.res._headers['etag'])
375     : Date.parse(this.res._headers['last-modified']) <= Date.parse(ifRange);
376 };
377
378 /**
379  * Redirect to `path`.
380  *
381  * @param {String} path
382  * @api private
383  */
384
385 SendStream.prototype.redirect = function(path){
386   if (listenerCount(this, 'directory') !== 0) {
387     return this.emit('directory');
388   }
389
390   if (this.hasTrailingSlash()) return this.error(403);
391   var res = this.res;
392   path += '/';
393   res.statusCode = 301;
394   res.setHeader('Content-Type', 'text/html; charset=utf-8');
395   res.setHeader('Location', path);
396   res.end('Redirecting to <a href="' + escapeHtml(path) + '">' + escapeHtml(path) + '</a>\n');
397 };
398
399 /**
400  * Pipe to `res.
401  *
402  * @param {Stream} res
403  * @return {Stream} res
404  * @api public
405  */
406
407 SendStream.prototype.pipe = function(res){
408   var self = this
409     , args = arguments
410     , root = this._root;
411
412   // references
413   this.res = res;
414
415   // decode the path
416   var path = decode(this.path)
417   if (path === -1) return this.error(400)
418
419   // null byte(s)
420   if (~path.indexOf('\0')) return this.error(400);
421
422   var parts
423   if (root !== null) {
424     // malicious path
425     if (upPathRegexp.test(normalize('.' + sep + path))) {
426       debug('malicious path "%s"', path)
427       return this.error(403)
428     }
429
430     // join / normalize from optional root dir
431     path = normalize(join(root, path))
432     root = normalize(root + sep)
433
434     // explode path parts
435     parts = path.substr(root.length).split(sep)
436   } else {
437     // ".." is malicious without "root"
438     if (upPathRegexp.test(path)) {
439       debug('malicious path "%s"', path)
440       return this.error(403)
441     }
442
443     // explode path parts
444     parts = normalize(path).split(sep)
445
446     // resolve the path
447     path = resolve(path)
448   }
449
450   // dotfile handling
451   if (containsDotFile(parts)) {
452     var access = this._dotfiles
453
454     // legacy support
455     if (access === undefined) {
456       access = parts[parts.length - 1][0] === '.'
457         ? (this._hidden ? 'allow' : 'ignore')
458         : 'allow'
459     }
460
461     debug('%s dotfile "%s"', access, path)
462     switch (access) {
463       case 'allow':
464         break
465       case 'deny':
466         return this.error(403)
467       case 'ignore':
468       default:
469         return this.error(404)
470     }
471   }
472
473   // index file support
474   if (this._index.length && this.path[this.path.length - 1] === '/') {
475     this.sendIndex(path);
476     return res;
477   }
478
479   this.sendFile(path);
480   return res;
481 };
482
483 /**
484  * Transfer `path`.
485  *
486  * @param {String} path
487  * @api public
488  */
489
490 SendStream.prototype.send = function(path, stat){
491   var len = stat.size;
492   var options = this.options
493   var opts = {}
494   var res = this.res;
495   var req = this.req;
496   var ranges = req.headers.range;
497   var offset = options.start || 0;
498
499   if (res._header) {
500     // impossible to send now
501     return this.headersAlreadySent();
502   }
503
504   debug('pipe "%s"', path)
505
506   // set header fields
507   this.setHeader(path, stat);
508
509   // set content-type
510   this.type(path);
511
512   // conditional GET support
513   if (this.isConditionalGET()
514     && this.isCachable()
515     && this.isFresh()) {
516     return this.notModified();
517   }
518
519   // adjust len to start/end options
520   len = Math.max(0, len - offset);
521   if (options.end !== undefined) {
522     var bytes = options.end - offset + 1;
523     if (len > bytes) len = bytes;
524   }
525
526   // Range support
527   if (ranges) {
528     ranges = parseRange(len, ranges);
529
530     // If-Range support
531     if (!this.isRangeFresh()) {
532       debug('range stale');
533       ranges = -2;
534     }
535
536     // unsatisfiable
537     if (-1 == ranges) {
538       debug('range unsatisfiable');
539       res.setHeader('Content-Range', 'bytes */' + stat.size);
540       return this.error(416);
541     }
542
543     // valid (syntactically invalid/multiple ranges are treated as a regular response)
544     if (-2 != ranges && ranges.length === 1) {
545       debug('range %j', ranges);
546
547       // Content-Range
548       res.statusCode = 206;
549       res.setHeader('Content-Range', 'bytes '
550         + ranges[0].start
551         + '-'
552         + ranges[0].end
553         + '/'
554         + len);
555
556       offset += ranges[0].start;
557       len = ranges[0].end - ranges[0].start + 1;
558     }
559   }
560
561   // clone options
562   for (var prop in options) {
563     opts[prop] = options[prop]
564   }
565
566   // set read options
567   opts.start = offset
568   opts.end = Math.max(offset, offset + len - 1)
569
570   // content-length
571   res.setHeader('Content-Length', len);
572
573   // HEAD support
574   if ('HEAD' == req.method) return res.end();
575
576   this.stream(path, opts)
577 };
578
579 /**
580  * Transfer file for `path`.
581  *
582  * @param {String} path
583  * @api private
584  */
585 SendStream.prototype.sendFile = function sendFile(path) {
586   var i = 0
587   var self = this
588
589   debug('stat "%s"', path);
590   fs.stat(path, function onstat(err, stat) {
591     if (err && err.code === 'ENOENT'
592       && !extname(path)
593       && path[path.length - 1] !== sep) {
594       // not found, check extensions
595       return next(err)
596     }
597     if (err) return self.onStatError(err)
598     if (stat.isDirectory()) return self.redirect(self.path)
599     self.emit('file', path, stat)
600     self.send(path, stat)
601   })
602
603   function next(err) {
604     if (self._extensions.length <= i) {
605       return err
606         ? self.onStatError(err)
607         : self.error(404)
608     }
609
610     var p = path + '.' + self._extensions[i++]
611
612     debug('stat "%s"', p)
613     fs.stat(p, function (err, stat) {
614       if (err) return next(err)
615       if (stat.isDirectory()) return next()
616       self.emit('file', p, stat)
617       self.send(p, stat)
618     })
619   }
620 }
621
622 /**
623  * Transfer index for `path`.
624  *
625  * @param {String} path
626  * @api private
627  */
628 SendStream.prototype.sendIndex = function sendIndex(path){
629   var i = -1;
630   var self = this;
631
632   function next(err){
633     if (++i >= self._index.length) {
634       if (err) return self.onStatError(err);
635       return self.error(404);
636     }
637
638     var p = join(path, self._index[i]);
639
640     debug('stat "%s"', p);
641     fs.stat(p, function(err, stat){
642       if (err) return next(err);
643       if (stat.isDirectory()) return next();
644       self.emit('file', p, stat);
645       self.send(p, stat);
646     });
647   }
648
649   next();
650 };
651
652 /**
653  * Stream `path` to the response.
654  *
655  * @param {String} path
656  * @param {Object} options
657  * @api private
658  */
659
660 SendStream.prototype.stream = function(path, options){
661   // TODO: this is all lame, refactor meeee
662   var finished = false;
663   var self = this;
664   var res = this.res;
665   var req = this.req;
666
667   // pipe
668   var stream = fs.createReadStream(path, options);
669   this.emit('stream', stream);
670   stream.pipe(res);
671
672   // response finished, done with the fd
673   onFinished(res, function onfinished(){
674     finished = true;
675     destroy(stream);
676   });
677
678   // error handling code-smell
679   stream.on('error', function onerror(err){
680     // request already finished
681     if (finished) return;
682
683     // clean up stream
684     finished = true;
685     destroy(stream);
686
687     // error
688     self.onStatError(err);
689   });
690
691   // end
692   stream.on('end', function onend(){
693     self.emit('end');
694   });
695 };
696
697 /**
698  * Set content-type based on `path`
699  * if it hasn't been explicitly set.
700  *
701  * @param {String} path
702  * @api private
703  */
704
705 SendStream.prototype.type = function(path){
706   var res = this.res;
707   if (res.getHeader('Content-Type')) return;
708   var type = mime.lookup(path);
709   var charset = mime.charsets.lookup(type);
710   debug('content-type %s', type);
711   res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
712 };
713
714 /**
715  * Set response header fields, most
716  * fields may be pre-defined.
717  *
718  * @param {String} path
719  * @param {Object} stat
720  * @api private
721  */
722
723 SendStream.prototype.setHeader = function setHeader(path, stat){
724   var res = this.res;
725
726   this.emit('headers', res, path, stat);
727
728   if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes');
729   if (!res.getHeader('Date')) res.setHeader('Date', new Date().toUTCString());
730   if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(this._maxage / 1000));
731
732   if (this._lastModified && !res.getHeader('Last-Modified')) {
733     var modified = stat.mtime.toUTCString()
734     debug('modified %s', modified)
735     res.setHeader('Last-Modified', modified)
736   }
737
738   if (this._etag && !res.getHeader('ETag')) {
739     var val = etag(stat)
740     debug('etag %s', val)
741     res.setHeader('ETag', val)
742   }
743 };
744
745 /**
746  * Determine if path parts contain a dotfile.
747  *
748  * @api private
749  */
750
751 function containsDotFile(parts) {
752   for (var i = 0; i < parts.length; i++) {
753     if (parts[i][0] === '.') {
754       return true
755     }
756   }
757
758   return false
759 }
760
761 /**
762  * decodeURIComponent.
763  *
764  * Allows V8 to only deoptimize this fn instead of all
765  * of send().
766  *
767  * @param {String} path
768  * @api private
769  */
770
771 function decode(path) {
772   try {
773     return decodeURIComponent(path)
774   } catch (err) {
775     return -1
776   }
777 }
778
779 /**
780  * Normalize the index option into an array.
781  *
782  * @param {boolean|string|array} val
783  * @api private
784  */
785
786 function normalizeList(val){
787   return [].concat(val || [])
788 }