mirror of
https://github.com/gyurix1968/guacamole-client.git
synced 2025-09-06 05:07:41 +00:00
326 lines
11 KiB
JavaScript
326 lines
11 KiB
JavaScript
/*
|
|
* Licensed to the Apache Software Foundation (ASF) under one
|
|
* or more contributor license agreements. See the NOTICE file
|
|
* distributed with this work for additional information
|
|
* regarding copyright ownership. The ASF licenses this file
|
|
* to you under the Apache License, Version 2.0 (the
|
|
* "License"); you may not use this file except in compliance
|
|
* with the License. You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
* KIND, either express or implied. See the License for the
|
|
* specific language governing permissions and limitations
|
|
* under the License.
|
|
*/
|
|
|
|
var Guacamole = Guacamole || {};
|
|
|
|
/**
|
|
* Simple Guacamole protocol parser that invokes an oninstruction event when
|
|
* full instructions are available from data received via receive().
|
|
*
|
|
* @constructor
|
|
*/
|
|
Guacamole.Parser = function Parser() {
|
|
|
|
/**
|
|
* Reference to this parser.
|
|
*
|
|
* @private
|
|
* @type {!Guacamole.Parser}
|
|
*/
|
|
var parser = this;
|
|
|
|
/**
|
|
* Current buffer of received data. This buffer grows until a full
|
|
* element is available. After a full element is available, that element
|
|
* is flushed into the element buffer.
|
|
*
|
|
* @private
|
|
* @type {!string}
|
|
*/
|
|
var buffer = '';
|
|
|
|
/**
|
|
* Buffer of all received, complete elements. After an entire instruction
|
|
* is read, this buffer is flushed, and a new instruction begins.
|
|
*
|
|
* @private
|
|
* @type {!string[]}
|
|
*/
|
|
var elementBuffer = [];
|
|
|
|
/**
|
|
* The character offset within the buffer of the current or most recently
|
|
* parsed element's terminator. If sufficient characters have not yet been
|
|
* read via calls to receive(), this may point to an offset well beyond the
|
|
* end of the buffer. If no characters for an element have yet been read,
|
|
* this will be -1.
|
|
*
|
|
* @private
|
|
* @type {!number}
|
|
*/
|
|
var elementEnd = -1;
|
|
|
|
/**
|
|
* The character offset within the buffer of the location that the parser
|
|
* should start looking for the next element length search or next element
|
|
* value.
|
|
*
|
|
* @private
|
|
* @type {!number}
|
|
*/
|
|
var startIndex = 0;
|
|
|
|
/**
|
|
* The declared length of the current element being parsed, in Unicode
|
|
* codepoints.
|
|
*
|
|
* @private
|
|
* @type {!number}
|
|
*/
|
|
var elementCodepoints = 0;
|
|
|
|
/**
|
|
* Appends the given instruction data packet to the internal buffer of
|
|
* this Guacamole.Parser, executing all completed instructions at
|
|
* the beginning of this buffer, if any.
|
|
*
|
|
* @param {!string} packet
|
|
* 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, isBuffer) {
|
|
|
|
if (isBuffer)
|
|
buffer = packet;
|
|
|
|
else {
|
|
|
|
// Truncate buffer as necessary
|
|
if (startIndex > 4096 && elementEnd >= startIndex) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
// While search is within currently received data
|
|
while (elementEnd < buffer.length) {
|
|
|
|
// If we are waiting for element data
|
|
if (elementEnd >= startIndex) {
|
|
|
|
// If we have enough data in the buffer to fill the element
|
|
// value, but the number of codepoints in the expected substring
|
|
// containing the element value value is less that its declared
|
|
// length, that can only be because the element contains
|
|
// characters split between high and low surrogates, and the
|
|
// actual end of the element value is further out. The minimum
|
|
// number of additional characters that must be read to satisfy
|
|
// the declared length is simply the difference between the
|
|
// number of codepoints actually present vs. the expected
|
|
// length.
|
|
var codepoints = Guacamole.Parser.codePointCount(buffer, startIndex, elementEnd);
|
|
if (codepoints < elementCodepoints) {
|
|
elementEnd += elementCodepoints - codepoints;
|
|
continue;
|
|
}
|
|
|
|
// If the current element ends with a character involving both
|
|
// a high and low surrogate, elementEnd points to the low
|
|
// surrogate and NOT the element terminator. We must shift the
|
|
// end and reevaluate.
|
|
else if (elementCodepoints && buffer.codePointAt(elementEnd - 1) >= 0x10000) {
|
|
elementEnd++;
|
|
continue;
|
|
}
|
|
|
|
// We now have enough data for the element. Parse.
|
|
var element = buffer.substring(startIndex, elementEnd);
|
|
var terminator = buffer.substring(elementEnd, elementEnd + 1);
|
|
|
|
// Add element to array
|
|
elementBuffer.push(element);
|
|
|
|
// If last element, handle instruction
|
|
if (terminator === ';') {
|
|
|
|
// Get opcode
|
|
var opcode = elementBuffer.shift();
|
|
|
|
// Call instruction handler.
|
|
if (parser.oninstruction !== null)
|
|
parser.oninstruction(opcode, elementBuffer);
|
|
|
|
// Clear elements
|
|
elementBuffer = [];
|
|
|
|
// Immediately truncate buffer if its contents have been
|
|
// completely parsed, so that the next call to receive()
|
|
// need not append to the buffer unnecessarily
|
|
if (elementEnd + 1 === buffer.length) {
|
|
elementEnd = -1;
|
|
buffer = '';
|
|
}
|
|
|
|
}
|
|
else if (terminator !== ',')
|
|
throw new Error('Element terminator of instruction was not ";" nor ",".');
|
|
|
|
// Start searching for length at character after
|
|
// element terminator
|
|
startIndex = elementEnd + 1;
|
|
|
|
}
|
|
|
|
// Search for end of length
|
|
var lengthEnd = buffer.indexOf('.', startIndex);
|
|
if (lengthEnd !== -1) {
|
|
|
|
// Parse length
|
|
elementCodepoints = parseInt(buffer.substring(elementEnd + 1, lengthEnd));
|
|
if (isNaN(elementCodepoints))
|
|
throw new Error('Non-numeric character in element length.');
|
|
|
|
// Calculate start of element
|
|
startIndex = lengthEnd + 1;
|
|
|
|
// Calculate location of element terminator
|
|
elementEnd = startIndex + elementCodepoints;
|
|
|
|
}
|
|
|
|
// If no period yet, continue search when more data
|
|
// is received
|
|
else {
|
|
startIndex = buffer.length;
|
|
break;
|
|
}
|
|
|
|
} // end parse loop
|
|
|
|
};
|
|
|
|
/**
|
|
* Fired once for every complete Guacamole instruction received, in order.
|
|
*
|
|
* @event
|
|
* @param {!string} opcode
|
|
* The Guacamole instruction opcode.
|
|
*
|
|
* @param {!string[]} parameters
|
|
* The parameters provided for the instruction, if any.
|
|
*/
|
|
this.oninstruction = null;
|
|
|
|
};
|
|
|
|
/**
|
|
* Returns the number of Unicode codepoints (not code units) within the given
|
|
* string. If character offsets are provided, only codepoints between those
|
|
* offsets are counted. Unlike the length property of a string, this function
|
|
* counts proper surrogate pairs as a single codepoint. High and low surrogate
|
|
* characters that are not part of a proper surrogate pair are counted
|
|
* separately as individual codepoints.
|
|
*
|
|
* @param {!string} str
|
|
* The string whose contents should be inspected.
|
|
*
|
|
* @param {number} [start=0]
|
|
* The index of the location in the given string where codepoint counting
|
|
* should start. If omitted, counting will begin at the start of the
|
|
* string.
|
|
*
|
|
* @param {number} [end]
|
|
* The index of the first location in the given string after where counting
|
|
* should stop (the character after the last character being counted). If
|
|
* omitted, all characters after the start location will be counted.
|
|
*
|
|
* @returns {!number}
|
|
* The number of Unicode codepoints within the requested portion of the
|
|
* given string.
|
|
*/
|
|
Guacamole.Parser.codePointCount = function codePointCount(str, start, end) {
|
|
|
|
// Count only characters within the specified region
|
|
str = str.substring(start || 0, end);
|
|
|
|
// Locate each proper Unicode surrogate pair (one high surrogate followed
|
|
// by one low surrogate)
|
|
var surrogatePairs = str.match(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g);
|
|
|
|
// Each surrogate pair represents a single codepoint but is represented by
|
|
// two characters in a JavaScript string, and thus is counted twice toward
|
|
// string length. Subtracting the number of surrogate pairs adjusts that
|
|
// length value such that it gives us the number of codepoints.
|
|
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 + ';';
|
|
|
|
};
|