GUACAMOLE-615: Correct parser calculation of element lengths.

This commit is contained in:
Mike Jumper
2023-04-21 16:51:27 -07:00
parent 82033adad0
commit 81f0e8c280
3 changed files with 227 additions and 75 deletions

View File

@@ -93,6 +93,22 @@ public class GuacamoleInstruction {
return args;
}
/**
* Appends the given value to the provided StringBuilder as a Guacamole
* instruction element, including length prefix.
*
* @param buff
* The StringBuilder to append the element to.
*
* @param element
* The string value of the element to append.
*/
private static void appendElement(StringBuilder buff, String element) {
buff.append(element.codePointCount(0, element.length()));
buff.append('.');
buff.append(element);
}
/**
* Returns this GuacamoleInstruction in the form it would be sent over the
* Guacamole protocol.
@@ -111,16 +127,12 @@ public class GuacamoleInstruction {
StringBuilder buff = new StringBuilder();
// Write opcode
buff.append(opcode.length());
buff.append('.');
buff.append(opcode);
appendElement(buff, opcode);
// Write argument values
for (String value : args) {
buff.append(',');
buff.append(value.length());
buff.append('.');
buff.append(value);
appendElement(buff, value);
}
// Write terminator

View File

@@ -21,7 +21,6 @@ package org.apache.guacamole.protocol;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
@@ -87,10 +86,18 @@ public class GuacamoleParser implements Iterator<GuacamoleInstruction> {
private State state = State.PARSING_LENGTH;
/**
* The length of the current element, if known.
* The length of the current element, if known, in Java characters. This
* value may be adjusted as an element is parsed to take surrogates into
* account.
*/
private int elementLength = 0;
/**
* The length of the current element, if known, in Unicode codepoints. This
* value will NOT change as an element is parsed.
*/
private int elementCodepoints;
/**
* The number of elements currently parsed.
*/
@@ -104,13 +111,22 @@ public class GuacamoleParser implements Iterator<GuacamoleInstruction> {
/**
* Appends data from the given buffer to the current instruction.
*
* @param chunk The buffer containing the data to append.
* @param offset The offset within the buffer where the data begins.
* @param length The length of the data to append.
* @return The number of characters appended, or 0 if complete instructions
* have already been parsed and must be read via next() before
* more data can be appended.
* @throws GuacamoleException If an error occurs while parsing the new data.
* @param chunk
* The buffer containing the data to append.
*
* @param offset
* The offset within the buffer where the data begins.
*
* @param length
* The length of the data to append.
*
* @return
* The number of characters appended, or 0 if complete instructions
* have already been parsed and must be read via next() before more
* data can be appended.
*
* @throws GuacamoleException
* If an error occurs while parsing the new data.
*/
public int append(char chunk[], int offset, int length) throws GuacamoleException {
@@ -156,39 +172,63 @@ public class GuacamoleParser implements Iterator<GuacamoleInstruction> {
}
// Save length
elementLength = parsedLength;
elementCodepoints = elementLength = parsedLength;
} // end parse length
// Parse element content, if available
if (state == State.PARSING_CONTENT && charsParsed + elementLength + 1 <= length) {
while (state == State.PARSING_CONTENT && charsParsed + elementLength + 1 <= length) {
// Read element
// Read element (which may not match element length if surrogate
// characters are present)
String element = new String(chunk, offset + charsParsed, elementLength);
// Verify element contains the number of whole Unicode characters
// expected, scheduling a future read if we don't yet have enough
// characters
int codepoints = element.codePointCount(0, element.length());
if (codepoints < elementCodepoints) {
elementLength += elementCodepoints - codepoints;
continue;
}
// If the current element ends with a character involving both
// a high and low surrogate, elementLength points to the low
// surrogate and NOT the element terminator. We must correct the
// length and reevaluate.
else if (Character.isSurrogatePair(chunk[offset + charsParsed + elementLength - 1],
chunk[offset + charsParsed + elementLength])) {
elementLength++;
continue;
}
charsParsed += elementLength;
elementLength = 0;
// Read terminator char following element
char terminator = chunk[offset + charsParsed++];
// Add element to currently parsed elements
elements[elementCount++] = element;
// If semicolon, store end-of-instruction
if (terminator == ';') {
state = State.COMPLETE;
parsedInstruction = new GuacamoleInstruction(elements[0],
Arrays.asList(elements).subList(1, elementCount));
}
// If comma, move on to next element
else if (terminator == ',')
state = State.PARSING_LENGTH;
// Read terminator char following element
char terminator = chunk[offset + charsParsed++];
switch (terminator) {
// If semicolon, store end-of-instruction
case ';':
state = State.COMPLETE;
parsedInstruction = new GuacamoleInstruction(elements[0],
Arrays.asList(elements).subList(1, elementCount));
break;
// If comma, move on to next element
case ',':
state = State.PARSING_LENGTH;
break;
// Otherwise, parse error
default:
state = State.ERROR;
throw new GuacamoleServerException("Element terminator of instruction was not ';' nor ','");
// Otherwise, parse error
else {
state = State.ERROR;
throw new GuacamoleServerException("Element terminator of instruction was not ';' nor ','");
}
} // end parse content