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 STATE_CONNECTED = 1;
27 var STATE_DISCONNECTED = 2;
29 var currentState = STATE_IDLE;
31 var POLLING_ENABLED = 1;
32 var POLLING_DISABLED = 0;
34 // Default to polling - will be turned off automatically if not needed
35 var pollingMode = POLLING_ENABLED;
37 var instructionHandler = null;
39 var sendingMessages = 0;
40 var outputMessageBuffer = "";
42 function sendMessage(message) {
44 // Do not attempt to send messages if not connected
45 if (currentState != STATE_CONNECTED)
48 // Add event to queue, restart send loop if finished.
49 outputMessageBuffer += message;
50 if (sendingMessages == 0)
51 sendPendingMessages();
55 function sendPendingMessages() {
57 if (outputMessageBuffer.length > 0) {
61 var message_xmlhttprequest = new XMLHttpRequest();
62 message_xmlhttprequest.open("POST", TUNNEL_WRITE);
63 message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
64 message_xmlhttprequest.setRequestHeader("Content-length", outputMessageBuffer.length);
66 // Once response received, send next queued event.
67 message_xmlhttprequest.onreadystatechange = function() {
68 if (message_xmlhttprequest.readyState == 4)
69 sendPendingMessages();
72 message_xmlhttprequest.send(outputMessageBuffer);
73 outputMessageBuffer = ""; // Clear buffer
82 function handleResponse(xmlhttprequest) {
85 var nextRequest = null;
87 var dataUpdateEvents = 0;
88 var instructionStart = 0;
91 function parseResponse() {
93 // Do not handle responses if not connected
94 if (currentState != STATE_CONNECTED) {
96 // Clean up interval if polling
98 clearInterval(interval);
103 // Start next request as soon as possible
104 if (xmlhttprequest.readyState >= 2 && nextRequest == null)
105 nextRequest = makeRequest();
107 // Parse stream when data is received and when complete.
108 if (xmlhttprequest.readyState == 3 ||
109 xmlhttprequest.readyState == 4) {
111 // Also poll every 30ms (some browsers don't repeatedly call onreadystatechange for new data)
112 if (pollingMode == POLLING_ENABLED) {
113 if (xmlhttprequest.readyState == 3 && interval == null)
114 interval = setInterval(parseResponse, 30);
115 else if (xmlhttprequest.readyState == 4 && interval != null)
116 clearInterval(interval);
119 // Halt on error during request
120 if (xmlhttprequest.status == 0 || xmlhttprequest.status != 200) {
125 var current = xmlhttprequest.responseText;
128 while ((instructionEnd = current.indexOf(";", startIndex)) != -1) {
130 // Start next search at next instruction
131 startIndex = instructionEnd+1;
133 var instruction = current.substr(instructionStart,
134 instructionEnd - instructionStart);
136 instructionStart = startIndex;
138 var opcodeEnd = instruction.indexOf(":");
142 if (opcodeEnd == -1) {
143 opcode = instruction;
144 parameters = new Array();
147 opcode = instruction.substr(0, opcodeEnd);
148 parameters = instruction.substr(opcodeEnd+1).split(",");
151 // If we're done parsing, handle the next response.
152 if (opcode.length == 0) {
154 delete xmlhttprequest;
156 handleResponse(nextRequest);
161 // Call instruction handler.
162 if (instructionHandler != null)
163 instructionHandler(opcode, parameters);
166 // Start search at end of string.
167 startIndex = current.length;
176 // If response polling enabled, attempt to detect if still
177 // necessary (via wrapping parseResponse())
178 if (pollingMode == POLLING_ENABLED) {
179 xmlhttprequest.onreadystatechange = function() {
181 // If we receive two or more readyState==3 events,
182 // there is no need to poll.
183 if (xmlhttprequest.readyState == 3) {
185 if (dataUpdateEvents >= 2) {
186 pollingMode = POLLING_DISABLED;
187 xmlhttprequest.onreadystatechange = parseResponse;
195 // Otherwise, just parse
197 xmlhttprequest.onreadystatechange = parseResponse;
204 function makeRequest() {
207 var xmlhttprequest = new XMLHttpRequest();
208 xmlhttprequest.open("POST", TUNNEL_READ);
209 xmlhttprequest.send(null);
211 return xmlhttprequest;
217 // Start tunnel and connect synchronously
218 var connect_xmlhttprequest = new XMLHttpRequest();
219 connect_xmlhttprequest.open("POST", TUNNEL_CONNECT, false);
220 connect_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
221 connect_xmlhttprequest.setRequestHeader("Content-length", 0);
222 connect_xmlhttprequest.send(null);
224 // Start reading data
225 currentState = STATE_CONNECTED;
226 handleResponse(makeRequest());
230 function disconnect() {
231 currentState = STATE_DISCONNECTED;
235 this.connect = connect;
236 this.disconnect = disconnect;
237 this.sendMessage = sendMessage;
238 this.setInstructionHandler = function(handler) {
239 instructionHandler = handler;