org.apache.guacamole.protocol.GuacamoleParser Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of guacamole-common Show documentation
Show all versions of guacamole-common Show documentation
The base Java API of the Guacamole project, providing Java support for
the Guacamole stack.
/*
* 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.
*/
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;
/**
* Parser for the Guacamole protocol. Arbitrary instruction data is appended,
* and instructions are returned as a result. Invalid instructions result in
* exceptions.
*
* @author Michael Jumper
*/
public class GuacamoleParser implements Iterator {
/**
* The maximum number of characters per instruction.
*/
public static final int INSTRUCTION_MAX_LENGTH = 8192;
/**
* The maximum number of digits to allow per length prefix.
*/
public static final int INSTRUCTION_MAX_DIGITS = 5;
/**
* The maximum number of elements per instruction, including the opcode.
*/
public static final int INSTRUCTION_MAX_ELEMENTS = 64;
/**
* All possible states of the instruction parser.
*/
private enum State {
/**
* The parser is currently waiting for data to complete the length prefix
* of the current element of the instruction.
*/
PARSING_LENGTH,
/**
* The parser has finished reading the length prefix and is currently
* waiting for data to complete the content of the instruction.
*/
PARSING_CONTENT,
/**
* The instruction has been fully parsed.
*/
COMPLETE,
/**
* The instruction cannot be parsed because of a protocol error.
*/
ERROR
}
/**
* The latest parsed instruction, if any.
*/
private GuacamoleInstruction parsedInstruction;
/**
* The parse state of the instruction.
*/
private State state = State.PARSING_LENGTH;
/**
* The length of the current element, if known.
*/
private int elementLength = 0;
/**
* The number of elements currently parsed.
*/
private int elementCount = 0;
/**
* All currently parsed elements.
*/
private final String elements[] = new String[INSTRUCTION_MAX_ELEMENTS];
/**
* 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.
*/
public int append(char chunk[], int offset, int length) throws GuacamoleException {
int charsParsed = 0;
// Do not exceed maximum number of elements
if (elementCount == INSTRUCTION_MAX_ELEMENTS && state != State.COMPLETE) {
state = State.ERROR;
throw new GuacamoleServerException("Instruction contains too many elements.");
}
// Parse element length
if (state == State.PARSING_LENGTH) {
int parsedLength = elementLength;
while (charsParsed < length) {
// Pull next character
char c = chunk[offset + charsParsed++];
// If digit, add to length
if (c >= '0' && c <= '9')
parsedLength = parsedLength*10 + c - '0';
// If period, switch to parsing content
else if (c == '.') {
state = State.PARSING_CONTENT;
break;
}
// If not digit, parse error
else {
state = State.ERROR;
throw new GuacamoleServerException("Non-numeric character in element length.");
}
}
// If too long, parse error
if (parsedLength > INSTRUCTION_MAX_LENGTH) {
state = State.ERROR;
throw new GuacamoleServerException("Instruction exceeds maximum length.");
}
// Save length
elementLength = parsedLength;
} // end parse length
// Parse element content, if available
if (state == State.PARSING_CONTENT && charsParsed + elementLength + 1 <= length) {
// Read element
String element = new String(chunk, offset + charsParsed, elementLength);
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;
// Otherwise, parse error
else {
state = State.ERROR;
throw new GuacamoleServerException("Element terminator of instruction was not ';' nor ','");
}
} // end parse content
return charsParsed;
}
/**
* Appends data from the given buffer to the current instruction.
*
* @param chunk 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[]) throws GuacamoleException {
return append(chunk, 0, chunk.length);
}
@Override
public boolean hasNext() {
return state == State.COMPLETE;
}
@Override
public GuacamoleInstruction next() {
// No instruction to return if not yet complete
if (state != State.COMPLETE)
return null;
// Reset for next instruction.
state = State.PARSING_LENGTH;
elementCount = 0;
elementLength = 0;
return parsedInstruction;
}
@Override
public void remove() {
throw new UnsupportedOperationException("GuacamoleParser does not support remove().");
}
}