From c9f2e451cb31536548449b27453bd245d1a60f72 Mon Sep 17 00:00:00 2001 From: Alexander Leitner Date: Mon, 24 Feb 2025 20:24:14 -0500 Subject: [PATCH] GUACAMOLE-2036: Implement the GuacamoleParser within the ReaderGuacamoleReader to support multibyte characters when reading instructions. --- .../guacamole/io/ReaderGuacamoleReader.java | 203 ++++-------------- .../io/ReaderGuacamoleReaderTest.java | 38 +++- 2 files changed, 77 insertions(+), 164 deletions(-) diff --git a/guacamole-common/src/main/java/org/apache/guacamole/io/ReaderGuacamoleReader.java b/guacamole-common/src/main/java/org/apache/guacamole/io/ReaderGuacamoleReader.java index 03caaa43a..357612947 100644 --- a/guacamole-common/src/main/java/org/apache/guacamole/io/ReaderGuacamoleReader.java +++ b/guacamole-common/src/main/java/org/apache/guacamole/io/ReaderGuacamoleReader.java @@ -24,13 +24,13 @@ import java.io.IOException; import java.io.Reader; import java.net.SocketException; import java.net.SocketTimeoutException; -import java.util.Deque; -import java.util.LinkedList; +import java.util.Arrays; import org.apache.guacamole.GuacamoleConnectionClosedException; import org.apache.guacamole.GuacamoleException; import org.apache.guacamole.GuacamoleServerException; import org.apache.guacamole.GuacamoleUpstreamTimeoutException; import org.apache.guacamole.protocol.GuacamoleInstruction; +import org.apache.guacamole.protocol.GuacamoleParser; /** * A GuacamoleReader which wraps a standard Java Reader, using that Reader as @@ -38,6 +38,11 @@ import org.apache.guacamole.protocol.GuacamoleInstruction; */ public class ReaderGuacamoleReader implements GuacamoleReader { + /** + * The GuacamoleParser instance for parsing instructions. + */ + private GuacamoleParser parser = new GuacamoleParser(); + /** * Wrapped Reader to be used for all input. */ @@ -57,10 +62,10 @@ public class ReaderGuacamoleReader implements GuacamoleReader { * The location within the received data buffer that parsing should begin * when more data is read. */ - private int parseStart; + private int parseStart = 0; /** - * The buffer holding all received, unparsed data. + * The buffer holding all received data. */ private char[] buffer = new char[20480]; @@ -74,7 +79,7 @@ public class ReaderGuacamoleReader implements GuacamoleReader { @Override public boolean available() throws GuacamoleException { try { - return input.ready() || usedLength != 0; + return input.ready() || usedLength > parseStart || parser.hasNext(); } catch (IOException e) { throw new GuacamoleServerException(e); @@ -83,97 +88,47 @@ public class ReaderGuacamoleReader implements GuacamoleReader { @Override public char[] read() throws GuacamoleException { + GuacamoleInstruction instruction = readInstruction(); + if (instruction == null) + return null; + return instruction.toString().toCharArray(); + } + + @Override + public GuacamoleInstruction readInstruction() throws GuacamoleException { try { + // Loop until the parser has prepared a full instruction + while (!parser.hasNext()) { - // While we're blocking, or input is available - for (;;) { + // Parse as much data from the buffer as we can + int parsed = 0; + while (parseStart < usedLength && (parsed = parser.append(buffer, parseStart, usedLength - parseStart)) != 0) { + parseStart += parsed; + } - // Length of element - int elementLength = 0; - - // Resume where we left off - int i = parseStart; - - // Parse instruction in buffer - while (i < usedLength) { - - // Read character - char readChar = buffer[i++]; - - // If digit, update length - if (readChar >= '0' && readChar <= '9') - elementLength = elementLength * 10 + readChar - '0'; - - // If not digit, check for end-of-length character - else if (readChar == '.') { - - // Check if element present in buffer - if (i + elementLength < usedLength) { - - // Get terminator - char terminator = buffer[i + elementLength]; - - // Move to character after terminator - i += elementLength + 1; - - // Reset length - elementLength = 0; - - // Continue here if necessary - parseStart = i; - - // If terminator is semicolon, we have a full - // instruction. - if (terminator == ';') { - - // Copy instruction data - char[] instruction = new char[i]; - System.arraycopy(buffer, 0, instruction, 0, i); - - // Update buffer - usedLength -= i; - parseStart = 0; - System.arraycopy(buffer, i, buffer, 0, usedLength); - - return instruction; - - } - - // Handle invalid terminator characters - else if (terminator != ',') - throw new GuacamoleServerException("Element terminator of instruction was not ';' nor ','"); - - } - - // Otherwise, read more data - else - break; + // If we still don't have a full instruction attempt to read more data into the buffer + if (!parser.hasNext()) { + // If we have already parsed some of the buffer and the buffer is almost full then we can trim the parsed data off the buffer + if (parseStart > 0 && buffer.length - usedLength < GuacamoleParser.INSTRUCTION_MAX_LENGTH) { + System.arraycopy(buffer, parseStart, buffer, 0, usedLength - parseStart); + usedLength -= parseStart; + parseStart = 0; } - // Otherwise, parse error - else - throw new GuacamoleServerException("Non-numeric character in element length."); + // Read more instruction data into the buffer + int numRead = input.read(buffer, usedLength, buffer.length - usedLength); + if (numRead == -1) + break; + + usedLength += numRead; } + + } - // If past threshold, resize buffer before reading - if (usedLength > buffer.length/2) { - char[] biggerBuffer = new char[buffer.length*2]; - System.arraycopy(buffer, 0, biggerBuffer, 0, usedLength); - buffer = biggerBuffer; - } - - // Attempt to fill buffer - int numRead = input.read(buffer, usedLength, buffer.length - usedLength); - if (numRead == -1) - return null; - - // Update used length - usedLength += numRead; - - } // End read loop + return parser.next(); } catch (SocketTimeoutException e) { @@ -188,80 +143,4 @@ public class ReaderGuacamoleReader implements GuacamoleReader { } - @Override - public GuacamoleInstruction readInstruction() throws GuacamoleException { - - // Get instruction - char[] instructionBuffer = read(); - - // If EOF, return EOF - if (instructionBuffer == null) - return null; - - // Start of element - int elementStart = 0; - - // Build list of elements - Deque elements = new LinkedList(); - while (elementStart < instructionBuffer.length) { - - // Find end of length - int lengthEnd = -1; - for (int i=elementStart; i