Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GUACAMOLE-615: Implement the GuacamoleParser within the ReaderGuacamoleReader. #1057

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,25 @@
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
* the Guacamole instruction stream.
*/
public class ReaderGuacamoleReader implements GuacamoleReader {

/**
* The GuacamoleParser instance for parsing instructions.
*/
private GuacamoleParser parser = new GuacamoleParser();

/**
* Wrapped Reader to be used for all input.
*/
Expand All @@ -57,10 +62,10 @@ public ReaderGuacamoleReader(Reader input) {
* 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];

Expand All @@ -74,7 +79,7 @@ public ReaderGuacamoleReader(Reader input) {
@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);
Expand All @@ -83,97 +88,47 @@ public boolean available() throws GuacamoleException {

@Override
public char[] read() throws GuacamoleException {
GuacamoleInstruction instruction = readInstruction();
if (instruction == null)
throw new GuacamoleException("No instruction available to read.");

try {

// While we're blocking, or input is available
for (;;) {

// 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;

}
return instruction.toString().toCharArray();
}

// Handle invalid terminator characters
else if (terminator != ',')
throw new GuacamoleServerException("Element terminator of instruction was not ';' nor ','");
@Override
public GuacamoleInstruction readInstruction() throws GuacamoleException {
try {
// Loop until the parser has prepared a full instruction
while (!parser.hasNext()) {

}
// 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;
}

// 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) {
Expand All @@ -188,80 +143,4 @@ else if (terminator != ',')

}

@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<String> elements = new LinkedList<String>();
while (elementStart < instructionBuffer.length) {

// Find end of length
int lengthEnd = -1;
for (int i=elementStart; i<instructionBuffer.length; i++) {
if (instructionBuffer[i] == '.') {
lengthEnd = i;
break;
}
}

// read() is required to return a complete instruction. If it does
// not, this is a severe internal error.
if (lengthEnd == -1)
throw new GuacamoleServerException("Read returned incomplete instruction.");

// Parse length
int length = Integer.parseInt(new String(
instructionBuffer,
elementStart,
lengthEnd - elementStart
));

// Parse element from just after period
elementStart = lengthEnd + 1;
String element = new String(
instructionBuffer,
elementStart,
length
);

// Append element to list of elements
elements.addLast(element);

// Read terminator after element
elementStart += length;
char terminator = instructionBuffer[elementStart];

// Continue reading instructions after terminator
elementStart++;

// If we've reached the end of the instruction
if (terminator == ';')
break;

}

// Pull opcode off elements list
String opcode = elements.removeFirst();

// Create instruction
GuacamoleInstruction instruction = new GuacamoleInstruction(
opcode,
elements.toArray(new String[elements.size()])
);

// Return parsed instruction
return instruction;

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class ReaderGuacamoleReaderTest {
public void testReader() throws GuacamoleException {

// Test string
final String test = "1.a,2.bc,3.def,10.helloworld;4.test,5.test2;0.;3.foo;";
final String test = "1.a,2.bc,3.def,10.helloworld;4.test,5.test2;0.;3.foo;1.\uD83E\uDD79;";

GuacamoleReader reader = new ReaderGuacamoleReader(new StringReader(test));

Expand Down Expand Up @@ -75,6 +75,12 @@ public void testReader() throws GuacamoleException {
assertEquals(0, instruction.getArgs().size());
assertEquals("foo", instruction.getOpcode());

// Validate fifth test instruction
instruction = reader.readInstruction();
assertNotNull(instruction);
assertEquals(0, instruction.getArgs().size());
assertEquals("\uD83E\uDD79", instruction.getOpcode());

// There should be no more instructions
instruction = reader.readInstruction();
assertNull(instruction);
Expand Down