Bug:Fix file validation issue
[vnfsdk/refrepo.git] / vnfmarket / src / main / webapp / vnfmarket / node_modules / socket.io-client / lib / vendor / web-socket-js / flash-src / WebSocket.as
1 // Copyright: Hiroshi Ichikawa <http://gimite.net/en/>
2 // License: New BSD License
3 // Reference: http://dev.w3.org/html5/websockets/
4 // Reference: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76
5
6 package {
7
8 import com.adobe.net.proxies.RFC2817Socket;
9 import com.gsolo.encryption.MD5;
10 import com.hurlant.crypto.tls.TLSConfig;
11 import com.hurlant.crypto.tls.TLSEngine;
12 import com.hurlant.crypto.tls.TLSSecurityParameters;
13 import com.hurlant.crypto.tls.TLSSocket;
14
15 import flash.display.*;
16 import flash.events.*;
17 import flash.external.*;
18 import flash.net.*;
19 import flash.system.*;
20 import flash.utils.*;
21
22 import mx.controls.*;
23 import mx.core.*;
24 import mx.events.*;
25 import mx.utils.*;
26
27 public class WebSocket extends EventDispatcher {
28   
29   private static var CONNECTING:int = 0;
30   private static var OPEN:int = 1;
31   private static var CLOSING:int = 2;
32   private static var CLOSED:int = 3;
33   
34   private var id:int;
35   private var rawSocket:Socket;
36   private var tlsSocket:TLSSocket;
37   private var tlsConfig:TLSConfig;
38   private var socket:Socket;
39   private var url:String;
40   private var scheme:String;
41   private var host:String;
42   private var port:uint;
43   private var path:String;
44   private var origin:String;
45   private var requestedProtocols:Array;
46   private var acceptedProtocol:String;
47   private var buffer:ByteArray = new ByteArray();
48   private var headerState:int = 0;
49   private var readyState:int = CONNECTING;
50   private var cookie:String;
51   private var headers:String;
52   private var noiseChars:Array;
53   private var expectedDigest:String;
54   private var logger:IWebSocketLogger;
55
56   public function WebSocket(
57       id:int, url:String, protocols:Array, origin:String,
58       proxyHost:String, proxyPort:int,
59       cookie:String, headers:String,
60       logger:IWebSocketLogger) {
61     this.logger = logger;
62     this.id = id;
63     initNoiseChars();
64     this.url = url;
65     var m:Array = url.match(/^(\w+):\/\/([^\/:]+)(:(\d+))?(\/.*)?(\?.*)?$/);
66     if (!m) fatal("SYNTAX_ERR: invalid url: " + url);
67     this.scheme = m[1];
68     this.host = m[2];
69     var defaultPort:int = scheme == "wss" ? 443 : 80;
70     this.port = parseInt(m[4]) || defaultPort;
71     this.path = (m[5] || "/") + (m[6] || "");
72     this.origin = origin;
73     this.requestedProtocols = protocols;
74     this.cookie = cookie;
75     // if present and not the empty string, headers MUST end with \r\n
76     // headers should be zero or more complete lines, for example
77     // "Header1: xxx\r\nHeader2: yyyy\r\n"
78     this.headers = headers;
79     
80     if (proxyHost != null && proxyPort != 0){
81       if (scheme == "wss") {
82         fatal("wss with proxy is not supported");
83       }
84       var proxySocket:RFC2817Socket = new RFC2817Socket();
85       proxySocket.setProxyInfo(proxyHost, proxyPort);
86       proxySocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
87       rawSocket = socket = proxySocket;
88     } else {
89       rawSocket = new Socket();
90       if (scheme == "wss") {
91         tlsConfig= new TLSConfig(TLSEngine.CLIENT,
92             null, null, null, null, null,
93             TLSSecurityParameters.PROTOCOL_VERSION);
94         tlsConfig.trustAllCertificates = true;
95         tlsConfig.ignoreCommonNameMismatch = true;
96         tlsSocket = new TLSSocket();
97         tlsSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
98         socket = tlsSocket;
99       } else {
100         rawSocket.addEventListener(ProgressEvent.SOCKET_DATA, onSocketData);
101         socket = rawSocket;
102       }
103     }
104     rawSocket.addEventListener(Event.CLOSE, onSocketClose);
105     rawSocket.addEventListener(Event.CONNECT, onSocketConnect);
106     rawSocket.addEventListener(IOErrorEvent.IO_ERROR, onSocketIoError);
107     rawSocket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, onSocketSecurityError);
108     rawSocket.connect(host, port);
109   }
110   
111   /**
112    * @return  This WebSocket's ID.
113    */
114   public function getId():int {
115     return this.id;
116   }
117   
118   /**
119    * @return this WebSocket's readyState.
120    */
121   public function getReadyState():int {
122     return this.readyState;
123   }
124
125   public function getAcceptedProtocol():String {
126     return this.acceptedProtocol;
127   }
128   
129   public function send(encData:String):int {
130     var data:String = decodeURIComponent(encData);
131     if (readyState == OPEN) {
132       socket.writeByte(0x00);
133       socket.writeUTFBytes(data);
134       socket.writeByte(0xff);
135       socket.flush();
136       logger.log("sent: " + data);
137       return -1;
138     } else if (readyState == CLOSING || readyState == CLOSED) {
139       var bytes:ByteArray = new ByteArray();
140       bytes.writeUTFBytes(data);
141       return bytes.length; // not sure whether it should include \x00 and \xff
142     } else {
143       fatal("invalid state");
144       return 0;
145     }
146   }
147   
148   public function close(isError:Boolean = false):void {
149     logger.log("close");
150     try {
151       if (readyState == OPEN && !isError) {
152         socket.writeByte(0xff);
153         socket.writeByte(0x00);
154         socket.flush();
155       }
156       socket.close();
157     } catch (ex:Error) { }
158     readyState = CLOSED;
159     this.dispatchEvent(new WebSocketEvent(isError ? "error" : "close"));
160   }
161   
162   private function onSocketConnect(event:Event):void {
163     logger.log("connected");
164
165     if (scheme == "wss") {
166       logger.log("starting SSL/TLS");
167       tlsSocket.startTLS(rawSocket, host, tlsConfig);
168     }
169     
170     var defaultPort:int = scheme == "wss" ? 443 : 80;
171     var hostValue:String = host + (port == defaultPort ? "" : ":" + port);
172     var key1:String = generateKey();
173     var key2:String = generateKey();
174     var key3:String = generateKey3();
175     expectedDigest = getSecurityDigest(key1, key2, key3);
176     var opt:String = "";
177     if (requestedProtocols.length > 0) {
178       opt += "Sec-WebSocket-Protocol: " + requestedProtocols.join(",") + "\r\n";
179     }
180     // if caller passes additional headers they must end with "\r\n"
181     if (headers) opt += headers;
182     
183     var req:String = StringUtil.substitute(
184       "GET {0} HTTP/1.1\r\n" +
185       "Upgrade: WebSocket\r\n" +
186       "Connection: Upgrade\r\n" +
187       "Host: {1}\r\n" +
188       "Origin: {2}\r\n" +
189       "Cookie: {3}\r\n" +
190       "Sec-WebSocket-Key1: {4}\r\n" +
191       "Sec-WebSocket-Key2: {5}\r\n" +
192       "{6}" +
193       "\r\n",
194       path, hostValue, origin, cookie, key1, key2, opt);
195     logger.log("request header:\n" + req);
196     socket.writeUTFBytes(req);
197     logger.log("sent key3: " + key3);
198     writeBytes(key3);
199     socket.flush();
200   }
201
202   private function onSocketClose(event:Event):void {
203     logger.log("closed");
204     readyState = CLOSED;
205     this.dispatchEvent(new WebSocketEvent("close"));
206   }
207
208   private function onSocketIoError(event:IOErrorEvent):void {
209     var message:String;
210     if (readyState == CONNECTING) {
211       message = "cannot connect to Web Socket server at " + url + " (IoError)";
212     } else {
213       message = "error communicating with Web Socket server at " + url + " (IoError)";
214     }
215     onError(message);
216   }
217
218   private function onSocketSecurityError(event:SecurityErrorEvent):void {
219     var message:String;
220     if (readyState == CONNECTING) {
221       message =
222           "cannot connect to Web Socket server at " + url + " (SecurityError)\n" +
223           "make sure the server is running and Flash socket policy file is correctly placed";
224     } else {
225       message = "error communicating with Web Socket server at " + url + " (SecurityError)";
226     }
227     onError(message);
228   }
229   
230   private function onError(message:String):void {
231     if (readyState == CLOSED) return;
232     logger.error(message);
233     close(readyState != CONNECTING);
234   }
235
236   private function onSocketData(event:ProgressEvent):void {
237     var pos:int = buffer.length;
238     socket.readBytes(buffer, pos);
239     for (; pos < buffer.length; ++pos) {
240       if (headerState < 4) {
241         // try to find "\r\n\r\n"
242         if ((headerState == 0 || headerState == 2) && buffer[pos] == 0x0d) {
243           ++headerState;
244         } else if ((headerState == 1 || headerState == 3) && buffer[pos] == 0x0a) {
245           ++headerState;
246         } else {
247           headerState = 0;
248         }
249         if (headerState == 4) {
250           var headerStr:String = readUTFBytes(buffer, 0, pos + 1);
251           logger.log("response header:\n" + headerStr);
252           if (!validateHeader(headerStr)) return;
253           removeBufferBefore(pos + 1);
254           pos = -1;
255         }
256       } else if (headerState == 4) {
257         if (pos == 15) {
258           var replyDigest:String = readBytes(buffer, 0, 16);
259           logger.log("reply digest: " + replyDigest);
260           if (replyDigest != expectedDigest) {
261             onError("digest doesn't match: " + replyDigest + " != " + expectedDigest);
262             return;
263           }
264           headerState = 5;
265           removeBufferBefore(pos + 1);
266           pos = -1;
267           readyState = OPEN;
268           this.dispatchEvent(new WebSocketEvent("open"));
269         }
270       } else {
271         if (buffer[pos] == 0xff && pos > 0) {
272           if (buffer[0] != 0x00) {
273             onError("data must start with \\x00");
274             return;
275           }
276           var data:String = readUTFBytes(buffer, 1, pos - 1);
277           logger.log("received: " + data);
278           this.dispatchEvent(new WebSocketEvent("message", encodeURIComponent(data)));
279           removeBufferBefore(pos + 1);
280           pos = -1;
281         } else if (pos == 1 && buffer[0] == 0xff && buffer[1] == 0x00) { // closing
282           logger.log("received closing packet");
283           removeBufferBefore(pos + 1);
284           pos = -1;
285           close();
286         }
287       }
288     }
289   }
290   
291   private function validateHeader(headerStr:String):Boolean {
292     var lines:Array = headerStr.split(/\r\n/);
293     if (!lines[0].match(/^HTTP\/1.1 101 /)) {
294       onError("bad response: " + lines[0]);
295       return false;
296     }
297     var header:Object = {};
298     var lowerHeader:Object = {};
299     for (var i:int = 1; i < lines.length; ++i) {
300       if (lines[i].length == 0) continue;
301       var m:Array = lines[i].match(/^(\S+): (.*)$/);
302       if (!m) {
303         onError("failed to parse response header line: " + lines[i]);
304         return false;
305       }
306       header[m[1].toLowerCase()] = m[2];
307       lowerHeader[m[1].toLowerCase()] = m[2].toLowerCase();
308     }
309     if (lowerHeader["upgrade"] != "websocket") {
310       onError("invalid Upgrade: " + header["Upgrade"]);
311       return false;
312     }
313     if (lowerHeader["connection"] != "upgrade") {
314       onError("invalid Connection: " + header["Connection"]);
315       return false;
316     }
317     if (!lowerHeader["sec-websocket-origin"]) {
318       if (lowerHeader["websocket-origin"]) {
319         onError(
320           "The WebSocket server speaks old WebSocket protocol, " +
321           "which is not supported by web-socket-js. " +
322           "It requires WebSocket protocol 76 or later. " +
323           "Try newer version of the server if available.");
324       } else {
325         onError("header Sec-WebSocket-Origin is missing");
326       }
327       return false;
328     }
329     var resOrigin:String = lowerHeader["sec-websocket-origin"];
330     if (resOrigin != origin) {
331       onError("origin doesn't match: '" + resOrigin + "' != '" + origin + "'");
332       return false;
333     }
334     if (requestedProtocols.length > 0) {
335       acceptedProtocol = header["sec-websocket-protocol"];
336       if (requestedProtocols.indexOf(acceptedProtocol) < 0) {
337         onError("protocol doesn't match: '" +
338           acceptedProtocol + "' not in '" + requestedProtocols.join(",") + "'");
339         return false;
340       }
341     }
342     return true;
343   }
344
345   private function removeBufferBefore(pos:int):void {
346     if (pos == 0) return;
347     var nextBuffer:ByteArray = new ByteArray();
348     buffer.position = pos;
349     buffer.readBytes(nextBuffer);
350     buffer = nextBuffer;
351   }
352   
353   private function initNoiseChars():void {
354     noiseChars = new Array();
355     for (var i:int = 0x21; i <= 0x2f; ++i) {
356       noiseChars.push(String.fromCharCode(i));
357     }
358     for (var j:int = 0x3a; j <= 0x7a; ++j) {
359       noiseChars.push(String.fromCharCode(j));
360     }
361   }
362   
363   private function generateKey():String {
364     var spaces:uint = randomInt(1, 12);
365     var max:uint = uint.MAX_VALUE / spaces;
366     var number:uint = randomInt(0, max);
367     var key:String = (number * spaces).toString();
368     var noises:int = randomInt(1, 12);
369     var pos:int;
370     for (var i:int = 0; i < noises; ++i) {
371       var char:String = noiseChars[randomInt(0, noiseChars.length - 1)];
372       pos = randomInt(0, key.length);
373       key = key.substr(0, pos) + char + key.substr(pos);
374     }
375     for (var j:int = 0; j < spaces; ++j) {
376       pos = randomInt(1, key.length - 1);
377       key = key.substr(0, pos) + " " + key.substr(pos);
378     }
379     return key;
380   }
381   
382   private function generateKey3():String {
383     var key3:String = "";
384     for (var i:int = 0; i < 8; ++i) {
385       key3 += String.fromCharCode(randomInt(0, 255));
386     }
387     return key3;
388   }
389   
390   private function getSecurityDigest(key1:String, key2:String, key3:String):String {
391     var bytes1:String = keyToBytes(key1);
392     var bytes2:String = keyToBytes(key2);
393     return MD5.rstr_md5(bytes1 + bytes2 + key3);
394   }
395   
396   private function keyToBytes(key:String):String {
397     var keyNum:uint = parseInt(key.replace(/[^\d]/g, ""));
398     var spaces:uint = 0;
399     for (var i:int = 0; i < key.length; ++i) {
400       if (key.charAt(i) == " ") ++spaces;
401     }
402     var resultNum:uint = keyNum / spaces;
403     var bytes:String = "";
404     for (var j:int = 3; j >= 0; --j) {
405       bytes += String.fromCharCode((resultNum >> (j * 8)) & 0xff);
406     }
407     return bytes;
408   }
409   
410   // Writes byte sequence to socket.
411   // bytes is String in special format where bytes[i] is i-th byte, not i-th character.
412   private function writeBytes(bytes:String):void {
413     for (var i:int = 0; i < bytes.length; ++i) {
414       socket.writeByte(bytes.charCodeAt(i));
415     }
416   }
417   
418   // Reads specified number of bytes from buffer, and returns it as special format String
419   // where bytes[i] is i-th byte (not i-th character).
420   private function readBytes(buffer:ByteArray, start:int, numBytes:int):String {
421     buffer.position = start;
422     var bytes:String = "";
423     for (var i:int = 0; i < numBytes; ++i) {
424       // & 0xff is to make \x80-\xff positive number.
425       bytes += String.fromCharCode(buffer.readByte() & 0xff);
426     }
427     return bytes;
428   }
429   
430   private function readUTFBytes(buffer:ByteArray, start:int, numBytes:int):String {
431     buffer.position = start;
432     var data:String = "";
433     for(var i:int = start; i < start + numBytes; ++i) {
434       // Workaround of a bug of ByteArray#readUTFBytes() that bytes after "\x00" is discarded.
435       if (buffer[i] == 0x00) {
436         data += buffer.readUTFBytes(i - buffer.position) + "\x00";
437         buffer.position = i + 1;
438       }
439     }
440     data += buffer.readUTFBytes(start + numBytes - buffer.position);
441     return data;
442   }
443   
444   private function randomInt(min:uint, max:uint):uint {
445     return min + Math.floor(Math.random() * (Number(max) - min + 1));
446   }
447   
448   private function fatal(message:String):void {
449     logger.error(message);
450     throw message;
451   }
452
453   // for debug
454   private function dumpBytes(bytes:String):void {
455     var output:String = "";
456     for (var i:int = 0; i < bytes.length; ++i) {
457       output += bytes.charCodeAt(i).toString() + ", ";
458     }
459     logger.log(output);
460   }
461   
462 }
463
464 }