From d39850966029ae19c89cfdb37848b3de3e3abf98 Mon Sep 17 00:00:00 2001 From: Michael Jumper Date: Wed, 13 Apr 2016 18:09:34 -0700 Subject: [PATCH] GUACAMOLE-44: Expose tunnel UUID to JavaScript. Document allowed internal use of the empty opcode. --- .../src/main/webapp/modules/Tunnel.js | 51 ++++++++++++++----- .../apache/guacamole/net/GuacamoleTunnel.java | 10 ++++ .../GuacamoleWebSocketTunnelEndpoint.java | 7 +++ .../rest/tunnel/TunnelRESTService.java | 4 -- .../GuacamoleWebSocketTunnelServlet.java | 7 +++ .../GuacamoleWebSocketTunnelListener.java | 7 +++ .../GuacamoleWebSocketTunnelServlet.java | 7 +++ 7 files changed, 77 insertions(+), 16 deletions(-) diff --git a/guacamole-common-js/src/main/webapp/modules/Tunnel.js b/guacamole-common-js/src/main/webapp/modules/Tunnel.js index 3fcb4099e..6ec479821 100644 --- a/guacamole-common-js/src/main/webapp/modules/Tunnel.js +++ b/guacamole-common-js/src/main/webapp/modules/Tunnel.js @@ -70,6 +70,14 @@ Guacamole.Tunnel = function() { */ this.receiveTimeout = 15000; + /** + * The UUID uniquely identifying this tunnel. If not yet known, this will + * be null. + * + * @type {String} + */ + this.uuid = null; + /** * Fired whenever an error is encountered by the tunnel. * @@ -99,6 +107,18 @@ Guacamole.Tunnel = function() { }; +/** + * The Guacamole protocol instruction opcode reserved for arbitrary internal + * use by tunnel implementations. The value of this opcode is guaranteed to be + * the empty string (""). Tunnel implementations may use this opcode for any + * purpose. It is currently used by the HTTP tunnel to mark the end of the HTTP + * response, and by the WebSocket tunnel to transmit the tunnel UUID. + * + * @constant + * @type {String} + */ +Guacamole.Tunnel.INTERNAL_DATA_OPCODE = ''; + /** * All possible tunnel states. */ @@ -152,8 +172,6 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain) { */ var tunnel = this; - var tunnel_uuid; - var TUNNEL_CONNECT = tunnelURL + "?connect"; var TUNNEL_READ = tunnelURL + "?read:"; var TUNNEL_WRITE = tunnelURL + "?write:"; @@ -286,7 +304,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain) { sendingMessages = true; var message_xmlhttprequest = new XMLHttpRequest(); - message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel_uuid); + message_xmlhttprequest.open("POST", TUNNEL_WRITE + tunnel.uuid); message_xmlhttprequest.withCredentials = withCredentials; message_xmlhttprequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=UTF-8"); @@ -517,7 +535,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain) { // Make request, increment request ID var xmlhttprequest = new XMLHttpRequest(); - xmlhttprequest.open("GET", TUNNEL_READ + tunnel_uuid + ":" + (request_id++)); + xmlhttprequest.open("GET", TUNNEL_READ + tunnel.uuid + ":" + (request_id++)); xmlhttprequest.withCredentials = withCredentials; xmlhttprequest.send(null); @@ -546,7 +564,7 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain) { reset_timeout(); // Get UUID from response - tunnel_uuid = connect_xmlhttprequest.responseText; + tunnel.uuid = connect_xmlhttprequest.responseText; tunnel.state = Guacamole.Tunnel.State.OPEN; if (tunnel.onstatechange) @@ -733,13 +751,7 @@ Guacamole.WebSocketTunnel = function(tunnelURL) { socket = new WebSocket(tunnelURL + "?" + data, "guacamole"); socket.onopen = function(event) { - reset_timeout(); - - tunnel.state = Guacamole.Tunnel.State.OPEN; - if (tunnel.onstatechange) - tunnel.onstatechange(tunnel.state); - }; socket.onclose = function(event) { @@ -794,8 +806,22 @@ Guacamole.WebSocketTunnel = function(tunnelURL) { // Get opcode var opcode = elements.shift(); + // Update state and UUID when first instruction received + if (tunnel.state !== Guacamole.Tunnel.State.OPEN) { + + // Associate tunnel UUID if received + if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE) + tunnel.uuid = elements[0]; + + // Tunnel is now open and UUID is available + tunnel.state = Guacamole.Tunnel.State.OPEN; + if (tunnel.onstatechange) + tunnel.onstatechange(tunnel.state); + + } + // Call instruction handler. - if (tunnel.oninstruction) + if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction) tunnel.oninstruction(opcode, elements); // Clear elements @@ -926,6 +952,7 @@ Guacamole.ChainedTunnel = function(tunnelChain) { tunnel.onstatechange = chained_tunnel.onstatechange; tunnel.oninstruction = chained_tunnel.oninstruction; tunnel.onerror = chained_tunnel.onerror; + chained_tunnel.uuid = tunnel.uuid; committedTunnel = tunnel; } diff --git a/guacamole-common/src/main/java/org/apache/guacamole/net/GuacamoleTunnel.java b/guacamole-common/src/main/java/org/apache/guacamole/net/GuacamoleTunnel.java index b53c474c8..77c00e1f2 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/net/GuacamoleTunnel.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/net/GuacamoleTunnel.java @@ -33,6 +33,16 @@ import org.apache.guacamole.io.GuacamoleWriter; */ public interface GuacamoleTunnel { + /** + * The Guacamole protocol instruction opcode reserved for arbitrary + * internal use by tunnel implementations. The value of this opcode is + * guaranteed to be the empty string (""). Tunnel implementations may use + * this opcode for any purpose. It is currently used by the HTTP tunnel to + * mark the end of the HTTP response, and by the WebSocket tunnel to + * transmit the tunnel UUID. + */ + static final String INTERNAL_DATA_OPCODE = ""; + /** * Acquires exclusive read access to the Guacamole instruction stream * and returns a GuacamoleReader for reading from that stream. diff --git a/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java b/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java index dbded9b8f..e0aa44224 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/websocket/GuacamoleWebSocketTunnelEndpoint.java @@ -36,6 +36,7 @@ import org.apache.guacamole.io.GuacamoleWriter; import org.apache.guacamole.net.GuacamoleTunnel; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleConnectionClosedException; +import org.apache.guacamole.protocol.GuacamoleInstruction; import org.apache.guacamole.protocol.GuacamoleStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -149,6 +150,12 @@ public abstract class GuacamoleWebSocketTunnelEndpoint extends Endpoint { try { + // Send tunnel UUID + remote.sendText(new GuacamoleInstruction( + GuacamoleTunnel.INTERNAL_DATA_OPCODE, + tunnel.getUUID().toString() + ).toString()); + try { // Attempt to read diff --git a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelRESTService.java b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelRESTService.java index f9cb92020..c89fdcd25 100644 --- a/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelRESTService.java +++ b/guacamole/src/main/java/org/apache/guacamole/rest/tunnel/TunnelRESTService.java @@ -124,10 +124,6 @@ public class TunnelRESTService { GuacamoleSession session = authenticationService.getGuacamoleSession(authToken); Map tunnels = session.getTunnels(); - // STUB: For sake of testing, if only one tunnel exists, use that - if (tunnels.size() == 1) - tunnelUUID = tunnels.keySet().iterator().next(); - // Pull tunnel with given UUID final StreamInterceptingTunnel tunnel = tunnels.get(tunnelUUID); if (tunnel == null) diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java index 2e02e3483..933ff654f 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty8/GuacamoleWebSocketTunnelServlet.java @@ -30,6 +30,7 @@ import org.eclipse.jetty.websocket.WebSocket.Connection; import org.eclipse.jetty.websocket.WebSocketServlet; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleConnectionClosedException; +import org.apache.guacamole.protocol.GuacamoleInstruction; import org.apache.guacamole.tunnel.http.HTTPTunnelRequest; import org.apache.guacamole.tunnel.TunnelRequest; import org.apache.guacamole.protocol.GuacamoleStatus; @@ -136,6 +137,12 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { try { + // Send tunnel UUID + connection.sendMessage(new GuacamoleInstruction( + GuacamoleTunnel.INTERNAL_DATA_OPCODE, + tunnel.getUUID().toString() + ).toString()); + try { // Attempt to read diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java index ce5be60ca..89105fc99 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/jetty9/GuacamoleWebSocketTunnelListener.java @@ -30,6 +30,7 @@ import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.io.GuacamoleReader; import org.apache.guacamole.io.GuacamoleWriter; import org.apache.guacamole.net.GuacamoleTunnel; +import org.apache.guacamole.protocol.GuacamoleInstruction; import org.apache.guacamole.protocol.GuacamoleStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -127,6 +128,12 @@ public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListe try { + // Send tunnel UUID + remote.sendString(new GuacamoleInstruction( + GuacamoleTunnel.INTERNAL_DATA_OPCODE, + tunnel.getUUID().toString() + ).toString()); + try { // Attempt to read diff --git a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java index 059a0bd7d..1b9098f9b 100644 --- a/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java +++ b/guacamole/src/main/java/org/apache/guacamole/tunnel/websocket/tomcat/GuacamoleWebSocketTunnelServlet.java @@ -35,6 +35,7 @@ import org.apache.catalina.websocket.WebSocketServlet; import org.apache.catalina.websocket.WsOutbound; import org.apache.guacamole.GuacamoleClientException; import org.apache.guacamole.GuacamoleConnectionClosedException; +import org.apache.guacamole.protocol.GuacamoleInstruction; import org.apache.guacamole.tunnel.http.HTTPTunnelRequest; import org.apache.guacamole.tunnel.TunnelRequest; import org.apache.guacamole.protocol.GuacamoleStatus; @@ -164,6 +165,12 @@ public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet { try { + // Send tunnel UUID + outbound.writeTextMessage(CharBuffer.wrap(new GuacamoleInstruction( + GuacamoleTunnel.INTERNAL_DATA_OPCODE, + tunnel.getUUID().toString() + ).toString())); + try { // Attempt to read