mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 21:27:40 +00:00
GUACAMOLE-615: Migrate tunnel implementations to common parser.
This commit is contained in:
@@ -92,30 +92,45 @@ Guacamole.Parser = function Parser() {
|
|||||||
*
|
*
|
||||||
* @param {!string} packet
|
* @param {!string} packet
|
||||||
* The instruction data to receive.
|
* The instruction data to receive.
|
||||||
|
*
|
||||||
|
* @param {!boolean} [isBuffer=false]
|
||||||
|
* Whether the provided data should be treated as an instruction buffer
|
||||||
|
* that grows continuously. If true, the data provided to receive()
|
||||||
|
* MUST always start with the data provided to the previous call. If
|
||||||
|
* false (the default), only the new data should be provided to
|
||||||
|
* receive(), and previously-received data will automatically be
|
||||||
|
* buffered by the parser as needed.
|
||||||
*/
|
*/
|
||||||
this.receive = function receive(packet) {
|
this.receive = function receive(packet, isBuffer) {
|
||||||
|
|
||||||
// Truncate buffer as necessary
|
if (isBuffer)
|
||||||
if (startIndex > 4096 && elementEnd >= startIndex) {
|
buffer = packet;
|
||||||
|
|
||||||
buffer = buffer.substring(startIndex);
|
else {
|
||||||
|
|
||||||
// Reset parse relative to truncation
|
// Truncate buffer as necessary
|
||||||
elementEnd -= startIndex;
|
if (startIndex > 4096 && elementEnd >= startIndex) {
|
||||||
startIndex = 0;
|
|
||||||
|
buffer = buffer.substring(startIndex);
|
||||||
|
|
||||||
|
// Reset parse relative to truncation
|
||||||
|
elementEnd -= startIndex;
|
||||||
|
startIndex = 0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Append data to buffer ONLY if there is outstanding data present. It
|
||||||
|
// is otherwise much faster to simply parse the received buffer as-is,
|
||||||
|
// and tunnel implementations can take advantage of this by preferring
|
||||||
|
// to send only complete instructions. Both the HTTP and WebSocket
|
||||||
|
// tunnel implementations included with Guacamole already do this.
|
||||||
|
if (buffer.length)
|
||||||
|
buffer += packet;
|
||||||
|
else
|
||||||
|
buffer = packet;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Append data to buffer ONLY if there is outstanding data present. It
|
|
||||||
// is otherwise much faster to simply parse the received buffer as-is,
|
|
||||||
// and tunnel implementations can take advantage of this by preferring
|
|
||||||
// to send only complete instructions. Both the HTTP and WebSocket
|
|
||||||
// tunnel implementations included with Guacamole already do this.
|
|
||||||
if (buffer.length)
|
|
||||||
buffer += packet;
|
|
||||||
else
|
|
||||||
buffer = packet;
|
|
||||||
|
|
||||||
// While search is within currently received data
|
// While search is within currently received data
|
||||||
while (elementEnd < buffer.length) {
|
while (elementEnd < buffer.length) {
|
||||||
|
|
||||||
@@ -257,3 +272,43 @@ Guacamole.Parser.codePointCount = function codePointCount(str, start, end) {
|
|||||||
var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
|
var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
|
||||||
return str.length - (surrogatePairs ? surrogatePairs.length : 0);
|
return str.length - (surrogatePairs ? surrogatePairs.length : 0);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts each of the values within the given array to strings, formatting
|
||||||
|
* those strings as length-prefixed elements of a complete Guacamole
|
||||||
|
* instruction.
|
||||||
|
*
|
||||||
|
* @param {!*[]} elements
|
||||||
|
* The values that should be encoded as the elements of a Guacamole
|
||||||
|
* instruction. Order of these elements is preserved. This array MUST have
|
||||||
|
* at least one element.
|
||||||
|
*
|
||||||
|
* @returns {!string}
|
||||||
|
* A complete Guacamole instruction consisting of each of the provided
|
||||||
|
* element values, in order.
|
||||||
|
*/
|
||||||
|
Guacamole.Parser.toInstruction = function toInstruction(elements) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given value to a length/string pair for use as an
|
||||||
|
* element in a Guacamole instruction.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {*} value
|
||||||
|
* The value to convert.
|
||||||
|
*
|
||||||
|
* @return {!string}
|
||||||
|
* The converted value.
|
||||||
|
*/
|
||||||
|
var toElement = function toElement(value) {
|
||||||
|
var str = '' + value;
|
||||||
|
return Guacamole.Parser.codePointCount(str) + "." + str;
|
||||||
|
};
|
||||||
|
|
||||||
|
var instr = toElement(elements[0]);
|
||||||
|
for (var i = 1; i < elements.length; i++)
|
||||||
|
instr += ',' + toElement(elements[i]);
|
||||||
|
|
||||||
|
return instr + ';';
|
||||||
|
|
||||||
|
};
|
||||||
|
@@ -436,37 +436,11 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Do not attempt to send empty messages
|
// Do not attempt to send empty messages
|
||||||
if (arguments.length === 0)
|
if (!arguments.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given value to a length/string pair for use as an
|
|
||||||
* element in a Guacamole instruction.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param value
|
|
||||||
* The value to convert.
|
|
||||||
*
|
|
||||||
* @return {!string}
|
|
||||||
* The converted value.
|
|
||||||
*/
|
|
||||||
function getElement(value) {
|
|
||||||
var string = new String(value);
|
|
||||||
return string.length + "." + string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialized message with first element
|
|
||||||
var message = getElement(arguments[0]);
|
|
||||||
|
|
||||||
// Append remaining elements
|
|
||||||
for (var i=1; i<arguments.length; i++)
|
|
||||||
message += "," + getElement(arguments[i]);
|
|
||||||
|
|
||||||
// Final terminator
|
|
||||||
message += ";";
|
|
||||||
|
|
||||||
// Add message to buffer
|
// Add message to buffer
|
||||||
outputMessageBuffer += message;
|
outputMessageBuffer += Guacamole.Parser.toInstruction(arguments);
|
||||||
|
|
||||||
// Send if not currently sending
|
// Send if not currently sending
|
||||||
if (!sendingMessages)
|
if (!sendingMessages)
|
||||||
@@ -546,14 +520,36 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
|
|||||||
|
|
||||||
var dataUpdateEvents = 0;
|
var dataUpdateEvents = 0;
|
||||||
|
|
||||||
// The location of the last element's terminator
|
var parser = new Guacamole.Parser();
|
||||||
var elementEnd = -1;
|
parser.oninstruction = function instructionReceived(opcode, args) {
|
||||||
|
|
||||||
// Where to start the next length search or the next element
|
// Switch to next request if end-of-stream is signalled
|
||||||
var startIndex = 0;
|
if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE && args.length === 0) {
|
||||||
|
|
||||||
// Parsed elements
|
// Reset parser state by simply switching to an entirely new
|
||||||
var elements = new Array();
|
// parser
|
||||||
|
parser = new Guacamole.Parser();
|
||||||
|
parser.oninstruction = instructionReceived;
|
||||||
|
|
||||||
|
// Clean up interval if polling
|
||||||
|
if (interval)
|
||||||
|
clearInterval(interval);
|
||||||
|
|
||||||
|
// Clean up object
|
||||||
|
xmlhttprequest.onreadystatechange = null;
|
||||||
|
xmlhttprequest.abort();
|
||||||
|
|
||||||
|
// Start handling next request
|
||||||
|
if (nextRequest)
|
||||||
|
handleResponse(nextRequest);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call instruction handler.
|
||||||
|
else if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction)
|
||||||
|
tunnel.oninstruction(opcode, args);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
function parseResponse() {
|
function parseResponse() {
|
||||||
|
|
||||||
@@ -614,83 +610,13 @@ Guacamole.HTTPTunnel = function(tunnelURL, crossDomain, extraTunnelHeaders) {
|
|||||||
// Do not attempt to parse if data could not be read
|
// Do not attempt to parse if data could not be read
|
||||||
catch (e) { return; }
|
catch (e) { return; }
|
||||||
|
|
||||||
// While search is within currently received data
|
try {
|
||||||
while (elementEnd < current.length) {
|
parser.receive(current, true);
|
||||||
|
}
|
||||||
// If we are waiting for element data
|
catch (e) {
|
||||||
if (elementEnd >= startIndex) {
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, e.message));
|
||||||
|
return;
|
||||||
// We now have enough data for the element. Parse.
|
}
|
||||||
var element = current.substring(startIndex, elementEnd);
|
|
||||||
var terminator = current.substring(elementEnd, elementEnd+1);
|
|
||||||
|
|
||||||
// Add element to array
|
|
||||||
elements.push(element);
|
|
||||||
|
|
||||||
// If last element, handle instruction
|
|
||||||
if (terminator === ";") {
|
|
||||||
|
|
||||||
// Get opcode
|
|
||||||
var opcode = elements.shift();
|
|
||||||
|
|
||||||
// Call instruction handler.
|
|
||||||
if (tunnel.oninstruction)
|
|
||||||
tunnel.oninstruction(opcode, elements);
|
|
||||||
|
|
||||||
// Clear elements
|
|
||||||
elements.length = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start searching for length at character after
|
|
||||||
// element terminator
|
|
||||||
startIndex = elementEnd + 1;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search for end of length
|
|
||||||
var lengthEnd = current.indexOf(".", startIndex);
|
|
||||||
if (lengthEnd !== -1) {
|
|
||||||
|
|
||||||
// Parse length
|
|
||||||
var length = parseInt(current.substring(elementEnd+1, lengthEnd));
|
|
||||||
|
|
||||||
// If we're done parsing, handle the next response.
|
|
||||||
if (length === 0) {
|
|
||||||
|
|
||||||
// Clean up interval if polling
|
|
||||||
if (interval)
|
|
||||||
clearInterval(interval);
|
|
||||||
|
|
||||||
// Clean up object
|
|
||||||
xmlhttprequest.onreadystatechange = null;
|
|
||||||
xmlhttprequest.abort();
|
|
||||||
|
|
||||||
// Start handling next request
|
|
||||||
if (nextRequest)
|
|
||||||
handleResponse(nextRequest);
|
|
||||||
|
|
||||||
// Done parsing
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate start of element
|
|
||||||
startIndex = lengthEnd + 1;
|
|
||||||
|
|
||||||
// Calculate location of element terminator
|
|
||||||
elementEnd = startIndex + length;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no period yet, continue search when more data
|
|
||||||
// is received
|
|
||||||
else {
|
|
||||||
startIndex = current.length;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // end parse loop
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,6 +749,16 @@ Guacamole.WebSocketTunnel = function(tunnelURL) {
|
|||||||
*/
|
*/
|
||||||
var tunnel = this;
|
var tunnel = this;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parser that this tunnel will use to parse received Guacamole
|
||||||
|
* instructions. The parser is created when the tunnel is (re-)connected.
|
||||||
|
* Initially, this will be null.
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @type {Guacamole.Parser}
|
||||||
|
*/
|
||||||
|
var parser = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The WebSocket used by this tunnel.
|
* The WebSocket used by this tunnel.
|
||||||
*
|
*
|
||||||
@@ -1016,36 +952,10 @@ Guacamole.WebSocketTunnel = function(tunnelURL) {
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
// Do not attempt to send empty messages
|
// Do not attempt to send empty messages
|
||||||
if (arguments.length === 0)
|
if (!arguments.length)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/**
|
socket.send(Guacamole.Parser.toInstruction(arguments));
|
||||||
* Converts the given value to a length/string pair for use as an
|
|
||||||
* element in a Guacamole instruction.
|
|
||||||
*
|
|
||||||
* @private
|
|
||||||
* @param {*} value
|
|
||||||
* The value to convert.
|
|
||||||
*
|
|
||||||
* @return {!string}
|
|
||||||
* The converted value.
|
|
||||||
*/
|
|
||||||
function getElement(value) {
|
|
||||||
var string = new String(value);
|
|
||||||
return string.length + "." + string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Initialized message with first element
|
|
||||||
var message = getElement(arguments[0]);
|
|
||||||
|
|
||||||
// Append remaining elements
|
|
||||||
for (var i=1; i<arguments.length; i++)
|
|
||||||
message += "," + getElement(arguments[i]);
|
|
||||||
|
|
||||||
// Final terminator
|
|
||||||
message += ";";
|
|
||||||
|
|
||||||
socket.send(message);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1056,6 +966,27 @@ Guacamole.WebSocketTunnel = function(tunnelURL) {
|
|||||||
// Mark the tunnel as connecting
|
// Mark the tunnel as connecting
|
||||||
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
tunnel.setState(Guacamole.Tunnel.State.CONNECTING);
|
||||||
|
|
||||||
|
parser = new Guacamole.Parser();
|
||||||
|
parser.oninstruction = function instructionReceived(opcode, args) {
|
||||||
|
|
||||||
|
// Update state and UUID when first instruction received
|
||||||
|
if (tunnel.uuid === null) {
|
||||||
|
|
||||||
|
// Associate tunnel UUID if received
|
||||||
|
if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE && args.length === 1)
|
||||||
|
tunnel.setUUID(args[0]);
|
||||||
|
|
||||||
|
// Tunnel is now open and UUID is available
|
||||||
|
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call instruction handler.
|
||||||
|
if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction)
|
||||||
|
tunnel.oninstruction(opcode, args);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
// Connect socket
|
// Connect socket
|
||||||
socket = new WebSocket(tunnelURL + "?" + data, "guacamole");
|
socket = new WebSocket(tunnelURL + "?" + data, "guacamole");
|
||||||
|
|
||||||
@@ -1084,72 +1015,12 @@ Guacamole.WebSocketTunnel = function(tunnelURL) {
|
|||||||
|
|
||||||
resetTimers();
|
resetTimers();
|
||||||
|
|
||||||
var message = event.data;
|
try {
|
||||||
var startIndex = 0;
|
parser.receive(event.data);
|
||||||
var elementEnd;
|
}
|
||||||
|
catch (e) {
|
||||||
var elements = [];
|
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, e.message));
|
||||||
|
}
|
||||||
do {
|
|
||||||
|
|
||||||
// Search for end of length
|
|
||||||
var lengthEnd = message.indexOf(".", startIndex);
|
|
||||||
if (lengthEnd !== -1) {
|
|
||||||
|
|
||||||
// Parse length
|
|
||||||
var length = parseInt(message.substring(elementEnd+1, lengthEnd));
|
|
||||||
|
|
||||||
// Calculate start of element
|
|
||||||
startIndex = lengthEnd + 1;
|
|
||||||
|
|
||||||
// Calculate location of element terminator
|
|
||||||
elementEnd = startIndex + length;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no period, incomplete instruction.
|
|
||||||
else
|
|
||||||
close_tunnel(new Guacamole.Status(Guacamole.Status.Code.SERVER_ERROR, "Incomplete instruction."));
|
|
||||||
|
|
||||||
// We now have enough data for the element. Parse.
|
|
||||||
var element = message.substring(startIndex, elementEnd);
|
|
||||||
var terminator = message.substring(elementEnd, elementEnd+1);
|
|
||||||
|
|
||||||
// Add element to array
|
|
||||||
elements.push(element);
|
|
||||||
|
|
||||||
// If last element, handle instruction
|
|
||||||
if (terminator === ";") {
|
|
||||||
|
|
||||||
// Get opcode
|
|
||||||
var opcode = elements.shift();
|
|
||||||
|
|
||||||
// Update state and UUID when first instruction received
|
|
||||||
if (tunnel.uuid === null) {
|
|
||||||
|
|
||||||
// Associate tunnel UUID if received
|
|
||||||
if (opcode === Guacamole.Tunnel.INTERNAL_DATA_OPCODE && elements.length === 1)
|
|
||||||
tunnel.setUUID(elements[0]);
|
|
||||||
|
|
||||||
// Tunnel is now open and UUID is available
|
|
||||||
tunnel.setState(Guacamole.Tunnel.State.OPEN);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Call instruction handler.
|
|
||||||
if (opcode !== Guacamole.Tunnel.INTERNAL_DATA_OPCODE && tunnel.oninstruction)
|
|
||||||
tunnel.oninstruction(opcode, elements);
|
|
||||||
|
|
||||||
// Clear elements
|
|
||||||
elements.length = 0;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Start searching for length at character after
|
|
||||||
// element terminator
|
|
||||||
startIndex = elementEnd + 1;
|
|
||||||
|
|
||||||
} while (startIndex < message.length);
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user