3 * Guacamole - Clientless Remote Desktop
4 * Copyright (C) 2010 Michael Jumper
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.
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.
16 * You should have received a copy of the GNU Affero General Public License
19 function GuacamoleHTTPTunnel(tunnelURL) {
21 var TUNNEL_CONNECT = tunnelURL + "?connect";
22 var TUNNEL_READ = tunnelURL + "?read";
23 var TUNNEL_WRITE = tunnelURL + "?write";
26 var pollResponse = 1; // Default to polling - will be turned off automatically if not needed
27 var instructionHandler = null;
29 var sendingMessages = 0;
30 var outputMessageBuffer = "";
32 function sendMessage(message) {
34 // Do not attempt to send messages if not connected
38 // Add event to queue, restart send loop if finished.
39 outputMessageBuffer += message;
40 if (sendingMessages == 0)
41 sendPendingMessages();
45 function sendPendingMessages() {
47 if (outputMessageBuffer.length > 0) {
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);
56 // Once response received, send next queued event.
57 message_xmlhttprequest.onreadystatechange = function() {
58 if (message_xmlhttprequest.readyState == 4)
59 sendPendingMessages();
62 message_xmlhttprequest.send(outputMessageBuffer);
63 outputMessageBuffer = ""; // Clear buffer
72 function handleResponse(xmlhttprequest) {
75 var nextRequest = null;
77 var dataUpdateEvents = 0;
78 var instructionStart = 0;
81 function parseResponse() {
83 // Do not handle responses if not connected
86 // Clean up interval if polling
88 clearInterval(interval);
93 // Start next request as soon as possible
94 if (xmlhttprequest.readyState >= 2 && nextRequest == null)
95 nextRequest = makeRequest();
97 // Parse stream when data is received and when complete.
98 if (xmlhttprequest.readyState == 3 ||
99 xmlhttprequest.readyState == 4) {
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);
109 // Halt on error during request
110 if (xmlhttprequest.status == 0 || xmlhttprequest.status != 200) {
115 var current = xmlhttprequest.responseText;
118 while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {
120 // Start next search at next instruction
121 startIndex = instructionEnd+1;
123 var instruction = current.substr(instructionStart,
124 instructionEnd - instructionStart);
126 instructionStart = startIndex;
128 var opcodeEnd = instruction.indexOf(":");
132 if (opcodeEnd == -1) {
133 opcode = instruction;
134 parameters = new Array();
137 opcode = instruction.substr(0, opcodeEnd);
138 parameters = instruction.substr(opcodeEnd+1).split(",");
141 // If we're done parsing, handle the next response.
142 if (opcode.length == 0) {
144 delete xmlhttprequest;
146 handleResponse(nextRequest);
151 // Call instruction handler.
152 if (instructionHandler != null)
153 instructionHandler(opcode, parameters);
156 // Start search at end of string.
157 startIndex = current.length;
166 // If response polling enabled, attempt to detect if still
167 // necessary (via wrapping parseResponse())
168 if (pollResponse == 1) {
169 xmlhttprequest.onreadystatechange = function() {
171 // If we receive two or more readyState==3 events,
172 // there is no need to poll.
173 if (xmlhttprequest.readyState == 3) {
175 if (dataUpdateEvents >= 2) {
177 xmlhttprequest.onreadystatechange = parseResponse;
185 // Otherwise, just parse
187 xmlhttprequest.onreadystatechange = parseResponse;
194 function makeRequest() {
197 var xmlhttprequest = new XMLHttpRequest();
198 xmlhttprequest.open("POST", TUNNEL_READ);
199 xmlhttprequest.send(null);
201 return xmlhttprequest;
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);
214 // Start reading data
216 handleResponse(makeRequest());
220 function disconnect() {
225 this.connect = connect;
226 this.disconnect = disconnect;
227 this.sendMessage = sendMessage;
228 this.setInstructionHandler = function(handler) {
229 instructionHandler = handler;
232 this.escapeGuacamoleString = function(str) {
234 var escapedString = "";
236 for (var i=0; i<str.length; i++) {
238 var c = str.charAt(i);
240 escapedString += "\\c";
242 escapedString += "\\s";
244 escapedString += "\\\\";
250 return escapedString;
254 this.unescapeGuacamoleString = function(str) {
256 var unescapedString = "";
258 for (var i=0; i<str.length; i++) {
260 var c = str.charAt(i);
261 if (c == "\\" && i<str.length-1) {
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 += "\\";
271 unescapedString += "\\" + escapeChar;
275 unescapedString += c;
279 return unescapedString;