Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / socket.io / lib / static.js
1
2 /*!
3 * socket.io-node
4 * Copyright(c) 2011 LearnBoost <dev@learnboost.com>
5 * MIT Licensed
6 */
7
8 /**
9  * Module dependencies.
10  */
11
12 var client = require('socket.io-client')
13   , cp = require('child_process')
14   , fs = require('fs')
15   , util = require('./util');
16
17 /**
18  * File type details.
19  *
20  * @api private
21  */
22
23 var mime = {
24     js: {
25         type: 'application/javascript'
26       , encoding: 'utf8'
27       , gzip: true
28     }
29   , swf: {
30         type: 'application/x-shockwave-flash'
31       , encoding: 'binary'
32       , gzip: false
33     }
34 };
35
36 /**
37  * Regexp for matching custom transport patterns. Users can configure their own
38  * socket.io bundle based on the url structure. Different transport names are
39  * concatinated using the `+` char. /socket.io/socket.io+websocket.js should
40  * create a bundle that only contains support for the websocket.
41  *
42  * @api private
43  */
44
45 var bundle = /\+((?:\+)?[\w\-]+)*(?:\.v\d+\.\d+\.\d+)?(?:\.js)$/
46   , versioning = /\.v\d+\.\d+\.\d+(?:\.js)$/;
47
48 /**
49  * Export the constructor
50  */
51
52 exports = module.exports = Static;
53
54 /**
55  * Static constructor
56  *
57  * @api public
58  */
59
60 function Static (manager) {
61   this.manager = manager;
62   this.cache = {};
63   this.paths = {};
64
65   this.init();
66 }
67
68 /**
69  * Initialize the Static by adding default file paths.
70  *
71  * @api public
72  */
73
74 Static.prototype.init = function () {
75   /**
76    * Generates a unique id based the supplied transports array
77    *
78    * @param {Array} transports The array with transport types
79    * @api private
80    */
81   function id (transports) {
82     var id = transports.join('').split('').map(function (char) {
83       return ('' + char.charCodeAt(0)).split('').pop();
84     }).reduce(function (char, id) {
85       return char +id;
86     });
87
88     return client.version + ':' + id;
89   }
90
91   /**
92    * Generates a socket.io-client file based on the supplied transports.
93    *
94    * @param {Array} transports The array with transport types
95    * @param {Function} callback Callback for the static.write
96    * @api private
97    */
98
99   function build (transports, callback) {
100     client.builder(transports, {
101           minify: self.manager.enabled('browser client minification')
102       }, function (err, content) {
103         callback(err, content ? new Buffer(content) : null, id(transports));
104       }
105     );
106   }
107
108   var self = this;
109
110   // add our default static files
111   this.add('/static/flashsocket/WebSocketMain.swf', {
112       file: client.dist + '/WebSocketMain.swf'
113   });
114
115   this.add('/static/flashsocket/WebSocketMainInsecure.swf', {
116       file: client.dist + '/WebSocketMainInsecure.swf'
117   });
118
119   // generates dedicated build based on the available transports
120   this.add('/socket.io.js', function (path, callback) {
121     build(self.manager.get('transports'), callback);
122   });
123
124   this.add('/socket.io.v', { mime: mime.js }, function (path, callback) {
125     build(self.manager.get('transports'), callback);
126   });
127
128   // allow custom builds based on url paths
129   this.add('/socket.io+', { mime: mime.js }, function (path, callback) {
130     var available = self.manager.get('transports')
131       , matches = path.match(bundle)
132       , transports = [];
133
134     if (!matches) return callback('No valid transports');
135
136     // make sure they valid transports
137     matches[0].split('.')[0].split('+').slice(1).forEach(function (transport) {
138       if (!!~available.indexOf(transport)) {
139         transports.push(transport);
140       }
141     });
142
143     if (!transports.length) return callback('No valid transports');
144     build(transports, callback);
145   });
146
147   // clear cache when transports change
148   this.manager.on('set:transports', function (key, value) {
149     delete self.cache['/socket.io.js'];
150     Object.keys(self.cache).forEach(function (key) {
151       if (bundle.test(key)) {
152         delete self.cache[key];
153       }
154     });
155   });
156 };
157
158 /**
159  * Gzip compress buffers.
160  *
161  * @param {Buffer} data The buffer that needs gzip compression
162  * @param {Function} callback
163  * @api public
164  */
165
166 Static.prototype.gzip = function (data, callback) {
167   var gzip = cp.spawn('gzip', ['-9', '-c', '-f', '-n'])
168     , encoding = Buffer.isBuffer(data) ? 'binary' : 'utf8'
169     , buffer = []
170     , err;
171
172   gzip.stdout.on('data', function (data) {
173     buffer.push(data);
174   });
175
176   gzip.stderr.on('data', function (data) {
177     err = data +'';
178     buffer.length = 0;
179   });
180
181   gzip.on('close', function () {
182     if (err) return callback(err);
183
184     var size = 0
185       , index = 0
186       , i = buffer.length
187       , content;
188
189     while (i--) {
190       size += buffer[i].length;
191     }
192
193     content = new Buffer(size);
194     i = buffer.length;
195
196     buffer.forEach(function (buffer) {
197       var length = buffer.length;
198
199       buffer.copy(content, index, 0, length);
200       index += length;
201     });
202
203     buffer.length = 0;
204     callback(null, content);
205   });
206
207   gzip.stdin.end(data, encoding);
208 };
209
210 /**
211  * Is the path a static file?
212  *
213  * @param {String} path The path that needs to be checked
214  * @api public
215  */
216
217 Static.prototype.has = function (path) {
218   // fast case
219   if (this.paths[path]) return this.paths[path];
220
221   var keys = Object.keys(this.paths)
222     , i = keys.length;
223  
224   while (i--) {
225     if (-~path.indexOf(keys[i])) return this.paths[keys[i]];
226   }
227
228   return false;
229 };
230
231 /**
232  * Add new paths new paths that can be served using the static provider.
233  *
234  * @param {String} path The path to respond to
235  * @param {Options} options Options for writing out the response
236  * @param {Function} [callback] Optional callback if no options.file is
237  * supplied this would be called instead.
238  * @api public
239  */
240
241 Static.prototype.add = function (path, options, callback) {
242   var extension = /(?:\.(\w{1,4}))$/.exec(path);
243
244   if (!callback && typeof options == 'function') {
245     callback = options;
246     options = {};
247   }
248
249   options.mime = options.mime || (extension ? mime[extension[1]] : false);
250
251   if (callback) options.callback = callback;
252   if (!(options.file || options.callback) || !options.mime) return false;
253
254   this.paths[path] = options;
255
256   return true;
257 };
258
259 /**
260  * Writes a static response.
261  *
262  * @param {String} path The path for the static content
263  * @param {HTTPRequest} req The request object
264  * @param {HTTPResponse} res The response object
265  * @api public
266  */
267
268 Static.prototype.write = function (path, req, res) {
269   /**
270    * Write a response without throwing errors because can throw error if the
271    * response is no longer writable etc.
272    *
273    * @api private
274    */
275
276   function write (status, headers, content, encoding) {
277     try {
278       res.writeHead(status, headers || undefined);
279
280       // only write content if it's not a HEAD request and we actually have
281       // some content to write (304's doesn't have content).
282       res.end(
283           req.method !== 'HEAD' && content ? content : ''
284         , encoding || undefined
285       );
286     } catch (e) {}
287   }
288
289   /**
290    * Answers requests depending on the request properties and the reply object.
291    *
292    * @param {Object} reply The details and content to reply the response with
293    * @api private
294    */
295
296   function answer (reply) {
297     var cached = req.headers['if-none-match'] === reply.etag;
298     if (cached && self.manager.enabled('browser client etag')) {
299       return write(304);
300     }
301
302     var accept = req.headers['accept-encoding'] || ''
303       , gzip = !!~accept.toLowerCase().indexOf('gzip')
304       , mime = reply.mime
305       , versioned = reply.versioned
306       , headers = {
307           'Content-Type': mime.type
308       };
309
310     // check if we can add a etag
311     if (self.manager.enabled('browser client etag') && reply.etag && !versioned) {
312       headers['Etag'] = reply.etag;
313     }
314
315     // see if we need to set Expire headers because the path is versioned
316     if (versioned) {
317       var expires = self.manager.get('browser client expires');
318       headers['Cache-Control'] = 'private, x-gzip-ok="", max-age=' + expires;
319       headers['Date'] = new Date().toUTCString();
320       headers['Expires'] = new Date(Date.now() + (expires * 1000)).toUTCString();
321     }
322
323     if (gzip && reply.gzip) {
324       headers['Content-Length'] = reply.gzip.length;
325       headers['Content-Encoding'] = 'gzip';
326       headers['Vary'] = 'Accept-Encoding';
327       write(200, headers, reply.gzip.content, mime.encoding);
328     } else {
329       headers['Content-Length'] = reply.length;
330       write(200, headers, reply.content, mime.encoding);
331     }
332
333     self.manager.log.debug('served static content ' + path);
334   }
335
336   var self = this
337     , details;
338
339   // most common case first
340   if (this.manager.enabled('browser client cache') && this.cache[path]) {
341     return answer(this.cache[path]);
342   } else if (this.manager.get('browser client handler')) {
343     return this.manager.get('browser client handler').call(this, req, res);
344   } else if ((details = this.has(path))) {
345     /**
346      * A small helper function that will let us deal with fs and dynamic files
347      *
348      * @param {Object} err Optional error
349      * @param {Buffer} content The data
350      * @api private
351      */
352
353     function ready (err, content, etag) {
354       if (err) {
355         self.manager.log.warn('Unable to serve file. ' + (err.message || err));
356         return write(500, null, 'Error serving static ' + path);
357       }
358
359       // store the result in the cache
360       var reply = self.cache[path] = {
361             content: content
362           , length: content.length
363           , mime: details.mime
364           , etag: etag || client.version
365           , versioned: versioning.test(path)
366         };
367
368       // check if gzip is enabled
369       if (details.mime.gzip && self.manager.enabled('browser client gzip')) {
370         self.gzip(content, function (err, content) {
371           if (!err) {
372             reply.gzip = {
373                 content: content
374               , length: content.length
375             }
376           }
377
378           answer(reply);
379         });
380       } else {
381         answer(reply);
382       }
383     }
384
385     if (details.file) {
386       fs.readFile(details.file, ready);
387     } else if(details.callback) {
388       details.callback.call(this, path, ready);
389     } else {
390       write(404, null, 'File handle not found');
391     }
392   } else {
393     write(404, null, 'File not found');
394   }
395 };