Added disconnect function to tunnel, added automatic disconnect on error receipt...
[guacamole-common-js.git] / src / main / resources / tunnel.js
1
2 /*
3  *  Guacamole - Clientless Remote Desktop
4  *  Copyright (C) 2010  Michael Jumper
5  *
6  *  This program is free software: you can redistribute it and/or modify
7  *  it under the terms of the GNU Affero General Public License as published by
8  *  the Free Software Foundation, either version 3 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU Affero General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Affero General Public License
17  */
18
19 function GuacamoleHTTPTunnel(tunnelURL) {
20
21     var TUNNEL_CONNECT = tunnelURL + "?connect";
22     var TUNNEL_READ    = tunnelURL + "?read";
23     var TUNNEL_WRITE   = tunnelURL + "?write";
24
25     var connected = 0;
26     var pollResponse = 1; // Default to polling - will be turned off automatically if not needed
27     var instructionHandler = null;
28
29     var sendingMessages = 0;
30     var outputMessageBuffer = "";
31
32     function sendMessage(message) {
33
34         // Do not attempt to send messages if not connected
35         if (!connected)
36             return;
37
38         // Add event to queue, restart send loop if finished.
39         outputMessageBuffer += message;
40         if (sendingMessages == 0)
41             sendPendingMessages();
42
43     };
44
45     function sendPendingMessages() {
46
47         if (outputMessageBuffer.length > 0) {
48
49             sendingMessages = 1;
50
51             var message_xmlhttprequest = new XMLHttpRequest();
52             message_xmlhttprequest.open("POST", TUNNEL_WRITE);
53             message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
54             message_xmlhttprequest.setRequestHeader("Content-length", outputMessageBuffer.length);
55
56             // Once response received, send next queued event.
57             message_xmlhttprequest.onreadystatechange = function() {
58                 if (message_xmlhttprequest.readyState == 4)
59                     sendPendingMessages();
60             }
61
62             message_xmlhttprequest.send(outputMessageBuffer);
63             outputMessageBuffer = ""; // Clear buffer
64
65         }
66         else
67             sendingMessages = 0;
68
69     }
70
71
72     function handleResponse(xmlhttprequest) {
73
74         var interval = null;
75         var nextRequest = null;
76
77         var dataUpdateEvents = 0;
78         var instructionStart = 0;
79         var startIndex = 0;
80
81         function parseResponse() {
82
83             // Do not handle responses if not connected
84             if (!connected) {
85                 
86                 // Clean up interval if polling
87                 if (interval != null)
88                     clearInterval(interval);
89                 
90                 return;
91             }
92
93             // Start next request as soon as possible
94             if (xmlhttprequest.readyState >= 2 && nextRequest == null)
95                 nextRequest = makeRequest();
96
97             // Parse stream when data is received and when complete.
98             if (xmlhttprequest.readyState == 3 ||
99                 xmlhttprequest.readyState == 4) {
100
101                 // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data)
102                 if (pollResponse == 1) {
103                     if (xmlhttprequest.readyState == 3 && interval == null)
104                         interval = setInterval(parseResponse, 30);
105                     else if (xmlhttprequest.readyState == 4 && interval != null)
106                         clearInterval(interval);
107                 }
108
109                 // Halt on error during request
110                 if (xmlhttprequest.status == 0 || xmlhttprequest.status != 200) {
111                     disconnect();
112                     return;
113                 }
114
115                 var current = xmlhttprequest.responseText;
116                 var instructionEnd;
117
118                 while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {
119
120                     // Start next search at next instruction
121                     startIndex = instructionEnd+1;
122
123                     var instruction = current.substr(instructionStart,
124                             instructionEnd - instructionStart);
125
126                     instructionStart = startIndex;
127
128                     var opcodeEnd = instruction.indexOf(":");
129
130                     var opcode;
131                     var parameters;
132                     if (opcodeEnd == -1) {
133                         opcode = instruction;
134                         parameters = new Array();
135                     }
136                     else {
137                         opcode = instruction.substr(0, opcodeEnd);
138                         parameters = instruction.substr(opcodeEnd+1).split(",");
139                     }
140
141                     // If we're done parsing, handle the next response.
142                     if (opcode.length == 0) {
143
144                         delete xmlhttprequest;
145                         if (nextRequest)
146                             handleResponse(nextRequest);
147
148                         break;
149                     }
150
151                     // Call instruction handler.
152                     if (instructionHandler != null)
153                         instructionHandler(opcode, parameters);
154                 }
155
156                 // Start search at end of string.
157                 startIndex = current.length;
158
159                 delete instruction;
160                 delete parameters;
161
162             }
163
164         }
165
166         // If response polling enabled, attempt to detect if still
167         // necessary (via wrapping parseResponse())
168         if (pollResponse == 1) {
169             xmlhttprequest.onreadystatechange = function() {
170
171                 // If we receive two or more readyState==3 events,
172                 // there is no need to poll.
173                 if (xmlhttprequest.readyState == 3) {
174                     dataUpdateEvents++;
175                     if (dataUpdateEvents >= 2) {
176                         pollResponse = 0;
177                         xmlhttprequest.onreadystatechange = parseResponse;
178                     }
179                 }
180
181                 parseResponse();
182             }
183         }
184
185         // Otherwise, just parse
186         else
187             xmlhttprequest.onreadystatechange = parseResponse;
188
189         parseResponse();
190
191     }
192
193
194     function makeRequest() {
195
196         // Download self
197         var xmlhttprequest = new XMLHttpRequest();
198         xmlhttprequest.open("POST", TUNNEL_READ);
199         xmlhttprequest.send(null);
200
201         return xmlhttprequest;
202
203     }
204
205     function connect() {
206
207         // Start tunnel and connect synchronously
208         var connect_xmlhttprequest = new XMLHttpRequest();
209         connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, false);
210         connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
211         connect_xmlhttprequest.setRequestHeader("Content-length", 0);
212         connect_xmlhttprequest.send(null);
213
214         // Start reading data
215         connected = 1;
216         handleResponse(makeRequest());
217
218     }
219
220     function disconnect() {
221         connected = 0;
222     }
223
224     // External API
225     this.connect = connect;
226     this.disconnect = disconnect;
227     this.sendMessage = sendMessage;
228     this.setInstructionHandler = function(handler) {
229         instructionHandler = handler;
230     }
231
232     this.escapeGuacamoleString = function(str) {
233
234         var escapedString = "";
235
236         for (var i=0; i<str.length; i++) {
237
238             var c = str.charAt(i);
239             if (c == ",")
240                 escapedString += "\\c";
241             else if (c == ";")
242                 escapedString += "\\s";
243             else if (c == "\\")
244                 escapedString += "\\\\";
245             else
246                 escapedString += c;
247
248         }
249
250         return escapedString;
251
252     }
253
254     this.unescapeGuacamoleString = function(str) {
255
256         var unescapedString = "";
257
258         for (var i=0; i<str.length; i++) {
259
260             var c = str.charAt(i);
261             if (c == "\\" && i<str.length-1) {
262
263                 var escapeChar = str.charAt(++i);
264                 if (escapeChar == "c")
265                     unescapedString += ",";
266                 else if (escapeChar == "s")
267                     unescapedString += ";";
268                 else if (escapeChar == "\\")
269                     unescapedString += "\\";
270                 else
271                     unescapedString += "\\" + escapeChar;
272
273             }
274             else
275                 unescapedString += c;
276
277         }
278
279         return unescapedString;
280
281     }
282
283 }