Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / xmlhttprequest / lib / XMLHttpRequest.js
1 /**
2  * Wrapper for built-in http.js to emulate the browser XMLHttpRequest object.
3  *
4  * This can be used with JS designed for browsers to improve reuse of code and
5  * allow the use of existing libraries.
6  *
7  * Usage: include("XMLHttpRequest.js") and use XMLHttpRequest per W3C specs.
8  *
9  * @author Dan DeFelippi <dan@driverdan.com>
10  * @contributor David Ellis <d.f.ellis@ieee.org>
11  * @license MIT
12  */
13
14 var Url = require("url")
15   , spawn = require("child_process").spawn
16   , fs = require('fs');
17
18 exports.XMLHttpRequest = function() {
19   /**
20    * Private variables
21    */
22   var self = this;
23   var http = require('http');
24   var https = require('https');
25
26   // Holds http.js objects
27   var client;
28   var request;
29   var response;
30
31   // Request settings
32   var settings = {};
33
34   // Set some default headers
35   var defaultHeaders = {
36     "User-Agent": "node-XMLHttpRequest",
37     "Accept": "*/*",
38   };
39
40   var headers = defaultHeaders;
41
42   // These headers are not user setable.
43   // The following are allowed but banned in the spec:
44   // * user-agent
45   var forbiddenRequestHeaders = [
46     "accept-charset",
47     "accept-encoding",
48     "access-control-request-headers",
49     "access-control-request-method",
50     "connection",
51     "content-length",
52     "content-transfer-encoding",
53     "cookie",
54     "cookie2",
55     "date",
56     "expect",
57     "host",
58     "keep-alive",
59     "origin",
60     "referer",
61     "te",
62     "trailer",
63     "transfer-encoding",
64     "upgrade",
65     "via"
66   ];
67
68   // These request methods are not allowed
69   var forbiddenRequestMethods = [
70     "TRACE",
71     "TRACK",
72     "CONNECT"
73   ];
74
75   // Send flag
76   var sendFlag = false;
77   // Error flag, used when errors occur or abort is called
78   var errorFlag = false;
79
80   // Event listeners
81   var listeners = {};
82
83   /**
84    * Constants
85    */
86
87   this.UNSENT = 0;
88   this.OPENED = 1;
89   this.HEADERS_RECEIVED = 2;
90   this.LOADING = 3;
91   this.DONE = 4;
92
93   /**
94    * Public vars
95    */
96
97   // Current state
98   this.readyState = this.UNSENT;
99
100   // default ready state change handler in case one is not set or is set late
101   this.onreadystatechange = null;
102
103   // Result & response
104   this.responseText = "";
105   this.responseXML = "";
106   this.status = null;
107   this.statusText = null;
108
109   /**
110    * Private methods
111    */
112
113   /**
114    * Check if the specified header is allowed.
115    *
116    * @param string header Header to validate
117    * @return boolean False if not allowed, otherwise true
118    */
119   var isAllowedHttpHeader = function(header) {
120     return (header && forbiddenRequestHeaders.indexOf(header.toLowerCase()) === -1);
121   };
122
123   /**
124    * Check if the specified method is allowed.
125    *
126    * @param string method Request method to validate
127    * @return boolean False if not allowed, otherwise true
128    */
129   var isAllowedHttpMethod = function(method) {
130     return (method && forbiddenRequestMethods.indexOf(method) === -1);
131   };
132
133   /**
134    * Public methods
135    */
136
137   /**
138    * Open the connection. Currently supports local server requests.
139    *
140    * @param string method Connection method (eg GET, POST)
141    * @param string url URL for the connection.
142    * @param boolean async Asynchronous connection. Default is true.
143    * @param string user Username for basic authentication (optional)
144    * @param string password Password for basic authentication (optional)
145    */
146   this.open = function(method, url, async, user, password) {
147     this.abort();
148     errorFlag = false;
149
150     // Check for valid request method
151     if (!isAllowedHttpMethod(method)) {
152       throw "SecurityError: Request method not allowed";
153       return;
154     }
155
156     settings = {
157       "method": method,
158       "url": url.toString(),
159       "async": (typeof async !== "boolean" ? true : async),
160       "user": user || null,
161       "password": password || null
162     };
163
164     setState(this.OPENED);
165   };
166
167   /**
168    * Sets a header for the request.
169    *
170    * @param string header Header name
171    * @param string value Header value
172    */
173   this.setRequestHeader = function(header, value) {
174     if (this.readyState != this.OPENED) {
175       throw "INVALID_STATE_ERR: setRequestHeader can only be called when state is OPEN";
176     }
177     if (!isAllowedHttpHeader(header)) {
178       console.warn('Refused to set unsafe header "' + header + '"');
179       return;
180     }
181     if (sendFlag) {
182       throw "INVALID_STATE_ERR: send flag is true";
183     }
184     headers[header] = value;
185   };
186
187   /**
188    * Gets a header from the server response.
189    *
190    * @param string header Name of header to get.
191    * @return string Text of the header or null if it doesn't exist.
192    */
193   this.getResponseHeader = function(header) {
194     if (typeof header === "string"
195       && this.readyState > this.OPENED
196       && response.headers[header.toLowerCase()]
197       && !errorFlag
198     ) {
199       return response.headers[header.toLowerCase()];
200     }
201
202     return null;
203   };
204
205   /**
206    * Gets all the response headers.
207    *
208    * @return string A string with all response headers separated by CR+LF
209    */
210   this.getAllResponseHeaders = function() {
211     if (this.readyState < this.HEADERS_RECEIVED || errorFlag) {
212       return "";
213     }
214     var result = "";
215
216     for (var i in response.headers) {
217       // Cookie headers are excluded
218       if (i !== "set-cookie" && i !== "set-cookie2") {
219         result += i + ": " + response.headers[i] + "\r\n";
220       }
221     }
222     return result.substr(0, result.length - 2);
223   };
224
225   /**
226    * Gets a request header
227    *
228    * @param string name Name of header to get
229    * @return string Returns the request header or empty string if not set
230    */
231   this.getRequestHeader = function(name) {
232     // @TODO Make this case insensitive
233     if (typeof name === "string" && headers[name]) {
234       return headers[name];
235     }
236
237     return "";
238   }
239
240   /**
241    * Sends the request to the server.
242    *
243    * @param string data Optional data to send as request body.
244    */
245   this.send = function(data) {
246     if (this.readyState != this.OPENED) {
247       throw "INVALID_STATE_ERR: connection must be opened before send() is called";
248     }
249
250     if (sendFlag) {
251       throw "INVALID_STATE_ERR: send has already been called";
252     }
253
254     var ssl = false, local = false;
255     var url = Url.parse(settings.url);
256
257     // Determine the server
258     switch (url.protocol) {
259       case 'https:':
260         ssl = true;
261         // SSL & non-SSL both need host, no break here.
262       case 'http:':
263         var host = url.hostname;
264         break;
265
266       case 'file:':
267         local = true;
268         break;
269
270       case undefined:
271       case '':
272         var host = "localhost";
273         break;
274
275       default:
276         throw "Protocol not supported.";
277     }
278
279     // Load files off the local filesystem (file://)
280     if (local) {
281       if (settings.method !== "GET") {
282         throw "XMLHttpRequest: Only GET method is supported";
283       }
284
285       if (settings.async) {
286         fs.readFile(url.pathname, 'utf8', function(error, data) {
287           if (error) {
288             self.handleError(error);
289           } else {
290             self.status = 200;
291             self.responseText = data;
292             setState(self.DONE);
293           }
294         });
295       } else {
296         try {
297           this.responseText = fs.readFileSync(url.pathname, 'utf8');
298           this.status = 200;
299           setState(self.DONE);
300         } catch(e) {
301           this.handleError(e);
302         }
303       }
304
305       return;
306     }
307
308     // Default to port 80. If accessing localhost on another port be sure
309     // to use http://localhost:port/path
310     var port = url.port || (ssl ? 443 : 80);
311     // Add query string if one is used
312     var uri = url.pathname + (url.search ? url.search : '');
313
314     // Set the Host header or the server may reject the request
315     headers["Host"] = host;
316     if (!((ssl && port === 443) || port === 80)) {
317       headers["Host"] += ':' + url.port;
318     }
319
320     // Set Basic Auth if necessary
321     if (settings.user) {
322       if (typeof settings.password == "undefined") {
323         settings.password = "";
324       }
325       var authBuf = new Buffer(settings.user + ":" + settings.password);
326       headers["Authorization"] = "Basic " + authBuf.toString("base64");
327     }
328
329     // Set content length header
330     if (settings.method === "GET" || settings.method === "HEAD") {
331       data = null;
332     } else if (data) {
333       headers["Content-Length"] = Buffer.byteLength(data);
334
335       if (!headers["Content-Type"]) {
336         headers["Content-Type"] = "text/plain;charset=UTF-8";
337       }
338     } else if (settings.method === "POST") {
339       // For a post with no data set Content-Length: 0.
340       // This is required by buggy servers that don't meet the specs.
341       headers["Content-Length"] = 0;
342     }
343
344     var options = {
345       host: host,
346       port: port,
347       path: uri,
348       method: settings.method,
349       headers: headers
350     };
351
352     // Reset error flag
353     errorFlag = false;
354
355     // Handle async requests
356     if (settings.async) {
357       // Use the proper protocol
358       var doRequest = ssl ? https.request : http.request;
359
360       // Request is being sent, set send flag
361       sendFlag = true;
362
363       // As per spec, this is called here for historical reasons.
364       self.dispatchEvent("readystatechange");
365
366       // Create the request
367       request = doRequest(options, function(resp) {
368         response = resp;
369         response.setEncoding("utf8");
370
371         setState(self.HEADERS_RECEIVED);
372         self.status = response.statusCode;
373
374         response.on('data', function(chunk) {
375           // Make sure there's some data
376           if (chunk) {
377             self.responseText += chunk;
378           }
379           // Don't emit state changes if the connection has been aborted.
380           if (sendFlag) {
381             setState(self.LOADING);
382           }
383         });
384
385         response.on('end', function() {
386           if (sendFlag) {
387             // Discard the 'end' event if the connection has been aborted
388             setState(self.DONE);
389             sendFlag = false;
390           }
391         });
392
393         response.on('error', function(error) {
394           self.handleError(error);
395         });
396       }).on('error', function(error) {
397         self.handleError(error);
398       });
399
400       // Node 0.4 and later won't accept empty data. Make sure it's needed.
401       if (data) {
402         request.write(data);
403       }
404
405       request.end();
406
407       self.dispatchEvent("loadstart");
408     } else { // Synchronous
409       // Create a temporary file for communication with the other Node process
410       var syncFile = ".node-xmlhttprequest-sync-" + process.pid;
411       fs.writeFileSync(syncFile, "", "utf8");
412       // The async request the other Node process executes
413       var execString = "var http = require('http'), https = require('https'), fs = require('fs');"
414         + "var doRequest = http" + (ssl ? "s" : "") + ".request;"
415         + "var options = " + JSON.stringify(options) + ";"
416         + "var responseText = '';"
417         + "var req = doRequest(options, function(response) {"
418         + "response.setEncoding('utf8');"
419         + "response.on('data', function(chunk) {"
420         + "responseText += chunk;"
421         + "});"
422         + "response.on('end', function() {"
423         + "fs.writeFileSync('" + syncFile + "', 'NODE-XMLHTTPREQUEST-STATUS:' + response.statusCode + ',' + responseText, 'utf8');"
424         + "});"
425         + "response.on('error', function(error) {"
426         + "fs.writeFileSync('" + syncFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');"
427         + "});"
428         + "}).on('error', function(error) {"
429         + "fs.writeFileSync('" + syncFile + "', 'NODE-XMLHTTPREQUEST-ERROR:' + JSON.stringify(error), 'utf8');"
430         + "});"
431         + (data ? "req.write('" + data.replace(/'/g, "\\'") + "');":"")
432         + "req.end();";
433       // Start the other Node Process, executing this string
434       syncProc = spawn(process.argv[0], ["-e", execString]);
435       while((self.responseText = fs.readFileSync(syncFile, 'utf8')) == "") {
436         // Wait while the file is empty
437       }
438       // Kill the child process once the file has data
439       syncProc.stdin.end();
440       // Remove the temporary file
441       fs.unlinkSync(syncFile);
442       if (self.responseText.match(/^NODE-XMLHTTPREQUEST-ERROR:/)) {
443         // If the file returned an error, handle it
444         var errorObj = self.responseText.replace(/^NODE-XMLHTTPREQUEST-ERROR:/, "");
445         self.handleError(errorObj);
446       } else {
447         // If the file returned okay, parse its data and move to the DONE state
448         self.status = self.responseText.replace(/^NODE-XMLHTTPREQUEST-STATUS:([0-9]*),.*/, "$1");
449         self.responseText = self.responseText.replace(/^NODE-XMLHTTPREQUEST-STATUS:[0-9]*,(.*)/, "$1");
450         setState(self.DONE);
451       }
452     }
453   };
454
455   /**
456    * Called when an error is encountered to deal with it.
457    */
458   this.handleError = function(error) {
459     this.status = 503;
460     this.statusText = error;
461     this.responseText = error.stack;
462     errorFlag = true;
463     setState(this.DONE);
464   };
465
466   /**
467    * Aborts a request.
468    */
469   this.abort = function() {
470     if (request) {
471       request.abort();
472       request = null;
473     }
474
475     headers = defaultHeaders;
476     this.responseText = "";
477     this.responseXML = "";
478
479     errorFlag = true;
480
481     if (this.readyState !== this.UNSENT
482         && (this.readyState !== this.OPENED || sendFlag)
483         && this.readyState !== this.DONE) {
484       sendFlag = false;
485       setState(this.DONE);
486     }
487     this.readyState = this.UNSENT;
488   };
489
490   /**
491    * Adds an event listener. Preferred method of binding to events.
492    */
493   this.addEventListener = function(event, callback) {
494     if (!(event in listeners)) {
495       listeners[event] = [];
496     }
497     // Currently allows duplicate callbacks. Should it?
498     listeners[event].push(callback);
499   };
500
501   /**
502    * Remove an event callback that has already been bound.
503    * Only works on the matching funciton, cannot be a copy.
504    */
505   this.removeEventListener = function(event, callback) {
506     if (event in listeners) {
507       // Filter will return a new array with the callback removed
508       listeners[event] = listeners[event].filter(function(ev) {
509         return ev !== callback;
510       });
511     }
512   };
513
514   /**
515    * Dispatch any events, including both "on" methods and events attached using addEventListener.
516    */
517   this.dispatchEvent = function(event) {
518     if (typeof self["on" + event] === "function") {
519       self["on" + event]();
520     }
521     if (event in listeners) {
522       for (var i = 0, len = listeners[event].length; i < len; i++) {
523         listeners[event][i].call(self);
524       }
525     }
526   };
527
528   /**
529    * Changes readyState and calls onreadystatechange.
530    *
531    * @param int state New state
532    */
533   var setState = function(state) {
534     if (self.readyState !== state) {
535       self.readyState = state;
536
537       if (settings.async || self.readyState < self.OPENED || self.readyState === self.DONE) {
538         self.dispatchEvent("readystatechange");
539       }
540
541       if (self.readyState === self.DONE && !errorFlag) {
542         self.dispatchEvent("load");
543         // @TODO figure out InspectorInstrumentation::didLoadXHR(cookie)
544         self.dispatchEvent("loadend");
545       }
546     }
547   };
548 };