Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / 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 'use strict'
9
10 /**
11  * Module dependencies.
12  * @private
13  */
14
15 var createError = require('http-errors')
16 var debug = require('debug')('send')
17 var deprecate = require('depd')('send')
18 var destroy = require('destroy')
19 var escapeHtml = require('escape-html')
20   , parseRange = require('range-parser')
21   , Stream = require('stream')
22   , mime = require('mime')
23   , fresh = require('fresh')
24   , path = require('path')
25   , fs = require('fs')
26   , normalize = path.normalize
27   , join = path.join
28 var etag = require('etag')
29 var EventEmitter = require('events').EventEmitter;
30 var ms = require('ms');
31 var onFinished = require('on-finished')
32 var statuses = require('statuses')
33
34 /**
35  * Variables.
36  */
37 var extname = path.extname
38 var maxMaxAge = 60 * 60 * 24 * 365 * 1000; // 1 year
39 var resolve = path.resolve
40 var sep = path.sep
41 var toString = Object.prototype.toString
42 var upPathRegexp = /(?:^|[\\\/])\.\.(?:[\\\/]|$)/
43
44 /**
45  * Module exports.
46  * @public
47  */
48
49 module.exports = send
50 module.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 {object} req
64  * @param {string} path
65  * @param {object} [options]
66  * @return {SendStream}
67  * @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  * @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 (this._dotfiles !== 'ignore' && this._dotfiles !== 'allow' && this._dotfiles !== 'deny') {
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, 'extensions option')
115     : []
116
117   this._index = opts.index !== undefined
118     ? normalizeList(opts.index, 'index option')
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, 'paths argument');
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  * @param {Error} [error]
239  * @private
240  */
241
242 SendStream.prototype.error = function error(status, error) {
243   // emit if listeners instead of responding
244   if (listenerCount(this, 'error') !== 0) {
245     return this.emit('error', createError(error, status, {
246       expose: false
247     }))
248   }
249
250   var res = this.res
251   var msg = statuses[status]
252
253   // wipe all existing headers
254   res._headers = null
255
256   // send basic response
257   res.statusCode = status
258   res.setHeader('Content-Type', 'text/plain; charset=UTF-8')
259   res.setHeader('Content-Length', Buffer.byteLength(msg))
260   res.setHeader('X-Content-Type-Options', 'nosniff')
261   res.end(msg)
262 }
263
264 /**
265  * Check if the pathname ends with "/".
266  *
267  * @return {Boolean}
268  * @api private
269  */
270
271 SendStream.prototype.hasTrailingSlash = function(){
272   return '/' == this.path[this.path.length - 1];
273 };
274
275 /**
276  * Check if this is a conditional GET request.
277  *
278  * @return {Boolean}
279  * @api private
280  */
281
282 SendStream.prototype.isConditionalGET = function(){
283   return this.req.headers['if-none-match']
284     || this.req.headers['if-modified-since'];
285 };
286
287 /**
288  * Strip content-* header fields.
289  *
290  * @private
291  */
292
293 SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields() {
294   var res = this.res
295   var headers = Object.keys(res._headers || {})
296
297   for (var i = 0; i < headers.length; i++) {
298     var header = headers[i]
299     if (header.substr(0, 8) === 'content-' && header !== 'content-location') {
300       res.removeHeader(header)
301     }
302   }
303 }
304
305 /**
306  * Respond with 304 not modified.
307  *
308  * @api private
309  */
310
311 SendStream.prototype.notModified = function(){
312   var res = this.res;
313   debug('not modified');
314   this.removeContentHeaderFields();
315   res.statusCode = 304;
316   res.end();
317 };
318
319 /**
320  * Raise error that headers already sent.
321  *
322  * @api private
323  */
324
325 SendStream.prototype.headersAlreadySent = function headersAlreadySent(){
326   var err = new Error('Can\'t set headers after they are sent.');
327   debug('headers already sent');
328   this.error(500, err);
329 };
330
331 /**
332  * Check if the request is cacheable, aka
333  * responded with 2xx or 304 (see RFC 2616 section 14.2{5,6}).
334  *
335  * @return {Boolean}
336  * @api private
337  */
338
339 SendStream.prototype.isCachable = function(){
340   var res = this.res;
341   return (res.statusCode >= 200 && res.statusCode < 300) || 304 == res.statusCode;
342 };
343
344 /**
345  * Handle stat() error.
346  *
347  * @param {Error} error
348  * @private
349  */
350
351 SendStream.prototype.onStatError = function onStatError(error) {
352   switch (error.code) {
353     case 'ENAMETOOLONG':
354     case 'ENOENT':
355     case 'ENOTDIR':
356       this.error(404, error)
357       break
358     default:
359       this.error(500, error)
360       break
361   }
362 }
363
364 /**
365  * Check if the cache is fresh.
366  *
367  * @return {Boolean}
368  * @api private
369  */
370
371 SendStream.prototype.isFresh = function(){
372   return fresh(this.req.headers, this.res._headers);
373 };
374
375 /**
376  * Check if the range is fresh.
377  *
378  * @return {Boolean}
379  * @api private
380  */
381
382 SendStream.prototype.isRangeFresh = function isRangeFresh(){
383   var ifRange = this.req.headers['if-range'];
384
385   if (!ifRange) return true;
386
387   return ~ifRange.indexOf('"')
388     ? ~ifRange.indexOf(this.res._headers['etag'])
389     : Date.parse(this.res._headers['last-modified']) <= Date.parse(ifRange);
390 };
391
392 /**
393  * Redirect to path.
394  *
395  * @param {string} path
396  * @private
397  */
398
399 SendStream.prototype.redirect = function redirect(path) {
400   if (listenerCount(this, 'directory') !== 0) {
401     this.emit('directory')
402     return
403   }
404
405   if (this.hasTrailingSlash()) {
406     this.error(403)
407     return
408   }
409
410   var loc = path + '/'
411   var msg = 'Redirecting to <a href="' + escapeHtml(loc) + '">' + escapeHtml(loc) + '</a>\n'
412   var res = this.res
413
414   // redirect
415   res.statusCode = 301
416   res.setHeader('Content-Type', 'text/html; charset=UTF-8')
417   res.setHeader('Content-Length', Buffer.byteLength(msg))
418   res.setHeader('X-Content-Type-Options', 'nosniff')
419   res.setHeader('Location', loc)
420   res.end(msg)
421 }
422
423 /**
424  * Pipe to `res.
425  *
426  * @param {Stream} res
427  * @return {Stream} res
428  * @api public
429  */
430
431 SendStream.prototype.pipe = function(res){
432   var self = this
433     , args = arguments
434     , root = this._root;
435
436   // references
437   this.res = res;
438
439   // decode the path
440   var path = decode(this.path)
441   if (path === -1) return this.error(400)
442
443   // null byte(s)
444   if (~path.indexOf('\0')) return this.error(400);
445
446   var parts
447   if (root !== null) {
448     // malicious path
449     if (upPathRegexp.test(normalize('.' + sep + path))) {
450       debug('malicious path "%s"', path)
451       return this.error(403)
452     }
453
454     // join / normalize from optional root dir
455     path = normalize(join(root, path))
456     root = normalize(root + sep)
457
458     // explode path parts
459     parts = path.substr(root.length).split(sep)
460   } else {
461     // ".." is malicious without "root"
462     if (upPathRegexp.test(path)) {
463       debug('malicious path "%s"', path)
464       return this.error(403)
465     }
466
467     // explode path parts
468     parts = normalize(path).split(sep)
469
470     // resolve the path
471     path = resolve(path)
472   }
473
474   // dotfile handling
475   if (containsDotFile(parts)) {
476     var access = this._dotfiles
477
478     // legacy support
479     if (access === undefined) {
480       access = parts[parts.length - 1][0] === '.'
481         ? (this._hidden ? 'allow' : 'ignore')
482         : 'allow'
483     }
484
485     debug('%s dotfile "%s"', access, path)
486     switch (access) {
487       case 'allow':
488         break
489       case 'deny':
490         return this.error(403)
491       case 'ignore':
492       default:
493         return this.error(404)
494     }
495   }
496
497   // index file support
498   if (this._index.length && this.path[this.path.length - 1] === '/') {
499     this.sendIndex(path);
500     return res;
501   }
502
503   this.sendFile(path);
504   return res;
505 };
506
507 /**
508  * Transfer `path`.
509  *
510  * @param {String} path
511  * @api public
512  */
513
514 SendStream.prototype.send = function(path, stat){
515   var len = stat.size;
516   var options = this.options
517   var opts = {}
518   var res = this.res;
519   var req = this.req;
520   var ranges = req.headers.range;
521   var offset = options.start || 0;
522
523   if (res._header) {
524     // impossible to send now
525     return this.headersAlreadySent();
526   }
527
528   debug('pipe "%s"', path)
529
530   // set header fields
531   this.setHeader(path, stat);
532
533   // set content-type
534   this.type(path);
535
536   // conditional GET support
537   if (this.isConditionalGET()
538     && this.isCachable()
539     && this.isFresh()) {
540     return this.notModified();
541   }
542
543   // adjust len to start/end options
544   len = Math.max(0, len - offset);
545   if (options.end !== undefined) {
546     var bytes = options.end - offset + 1;
547     if (len > bytes) len = bytes;
548   }
549
550   // Range support
551   if (ranges) {
552     ranges = parseRange(len, ranges);
553
554     // If-Range support
555     if (!this.isRangeFresh()) {
556       debug('range stale');
557       ranges = -2;
558     }
559
560     // unsatisfiable
561     if (-1 == ranges) {
562       debug('range unsatisfiable');
563       res.setHeader('Content-Range', 'bytes */' + stat.size);
564       return this.error(416);
565     }
566
567     // valid (syntactically invalid/multiple ranges are treated as a regular response)
568     if (-2 != ranges && ranges.length === 1) {
569       debug('range %j', ranges);
570
571       // Content-Range
572       res.statusCode = 206;
573       res.setHeader('Content-Range', 'bytes '
574         + ranges[0].start
575         + '-'
576         + ranges[0].end
577         + '/'
578         + len);
579
580       offset += ranges[0].start;
581       len = ranges[0].end - ranges[0].start + 1;
582     }
583   }
584
585   // clone options
586   for (var prop in options) {
587     opts[prop] = options[prop]
588   }
589
590   // set read options
591   opts.start = offset
592   opts.end = Math.max(offset, offset + len - 1)
593
594   // content-length
595   res.setHeader('Content-Length', len);
596
597   // HEAD support
598   if ('HEAD' == req.method) return res.end();
599
600   this.stream(path, opts)
601 };
602
603 /**
604  * Transfer file for `path`.
605  *
606  * @param {String} path
607  * @api private
608  */
609 SendStream.prototype.sendFile = function sendFile(path) {
610   var i = 0
611   var self = this
612
613   debug('stat "%s"', path);
614   fs.stat(path, function onstat(err, stat) {
615     if (err && err.code === 'ENOENT'
616       && !extname(path)
617       && path[path.length - 1] !== sep) {
618       // not found, check extensions
619       return next(err)
620     }
621     if (err) return self.onStatError(err)
622     if (stat.isDirectory()) return self.redirect(self.path)
623     self.emit('file', path, stat)
624     self.send(path, stat)
625   })
626
627   function next(err) {
628     if (self._extensions.length <= i) {
629       return err
630         ? self.onStatError(err)
631         : self.error(404)
632     }
633
634     var p = path + '.' + self._extensions[i++]
635
636     debug('stat "%s"', p)
637     fs.stat(p, function (err, stat) {
638       if (err) return next(err)
639       if (stat.isDirectory()) return next()
640       self.emit('file', p, stat)
641       self.send(p, stat)
642     })
643   }
644 }
645
646 /**
647  * Transfer index for `path`.
648  *
649  * @param {String} path
650  * @api private
651  */
652 SendStream.prototype.sendIndex = function sendIndex(path){
653   var i = -1;
654   var self = this;
655
656   function next(err){
657     if (++i >= self._index.length) {
658       if (err) return self.onStatError(err);
659       return self.error(404);
660     }
661
662     var p = join(path, self._index[i]);
663
664     debug('stat "%s"', p);
665     fs.stat(p, function(err, stat){
666       if (err) return next(err);
667       if (stat.isDirectory()) return next();
668       self.emit('file', p, stat);
669       self.send(p, stat);
670     });
671   }
672
673   next();
674 };
675
676 /**
677  * Stream `path` to the response.
678  *
679  * @param {String} path
680  * @param {Object} options
681  * @api private
682  */
683
684 SendStream.prototype.stream = function(path, options){
685   // TODO: this is all lame, refactor meeee
686   var finished = false;
687   var self = this;
688   var res = this.res;
689   var req = this.req;
690
691   // pipe
692   var stream = fs.createReadStream(path, options);
693   this.emit('stream', stream);
694   stream.pipe(res);
695
696   // response finished, done with the fd
697   onFinished(res, function onfinished(){
698     finished = true;
699     destroy(stream);
700   });
701
702   // error handling code-smell
703   stream.on('error', function onerror(err){
704     // request already finished
705     if (finished) return;
706
707     // clean up stream
708     finished = true;
709     destroy(stream);
710
711     // error
712     self.onStatError(err);
713   });
714
715   // end
716   stream.on('end', function onend(){
717     self.emit('end');
718   });
719 };
720
721 /**
722  * Set content-type based on `path`
723  * if it hasn't been explicitly set.
724  *
725  * @param {String} path
726  * @api private
727  */
728
729 SendStream.prototype.type = function type(path) {
730   var res = this.res;
731
732   if (res.getHeader('Content-Type')) return;
733
734   var type = mime.lookup(path);
735
736   if (!type) {
737     debug('no content-type');
738     return;
739   }
740
741   var charset = mime.charsets.lookup(type);
742
743   debug('content-type %s', type);
744   res.setHeader('Content-Type', type + (charset ? '; charset=' + charset : ''));
745 };
746
747 /**
748  * Set response header fields, most
749  * fields may be pre-defined.
750  *
751  * @param {String} path
752  * @param {Object} stat
753  * @api private
754  */
755
756 SendStream.prototype.setHeader = function setHeader(path, stat){
757   var res = this.res;
758
759   this.emit('headers', res, path, stat);
760
761   if (!res.getHeader('Accept-Ranges')) res.setHeader('Accept-Ranges', 'bytes');
762   if (!res.getHeader('Cache-Control')) res.setHeader('Cache-Control', 'public, max-age=' + Math.floor(this._maxage / 1000));
763
764   if (this._lastModified && !res.getHeader('Last-Modified')) {
765     var modified = stat.mtime.toUTCString()
766     debug('modified %s', modified)
767     res.setHeader('Last-Modified', modified)
768   }
769
770   if (this._etag && !res.getHeader('ETag')) {
771     var val = etag(stat)
772     debug('etag %s', val)
773     res.setHeader('ETag', val)
774   }
775 };
776
777 /**
778  * Determine if path parts contain a dotfile.
779  *
780  * @api private
781  */
782
783 function containsDotFile(parts) {
784   for (var i = 0; i < parts.length; i++) {
785     if (parts[i][0] === '.') {
786       return true
787     }
788   }
789
790   return false
791 }
792
793 /**
794  * decodeURIComponent.
795  *
796  * Allows V8 to only deoptimize this fn instead of all
797  * of send().
798  *
799  * @param {String} path
800  * @api private
801  */
802
803 function decode(path) {
804   try {
805     return decodeURIComponent(path)
806   } catch (err) {
807     return -1
808   }
809 }
810
811 /**
812  * Normalize the index option into an array.
813  *
814  * @param {boolean|string|array} val
815  * @param {string} name
816  * @private
817  */
818
819 function normalizeList(val, name) {
820   var list = [].concat(val || [])
821
822   for (var i = 0; i < list.length; i++) {
823     if (typeof list[i] !== 'string') {
824       throw new TypeError(name + ' must be array of strings or false')
825     }
826   }
827
828   return list
829 }